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,14 @@
import { describe, expect, it } from "vitest";
import { BootstrapAdminSchema, BookQuerySchema } from "./index.js";
describe("shared contracts", () => {
it("validates bootstrap admin payloads", () => {
expect(BootstrapAdminSchema.parse({ email: "admin@example.com", password: "password123" }).email).toBe(
"admin@example.com"
);
});
it("normalizes catalogue query defaults", () => {
expect(BookQuerySchema.parse({}).limit).toBe(50);
});
});

View File

@ -0,0 +1,120 @@
import { z } from "zod";
export const RoleSchema = z.enum(["admin", "user"]);
export type Role = z.infer<typeof RoleSchema>;
export const UserSchema = z.object({
id: z.number().int().positive(),
email: z.string().email(),
name: z.string().nullable(),
role: RoleSchema,
createdAt: z.string()
});
export type UserDto = z.infer<typeof UserSchema>;
export const BootstrapAdminSchema = z.object({
email: z.string().email(),
password: z.string().min(8),
name: z.string().min(1).max(120).optional()
});
export type BootstrapAdminDto = z.infer<typeof BootstrapAdminSchema>;
export const LoginSchema = z.object({
email: z.string().email(),
password: z.string().min(1)
});
export type LoginDto = z.infer<typeof LoginSchema>;
export const CreateUserSchema = z.object({
email: z.string().email(),
password: z.string().min(8),
name: z.string().min(1).max(120).optional(),
role: RoleSchema.default("user")
});
export type CreateUserDto = z.infer<typeof CreateUserSchema>;
export const UpdateUserSchema = z.object({
email: z.string().email().optional(),
password: z.string().min(8).optional(),
name: z.string().min(1).max(120).nullable().optional(),
role: RoleSchema.optional()
});
export type UpdateUserDto = z.infer<typeof UpdateUserSchema>;
export const LibrarySchema = z.object({
id: z.number().int().positive(),
name: z.string(),
path: z.string(),
enabled: z.boolean(),
createdAt: z.string(),
updatedAt: z.string()
});
export type LibraryDto = z.infer<typeof LibrarySchema>;
export const CreateLibrarySchema = z.object({
name: z.string().min(1).max(160),
path: z.string().min(1),
enabled: z.boolean().default(true)
});
export type CreateLibraryDto = z.infer<typeof CreateLibrarySchema>;
export const UpdateLibrarySchema = z.object({
name: z.string().min(1).max(160).optional(),
path: z.string().min(1).optional(),
enabled: z.boolean().optional()
});
export type UpdateLibraryDto = z.infer<typeof UpdateLibrarySchema>;
export const BookSchema = z.object({
id: z.number().int().positive(),
libraryId: z.number().int().positive(),
title: z.string(),
author: z.string().nullable(),
description: z.string().nullable(),
isbn: z.string().nullable(),
language: z.string().nullable(),
publisher: z.string().nullable(),
publishedDate: z.string().nullable(),
format: z.enum(["epub", "pdf"]),
filePath: z.string(),
coverPath: z.string().nullable(),
fileSize: z.number().int().nonnegative(),
fileMtime: z.string(),
createdAt: z.string(),
updatedAt: z.string()
});
export type BookDto = z.infer<typeof BookSchema>;
export const BookQuerySchema = z.object({
q: z.string().optional(),
format: z.enum(["epub", "pdf"]).optional(),
libraryId: z.coerce.number().int().positive().optional(),
limit: z.coerce.number().int().min(1).max(100).default(50),
offset: z.coerce.number().int().min(0).default(0)
});
export type BookQueryDto = z.infer<typeof BookQuerySchema>;
export const ProgressSchema = z.object({
bookId: z.number().int().positive(),
locator: z.string().min(1),
percent: z.number().min(0).max(100),
updatedAt: z.string()
});
export type ProgressDto = z.infer<typeof ProgressSchema>;
export const UpdateProgressSchema = z.object({
locator: z.string().min(1),
percent: z.number().min(0).max(100)
});
export type UpdateProgressDto = z.infer<typeof UpdateProgressSchema>;
export const JobSchema = z.object({
id: z.number().int().positive(),
type: z.string(),
status: z.enum(["queued", "running", "succeeded", "failed"]),
detail: z.string().nullable(),
error: z.string().nullable(),
createdAt: z.string(),
updatedAt: z.string()
});
export type JobDto = z.infer<typeof JobSchema>;