chore: initial commit — monorepo ReadaBook (API NestJS, web PWA, Docker)
This commit is contained in:
31
apps/api/src/progress/progress.controller.ts
Normal file
31
apps/api/src/progress/progress.controller.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import { Body, Controller, Get, Param, Put, UseGuards } from "@nestjs/common";
|
||||
import { UpdateProgressDto, UpdateProgressSchema } from "@readabook/shared";
|
||||
import { AuthGuard } from "../auth/auth.guard.js";
|
||||
import { CurrentUser, CurrentUserParam } from "../auth/current-user.js";
|
||||
import { ZodValidationPipe } from "../common/zod-validation.pipe.js";
|
||||
import { ProgressService } from "./progress.service.js";
|
||||
|
||||
@Controller("progress")
|
||||
@UseGuards(AuthGuard)
|
||||
export class ProgressController {
|
||||
constructor(private readonly progress: ProgressService) {}
|
||||
|
||||
@Get("continue")
|
||||
continue(@CurrentUserParam() user: CurrentUser) {
|
||||
return this.progress.continueReading(user.id);
|
||||
}
|
||||
|
||||
@Get(":bookId")
|
||||
get(@CurrentUserParam() user: CurrentUser, @Param("bookId") bookId: string) {
|
||||
return this.progress.get(user.id, Number(bookId));
|
||||
}
|
||||
|
||||
@Put(":bookId")
|
||||
update(
|
||||
@CurrentUserParam() user: CurrentUser,
|
||||
@Param("bookId") bookId: string,
|
||||
@Body(new ZodValidationPipe(UpdateProgressSchema)) body: UpdateProgressDto
|
||||
) {
|
||||
return this.progress.upsert(user.id, Number(bookId), body);
|
||||
}
|
||||
}
|
||||
12
apps/api/src/progress/progress.module.ts
Normal file
12
apps/api/src/progress/progress.module.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { Module } from "@nestjs/common";
|
||||
import { AuthModule } from "../auth/auth.module.js";
|
||||
import { DatabaseModule } from "../database/database.module.js";
|
||||
import { ProgressController } from "./progress.controller.js";
|
||||
import { ProgressService } from "./progress.service.js";
|
||||
|
||||
@Module({
|
||||
imports: [AuthModule, DatabaseModule],
|
||||
controllers: [ProgressController],
|
||||
providers: [ProgressService]
|
||||
})
|
||||
export class ProgressModule {}
|
||||
67
apps/api/src/progress/progress.service.ts
Normal file
67
apps/api/src/progress/progress.service.ts
Normal file
@ -0,0 +1,67 @@
|
||||
import { Injectable, NotFoundException } from "@nestjs/common";
|
||||
import { desc, eq, sql } from "drizzle-orm";
|
||||
import { UpdateProgressDto } from "@readabook/shared";
|
||||
import { DatabaseService } from "../database/database.service.js";
|
||||
import { books, progress } from "../database/schema.js";
|
||||
|
||||
@Injectable()
|
||||
export class ProgressService {
|
||||
constructor(private readonly database: DatabaseService) {}
|
||||
|
||||
upsert(userId: number, bookId: number, input: UpdateProgressDto) {
|
||||
const book = this.database.db.select({ id: books.id }).from(books).where(eq(books.id, bookId)).get();
|
||||
if (!book) {
|
||||
throw new NotFoundException("Book not found");
|
||||
}
|
||||
const now = this.database.now();
|
||||
this.database.sqlite
|
||||
.prepare(
|
||||
`
|
||||
INSERT INTO progress(user_id, book_id, locator, percent, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT(user_id, book_id) DO UPDATE SET
|
||||
locator = excluded.locator,
|
||||
percent = excluded.percent,
|
||||
updated_at = excluded.updated_at
|
||||
`
|
||||
)
|
||||
.run(userId, bookId, input.locator, Math.round(input.percent), now, now);
|
||||
return this.get(userId, bookId);
|
||||
}
|
||||
|
||||
get(userId: number, bookId: number) {
|
||||
const row = this.database.db
|
||||
.select({
|
||||
bookId: progress.bookId,
|
||||
locator: progress.locator,
|
||||
percent: progress.percent,
|
||||
updatedAt: progress.updatedAt
|
||||
})
|
||||
.from(progress)
|
||||
.where(sql`${progress.userId} = ${userId} AND ${progress.bookId} = ${bookId}`)
|
||||
.get();
|
||||
if (!row) {
|
||||
throw new NotFoundException("Progress not found");
|
||||
}
|
||||
return row;
|
||||
}
|
||||
|
||||
continueReading(userId: number) {
|
||||
return this.database.db
|
||||
.select({
|
||||
book: books,
|
||||
progress: {
|
||||
bookId: progress.bookId,
|
||||
locator: progress.locator,
|
||||
percent: progress.percent,
|
||||
updatedAt: progress.updatedAt
|
||||
}
|
||||
})
|
||||
.from(progress)
|
||||
.innerJoin(books, eq(progress.bookId, books.id))
|
||||
.where(eq(progress.userId, userId))
|
||||
.orderBy(desc(progress.updatedAt))
|
||||
.limit(20)
|
||||
.all();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user