chore: initial commit — monorepo ReadaBook (API NestJS, web PWA, Docker)

This commit is contained in:
Git Agent
2026-08-23 09:56:53 +02:00
commit 8f1140127f
79 changed files with 6456 additions and 0 deletions

View 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();
}
}