From e9fa6616faf4c9985f21addc851f5e4d74dc8879 Mon Sep 17 00:00:00 2001 From: mijinummi Date: Mon, 23 Feb 2026 16:50:00 +0100 Subject: [PATCH 1/3] ci: add PR validation job to enforce title/description (#234) --- .github/workflows/ci-cd.yml | 35 +++++++++++++++++++++++++++++++++++ CONTRIBUTING.md | 2 ++ 2 files changed, 37 insertions(+) create mode 100644 .github/workflows/ci-cd.yml diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml new file mode 100644 index 0000000..39f0e4e --- /dev/null +++ b/.github/workflows/ci-cd.yml @@ -0,0 +1,35 @@ +name: CI/CD + +on: + pull_request: + types: [opened, edited, synchronize] + +jobs: + validate-pr: + runs-on: ubuntu-latest + steps: + - name: Validate PR title + uses: amannn/action-semantic-pull-request@v5 + with: + types: | + feat + fix + docs + chore + test + refactor + ci + requireScope: false + + - name: Validate PR description + run: | + if ! grep -qE ".{20,}" <<< "${{ github.event.pull_request.body }}"; then + echo "❌ PR description too short. Please provide context." + exit 1 + fi + + build-and-deploy: + needs: validate-pr # enforce validation before build + runs-on: ubuntu-latest + steps: + # existing build steps here diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0db5929..e361772 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -33,3 +33,5 @@ npm --workspace backend exec -- tsc --noEmit -p tsconfig.json ## Branch Protection main and develop require status checks: lint-imports, build, type-check. Require branches to be up-to-date before merging. + + From 19e52d61d77017325ba0e629d971c072ee5988f9 Mon Sep 17 00:00:00 2001 From: mijinummi Date: Mon, 23 Feb 2026 16:50:57 +0100 Subject: [PATCH 2/3] docs(contributing): update PR standards section (#234) --- CONTRIBUTING.md | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e361772..833df93 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -35,3 +35,41 @@ main and develop require status checks: lint-imports, build, type-check. Require branches to be up-to-date before merging. +## Pull Request Standards + +To maintain a clean commit history and make reviews efficient, all pull requests must meet the following requirements: + +### Branching Convention +- Use descriptive branch names: + - `feature/your-feature-name` + - `fix/issue-number` + - `chore/tooling-update` +- Avoid vague names like `update` or `patch`. + +### PR Title +- Must follow **Conventional Commits** style: + - Format: `(optional-scope): short description (#issue-number)` + - Allowed types: `feat`, `fix`, `docs`, `chore`, `test`, `refactor`, `ci` +- Examples: + - `fix(streaks): use user timezone for date strings (#241)` + - `feat(auth): add refresh token support (#250)` + - `docs(contributing): clarify PR standards (#234)` + +### PR Description +- Must provide enough context for maintainers to understand the change without reading every line of code. +- Minimum requirements: + - **Problem**: What issue does this PR solve? + - **Solution**: How was it solved? + - **Acceptance Criteria**: What conditions prove the fix works? + - **Testing Notes**: How was it tested? +- Avoid minimal descriptions like only writing `Closes #22`. + +### CI/CD Enforcement +- The CI pipeline will automatically reject PRs that: + - Have non-compliant titles (e.g., `update`, `fix bug`). + - Have descriptions shorter than 20 characters or missing context. +- The `build-and-deploy` job depends on PR validation, so failing validation will block merges. + +--- + +By following these standards, contributors ensure their PRs are clear, maintainable, and easy to review. From aa999186566c4b86de0c079a4a856ff1ea32e010 Mon Sep 17 00:00:00 2001 From: mijinummi Date: Mon, 23 Feb 2026 17:06:03 +0100 Subject: [PATCH 3/3] feat(puzzles): add API client, types, and hooks (#253) --- frontend/hooks/usePuzzles.ts | 30 ++++++++++++++++++++++++++++++ frontend/lib/api/puzzleApi.ts | 22 ++++++++++++++++++++++ frontend/lib/types/puzzles.ts | 16 ++++++++++++++++ package-lock.json | 27 +++++++++++++++++++++++++++ package.json | 1 + 5 files changed, 96 insertions(+) create mode 100644 frontend/hooks/usePuzzles.ts create mode 100644 frontend/lib/api/puzzleApi.ts create mode 100644 frontend/lib/types/puzzles.ts diff --git a/frontend/hooks/usePuzzles.ts b/frontend/hooks/usePuzzles.ts new file mode 100644 index 0000000..b87dd07 --- /dev/null +++ b/frontend/hooks/usePuzzles.ts @@ -0,0 +1,30 @@ +// frontend/hooks/usePuzzles.ts +import { useQuery } from '@tanstack/react-query'; +import { + getPuzzles, + getPuzzleById, + getDailyQuestPuzzles, +} from '../lib/api/puzzleApi'; +import { PuzzleQueryParams, Puzzle } from '../lib/types/puzzles'; + +export function usePuzzles(query: PuzzleQueryParams) { + return useQuery({ + queryKey: ['puzzles', query], + queryFn: () => getPuzzles(query), + }); +} + +export function usePuzzle(id: string) { + return useQuery({ + queryKey: ['puzzle', id], + queryFn: () => getPuzzleById(id), + enabled: !!id, + }); +} + +export function useDailyQuestPuzzles() { + return useQuery({ + queryKey: ['dailyQuestPuzzles'], + queryFn: getDailyQuestPuzzles, + }); +} diff --git a/frontend/lib/api/puzzleApi.ts b/frontend/lib/api/puzzleApi.ts new file mode 100644 index 0000000..b7891ed --- /dev/null +++ b/frontend/lib/api/puzzleApi.ts @@ -0,0 +1,22 @@ +// frontend/lib/api/puzzleApi.ts +import axios from 'axios'; +import { Puzzle, PuzzleQueryParams } from '../types/puzzles'; + +const api = axios.create({ + baseURL: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000', +}); + +export async function getPuzzles(query: PuzzleQueryParams): Promise { + const response = await api.get('/puzzles', { params: query }); + return response.data; +} + +export async function getPuzzleById(id: string): Promise { + const response = await api.get(`/puzzles/${id}`); + return response.data; +} + +export async function getDailyQuestPuzzles(): Promise { + const response = await api.get('/puzzles/daily-quest'); + return response.data; +} diff --git a/frontend/lib/types/puzzles.ts b/frontend/lib/types/puzzles.ts new file mode 100644 index 0000000..fdca453 --- /dev/null +++ b/frontend/lib/types/puzzles.ts @@ -0,0 +1,16 @@ +export interface Puzzle { + id: string; + title: string; + description: string; + type: 'logic' | 'coding' | 'blockchain'; + difficulty: 'easy' | 'medium' | 'hard'; + categoryId: string; + timeLimit?: number; +} + +export interface PuzzleQueryParams { + categoryId?: string; + difficulty?: string; + page?: number; + limit?: number; +} diff --git a/package-lock.json b/package-lock.json index 14664cc..2f5e5da 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "dependencies": { "@nestjs/common": "^11.1.14", "@nestjs/core": "^11.1.14", + "@tanstack/react-query": "^5.90.21", "minimatch": "^10.1.1", "reflect-metadata": "^0.2.2", "rxjs": "^7.8.2", @@ -5082,6 +5083,32 @@ "tailwindcss": "4.1.18" } }, + "node_modules/@tanstack/query-core": { + "version": "5.90.20", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.90.20.tgz", + "integrity": "sha512-OMD2HLpNouXEfZJWcKeVKUgQ5n+n3A2JFmBaScpNDUqSrQSjiveC7dKMe53uJUg1nDG16ttFPz2xfilz6i2uVg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.90.21", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.21.tgz", + "integrity": "sha512-0Lu6y5t+tvlTJMTO7oh5NSpJfpg/5D41LlThfepTixPYkJ0sE2Jj0m0f6yYqujBwIXlId87e234+MxG3D3g7kg==", + "license": "MIT", + "dependencies": { + "@tanstack/query-core": "5.90.20" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18 || ^19" + } + }, "node_modules/@tokenizer/inflate": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.4.1.tgz", diff --git a/package.json b/package.json index 46cd867..d0f8b60 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "dependencies": { "@nestjs/common": "^11.1.14", "@nestjs/core": "^11.1.14", + "@tanstack/react-query": "^5.90.21", "minimatch": "^10.1.1", "reflect-metadata": "^0.2.2", "rxjs": "^7.8.2",