chore: initial commit — monorepo ReadaBook (API NestJS, web PWA, Docker)
This commit is contained in:
14
packages/shared/src/index.test.ts
Normal file
14
packages/shared/src/index.test.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
120
packages/shared/src/index.ts
Normal file
120
packages/shared/src/index.ts
Normal 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>;
|
||||
Reference in New Issue
Block a user