Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 4 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,5 +100,7 @@ v1 отдаётся с корня, v2 — с префиксом `/v2`. v1 нам
- **Тесты не читают `.env`**: `dotenv` в `src/plugins/env.ts` отключён при
`NODE_ENV=test`, переменные приходят из `vitest.config.ts`. Иначе прогон
зависел бы от локального файла разработчика.
- **База**: in-memory SQLite (`src/plugins/drizzle.ts`), пересоздаётся при каждом
запуске. Для постоянного хранения нужен файл или настоящая СУБД.
- **База**: PGlite (`src/plugins/drizzle.ts`) — postgres в wasm, внутри процесса
и в памяти, пересоздаётся при каждом запуске. Взят ради нативных типов:
`timestamptz` в схеме это время, а не integer с кодеком поверх. Для
постоянного хранения нужен каталог данных или отдельный сервер postgres.
13 changes: 7 additions & 6 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,8 @@
# TypeScript сам, снимая типы. Поэтому образ копирует исходники как есть.
FROM node:26-slim AS deps

# better-sqlite3 — нативный модуль, и под slim готового бинаря может не быть:
# без тулчейна установка падает на сборке.
RUN apt-get update \
&& apt-get install -y --no-install-recommends python3 make g++ ca-certificates \
&& rm -rf /var/lib/apt/lists/*
# Тулчейна для нативных модулей больше нет: PGlite — wasm, собирать под
# платформу нечего.

# corepack из образов node 26 убран, поэтому pnpm ставится явно — версией из
# packageManager, чтобы лок-файл читался тем же, что и локально.
Expand Down Expand Up @@ -34,4 +31,8 @@ COPY --chown=node:node tsp-output ./tsp-output
EXPOSE 3000

# Через node_modules напрямую, а не pnpm exec: в рантайме pnpm не нужен.
CMD ["node", "node_modules/fastify-cli/cli.js", "start", "-l", "info", "-a", "0.0.0.0", "src/app.ts"]
#
# --plugin-timeout поднят с дефолтных 10 секунд: drizzle поднимает PGlite и
# прогоняет миграции, и на холодном старте в контейнере с урезанным CPU это
# укладывается не всегда. Без запаса контейнер просто не поднимется.
CMD ["node", "node_modules/fastify-cli/cli.js", "start", "-l", "info", "-a", "0.0.0.0", "--plugin-timeout", "60000", "src/app.ts"]
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ migration-generate:
# check вызывается флагами, а не через конфиг: читая drizzle.config.ts, он
# принимает dialect за параметр AWS Data API и падает (drizzle-kit 0.31).
migration-check:
pnpm exec drizzle-kit check --dialect sqlite --out ./drizzle
pnpm exec drizzle-kit check --dialect postgresql --out ./drizzle
pnpm exec drizzle-kit generate
@test -z "$$(git status --porcelain drizzle)" || { \
echo "Схема изменилась без миграции — запустите make migration-generate:"; \
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ REST API на [Fastify](https://fastify.dev/), собранный «по-взр
проверка запросов тоже не пишутся руками. `make generate-check` в CI не даёт
сгенерированному разойтись со спекой.
- **База через [Drizzle](https://orm.drizzle.team/)**: схема в `src/db/schema.ts`,
миграции генерируются по ней.
миграции генерируются по ней. Под ней [PGlite](https://pglite.dev/) — postgres
в wasm, внутри процесса и в памяти: отдельного сервиса не нужно, а типы
нативные, `timestamptz` в схеме это время, а не число с кодеком поверх.
- **Валидация входа** отдельным слоем в `src/validators/`, а не внутри обработчика.
- **Две версии API из одной спеки.** `@added(Versions.v2)` в TypeSpec — и v2
получает поле, которого нет в v1; каждая версия отдаётся со своим документом
Expand Down
4 changes: 2 additions & 2 deletions compose.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Локальный запуск собранного образа. Базы отдельным сервисом нет: она
# in-memory внутри процесса и пересоздаётся при каждом старте.
# Локальный запуск собранного образа. Базы отдельным сервисом нет: PGlite —
# postgres в wasm — живёт в памяти процесса и пересоздаётся при каждом старте.
services:
api:
build: .
Expand Down
2 changes: 1 addition & 1 deletion drizzle.config.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { defineConfig } from "drizzle-kit";

export default defineConfig({
dialect: "sqlite",
dialect: "postgresql",
schema: "./src/db/schema.ts",
// out: "./drizzle",
});
31 changes: 31 additions & 0 deletions drizzle/0000_nasty_marvel_zombies.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
CREATE TABLE "course_lessons" (
"id" integer PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY (sequence name "course_lessons_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1),
"name" text NOT NULL,
"courseId" integer NOT NULL,
"body" text NOT NULL,
"created_at" timestamp with time zone NOT NULL,
"updated_at" timestamp with time zone NOT NULL
);
--> statement-breakpoint
CREATE TABLE "courses" (
"id" integer PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY (sequence name "courses_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1),
"name" text NOT NULL,
"creator_id" integer NOT NULL,
"description" text NOT NULL,
"created_at" timestamp with time zone NOT NULL,
"updated_at" timestamp with time zone NOT NULL
);
--> statement-breakpoint
CREATE TABLE "users" (
"id" integer PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY (sequence name "users_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1),
"full_name" text,
"email" text NOT NULL,
"password_digest" text NOT NULL,
"phone" text,
"created_at" timestamp with time zone NOT NULL,
"updated_at" timestamp with time zone NOT NULL,
CONSTRAINT "users_email_unique" UNIQUE("email")
);
--> statement-breakpoint
ALTER TABLE "course_lessons" ADD CONSTRAINT "course_lessons_courseId_courses_id_fk" FOREIGN KEY ("courseId") REFERENCES "public"."courses"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "courses" ADD CONSTRAINT "courses_creator_id_users_id_fk" FOREIGN KEY ("creator_id") REFERENCES "public"."users"("id") ON DELETE restrict ON UPDATE no action;
27 changes: 0 additions & 27 deletions drizzle/0000_omniscient_warbound.sql

This file was deleted.

1 change: 0 additions & 1 deletion drizzle/0001_friendly_forge.sql

This file was deleted.

48 changes: 0 additions & 48 deletions drizzle/0002_handy_paladin.sql

This file was deleted.

28 changes: 0 additions & 28 deletions drizzle/0003_common_sabra.sql

This file was deleted.

1 change: 0 additions & 1 deletion drizzle/0004_giant_hammerhead.sql

This file was deleted.

28 changes: 0 additions & 28 deletions drizzle/0005_curious_joshua_kane.sql

This file was deleted.

Loading
Loading