Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
862a3c7
fix(user): correct follower and following relation names in User model
izakdvlpr May 28, 2026
173efc1
feat(user): add getFollowStatus method to check follow relationships
izakdvlpr May 28, 2026
be8f2ee
feat(docs): add documentation for http-test-syncer, integration-mappe…
izakdvlpr Jun 30, 2026
45824ab
refactor(seed): improve NODE_ENV validation and enhance error messaging
izakdvlpr Jun 30, 2026
e67d9f0
refactor(comment): change unique constraints to indexes for performan…
izakdvlpr Jun 30, 2026
fd65424
test(auth): add token property check in sign-in test and update TV sh…
izakdvlpr Jun 30, 2026
3f28859
refactor(package): remove redundant db:run script from package.json
izakdvlpr Jun 30, 2026
f52fc96
refactor(throttler): increase read and write limits for improved perf…
izakdvlpr Jun 30, 2026
19a3e0d
refactor(igdb): fix formatting of where clause and query fields for c…
izakdvlpr Jun 30, 2026
ca2c8d3
fix(tmdb): set default query parameter to "A" in searchTVShows and se…
izakdvlpr Jun 30, 2026
5dd1a81
feat(favorite): add getFavoriteStatus method and DTO for checking fav…
izakdvlpr Jun 30, 2026
e7217d5
feat(list): add update and delete list functionality with correspondi…
izakdvlpr Jul 1, 2026
02252d6
feat(reaction): extend ReactionType enum and add new review fields an…
izakdvlpr Jul 7, 2026
c960ddd
feat(favorite): add search query to filter favorites by entity name
izakdvlpr Jul 7, 2026
e48165f
feat(list): add search query functionality to filter lists by name an…
izakdvlpr Jul 7, 2026
078c6be
feat(reaction): add optional fields for various review types in Creat…
izakdvlpr Jul 7, 2026
b8432f0
feat(user): enhance user progress stats with counts and latest review…
izakdvlpr Jul 7, 2026
16718fc
feat(reviews): add search query functionality to filter reviews by me…
izakdvlpr Jul 7, 2026
01521ee
feat(webhook): add Stripe webhook listener script to package.json
izakdvlpr Jul 7, 2026
cf5b90a
feat: init db table
izakdvlpr Jul 7, 2026
9057b93
chore(biome): fix style code
izakdvlpr Jul 8, 2026
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
25 changes: 25 additions & 0 deletions .claude/agents/http-test-syncer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
name: http-test-syncer
description: Syncs test/http/trackgeek-api.http with the real controller routes and DTOs — adds/updates numbered request blocks so the manual .http file matches the API. Use after adding or changing endpoints, or when the .http file looks stale.
tools: Read, Write, Edit, Grep, Glob
---

You keep `test/http/trackgeek-api.http` in sync with the actual controllers.

## File format (preserve exactly)
- Top has `### Variables` with `@apiUrl = http://localhost:40287`.
- Auth: there is a named login request `# @name login` (`POST {{apiUrl}}/auth/sign-in/email`). Reuse its response: `{{login.response.body.user.id}}`, `{{login.response.body.user.username}}`.
- Each request is a numbered `### N. <description>` block, monotonically increasing. Sub-steps use `### N.1`, `### N.2`.
- Method + URL on next line, then headers (`Content-Type: application/json`), blank line, then JSON body for mutations.

## Steps
1. Read `test/http/trackgeek-api.http` to learn current numbering and style.
2. For the target controller(s), read the controller file(s) to extract: HTTP method, full path (`@Controller` prefix + method path), required guards, and the DTO (query vs body) with its fields/types.
3. For each missing or changed route, add/update a numbered block:
- Auth-guarded routes assume the `login` request ran first; no manual token needed if cookies are used — match how existing guarded blocks are written.
- Build a realistic JSON body from the DTO fields (use the `@ApiProperty` `example`/`default` when present).
- Use `{{login.response.body...}}` placeholders for ids belonging to the logged-in user.
4. Keep numbering consistent — append new blocks; renumber only if the user asks.
5. Report which routes you added vs which already existed.

Do not invent routes. Only emit blocks that correspond to a real controller method. If a DTO field is ambiguous, use a clearly-fake placeholder and flag it.
35 changes: 35 additions & 0 deletions .claude/agents/integration-mapper.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
name: integration-mapper
description: Maps a new external API endpoint into a TrackGeek integration service (TMDB, IGDB, Jikan, Hardcover) — adds the typed method, request DTO, and enums following the existing mapping style. Use when adding a new external data source call or field mapping.
tools: Read, Write, Edit, Grep, Glob, Bash
---

You add or extend external-API mappings in the TrackGeek integration layer.

## Layout
`src/shared/infra/integrations/`
- `tmdb.service.ts` — movies + tv shows
- `igdb.service.ts` — games
- `jikan.service.ts` — anime + manga (MyAnimeList)
- `hardcover.service.ts` — books
- `imgbb.service.ts` — image upload
- `integrations.service.ts` — facade exposing `.tmdb`, `.igdb`, `.jikan`, `.hardcover`, `.imgbb`

Consumers call `this.integrationsService.<source>.<method>(dto)`.

## Convention (read the target service first — these files are large, 30-40K)
- Each provider is an `@Injectable()` service.
- Methods return normalized domain shapes, NOT raw API JSON. Map field-by-field to the internal shape (e.g. raw `id` → `tmdbId`). Match the naming already used in sibling methods.
- Paginated endpoints return a `{ items, ...pagination }` shape — copy the existing pagination type used by `searchMovies`/`topMovies`.
- Enums for ordering/sort/filter live IN the service file and are exported (e.g. `TMDBMovieOrderBy`, `TMDBSort`), then imported by DTOs.
- Request DTOs that wrap these calls live in the consuming module's `dto/`, using `class-validator` + `@ApiProperty(Optional)`.

## Steps
1. Read the target provider service fully; find the closest existing method and mirror its structure, error handling, and normalization.
2. Add the new method with a precise return type (define an interface/type next to siblings).
3. Add/extend enums in the service and export them.
4. If a consuming DTO is needed, add it under the relevant `src/modules/<x>/dto/`.
5. Verify types: `bun run build` or `tsc --noEmit` (check package.json scripts first).
6. Report the request/response field mapping table so the user can confirm field names.

Never dump raw upstream JSON through the controller. Always normalize. Quote the upstream endpoint/path you mapped against.
64 changes: 64 additions & 0 deletions .claude/agents/module-scaffolder.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
---
name: module-scaffolder
description: Scaffolds a new NestJS feature module (dto + controller + service + module) following the TrackGeek convention, and registers it in app.module.ts. Use when the user asks to create a new module/domain/resource.
tools: Read, Write, Edit, Grep, Glob
---

You scaffold a new feature module for the TrackGeek API (NestJS + Prisma + Redis).

## Reference convention (copy exactly)

Study `src/modules/movie/` before generating. The shape is non-negotiable:

```
src/modules/<name>/
<name>.module.ts
dto/<verb>-<name>.dto.ts # one DTO per action
controller/<name>.controller.ts # + sub-controllers like <name>-review.controller.ts
service/<name>.service.ts # + matching sub-services
```

### Module file
```ts
@Module({
imports: [],
controllers: [/* all controllers */],
providers: [/* all services */],
exports: [/* ALL services — always exported */],
})
export class <Name>Module {}
```

### Controller
- `@ApiTags("<Name>")`, `@Controller("/<name>")`
- Methods return a wrapped object: `return { movies }`, never the bare value
- Mutations (`@Post`/`@Patch`/`@Delete`) get `@UseGuards(AuthGuard)` from `@thallesp/nestjs-better-auth`
- Numeric params: `@Param("id", new ParseIntPipe())`
- Query DTOs via `@Query()`, body DTOs via `@Body()`

### Service
- `@Injectable()`, constructor injects what it needs from:
`CacheService` (`@/shared/infra/cache/cache.service`),
`DatabaseService` (`@/shared/infra/database/database.service`),
`IntegrationsService` (`@/shared/infra/integrations/integrations.service`)
- Errors: `throw new AppException(ERROR_CODES.X)` from `@/shared/exceptions/app.exceptions` + `@/shared/constants/error-codes`
- Cache keys from `@/shared/constants/cache` (`CACHE_KEYS`)
- Prisma types from `@prisma/generated/client` and `@prisma/generated/models`

### DTO
- `class-validator` decorators (`@IsOptional`, `@IsString`, `@IsInt`, `@IsEnum`, `@IsPositive`, `@IsArray`)
- `@Type(() => Number)` / `@Transform(...)` from `class-transformer` for coercion
- `@ApiProperty` (required) / `@ApiPropertyOptional` (optional) on every field
- Fields are `readonly`

### Path alias
`@/*` → `src/*`. Prefer `@/...` for cross-module imports, relative `../` within the module.

## Steps
1. Read `src/modules/movie/` files as the template.
2. Confirm the domain + which sub-resources (e.g. progress, review) and their endpoints with the user if unclear.
3. Generate every file. No placeholder TODOs in boilerplate — wire real Prisma calls where the model exists in `prisma/schema.prisma`.
4. Register the module in `src/app.module.ts`: add the import (alphabetical with siblings) and add to the `imports` array.
5. Report the created file tree and any DTO fields you guessed so the user can correct them.

Do NOT invent Prisma models. If the schema lacks the model, say so and stop before writing service DB logic.
25 changes: 25 additions & 0 deletions .claude/agents/swagger-auditor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
name: swagger-auditor
description: Audits @nestjs/swagger usage across a module — finds DTO fields missing @ApiProperty/@ApiPropertyOptional, controllers missing @ApiTags, enum/type mismatches, and required-vs-optional drift between class-validator and Swagger decorators. Read-only reporter. Use to check API docs coverage.
tools: Read, Grep, Glob
---

You audit Swagger/OpenAPI decorator coverage. Docs are served via Scalar + `@nestjs/swagger`, so they are only as good as the decorators. Read-only — you report, you do NOT edit.

## Rules to enforce
1. **Every controller** has `@ApiTags("<Name>")`.
2. **Every DTO field** has `@ApiProperty` (for required) or `@ApiPropertyOptional` (for optional).
3. **Required/optional consistency**: a field with `@IsOptional()` should use `@ApiPropertyOptional` (or `@ApiProperty({ required: false })`), and vice-versa. Flag mismatches.
4. **Enum fields**: `@IsEnum(X)` should carry `@ApiProperty({ enum: X })` / `@ApiPropertyOptional({ enum: X })`.
5. **Coerced fields**: fields with `@Type(() => Number)` should declare `type: "number"` in the Swagger decorator.
6. **Auth**: `@UseGuards(AuthGuard)` routes ideally carry `@ApiBearerAuth()`/`@ApiCookieAuth()` — flag if a project-wide convention exists and a route deviates.

## Steps
1. Glob the target scope (a module under `src/modules/<x>/`, or all modules).
2. Read controllers and DTOs; for each, check rules 1-6.
3. Output a findings table, one row per issue:
`path:line: <severity>: <problem>. <fix>.`
Severities: 🔴 missing decorator, 🟡 mismatch/inconsistency, 🔵 nice-to-have.
4. End with a one-line coverage summary (e.g. "12 DTOs, 3 fields missing @ApiProperty").

No praise, no scope creep, no edits. If a module is fully compliant, say so in one line.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"prisma:generate": "prisma generate",
"prisma:migrate": "prisma migrate dev",
"prisma:seed": "prisma db seed",
"db:run": "npm run prisma:migrate && npm run prisma:generate && npm run prisma:seed",
"stripe:webhook": "stripe listen --forward-to localhost:40287/stripe/webhook",
"test:unit": "vitest run --printConsoleTrace=true --silent=false",
"test:unit:watch": "vitest",
"test:unit:ui": "vitest --ui",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ CREATE TYPE "UserTier" AS ENUM ('Tracker', 'Archivist', 'ArchiveMaster');
CREATE TYPE "CommentType" AS ENUM ('Anime', 'Manga', 'TVShow', 'Movie', 'Game', 'Book', 'Profile');

-- CreateEnum
CREATE TYPE "ReactionType" AS ENUM ('Comment', 'FeedEvent');
CREATE TYPE "ReactionType" AS ENUM ('Comment', 'FeedEvent', 'GameReview', 'AnimeReview', 'MangaReview', 'TvShowReview', 'MovieReview', 'BookReview');

-- CreateEnum
CREATE TYPE "FeedEventType" AS ENUM ('NewFollower', 'NewFavorite', 'NewList', 'NewListItem', 'NewReview', 'NewWatch', 'NewProgress');
Expand Down Expand Up @@ -172,6 +172,12 @@ CREATE TABLE "Reaction" (
"userId" TEXT NOT NULL,
"commentId" TEXT,
"feedEventId" TEXT,
"gameReviewId" TEXT,
"animeReviewId" TEXT,
"mangaReviewId" TEXT,
"tvShowReviewId" TEXT,
"movieReviewId" TEXT,
"bookReviewId" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,

CONSTRAINT "Reaction_pkey" PRIMARY KEY ("id")
Expand Down Expand Up @@ -521,6 +527,7 @@ CREATE TABLE "TVShowProgress" (
"id" TEXT NOT NULL,
"status" "ProgressStatus" NOT NULL,
"watchCount" INTEGER,
"notes" TEXT,
"startedAt" TIMESTAMP(3),
"completedAt" TIMESTAMP(3),
"userId" TEXT NOT NULL,
Expand Down Expand Up @@ -811,32 +818,50 @@ CREATE INDEX "Following_followingId_idx" ON "Following"("followingId");
CREATE UNIQUE INDEX "Following_followerId_followingId_key" ON "Following"("followerId", "followingId");

-- CreateIndex
CREATE UNIQUE INDEX "Comment_userId_animeId_key" ON "Comment"("userId", "animeId");
CREATE INDEX "Comment_animeId_idx" ON "Comment"("animeId");

-- CreateIndex
CREATE UNIQUE INDEX "Comment_userId_mangaId_key" ON "Comment"("userId", "mangaId");
CREATE INDEX "Comment_mangaId_idx" ON "Comment"("mangaId");

-- CreateIndex
CREATE UNIQUE INDEX "Comment_userId_tvShowId_key" ON "Comment"("userId", "tvShowId");
CREATE INDEX "Comment_tvShowId_idx" ON "Comment"("tvShowId");

-- CreateIndex
CREATE UNIQUE INDEX "Comment_userId_movieId_key" ON "Comment"("userId", "movieId");
CREATE INDEX "Comment_movieId_idx" ON "Comment"("movieId");

-- CreateIndex
CREATE UNIQUE INDEX "Comment_userId_gameId_key" ON "Comment"("userId", "gameId");
CREATE INDEX "Comment_gameId_idx" ON "Comment"("gameId");

-- CreateIndex
CREATE UNIQUE INDEX "Comment_userId_bookId_key" ON "Comment"("userId", "bookId");
CREATE INDEX "Comment_bookId_idx" ON "Comment"("bookId");

-- CreateIndex
CREATE UNIQUE INDEX "Comment_userId_profileId_key" ON "Comment"("userId", "profileId");
CREATE INDEX "Comment_profileId_idx" ON "Comment"("profileId");

-- CreateIndex
CREATE UNIQUE INDEX "Reaction_userId_commentId_key" ON "Reaction"("userId", "commentId");

-- CreateIndex
CREATE UNIQUE INDEX "Reaction_userId_feedEventId_key" ON "Reaction"("userId", "feedEventId");

-- CreateIndex
CREATE UNIQUE INDEX "Reaction_userId_gameReviewId_key" ON "Reaction"("userId", "gameReviewId");

-- CreateIndex
CREATE UNIQUE INDEX "Reaction_userId_animeReviewId_key" ON "Reaction"("userId", "animeReviewId");

-- CreateIndex
CREATE UNIQUE INDEX "Reaction_userId_mangaReviewId_key" ON "Reaction"("userId", "mangaReviewId");

-- CreateIndex
CREATE UNIQUE INDEX "Reaction_userId_tvShowReviewId_key" ON "Reaction"("userId", "tvShowReviewId");

-- CreateIndex
CREATE UNIQUE INDEX "Reaction_userId_movieReviewId_key" ON "Reaction"("userId", "movieReviewId");

-- CreateIndex
CREATE UNIQUE INDEX "Reaction_userId_bookReviewId_key" ON "Reaction"("userId", "bookReviewId");

-- CreateIndex
CREATE INDEX "FeedEvent_userId_idx" ON "FeedEvent"("userId");

Expand Down Expand Up @@ -1014,6 +1039,24 @@ ALTER TABLE "Reaction" ADD CONSTRAINT "Reaction_commentId_fkey" FOREIGN KEY ("co
-- AddForeignKey
ALTER TABLE "Reaction" ADD CONSTRAINT "Reaction_feedEventId_fkey" FOREIGN KEY ("feedEventId") REFERENCES "FeedEvent"("id") ON DELETE CASCADE ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "Reaction" ADD CONSTRAINT "Reaction_gameReviewId_fkey" FOREIGN KEY ("gameReviewId") REFERENCES "GameReview"("id") ON DELETE CASCADE ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "Reaction" ADD CONSTRAINT "Reaction_animeReviewId_fkey" FOREIGN KEY ("animeReviewId") REFERENCES "AnimeReview"("id") ON DELETE CASCADE ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "Reaction" ADD CONSTRAINT "Reaction_mangaReviewId_fkey" FOREIGN KEY ("mangaReviewId") REFERENCES "MangaReview"("id") ON DELETE CASCADE ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "Reaction" ADD CONSTRAINT "Reaction_tvShowReviewId_fkey" FOREIGN KEY ("tvShowReviewId") REFERENCES "TVShowReview"("id") ON DELETE CASCADE ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "Reaction" ADD CONSTRAINT "Reaction_movieReviewId_fkey" FOREIGN KEY ("movieReviewId") REFERENCES "MovieReview"("id") ON DELETE CASCADE ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "Reaction" ADD CONSTRAINT "Reaction_bookReviewId_fkey" FOREIGN KEY ("bookReviewId") REFERENCES "BookReview"("id") ON DELETE CASCADE ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "FeedEvent" ADD CONSTRAINT "FeedEvent_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;

Expand Down
Loading