-
Notifications
You must be signed in to change notification settings - Fork 5
feat(mono): Add worker app #121
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
31 commits
Select commit
Hold shift + click to select a range
bcdc1bd
feat(worker): :boom: Initial Commit, intial entry point, prisma
Nudelsuppe42 52d761a
feat(worker): :sparkles: Basic structure
Nudelsuppe42 fd8e85b
chore(worker): :test_tube: Remove test cron job [ci skip]
Nudelsuppe42 552abc1
feat(worker/tasks): :sparkles: Add buildteam webhook audit logging task
Nudelsuppe42 3d21935
feat(worker/cron): :sparkles: Remove legacy cron jobs before adding c…
Nudelsuppe42 97caa52
feat(worker/tasks): :sparkles: Use Zod for input validation
Nudelsuppe42 04094ba
feat(worker/tasks): :sparkles: Implement Review Activity Check
Nudelsuppe42 4d51378
fix(worker): :bug: Change prisma import in review activity util
Nudelsuppe42 6936bbb
refactor(worker): :recycle: Simplify execution logic of tasks
Nudelsuppe42 b54d2ec
feat(worker/tasks): :sparkles: Add Review Activity Check task and cro…
Nudelsuppe42 2911c94
feat(worker/discord): :sparkles: Support advanced DM template for dis…
Nudelsuppe42 5444c2c
feat(worker/tasks): :sparkles: Internal Logging task, rename buildtea…
Nudelsuppe42 11d8b44
feat(worker/tasks): :sparkles: Add purge claims and verifications tas…
Nudelsuppe42 3fbf1a9
feat(worker): Add application reminder as weekly cron job
Nudelsuppe42 7449f8c
feat(worker): Add Dockerfile and GitHub Actions workflow for worker d…
Nudelsuppe42 0020cf8
fix(worker/tasks): :bug: Switch to unknown type from void
Nudelsuppe42 34543ff
fix(worker/tasks): :bug: Use nativeEnum instead of enum for zod
Nudelsuppe42 4c75e9b
fix(worker): :bug: Update zod import to named import for consistency
Nudelsuppe42 5e90876
fix(worker/tasks): :bug: Use Promise.all instead of single promises
Nudelsuppe42 72ca745
fix(worker): :bug: Typo
Nudelsuppe42 7b8ccb2
fix(worker/Dockerfile): :bug: Use corepack/yarn 4 for turbo prune
Nudelsuppe42 97afb5d
fix(worker): :bug: Use relative imports
Nudelsuppe42 d7adf89
style(worker): :art: Prettier
Nudelsuppe42 6f06688
fix(worker/tasks): :bug: Final bug fixes
Nudelsuppe42 6535b65
fix(worker): Imports again
Nudelsuppe42 ddbecac
fix(worker): Imports again
Nudelsuppe42 8dbc010
fix(worker): Imports again
Nudelsuppe42 a675a5e
fix(worker/tasks): Update validation for Discord DM payload schema
Nudelsuppe42 df1bfcb
feat(worker): Remove errored jobs if count > 200
Nudelsuppe42 363d38f
fix(worker): Set body if body is not json
Nudelsuppe42 5ac8deb
fix(worker/tasks): Add JSDoc
Nudelsuppe42 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,104 @@ | ||
| name: Docker Publish Worker | ||
|
|
||
| permissions: | ||
| contents: write | ||
| packages: write | ||
|
|
||
| on: | ||
| push: | ||
| branches: [main] | ||
| tags: ['worker-v*.*.*'] | ||
|
|
||
| jobs: | ||
| check: | ||
| runs-on: ubuntu-latest | ||
| outputs: | ||
| has_changes: ${{ steps.check_for_changes.outputs.has_changes }} | ||
| steps: | ||
| - uses: actions/checkout@v3 | ||
| with: | ||
| fetch-depth: 3 | ||
| - uses: actions/setup-node@v3 | ||
| with: | ||
| node-version: '22.14.0' | ||
| - name: Install turbo | ||
| run: npm install -g turbo@2.4.4 && npm install -g turbo-ignore | ||
| - name: Check for changes | ||
| id: check_for_changes | ||
| run: | | ||
| if [[ "${GITHUB_REF}" == refs/tags/worker-v* ]]; then | ||
| echo "has_changes=true" >> "$GITHUB_OUTPUT" | ||
| exit 0 | ||
| fi | ||
|
|
||
| npx turbo-ignore worker --fallback=HEAD^1 && echo "has_changes=false" >> "$GITHUB_OUTPUT" || echo "has_changes=true" >> "$GITHUB_OUTPUT" | ||
| build: | ||
| runs-on: ubuntu-latest | ||
| needs: check | ||
| if: needs.check.outputs.has_changes == 'true' | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| with: | ||
| fetch-depth: 3 | ||
| - name: Install turbo | ||
| run: npm install -g turbo@^2 | ||
| - name: Login to Docker | ||
| run: | | ||
| echo "${{ github.token }}" | docker login https://ghcr.io -u ${GITHUB_ACTOR} --password-stdin | ||
| - name: Setup Docker Buildx | ||
| uses: docker/setup-buildx-action@v3 | ||
| - name: Resolve release tags | ||
| id: release_meta | ||
| run: | | ||
| if [[ "${GITHUB_REF}" == refs/tags/worker-v* ]]; then | ||
| VERSION="${GITHUB_REF_NAME#worker-v}" | ||
| if [[ ! "${VERSION}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then | ||
| echo "Invalid release tag format: ${GITHUB_REF_NAME}. Expected worker-vX.Y.Z" | ||
| exit 1 | ||
| fi | ||
|
|
||
| IFS='.' read -r MAJOR MINOR PATCH <<< "${VERSION}" | ||
| echo "is_release=true" >> "$GITHUB_OUTPUT" | ||
| echo "version=${VERSION}" >> "$GITHUB_OUTPUT" | ||
| echo "major=${MAJOR}" >> "$GITHUB_OUTPUT" | ||
| echo "minor=${MINOR}" >> "$GITHUB_OUTPUT" | ||
| exit 0 | ||
| fi | ||
|
|
||
| echo "is_release=false" >> "$GITHUB_OUTPUT" | ||
| - name: Build and push Docker image | ||
| env: | ||
| DOCKER_BUILDKIT: 1 | ||
| IS_RELEASE: ${{ steps.release_meta.outputs.is_release }} | ||
| RELEASE_VERSION: ${{ steps.release_meta.outputs.version }} | ||
| RELEASE_MAJOR: ${{ steps.release_meta.outputs.major }} | ||
| RELEASE_MINOR: ${{ steps.release_meta.outputs.minor }} | ||
| run: | | ||
| IMAGE="ghcr.io/buildtheearth/website-worker" | ||
| TAGS=( | ||
| --tag "${IMAGE}:sha-$(git rev-parse --short=12 HEAD)" | ||
| ) | ||
|
|
||
| if [[ "${IS_RELEASE}" == "true" ]]; then | ||
| TAGS+=( | ||
| --tag "${IMAGE}:${RELEASE_VERSION}" | ||
| --tag "${IMAGE}:${RELEASE_MAJOR}.${RELEASE_MINOR}" | ||
| --tag "${IMAGE}:${RELEASE_MAJOR}" | ||
| --tag "${IMAGE}:latest" | ||
| ) | ||
| fi | ||
|
|
||
| docker buildx build . \ | ||
| --file apps/worker/Dockerfile \ | ||
| --label org.opencontainers.image.source="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}" \ | ||
| --label org.opencontainers.image.revision="${GITHUB_SHA}" \ | ||
| --label org.opencontainers.image.version="${RELEASE_VERSION:-sha-$(git rev-parse --short=12 HEAD)}" \ | ||
| "${TAGS[@]}" \ | ||
| --push | ||
| - name: Publish GitHub release | ||
| if: steps.release_meta.outputs.is_release == 'true' | ||
| uses: softprops/action-gh-release@v2 | ||
| with: | ||
| tag_name: ${{ github.ref_name }} | ||
| name: worker-v${{ steps.release_meta.outputs.version }} | ||
| generate_release_notes: true |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| { | ||
| "workspaceId": "1609eb2b-2a36-4273-bd12-470f3f3ad35e", | ||
| "defaultEnvironment": "dev", | ||
| "gitBranchToEnvironmentMapping": null | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| FROM node:22-alpine AS base | ||
|
|
||
| FROM base AS builder | ||
| RUN apk update | ||
| RUN apk add --no-cache libc6-compat | ||
| WORKDIR /app | ||
|
|
||
| # Run turbo | ||
| RUN corepack enable | ||
| RUN corepack prepare yarn@4.9.1 --activate | ||
| COPY . . | ||
| RUN yarn dlx turbo@2.1.1 prune worker --docker | ||
|
|
||
| # Add lockfile and package.json | ||
| FROM base AS installer | ||
| RUN apk update | ||
| RUN apk add --no-cache libc6-compat | ||
| WORKDIR /app | ||
|
|
||
| # Install dependencies | ||
| COPY --from=builder /app/out/json/ . | ||
| # Enable corepack to use the correct Yarn version | ||
| RUN corepack enable | ||
| RUN corepack prepare yarn@4.9.1 --activate | ||
| RUN yarn install | ||
|
|
||
| # Build the project | ||
| COPY --from=builder /app/out/full/ . | ||
| RUN yarn turbo run build --filter=worker... | ||
|
|
||
| FROM base AS runner | ||
| WORKDIR /app | ||
|
|
||
| COPY --from=installer /app . | ||
|
|
||
| CMD ["node", "apps/worker/dist/main.js"] | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| <!-- markdownlint-disable --> | ||
| <div align="center"> | ||
|
|
||
| <img width="128" src="https://github.com/BuildTheEarth/assets/blob/main/logos/logo.png?raw=true" /> | ||
|
|
||
| # Website Worker | ||
|
|
||
| _Independent worker for handling events._ | ||
|
|
||
|  | ||
| [](https://discord.gg/buildtheearth) | ||
|
|
||
| </div> | ||
| <!-- markdownlint-restore --> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| { | ||
| "name": "worker", | ||
| "version": "1.0.0", | ||
| "private": true, | ||
| "packageManager": "yarn@4.9.1+sha512.f95ce356460e05be48d66401c1ae64ef84d163dd689964962c6888a9810865e39097a5e9de748876c2e0bf89b232d583c33982773e9903ae7a76257270986538", | ||
| "scripts": { | ||
| "dev": "infisical run --path=\"/worker\" -- tsx watch --tsconfig tsconfig.runtime.json src/main.ts", | ||
| "build": "tsc", | ||
| "start": "infisical run --path=\"/worker\" -- tsx --tsconfig tsconfig.runtime.json dist/main.js", | ||
| "prettier": "prettier ./src --write --ignore-unknown", | ||
| "test": "infisical run --path=\"/worker\" -- tsx --tsconfig tsconfig.test.json test/index.test.ts" | ||
| }, | ||
| "devDependencies": { | ||
| "@repo/prettier-config": "workspace:^", | ||
| "@repo/typescript-config": "workspace:^", | ||
| "@types/node": "^22", | ||
| "tsx": "^4.22.3", | ||
| "typescript": "^5.6.3" | ||
| }, | ||
| "dependencies": { | ||
| "@prisma/adapter-pg": "^7.8.0", | ||
| "@repo/db": "*", | ||
| "bullmq": "^5.77.0", | ||
| "ioredis": "^5.10.1", | ||
| "zod": "^4.1.13", | ||
| "winston": "^3.19.0" | ||
| }, | ||
| "prettier": "@repo/prettier-config", | ||
| "lint-staged": { | ||
| "*": "prettier --write --ignore-unknown" | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| import { logger } from './logger'; | ||
| import prisma from './prisma'; | ||
|
|
||
| type WebhookPayload = { | ||
| type: string; | ||
| data: any; | ||
| }; | ||
| export type WebhookBuildTeam = | ||
| | { | ||
| url: string; | ||
| } | ||
| | { id: string } | ||
| | { slug: string }; | ||
|
|
||
| export class BuildTeamWebhook { | ||
| async send( | ||
| destination: WebhookBuildTeam, | ||
| type: string, | ||
| data: WebhookPayload['data'], | ||
| ): Promise<{ ok: boolean; status: number; error?: string }> { | ||
| const url = 'url' in destination ? destination.url : await this.resolveUrl(destination); | ||
|
|
||
| if (!url) { | ||
| return { ok: true, status: 202 }; | ||
|
kyanvde marked this conversation as resolved.
|
||
| } | ||
|
Nudelsuppe42 marked this conversation as resolved.
|
||
|
|
||
| try { | ||
| const res = await fetch(url, { | ||
| method: 'POST', | ||
| headers: { | ||
| 'Content-Type': 'application/json', | ||
| 'User-Agent': 'BuildTheEarth Worker', | ||
| Accept: 'application/json', | ||
| }, | ||
| body: JSON.stringify({ type, data }), | ||
| }); | ||
|
|
||
| const body = await res.text(); | ||
|
|
||
| if (!res.ok) { | ||
| logger.error('BuildTeam webhook request failed', { status: res.status }); | ||
| return { ok: false, status: res.status, error: typeof body === 'string' ? body : undefined }; | ||
| } | ||
|
|
||
| logger.debug('BuildTeam webhook sent', { status: res.status }); | ||
| return { ok: true, status: res.status }; | ||
| } catch (err: any) { | ||
| logger.error('BuildTeam webhook error', { error: err?.message }); | ||
| return { ok: false, status: 0, error: err?.message }; | ||
| } | ||
| } | ||
|
|
||
| private async resolveUrl(destination: WebhookBuildTeam): Promise<string | null> { | ||
| const bt = await prisma.buildTeam.findUnique({ | ||
| where: | ||
| 'id' in destination ? { id: destination.id } : 'slug' in destination ? { slug: destination.slug } : { id: '' }, | ||
| }); | ||
| return bt?.webhook || null; | ||
| } | ||
| } | ||
|
|
||
| export default new BuildTeamWebhook(); | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| /* | ||
| * Static constant configuration values | ||
| */ | ||
|
|
||
| import { remove } from 'winston'; | ||
|
|
||
| export const config = { | ||
| // The number of worker threads to spawn for processing background jobs | ||
| workerThreadCount: 5, | ||
| eventQueueName: 'EventQueue', | ||
| retryOptions: { | ||
| attempts: 3, | ||
| backoff: { | ||
| type: 'exponential', | ||
| delay: 1000, // Initial delay of 1 second for retries | ||
| }, | ||
| }, | ||
| removalOptions: { | ||
| removeOnComplete: { | ||
| age: 3600, // 1h | ||
| count: 200, | ||
| }, | ||
| removeOnFail: { | ||
| count: 200, | ||
| }, | ||
| }, | ||
| webhooks: { | ||
| errorReporting: process.env.DISCORD_WEBHOOK_ERRORS || '', | ||
| logging: process.env.DISCORD_WEBHOOK_LOGGING || '', | ||
| }, | ||
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| import { z } from 'zod'; | ||
|
|
||
| export type DiscordDmResult = { | ||
| success: string[]; | ||
| failure: string[]; | ||
| }; | ||
|
|
||
| export enum DiscordBotEmojisRaw { | ||
| WARN = '<:warn:1441532241628102686>', | ||
| UNMUTE = '<:unmute:1441532234573156433>', | ||
| UNBAN = '<:unban:1441532232627130548>', | ||
| MUTE = '<:mute:1441532230643224587>', | ||
| KICK = '<:kick:1441532228550131725>', | ||
| INVALID = '<:invalid:1441532226427813901>', | ||
| INFORMATION = '<:information:1441532225119191175>', | ||
| INPROGRESS = '<:inprogress:1441532224473268234>', | ||
| FORWARDED = '<:forwarded:1441532223298863305>', | ||
| DUPLICATE = '<:duplicate:1441532221470146663>', | ||
| DENIED = '<:denied:1441532217779294350>', | ||
| BAN = '<:ban:1441532215828676790>', | ||
| APPROVED = '<:approved:1441532214562128034>', | ||
| } | ||
| export enum DiscordBotEmojis { | ||
| WARN = 'WARN', | ||
| UNMUTE = 'UNMUTE', | ||
| UNBAN = 'UNBAN', | ||
| MUTE = 'MUTE', | ||
| KICK = 'KICK', | ||
| INVALID = 'INVALID', | ||
| INFORMATION = 'INFORMATION', | ||
| INPROGRESS = 'INPROGRESS', | ||
| FORWARDED = 'FORWARDED', | ||
| DUPLICATE = 'DUPLICATE', | ||
| DENIED = 'DENIED', | ||
| BAN = 'BAN', | ||
| APPROVED = 'APPROVED', | ||
| } | ||
|
|
||
| export const discordBotMessageMessageSchema = z.object({ | ||
| title: z.string(), | ||
| emoji: z.nativeEnum(DiscordBotEmojis), | ||
| body: z.string(), | ||
| footer: z.string().optional(), | ||
| }); | ||
|
|
||
| export async function sendDiscordDm( | ||
| message: string | z.infer<typeof discordBotMessageMessageSchema>, | ||
| users: string[], | ||
| ): Promise<DiscordDmResult> { | ||
| try { | ||
| const content = | ||
| typeof message === 'string' | ||
| ? message | ||
| : `## ${DiscordBotEmojisRaw[message.emoji]} ${message.title}\n\n${message.body}${ | ||
| message.footer ? `\n\n-# ${message.footer}` : '' | ||
| }`; | ||
|
|
||
| const res = await fetch(process.env.DISCORD_BOT_API_URL + '/api/v1/website/message/blank', { | ||
| method: 'POST', | ||
| headers: { | ||
| 'Content-type': 'application/json', | ||
| authorization: `Bearer ${process.env.DISCORD_BOT_SECRET}`, | ||
| }, | ||
| body: JSON.stringify({ params: { text: content }, ids: users }), | ||
| }); | ||
| const json = await res.json(); | ||
| return { | ||
| success: json.success || [], | ||
| failure: json.failed || [], | ||
| }; | ||
| } catch (e) { | ||
| console.error(e); | ||
| return { | ||
|
Nudelsuppe42 marked this conversation as resolved.
Nudelsuppe42 marked this conversation as resolved.
|
||
| success: [], | ||
| failure: users, | ||
| }; | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.