Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ node_modules
/.kotlin/

.env*
**/.venv/
**/__pycache__/
**/*.egg-info/

# Ignore the static folder, it will be generated in the build process for production use.
**/static/*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
name: "Build & Upload Docker Image for codebloom-leetcode-auth-stealer-service"
description: "Build & (optionally) upload Docker Image to Docker Registry"

inputs:
ENVIRONMENT:
description: "Deployment environment (staging or production)."
required: false
default: production
SECRETS:
description: "Pass toJSON(secrets) from the calling workflow to expose all secrets as env vars."
required: false
DOCKER_UPLOAD:
description: "Boolean indicating whether the image should be uploaded to Docker registry or not."
required: false
default: true
ARCH:
description: "Target architecture to build natively (amd64 or arm64). Each arch pushes to its own Docker repository (codebloom-leetcode-auth-stealer-service / codebloom-leetcode-auth-stealer-service-arm)."
required: false
default: amd64

outputs:
tag:
description: "Built Docker image tag (git SHA)"
value: ${{ steps.build-image.outputs.tag }}

runs:
using: "composite"
steps:
- name: Setup CI
uses: tahminator/pipeline/actions/setup@a424f92d486342e1255fcdf377a7569eb230aabe
with:
LINT_CI: true
SECRETS: ${{ inputs.SECRETS }}

- name: Run script
id: build-image
shell: bash
run: |
bun run .github/scripts/build-image \
--docker-upload="${{ inputs.DOCKER_UPLOAD }}" \
--getGhaOutput=true \
--type="leetcode-auth-stealer-service" \
--arch="${{ inputs.ARCH }}" \
--environment="${{ inputs.ENVIRONMENT }}" \
--prId="${{ github.event.pull_request.number }}"
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: "Re-Deploy codebloom-leetcode-auth-stealer-service to K8s"
description: "Deploy the auth service to K8s by opening and merging a GitOps image-tag PR."

inputs:
SECRETS:
description: "Pass toJSON(secrets) from the calling workflow to expose all secrets as env vars."
required: false
ENVIRONMENT:
description: '"staging" or "production"'
required: false
default: production
SHA:
description: "Commit SHA of the built image. Defaults to GITHUB_SHA when omitted."
required: false

runs:
using: "composite"
steps:
- name: Setup CI
uses: tahminator/pipeline/actions/setup@a424f92d486342e1255fcdf377a7569eb230aabe
with:
LINT_CI: true
SECRETS: ${{ inputs.SECRETS }}

- name: Run script
shell: bash
run: |
bun run .github/scripts/redeploy \
--environment="${{ inputs.ENVIRONMENT }}" \
--sha="${{ inputs.SHA }}" \
--type="leetcode-auth-stealer-service"
47 changes: 26 additions & 21 deletions .github/scripts/build-image/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ const { environment, dockerUpload, getGhaOutput, githubOutputFile, type, arch, p
default: process.env.GITHUB_OUTPUT,
})
.option("type", {
choices: ["standup-bot", "web"] satisfies Type[],
choices: ["standup-bot", "web", "leetcode-auth-stealer-service"] satisfies Type[],
describe: "Type to build",
demandOption: true,
default: "web" as Type,
Expand All @@ -56,16 +56,8 @@ const { environment, dockerUpload, getGhaOutput, githubOutputFile, type, arch, p

const tagPrefix = environment === "staging" ? "staging-" : "";

const dockerRepository =
type === "web"
? arch === "arm64"
? "codebloom-arm"
: "codebloom"
: arch === "arm64"
? "codebloom-standup-bot-arm"
: "codebloom-standup-bot";
// both types build natively per-arch on their own runner (no QEMU), one
// repo per arch, matrixed at the workflow level.
const baseRepository = type === "web" ? "codebloom" : `codebloom-${type}`;
const dockerRepository = `${baseRepository}${arch === "arm64" ? "-arm" : ""}`;
const platforms = [`linux/${arch}`];

async function main() {
Expand Down Expand Up @@ -109,7 +101,7 @@ async function main() {
})
.replace(/(\d+)\/(\d+)\/(\d+),\s(\d+):(\d+):(\d+)/, "$3.$1.$2-$4.$5.$6");

const gitSha = (await $`git rev-parse --short HEAD`.text()).trim();
const gitSha = (await $`git rev-parse HEAD`.text()).trim().slice(0, 7);

await using dockerClient = await DockerClient.create(dockerHubUsername, dockerHubPat);

Expand All @@ -120,21 +112,34 @@ async function main() {

const buildArgs = {
...(type === "web" ? { COMMIT_SHA: gitSha } : {}),
...(environment === "staging"
...(environment === "staging" && type !== "leetcode-auth-stealer-service"
? {
VITE_STAGING: true,
}
: {}),
};

await dockerClient.buildImage({
dockerRepository,
dockerFileLocation: type === "web" ? "infra/Dockerfile" : "internal/standup-bot/Dockerfile",
tags,
shouldUpload: dockerUpload,
buildArgs,
platforms,
});
const originalCwd = process.cwd();
try {
if (type === "leetcode-auth-stealer-service") {
process.chdir("internal/leetcode-auth-stealer-service");
}
await dockerClient.buildImage({
dockerRepository,
dockerFileLocation:
type === "web"
? "infra/Dockerfile"
: type === "standup-bot"
? "internal/standup-bot/Dockerfile"
: "Dockerfile",
tags,
shouldUpload: dockerUpload,
buildArgs,
platforms,
});
} finally {
process.chdir(originalCwd);
}

console.log("Image pushed successfully.");

Expand Down
8 changes: 5 additions & 3 deletions .github/scripts/redeploy/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const { environment, sha, type } = await yargs(hideBin(process.argv))
default: "",
})
.option("type", {
choices: ["standup-bot", "web"] satisfies Type[],
choices: ["standup-bot", "web", "leetcode-auth-stealer-service"] satisfies Type[],
describe: "Type to build",
demandOption: true,
default: "web" as Type,
Expand All @@ -44,11 +44,13 @@ async function main() {
});
}

const dockerRepository = type === "web" ? "codebloom" : `codebloom-${type}`;

await ghClient.updateK8sTagWithPR({
originRepo: ["Patina-Network", "codebloom"],
manifestRepo: ["Patina-Network", "k8s-manifests"],
kustomizationFilePath: `base/${environment}/${type === "web" ? "codebloom" : "codebloom-standup-bot"}/kustomization.yaml`,
imageName: `patinanetwork/${type === "web" ? "codebloom-arm" : "codebloom-standup-bot-arm"}`,
kustomizationFilePath: `base/${environment}/${dockerRepository}/kustomization.yaml`,
imageName: `patinanetwork/${dockerRepository}-arm`,
environment: `${environment}`,
newTag: environment === "staging" ? `staging-${resolvedSha}` : resolvedSha,
});
Expand Down
2 changes: 1 addition & 1 deletion .github/scripts/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ export type Environment = "staging" | "production";

export type Location = "backend" | "frontend";

export type Type = "web" | "standup-bot";
export type Type = "web" | "standup-bot" | "leetcode-auth-stealer-service";
96 changes: 96 additions & 0 deletions .github/workflows/ci-cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,54 @@ jobs:
DOCKER_UPLOAD: "true"
ARCH: ${{ matrix.arch }}

buildStagingLeetcodeAuthServiceImage:
name: Build staging image for codebloom-leetcode-auth-stealer-service (${{ matrix.arch }})
runs-on: ${{ matrix.runner }}

if: github.event_name == 'pull_request'

strategy:
matrix:
include:
- arch: amd64
runner: ubuntu-latest
- arch: arm64
runner: ubuntu-24.04-arm

steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
ref: ${{ github.sha }}

- name: Run workflow
uses: ./.github/composite/build-image/internal/leetcode-auth-stealer-service
with:
SECRETS: ${{ toJSON(secrets) }}
ENVIRONMENT: staging
DOCKER_UPLOAD: "true"
ARCH: ${{ matrix.arch }}

deployStagingLeetcodeAuthService:
name: Deploy staging codebloom-leetcode-auth-stealer-service to K8s
runs-on: ubuntu-latest
needs: buildStagingLeetcodeAuthServiceImage

if: github.event_name == 'pull_request'

steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
ref: ${{ github.sha }}

- name: Run workflow
uses: ./.github/composite/redeploy/internal/leetcode-auth-stealer-service
with:
SECRETS: ${{ toJSON(secrets) }}
ENVIRONMENT: staging
SHA: ${{ github.sha }}

validateDBSchema:
name: Validate DB Schema on Prod DB
runs-on: ubuntu-latest
Expand Down Expand Up @@ -198,6 +246,54 @@ jobs:
SECRETS: ${{ toJSON(secrets) }}
ARCH: ${{ matrix.arch }}

buildLeetcodeAuthServiceImage:
name: Build codebloom-leetcode-auth-stealer-service Docker Image & Upload to Registry (${{ matrix.arch }})
runs-on: ${{ matrix.runner }}

if: github.event_name == 'push' && github.ref == 'refs/heads/main'

strategy:
matrix:
include:
- arch: amd64
runner: ubuntu-latest
- arch: arm64
runner: ubuntu-24.04-arm

steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
ref: ${{ github.sha }}

- name: Run workflow
uses: ./.github/composite/build-image/internal/leetcode-auth-stealer-service
with:
SECRETS: ${{ toJSON(secrets) }}
ENVIRONMENT: production
DOCKER_UPLOAD: "true"
ARCH: ${{ matrix.arch }}

deployLeetcodeAuthService:
name: Deploy codebloom-leetcode-auth-stealer-service to K8s
runs-on: ubuntu-latest
needs: buildLeetcodeAuthServiceImage

if: github.event_name == 'push' && github.ref == 'refs/heads/main'

steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
ref: ${{ github.sha }}

- name: Run workflow
uses: ./.github/composite/redeploy/internal/leetcode-auth-stealer-service
with:
SECRETS: ${{ toJSON(secrets) }}
ENVIRONMENT: production
SHA: ${{ github.sha }}

deploy:
name: deploy codebloom to K8S
runs-on: ubuntu-latest
Expand Down
16 changes: 14 additions & 2 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,19 @@
"request": "attach",
"name": "Debug (Attach) - Remote (VSCode)",
"hostName": "127.0.0.1",
"port": 5005,
"port": 5005
},
{
"name": "Debug leetcode-auth-stealer-service (will launch 4 u)",
"type": "debugpy",
"request": "launch",
"program": "${workspaceFolder}/internal/leetcode-auth-stealer-service/src/server.py",
"cwd": "${workspaceFolder}/internal/leetcode-auth-stealer-service",
"console": "integratedTerminal",
"env": {
"GRPC_REFLECTION": "true",
"GRPC_VERBOSITY": "ERROR"
}
}
]
}
}
16 changes: 12 additions & 4 deletions Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@ copy-stg *args:
backend-dev *args:
dotenvx run -f .env -f .env.shared -- ./mvnw -Dspring-boot.run.profiles=dev spring-boot:run {{args}}

# Run the leetcode auth stealer service
auth-dev *args:
cd ./internal/leetcode-auth-stealer-service && uv sync && GRPC_REFLECTION=true GRPC_VERBOSITY=ERROR uv run src/server.py

# Generate the leetcode auth stealer gRPC stubs for the main codebloom service
auth-gen *args:
cd ./internal/leetcode-auth-stealer-service && uv sync && uv run src/generate.py

# Run the backend Spring server with an exposed debugger at :5005
backend-dev-debug *args:
dotenvx run -- ./mvnw \
Expand Down Expand Up @@ -75,10 +83,10 @@ email-dev *args:
email-gen *args:
cd email && bash email.sh {{args}}

# Run the dev servers (backend & frontend)
# Run the dev servers (backend & frontend & auth service)
dev *args:
npx concurrently "just backend-dev" "just frontend-dev" {{args}}
npx concurrently "just backend-dev" "just frontend-dev" "just auth-dev" {{args}}

# Run the dev servers (backend & frontend) but the backend will launch a debugger server.
# Run the dev servers (backend & frontend & auth service) but the backend will launch a debugger server.
devd *args:
npx concurrently "just backend-dev-debug" "just frontend-dev" {{args}}
npx concurrently "just backend-dev-debug" "just frontend-dev" "just-auth-dev" {{args}}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
-- at most one active (INCOMPLETE/PROCESSING) job may exist per question.
-- existing duplicates must be resolved first, otherwise the index creation fails.
WITH ranked AS (
SELECT
id,
ROW_NUMBER() OVER (
PARTITION BY "questionId"
ORDER BY
(status = 'PROCESSING') DESC,
"processedAt" DESC NULLS LAST,
"createdAt" DESC
) AS rn
FROM "Job"
WHERE status IN ('INCOMPLETE', 'PROCESSING')
)
UPDATE "Job" j
SET
status = 'COMPLETE',
"completedAt" = NOW()
FROM ranked r
WHERE j.id = r.id
AND r.rn > 1;

CREATE UNIQUE INDEX IF NOT EXISTS "Job_questionId_active_unique"
ON "Job" ("questionId")
WHERE status IN ('INCOMPLETE', 'PROCESSING');
Loading
Loading