phucsd commited on
Commit
a4836dc
·
verified ·
1 Parent(s): 0676dc0

Deploy GitHub commit 302c19c3bb59a508ad8f0b239bf0fca864f37f70

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .dockerignore +13 -0
  2. .env.example +8 -0
  3. .github/workflows/deploy-hf.yml +41 -0
  4. .github/workflows/validation.yml +38 -0
  5. .gitignore +12 -0
  6. .prettierignore +6 -0
  7. Dockerfile +40 -0
  8. README.md +92 -5
  9. apps/server/package.json +28 -0
  10. apps/server/src/app.ts +71 -0
  11. apps/server/src/config/environment.ts +54 -0
  12. apps/server/src/config/environment.unit.test.ts +8 -0
  13. apps/server/src/database/create-database.ts +16 -0
  14. apps/server/src/database/database.ts +9 -0
  15. apps/server/src/database/sqlite-system-database.ts +63 -0
  16. apps/server/src/http/routes.integration.test.ts +79 -0
  17. apps/server/src/index.ts +37 -0
  18. apps/server/src/storage/filesystem-object-storage.ts +55 -0
  19. apps/server/src/storage/filesystem-object-storage.unit.test.ts +30 -0
  20. apps/server/src/storage/object-storage.ts +8 -0
  21. apps/server/src/system/health-service.ts +10 -0
  22. apps/server/src/system/health-service.unit.test.ts +16 -0
  23. apps/server/src/system/readiness-service.ts +24 -0
  24. apps/server/src/system/version-service.ts +16 -0
  25. apps/server/tsconfig.build.json +9 -0
  26. apps/server/tsconfig.json +7 -0
  27. apps/web/index.html +14 -0
  28. apps/web/package.json +30 -0
  29. apps/web/src/App.test.tsx +56 -0
  30. apps/web/src/App.tsx +208 -0
  31. apps/web/src/main.tsx +11 -0
  32. apps/web/src/styles.css +229 -0
  33. apps/web/src/test/setup.ts +5 -0
  34. apps/web/tsconfig.json +8 -0
  35. apps/web/vite.config.ts +14 -0
  36. docs/architecture/DEPLOYMENT.md +21 -0
  37. docs/architecture/FOUNDATION.md +29 -0
  38. docs/architecture/LOCAL_DEVELOPMENT.md +22 -0
  39. docs/decisions/0001-modular-monolith.md +16 -0
  40. docs/decisions/0002-single-hf-docker-space.md +16 -0
  41. docs/decisions/0003-storage-abstraction.md +16 -0
  42. docs/decisions/0004-database-abstraction.md +16 -0
  43. eslint.config.js +47 -0
  44. package-lock.json +0 -0
  45. package.json +39 -0
  46. packages/contracts/package.json +21 -0
  47. packages/contracts/src/index.ts +32 -0
  48. packages/contracts/tsconfig.build.json +4 -0
  49. packages/contracts/tsconfig.json +5 -0
  50. packages/design-system/package.json +26 -0
.dockerignore ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .git
2
+ .github
3
+ .env
4
+ .env.*
5
+ !.env.example
6
+ node_modules
7
+ **/dist
8
+ coverage
9
+ playwright-report
10
+ test-results
11
+ .data
12
+ .tmp
13
+ *.log
.env.example ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ NODE_ENV=development
2
+ PORT=7860
3
+ DATA_DIR=/data
4
+ DATABASE_URL=file:/data/database/oloka-dev.db
5
+ APP_VERSION=0.1.0
6
+ GIT_COMMIT_SHA=local
7
+ BUILD_TIMESTAMP=development
8
+ LOG_LEVEL=info
.github/workflows/deploy-hf.yml ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Deploy development Space
2
+
3
+ on:
4
+ push:
5
+ branches: [develop]
6
+
7
+ permissions:
8
+ contents: read
9
+
10
+ jobs:
11
+ validation:
12
+ uses: ./.github/workflows/validation.yml
13
+
14
+ deploy:
15
+ needs: validation
16
+ runs-on: ubuntu-latest
17
+ timeout-minutes: 15
18
+ steps:
19
+ - uses: actions/checkout@v4
20
+ with:
21
+ fetch-depth: 0
22
+ - name: Install Hugging Face Hub client
23
+ run: python -m pip install --disable-pip-version-check huggingface-hub==1.23.0
24
+ - name: Sync validated commit to Hugging Face
25
+ env:
26
+ HF_TOKEN: ${{ secrets.HF_TOKEN }}
27
+ run: |
28
+ test -n "$HF_TOKEN"
29
+ python - <<'PY'
30
+ import os
31
+ from huggingface_hub import HfApi
32
+
33
+ HfApi(token=os.environ["HF_TOKEN"]).upload_folder(
34
+ repo_id="phucsd/Oloka-Video-Dev",
35
+ repo_type="space",
36
+ folder_path=".",
37
+ ignore_patterns=[".git/**", ".git"],
38
+ delete_patterns="*",
39
+ commit_message=f"Deploy GitHub commit {os.environ['GITHUB_SHA']}",
40
+ )
41
+ PY
.github/workflows/validation.yml ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Validation
2
+
3
+ on:
4
+ pull_request:
5
+ push:
6
+ branches: [main]
7
+ workflow_call:
8
+
9
+ permissions:
10
+ contents: read
11
+
12
+ jobs:
13
+ validate:
14
+ runs-on: ubuntu-latest
15
+ timeout-minutes: 25
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+ - uses: actions/setup-node@v4
19
+ with:
20
+ node-version: 22
21
+ cache: npm
22
+ - run: npm ci
23
+ - run: npm run format:check
24
+ - run: npm run lint
25
+ - run: npm run typecheck
26
+ - run: npm run test:unit
27
+ - run: npm run test:integration
28
+ - run: npm run test:frontend
29
+ - run: npm run build
30
+ - run: npx playwright install --with-deps chromium
31
+ - run: npm run test:e2e
32
+ - name: Build production image
33
+ run: |
34
+ docker build \
35
+ --build-arg APP_VERSION=0.1.0 \
36
+ --build-arg GIT_COMMIT_SHA=${GITHUB_SHA} \
37
+ --build-arg BUILD_TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ) \
38
+ -t oloka-video:${GITHUB_SHA} .
.gitignore ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ node_modules/
2
+ dist/
3
+ coverage/
4
+ .env
5
+ .env.*
6
+ !.env.example
7
+ .data/
8
+ .tmp/
9
+ playwright-report/
10
+ test-results/
11
+ *.log
12
+ .DS_Store
.prettierignore ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ node_modules
2
+ dist
3
+ coverage
4
+ playwright-report
5
+ test-results
6
+ package-lock.json
Dockerfile ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # syntax=docker/dockerfile:1.7
2
+ FROM node:22-bookworm-slim AS dependencies
3
+ WORKDIR /app
4
+ COPY package.json package-lock.json ./
5
+ COPY apps/server/package.json apps/server/package.json
6
+ COPY apps/web/package.json apps/web/package.json
7
+ COPY packages/contracts/package.json packages/contracts/package.json
8
+ COPY packages/design-system/package.json packages/design-system/package.json
9
+ RUN npm ci
10
+
11
+ FROM dependencies AS build
12
+ COPY . .
13
+ RUN npm run build
14
+ RUN npm prune --omit=dev
15
+
16
+ FROM node:22-bookworm-slim AS runtime
17
+ ARG APP_VERSION=0.1.0
18
+ ARG GIT_COMMIT_SHA=unknown
19
+ ARG BUILD_TIMESTAMP=unknown
20
+ ENV NODE_ENV=production \
21
+ PORT=7860 \
22
+ DATA_DIR=/data \
23
+ DATABASE_URL=file:/data/database/oloka-dev.db \
24
+ APP_VERSION=${APP_VERSION} \
25
+ GIT_COMMIT_SHA=${GIT_COMMIT_SHA} \
26
+ BUILD_TIMESTAMP=${BUILD_TIMESTAMP} \
27
+ LOG_LEVEL=info
28
+ WORKDIR /app
29
+ RUN mkdir -p /data/database && chown -R 1000:1000 /data /app
30
+ COPY --from=build --chown=1000:1000 /app/package.json /app/package-lock.json ./
31
+ COPY --from=build --chown=1000:1000 /app/node_modules ./node_modules
32
+ COPY --from=build --chown=1000:1000 /app/apps/server/dist ./apps/server/dist
33
+ COPY --from=build --chown=1000:1000 /app/apps/web/dist ./apps/web/dist
34
+ COPY --from=build --chown=1000:1000 /app/packages/contracts/package.json ./packages/contracts/package.json
35
+ COPY --from=build --chown=1000:1000 /app/packages/contracts/dist ./packages/contracts/dist
36
+ USER 1000:1000
37
+ EXPOSE 7860
38
+ HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
39
+ CMD ["node", "-e", "fetch('http://127.0.0.1:7860/api/health').then(r=>{if(!r.ok)process.exit(1)}).catch(()=>process.exit(1))"]
40
+ CMD ["node", "apps/server/dist/index.js"]
README.md CHANGED
@@ -1,10 +1,97 @@
1
  ---
2
- title: Oloka Video Dev
3
- emoji: 🔥
4
- colorFrom: red
5
- colorTo: indigo
6
  sdk: docker
 
7
  pinned: false
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: Oloka Video
3
+ emoji: 🎬
4
+ colorFrom: green
5
+ colorTo: gray
6
  sdk: docker
7
+ app_port: 7860
8
  pinned: false
9
  ---
10
 
11
+ # Oloka Video
12
+
13
+ Oloka Video is a greenfield TypeScript modular monolith. This foundation hosts a React frontend and a Fastify API in one Docker image on port `7860`, with explicit database, storage, contract, and design-system boundaries.
14
+
15
+ ## Repository layout
16
+
17
+ - `apps/web` — React status experience built by Vite.
18
+ - `apps/server` — Fastify API and production static-file host.
19
+ - `packages/contracts` — shared Zod API schemas and TypeScript types.
20
+ - `packages/design-system` — design tokens and reusable UI primitives.
21
+ - `docs/architecture` — architecture and operations guides.
22
+ - `docs/decisions` — architecture decision records.
23
+
24
+ ## Run locally
25
+
26
+ Requirements: Node.js 22+, npm 10+, and a writable data directory.
27
+
28
+ ```bash
29
+ npm ci
30
+ npm run dev
31
+ ```
32
+
33
+ The API listens on `http://localhost:7860`; Vite serves the local frontend on `http://localhost:5173` and proxies `/api` to the backend. On Windows, override the Linux online defaults:
34
+
35
+ ```powershell
36
+ $env:DATA_DIR = "$PWD\.data"
37
+ $env:DATABASE_URL = "file:$($PWD.Path.Replace('\','/'))/.data/database/oloka-dev.db"
38
+ npm run dev
39
+ ```
40
+
41
+ Run all local validation:
42
+
43
+ ```bash
44
+ npm run format:check
45
+ npm run lint
46
+ npm run typecheck
47
+ npm test
48
+ npm run build
49
+ npm run test:e2e
50
+ ```
51
+
52
+ ## Build and run Docker
53
+
54
+ ```bash
55
+ docker build \
56
+ --build-arg APP_VERSION=0.1.0 \
57
+ --build-arg GIT_COMMIT_SHA=$(git rev-parse HEAD) \
58
+ --build-arg BUILD_TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ) \
59
+ -t oloka-video:dev .
60
+
61
+ docker run --rm -p 7860:7860 -v oloka-video-data:/data oloka-video:dev
62
+ ```
63
+
64
+ The named volume is mounted at `/data`; the development SQLite database is created at `/data/database/oloka-dev.db`. The process runs as UID `1000`.
65
+
66
+ ## Verify the service
67
+
68
+ ```bash
69
+ curl http://localhost:7860/api/health
70
+ curl http://localhost:7860/api/ready
71
+ curl http://localhost:7860/api/version
72
+ ```
73
+
74
+ `health` only confirms that the application process responds. `ready` performs live database and filesystem read/write checks. `version` exposes immutable build identity.
75
+
76
+ ## Configure Hugging Face
77
+
78
+ 1. Create the Docker Space `phucsd/Oloka-Video-Dev` and select Docker SDK.
79
+ 2. Configure persistent storage so Hugging Face mounts it at `/data`.
80
+ 3. In the GitHub repository, open **Settings → Secrets and variables → Actions** and add a repository secret named `HF_TOKEN`. Use a Hugging Face token with write access to the target Space.
81
+ 4. Push a validated change to `develop`. `.github/workflows/deploy-hf.yml` runs the full validation workflow and only pushes to the Space after it succeeds.
82
+ 5. Open the Space **Logs** tab to view Docker build logs and structured runtime logs. The public application is available at `https://phucsd-oloka-video-dev.hf.space` after the Space reaches `RUNNING`.
83
+
84
+ No token belongs in `.env`, Git history, Docker build arguments, or the Space repository.
85
+
86
+ ## Roll back a deployment
87
+
88
+ Find the last known-good commit in GitHub Actions or `GET /api/version`, then create a revert commit on `develop` and push it. The same validation gate redeploys that source state:
89
+
90
+ ```bash
91
+ git revert <bad-commit-sha>
92
+ git push origin develop
93
+ ```
94
+
95
+ Avoid force-resetting shared GitHub history. If the Space itself is unhealthy, pause it while investigating and restart after a validated rollback is available.
96
+
97
+ See [foundation architecture](docs/architecture/FOUNDATION.md), [deployment](docs/architecture/DEPLOYMENT.md), and [local development](docs/architecture/LOCAL_DEVELOPMENT.md) for deeper operational details.
apps/server/package.json ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "@oloka/server",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "scripts": {
8
+ "predev": "npm run build -w @oloka/contracts",
9
+ "build": "tsc -p tsconfig.build.json",
10
+ "dev": "tsx watch src/index.ts",
11
+ "typecheck": "tsc -p tsconfig.json --noEmit",
12
+ "test": "vitest run",
13
+ "pretest": "npm run build -w @oloka/contracts",
14
+ "test:unit": "npm run build -w @oloka/contracts && vitest run --exclude **/*.integration.test.ts",
15
+ "test:integration": "npm run build -w @oloka/contracts && vitest run src/http/routes.integration.test.ts"
16
+ },
17
+ "dependencies": {
18
+ "@fastify/static": "10.1.2",
19
+ "@oloka/contracts": "0.1.0",
20
+ "fastify": "5.11.2",
21
+ "zod": "4.0.17"
22
+ },
23
+ "devDependencies": {
24
+ "@types/node": "24.2.1",
25
+ "tsx": "4.20.3",
26
+ "vitest": "3.2.7"
27
+ }
28
+ }
apps/server/src/app.ts ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import fastifyStatic from "@fastify/static";
2
+ import {
3
+ healthResponseSchema,
4
+ readyResponseSchema,
5
+ versionResponseSchema,
6
+ } from "@oloka/contracts";
7
+ import Fastify, { type FastifyInstance } from "fastify";
8
+ import { resolve } from "node:path";
9
+ import type { AppEnvironment } from "./config/environment.js";
10
+ import { createDatabase } from "./database/create-database.js";
11
+ import { FilesystemObjectStorage } from "./storage/filesystem-object-storage.js";
12
+ import { HealthService } from "./system/health-service.js";
13
+ import { ReadinessService } from "./system/readiness-service.js";
14
+ import { VersionService } from "./system/version-service.js";
15
+
16
+ export interface BuildApplicationOptions {
17
+ environment: AppEnvironment;
18
+ serveFrontend?: boolean;
19
+ }
20
+
21
+ export async function buildApplication(
22
+ options: BuildApplicationOptions,
23
+ ): Promise<FastifyInstance> {
24
+ const { environment } = options;
25
+ const app = Fastify({
26
+ logger:
27
+ environment.logLevel === "silent"
28
+ ? false
29
+ : { level: environment.logLevel },
30
+ });
31
+ const database = await createDatabase(environment.databaseUrl);
32
+ database.migrate();
33
+ const storage = new FilesystemObjectStorage(environment.dataDir);
34
+ const initialStorageState = await storage.checkReadiness();
35
+ if (initialStorageState.status !== "ready") {
36
+ await database.close();
37
+ throw new Error(
38
+ `Persistent storage startup check failed: ${initialStorageState.message ?? "unknown error"}`,
39
+ );
40
+ }
41
+
42
+ app.get("/api/health", () =>
43
+ healthResponseSchema.parse(new HealthService().getHealth()),
44
+ );
45
+ const readinessService = new ReadinessService(database, storage);
46
+ app.get("/api/ready", async (_request, reply) => {
47
+ const result = readyResponseSchema.parse(
48
+ await readinessService.getReadiness(),
49
+ );
50
+ return reply.code(result.status === "ready" ? 200 : 503).send(result);
51
+ });
52
+ app.get("/api/version", () =>
53
+ versionResponseSchema.parse(new VersionService(environment).getVersion()),
54
+ );
55
+
56
+ if (options.serveFrontend !== false) {
57
+ const webRoot = resolve(process.cwd(), "apps/web/dist");
58
+ await app.register(fastifyStatic, { root: webRoot, wildcard: false });
59
+ app.setNotFoundHandler(async (request, reply) => {
60
+ if (request.url.startsWith("/api/"))
61
+ return reply.code(404).send({ error: "Not Found" });
62
+ return reply.sendFile("index.html");
63
+ });
64
+ }
65
+
66
+ app.addHook("onClose", async () => {
67
+ await Promise.all([storage.close(), database.close()]);
68
+ });
69
+
70
+ return app;
71
+ }
apps/server/src/config/environment.ts ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { z } from "zod";
2
+
3
+ const environmentSchema = z.object({
4
+ NODE_ENV: z
5
+ .enum(["development", "test", "production"])
6
+ .default("development"),
7
+ PORT: z.coerce.number().int().min(1).max(65_535).default(7860),
8
+ DATA_DIR: z.string().min(1).default("/data"),
9
+ DATABASE_URL: z.string().min(1).optional(),
10
+ APP_VERSION: z
11
+ .string()
12
+ .regex(/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/)
13
+ .default("0.1.0"),
14
+ GIT_COMMIT_SHA: z.string().min(1).default("local"),
15
+ BUILD_TIMESTAMP: z.string().min(1).default("development"),
16
+ LOG_LEVEL: z
17
+ .enum(["fatal", "error", "warn", "info", "debug", "trace", "silent"])
18
+ .default("info"),
19
+ });
20
+
21
+ export interface AppEnvironment {
22
+ nodeEnv: "development" | "test" | "production";
23
+ port: number;
24
+ dataDir: string;
25
+ databaseUrl: string;
26
+ appVersion: string;
27
+ gitCommitSha: string;
28
+ buildTimestamp: string;
29
+ logLevel: "fatal" | "error" | "warn" | "info" | "debug" | "trace" | "silent";
30
+ }
31
+
32
+ export function parseEnvironment(input: NodeJS.ProcessEnv): AppEnvironment {
33
+ const parsed = environmentSchema.parse(input);
34
+ const databaseUrl =
35
+ parsed.DATABASE_URL ?? `file:${parsed.DATA_DIR}/database/oloka-dev.db`;
36
+
37
+ if (
38
+ !databaseUrl.startsWith("file:") &&
39
+ !databaseUrl.startsWith("postgresql://")
40
+ ) {
41
+ throw new Error("DATABASE_URL must use file: or postgresql://");
42
+ }
43
+
44
+ return {
45
+ nodeEnv: parsed.NODE_ENV,
46
+ port: parsed.PORT,
47
+ dataDir: parsed.DATA_DIR,
48
+ databaseUrl,
49
+ appVersion: parsed.APP_VERSION,
50
+ gitCommitSha: parsed.GIT_COMMIT_SHA,
51
+ buildTimestamp: parsed.BUILD_TIMESTAMP,
52
+ logLevel: parsed.LOG_LEVEL,
53
+ };
54
+ }
apps/server/src/config/environment.unit.test.ts ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ import { describe, expect, it } from "vitest";
2
+ import { parseEnvironment } from "./environment.js";
3
+
4
+ describe("parseEnvironment", () => {
5
+ it("rejects an invalid public port", () => {
6
+ expect(() => parseEnvironment({ PORT: "70000" })).toThrow(/PORT/);
7
+ });
8
+ });
apps/server/src/database/create-database.ts ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { SystemDatabase } from "./database.js";
2
+ import { SqliteSystemDatabase } from "./sqlite-system-database.js";
3
+
4
+ export async function createDatabase(
5
+ databaseUrl: string,
6
+ ): Promise<SystemDatabase> {
7
+ if (databaseUrl.startsWith("file:")) {
8
+ return SqliteSystemDatabase.connect(databaseUrl);
9
+ }
10
+ if (databaseUrl.startsWith("postgresql://")) {
11
+ throw new Error(
12
+ "PostgreSQL adapter is not enabled in the online development foundation",
13
+ );
14
+ }
15
+ throw new Error("Unsupported DATABASE_URL");
16
+ }
apps/server/src/database/database.ts ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ import type { ReadyResponse } from "@oloka/contracts";
2
+
3
+ export type DatabaseReadiness = ReadyResponse["checks"]["database"];
4
+
5
+ export interface SystemDatabase {
6
+ migrate(): void;
7
+ checkReadiness(): Promise<DatabaseReadiness>;
8
+ close(): Promise<void>;
9
+ }
apps/server/src/database/sqlite-system-database.ts ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { mkdir } from "node:fs/promises";
2
+ import { dirname } from "node:path";
3
+ import { DatabaseSync } from "node:sqlite";
4
+ import { fileURLToPath } from "node:url";
5
+ import type { DatabaseReadiness, SystemDatabase } from "./database.js";
6
+
7
+ export class SqliteSystemDatabase implements SystemDatabase {
8
+ private constructor(private readonly database: DatabaseSync) {}
9
+
10
+ static async connect(databaseUrl: string): Promise<SqliteSystemDatabase> {
11
+ if (!databaseUrl.startsWith("file:")) {
12
+ throw new Error("The SQLite adapter requires a file: DATABASE_URL");
13
+ }
14
+ const databasePath = fileURLToPath(databaseUrl);
15
+ await mkdir(dirname(databasePath), { recursive: true });
16
+ return new SqliteSystemDatabase(new DatabaseSync(databasePath));
17
+ }
18
+
19
+ migrate(): void {
20
+ this.database.exec(`
21
+ PRAGMA journal_mode = WAL;
22
+ CREATE TABLE IF NOT EXISTS schema_migrations (
23
+ version INTEGER PRIMARY KEY,
24
+ name TEXT NOT NULL,
25
+ applied_at TEXT NOT NULL
26
+ );
27
+ CREATE TABLE IF NOT EXISTS system_metadata (
28
+ key TEXT PRIMARY KEY,
29
+ value TEXT NOT NULL,
30
+ updated_at TEXT NOT NULL
31
+ );
32
+ INSERT OR IGNORE INTO schema_migrations (version, name, applied_at)
33
+ VALUES (1, 'foundation_system_tables', datetime('now'));
34
+ `);
35
+ }
36
+
37
+ checkReadiness(): Promise<DatabaseReadiness> {
38
+ try {
39
+ const result = this.database.prepare("SELECT 1 AS healthy").get() as
40
+ | { healthy?: number }
41
+ | undefined;
42
+ return Promise.resolve(
43
+ result?.healthy === 1
44
+ ? { status: "ready" }
45
+ : {
46
+ status: "not_ready",
47
+ message: "Database health query returned an unexpected result",
48
+ },
49
+ );
50
+ } catch (error) {
51
+ return Promise.resolve({
52
+ status: "not_ready",
53
+ message:
54
+ error instanceof Error ? error.message : "Unknown database error",
55
+ });
56
+ }
57
+ }
58
+
59
+ close(): Promise<void> {
60
+ this.database.close();
61
+ return Promise.resolve();
62
+ }
63
+ }
apps/server/src/http/routes.integration.test.ts ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { mkdtemp, rm } from "node:fs/promises";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { pathToFileURL } from "node:url";
5
+ import { afterEach, describe, expect, it } from "vitest";
6
+ import { buildApplication } from "../app.js";
7
+ import { parseEnvironment } from "../config/environment.js";
8
+
9
+ const temporaryDirectories: string[] = [];
10
+
11
+ afterEach(async () => {
12
+ await Promise.all(
13
+ temporaryDirectories
14
+ .splice(0)
15
+ .map((path) => rm(path, { recursive: true, force: true })),
16
+ );
17
+ });
18
+
19
+ async function createTestApplication() {
20
+ const dataDir = await mkdtemp(join(tmpdir(), "oloka-api-"));
21
+ temporaryDirectories.push(dataDir);
22
+ return buildApplication({
23
+ environment: parseEnvironment({
24
+ NODE_ENV: "test",
25
+ DATA_DIR: dataDir,
26
+ DATABASE_URL: pathToFileURL(join(dataDir, "database", "test.db")).href,
27
+ APP_VERSION: "1.2.3",
28
+ GIT_COMMIT_SHA: "abc123",
29
+ BUILD_TIMESTAMP: "2026-08-05T01:00:00.000Z",
30
+ LOG_LEVEL: "silent",
31
+ }),
32
+ serveFrontend: false,
33
+ });
34
+ }
35
+
36
+ describe("system API", () => {
37
+ it("GET /api/health confirms the process is running", async () => {
38
+ const app = await createTestApplication();
39
+ const response = await app.inject({ method: "GET", url: "/api/health" });
40
+
41
+ expect(response.statusCode).toBe(200);
42
+ expect(response.json()).toMatchObject({ status: "ok" });
43
+
44
+ await app.close();
45
+ });
46
+
47
+ it("GET /api/ready checks the database, storage, and configuration", async () => {
48
+ const app = await createTestApplication();
49
+ const response = await app.inject({ method: "GET", url: "/api/ready" });
50
+
51
+ expect(response.statusCode).toBe(200);
52
+ expect(response.json()).toEqual({
53
+ status: "ready",
54
+ checks: {
55
+ database: { status: "ready" },
56
+ storage: { status: "ready" },
57
+ configuration: { status: "ready" },
58
+ },
59
+ });
60
+
61
+ await app.close();
62
+ });
63
+
64
+ it("GET /api/version returns immutable build metadata", async () => {
65
+ const app = await createTestApplication();
66
+ const response = await app.inject({ method: "GET", url: "/api/version" });
67
+
68
+ expect(response.statusCode).toBe(200);
69
+ expect(response.json()).toEqual({
70
+ name: "Oloka Video",
71
+ version: "1.2.3",
72
+ environment: "test",
73
+ gitCommitSha: "abc123",
74
+ buildTimestamp: "2026-08-05T01:00:00.000Z",
75
+ });
76
+
77
+ await app.close();
78
+ });
79
+ });
apps/server/src/index.ts ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { buildApplication } from "./app.js";
2
+ import { parseEnvironment } from "./config/environment.js";
3
+
4
+ async function start(): Promise<void> {
5
+ const environment = parseEnvironment(process.env);
6
+ const app = await buildApplication({ environment });
7
+ let shuttingDown = false;
8
+
9
+ const shutdown = async (signal: NodeJS.Signals) => {
10
+ if (shuttingDown) return;
11
+ shuttingDown = true;
12
+ app.log.info({ signal }, "graceful shutdown started");
13
+ await app.close();
14
+ process.exitCode = 0;
15
+ };
16
+
17
+ process.once("SIGTERM", () => void shutdown("SIGTERM"));
18
+ process.once("SIGINT", () => void shutdown("SIGINT"));
19
+
20
+ await app.listen({ host: "0.0.0.0", port: environment.port });
21
+ app.log.info(
22
+ {
23
+ port: environment.port,
24
+ environment: environment.nodeEnv,
25
+ version: environment.appVersion,
26
+ gitCommitSha: environment.gitCommitSha,
27
+ },
28
+ "Oloka Video is listening",
29
+ );
30
+ }
31
+
32
+ start().catch((error: unknown) => {
33
+ process.stderr.write(
34
+ `${JSON.stringify({ level: "fatal", message: "startup failed", error: error instanceof Error ? error.message : String(error) })}\n`,
35
+ );
36
+ process.exitCode = 1;
37
+ });
apps/server/src/storage/filesystem-object-storage.ts ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { mkdir, open, unlink } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import type { ObjectStorage, ReadinessResult } from "./object-storage.js";
4
+
5
+ export class FilesystemObjectStorage implements ObjectStorage {
6
+ readonly #probePath: string;
7
+ #readinessQueue: Promise<ReadinessResult> = Promise.resolve({
8
+ status: "ready",
9
+ });
10
+
11
+ constructor(private readonly rootPath: string) {
12
+ this.#probePath = join(rootPath, ".oloka-readiness");
13
+ }
14
+
15
+ checkReadiness(): Promise<ReadinessResult> {
16
+ this.#readinessQueue = this.#readinessQueue.then(
17
+ () => this.runReadinessCheck(),
18
+ () => this.runReadinessCheck(),
19
+ );
20
+ return this.#readinessQueue;
21
+ }
22
+
23
+ async close(): Promise<void> {
24
+ await this.#readinessQueue.catch(() => undefined);
25
+ await unlink(this.#probePath).catch((error: NodeJS.ErrnoException) => {
26
+ if (error.code !== "ENOENT") throw error;
27
+ });
28
+ }
29
+
30
+ private async runReadinessCheck(): Promise<ReadinessResult> {
31
+ try {
32
+ await mkdir(this.rootPath, { recursive: true });
33
+ const handle = await open(this.#probePath, "w+");
34
+ try {
35
+ const marker = Buffer.from("1");
36
+ await handle.write(marker, 0, marker.length, 0);
37
+ await handle.sync();
38
+ const verification = Buffer.alloc(1);
39
+ const result = await handle.read(verification, 0, 1, 0);
40
+ if (result.bytesRead !== 1 || verification.toString() !== "1") {
41
+ throw new Error("Storage write verification failed");
42
+ }
43
+ } finally {
44
+ await handle.close();
45
+ }
46
+ return { status: "ready" };
47
+ } catch (error) {
48
+ return {
49
+ status: "not_ready",
50
+ message:
51
+ error instanceof Error ? error.message : "Unknown storage error",
52
+ };
53
+ }
54
+ }
55
+ }
apps/server/src/storage/filesystem-object-storage.unit.test.ts ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { mkdtemp, readdir, rm } from "node:fs/promises";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { afterEach, describe, expect, it } from "vitest";
5
+ import { FilesystemObjectStorage } from "./filesystem-object-storage.js";
6
+
7
+ const temporaryDirectories: string[] = [];
8
+
9
+ afterEach(async () => {
10
+ await Promise.all(
11
+ temporaryDirectories
12
+ .splice(0)
13
+ .map((path) => rm(path, { recursive: true, force: true })),
14
+ );
15
+ });
16
+
17
+ describe("FilesystemObjectStorage", () => {
18
+ it("proves the configured storage is writable and cleans up its fixed probe", async () => {
19
+ const root = await mkdtemp(join(tmpdir(), "oloka-storage-"));
20
+ temporaryDirectories.push(root);
21
+ const storage = new FilesystemObjectStorage(root);
22
+
23
+ await expect(storage.checkReadiness()).resolves.toEqual({
24
+ status: "ready",
25
+ });
26
+ await storage.close();
27
+
28
+ expect(await readdir(root)).toEqual([]);
29
+ });
30
+ });
apps/server/src/storage/object-storage.ts ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ import type { ReadyResponse } from "@oloka/contracts";
2
+
3
+ export type ReadinessResult = ReadyResponse["checks"]["storage"];
4
+
5
+ export interface ObjectStorage {
6
+ checkReadiness(): Promise<ReadinessResult>;
7
+ close(): Promise<void>;
8
+ }
apps/server/src/system/health-service.ts ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { HealthResponse } from "@oloka/contracts";
2
+
3
+ export class HealthService {
4
+ getHealth(): HealthResponse {
5
+ return {
6
+ status: "ok",
7
+ timestamp: new Date().toISOString(),
8
+ };
9
+ }
10
+ }
apps/server/src/system/health-service.unit.test.ts ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { describe, expect, it, vi } from "vitest";
2
+ import { HealthService } from "./health-service.js";
3
+
4
+ describe("HealthService", () => {
5
+ it("reports process health with the current timestamp", () => {
6
+ vi.useFakeTimers();
7
+ vi.setSystemTime(new Date("2026-08-05T01:00:00.000Z"));
8
+
9
+ expect(new HealthService().getHealth()).toEqual({
10
+ status: "ok",
11
+ timestamp: "2026-08-05T01:00:00.000Z",
12
+ });
13
+
14
+ vi.useRealTimers();
15
+ });
16
+ });
apps/server/src/system/readiness-service.ts ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { ReadyResponse } from "@oloka/contracts";
2
+ import type { SystemDatabase } from "../database/database.js";
3
+ import type { ObjectStorage } from "../storage/object-storage.js";
4
+
5
+ export class ReadinessService {
6
+ constructor(
7
+ private readonly database: SystemDatabase,
8
+ private readonly storage: ObjectStorage,
9
+ ) {}
10
+
11
+ async getReadiness(): Promise<ReadyResponse> {
12
+ const [database, storage] = await Promise.all([
13
+ this.database.checkReadiness(),
14
+ this.storage.checkReadiness(),
15
+ ]);
16
+ const configuration = { status: "ready" as const };
17
+ const status =
18
+ database.status === "ready" && storage.status === "ready"
19
+ ? "ready"
20
+ : "not_ready";
21
+
22
+ return { status, checks: { database, storage, configuration } };
23
+ }
24
+ }
apps/server/src/system/version-service.ts ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { VersionResponse } from "@oloka/contracts";
2
+ import type { AppEnvironment } from "../config/environment.js";
3
+
4
+ export class VersionService {
5
+ constructor(private readonly environment: AppEnvironment) {}
6
+
7
+ getVersion(): VersionResponse {
8
+ return {
9
+ name: "Oloka Video",
10
+ version: this.environment.appVersion,
11
+ environment: this.environment.nodeEnv,
12
+ gitCommitSha: this.environment.gitCommitSha,
13
+ buildTimestamp: this.environment.buildTimestamp,
14
+ };
15
+ }
16
+ }
apps/server/tsconfig.build.json ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "dist",
5
+ "rootDir": "src",
6
+ "sourceMap": true
7
+ },
8
+ "exclude": ["src/**/*.test.ts", "src/**/*.test.tsx"]
9
+ }
apps/server/tsconfig.json ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "types": ["node", "vitest/globals"]
5
+ },
6
+ "include": ["src"]
7
+ }
apps/web/index.html ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <meta name="theme-color" content="#f4f5ef" />
7
+ <meta name="description" content="Oloka Video online foundation status." />
8
+ <title>Oloka Video</title>
9
+ </head>
10
+ <body>
11
+ <div id="root"></div>
12
+ <script type="module" src="/src/main.tsx"></script>
13
+ </body>
14
+ </html>
apps/web/package.json ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "@oloka/web",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "predev": "npm run build -w @oloka/contracts && npm run build -w @oloka/design-system",
8
+ "dev": "vite --host 0.0.0.0 --port 5173",
9
+ "build": "vite build",
10
+ "typecheck": "tsc -p tsconfig.json --noEmit",
11
+ "pretest": "npm run build -w @oloka/contracts && npm run build -w @oloka/design-system",
12
+ "test": "vitest run"
13
+ },
14
+ "dependencies": {
15
+ "@oloka/contracts": "0.1.0",
16
+ "@oloka/design-system": "0.1.0",
17
+ "react": "19.1.1",
18
+ "react-dom": "19.1.1"
19
+ },
20
+ "devDependencies": {
21
+ "@testing-library/jest-dom": "6.6.4",
22
+ "@testing-library/react": "16.3.0",
23
+ "@types/react": "19.1.10",
24
+ "@types/react-dom": "19.1.7",
25
+ "@vitejs/plugin-react": "4.7.0",
26
+ "jsdom": "26.1.0",
27
+ "vite": "7.3.6",
28
+ "vitest": "3.2.7"
29
+ }
30
+ }
apps/web/src/App.test.tsx ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { render, screen } from "@testing-library/react";
2
+ import { beforeEach, describe, expect, it, vi } from "vitest";
3
+ import { App } from "./App";
4
+
5
+ describe("App", () => {
6
+ beforeEach(() => vi.restoreAllMocks());
7
+
8
+ it("renders connected state from the real API responses", async () => {
9
+ vi.spyOn(globalThis, "fetch")
10
+ .mockResolvedValueOnce(
11
+ new Response(
12
+ JSON.stringify({ status: "ok", timestamp: "2026-08-05T00:00:00Z" }),
13
+ ),
14
+ )
15
+ .mockResolvedValueOnce(
16
+ new Response(
17
+ JSON.stringify({
18
+ status: "ready",
19
+ checks: {
20
+ database: { status: "ready" },
21
+ storage: { status: "ready" },
22
+ configuration: { status: "ready" },
23
+ },
24
+ }),
25
+ ),
26
+ )
27
+ .mockResolvedValueOnce(
28
+ new Response(
29
+ JSON.stringify({
30
+ name: "Oloka Video",
31
+ version: "1.2.3",
32
+ environment: "development",
33
+ gitCommitSha: "abc123",
34
+ buildTimestamp: "2026-08-05T00:00:00Z",
35
+ }),
36
+ ),
37
+ );
38
+
39
+ render(<App />);
40
+
41
+ expect(await screen.findByText("Connected")).toBeInTheDocument();
42
+ expect(screen.getByText("1.2.3")).toBeInTheDocument();
43
+ expect(screen.getByText("abc123")).toBeInTheDocument();
44
+ });
45
+
46
+ it("renders an error state when an API request fails", async () => {
47
+ vi.spyOn(globalThis, "fetch").mockRejectedValue(
48
+ new Error("Network unavailable"),
49
+ );
50
+
51
+ render(<App />);
52
+
53
+ expect(await screen.findByText("Connection error")).toBeInTheDocument();
54
+ expect(screen.getByText("Network unavailable")).toBeInTheDocument();
55
+ });
56
+ });
apps/web/src/App.tsx ADDED
@@ -0,0 +1,208 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import {
2
+ healthResponseSchema,
3
+ readyResponseSchema,
4
+ versionResponseSchema,
5
+ type ReadyResponse,
6
+ type VersionResponse,
7
+ } from "@oloka/contracts";
8
+ import {
9
+ DefinitionRow,
10
+ StatusPill,
11
+ Surface,
12
+ type StatusTone,
13
+ } from "@oloka/design-system";
14
+ import { useEffect, useState } from "react";
15
+
16
+ type DashboardState =
17
+ | { kind: "loading" }
18
+ | { kind: "connected"; ready: ReadyResponse; version: VersionResponse }
19
+ | { kind: "error"; message: string };
20
+
21
+ async function getJson(path: string): Promise<unknown> {
22
+ const response = await fetch(path, {
23
+ headers: { accept: "application/json" },
24
+ });
25
+ if (!response.ok) throw new Error(`${path} returned ${response.status}`);
26
+ return response.json();
27
+ }
28
+
29
+ async function loadSystemState(): Promise<
30
+ Extract<DashboardState, { kind: "connected" }>
31
+ > {
32
+ const [healthData, readyData, versionData] = await Promise.all([
33
+ getJson("/api/health"),
34
+ getJson("/api/ready"),
35
+ getJson("/api/version"),
36
+ ]);
37
+ healthResponseSchema.parse(healthData);
38
+ return {
39
+ kind: "connected",
40
+ ready: readyResponseSchema.parse(readyData),
41
+ version: versionResponseSchema.parse(versionData),
42
+ };
43
+ }
44
+
45
+ function ReadinessBadge({ value }: { value: "ready" | "not_ready" }) {
46
+ return (
47
+ <StatusPill tone={value === "ready" ? "success" : "danger"}>
48
+ {value === "ready" ? "Ready" : "Not ready"}
49
+ </StatusPill>
50
+ );
51
+ }
52
+
53
+ export function App() {
54
+ const [state, setState] = useState<DashboardState>({ kind: "loading" });
55
+
56
+ useEffect(() => {
57
+ let active = true;
58
+ loadSystemState()
59
+ .then((result) => active && setState(result))
60
+ .catch((error: unknown) => {
61
+ if (active)
62
+ setState({
63
+ kind: "error",
64
+ message:
65
+ error instanceof Error
66
+ ? error.message
67
+ : "Unknown connection error",
68
+ });
69
+ });
70
+ return () => {
71
+ active = false;
72
+ };
73
+ }, []);
74
+
75
+ const connectionTone: StatusTone =
76
+ state.kind === "connected"
77
+ ? "success"
78
+ : state.kind === "error"
79
+ ? "danger"
80
+ : "neutral";
81
+ const connectionLabel =
82
+ state.kind === "connected"
83
+ ? "Connected"
84
+ : state.kind === "error"
85
+ ? "Connection error"
86
+ : "Connecting";
87
+
88
+ return (
89
+ <main className="page-shell">
90
+ <header className="topbar">
91
+ <a className="brand" href="/" aria-label="Oloka Video home">
92
+ <span className="brand-mark">O</span>
93
+ <span>Oloka Video</span>
94
+ </a>
95
+ <span className="environment-label">Online foundation</span>
96
+ </header>
97
+
98
+ <div className="content">
99
+ <section className="hero" aria-labelledby="page-title">
100
+ <div>
101
+ <p className="eyebrow">System overview</p>
102
+ <h1 id="page-title">A dependable start for every frame.</h1>
103
+ <p className="hero-copy">
104
+ Live infrastructure status for the Oloka Video development
105
+ environment.
106
+ </p>
107
+ </div>
108
+ <StatusPill tone={connectionTone}>{connectionLabel}</StatusPill>
109
+ </section>
110
+
111
+ {state.kind === "loading" && (
112
+ <Surface
113
+ className="state-panel loading-panel"
114
+ aria-live="polite"
115
+ aria-busy="true"
116
+ >
117
+ <span className="loading-line" />
118
+ <div>
119
+ <strong>Connecting to Oloka Video</strong>
120
+ <p>Checking the API and persistent services.</p>
121
+ </div>
122
+ </Surface>
123
+ )}
124
+
125
+ {state.kind === "error" && (
126
+ <Surface className="state-panel error-panel" role="alert">
127
+ <span className="state-symbol">!</span>
128
+ <div>
129
+ <strong>We could not reach the foundation services.</strong>
130
+ <p>{state.message}</p>
131
+ </div>
132
+ </Surface>
133
+ )}
134
+
135
+ {state.kind === "connected" && (
136
+ <div className="dashboard-grid">
137
+ <Surface className="status-card">
138
+ <div className="card-heading">
139
+ <div>
140
+ <p className="eyebrow">Runtime</p>
141
+ <h2>Service health</h2>
142
+ </div>
143
+ <StatusPill tone="success">API online</StatusPill>
144
+ </div>
145
+ <dl>
146
+ <DefinitionRow
147
+ label="Database"
148
+ value={
149
+ <ReadinessBadge
150
+ value={state.ready.checks.database.status}
151
+ />
152
+ }
153
+ />
154
+ <DefinitionRow
155
+ label="Persistent storage"
156
+ value={
157
+ <ReadinessBadge value={state.ready.checks.storage.status} />
158
+ }
159
+ />
160
+ <DefinitionRow
161
+ label="Configuration"
162
+ value={
163
+ <ReadinessBadge
164
+ value={state.ready.checks.configuration.status}
165
+ />
166
+ }
167
+ />
168
+ </dl>
169
+ </Surface>
170
+
171
+ <Surface className="status-card build-card">
172
+ <div className="card-heading">
173
+ <div>
174
+ <p className="eyebrow">Release</p>
175
+ <h2>Build identity</h2>
176
+ </div>
177
+ <span className="version-chip">v{state.version.version}</span>
178
+ </div>
179
+ <dl>
180
+ <DefinitionRow
181
+ label="Application version"
182
+ value={state.version.version}
183
+ />
184
+ <DefinitionRow
185
+ label="Git commit"
186
+ value={<code>{state.version.gitCommitSha}</code>}
187
+ />
188
+ <DefinitionRow
189
+ label="Environment"
190
+ value={state.version.environment}
191
+ />
192
+ <DefinitionRow
193
+ label="Built"
194
+ value={state.version.buildTimestamp}
195
+ />
196
+ </dl>
197
+ </Surface>
198
+ </div>
199
+ )}
200
+ </div>
201
+
202
+ <footer>
203
+ <span>Oloka Video</span>
204
+ <span>Foundation status updates on every load.</span>
205
+ </footer>
206
+ </main>
207
+ );
208
+ }
apps/web/src/main.tsx ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { StrictMode } from "react";
2
+ import { createRoot } from "react-dom/client";
3
+ import "@oloka/design-system/tokens.css";
4
+ import "./styles.css";
5
+ import { App } from "./App";
6
+
7
+ createRoot(document.getElementById("root")!).render(
8
+ <StrictMode>
9
+ <App />
10
+ </StrictMode>,
11
+ );
apps/web/src/styles.css ADDED
@@ -0,0 +1,229 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ * {
2
+ box-sizing: border-box;
3
+ }
4
+ html {
5
+ background: var(--color-canvas);
6
+ color: var(--color-ink);
7
+ font-family: var(--font-sans);
8
+ }
9
+ body {
10
+ margin: 0;
11
+ min-width: 320px;
12
+ }
13
+ button,
14
+ input {
15
+ font: inherit;
16
+ }
17
+
18
+ .page-shell {
19
+ min-height: 100vh;
20
+ position: relative;
21
+ }
22
+ .page-shell::before {
23
+ background: linear-gradient(135deg, rgba(11, 104, 91, 0.08), transparent 55%);
24
+ border-radius: 50%;
25
+ content: "";
26
+ height: 32rem;
27
+ position: fixed;
28
+ right: -14rem;
29
+ top: -17rem;
30
+ width: 32rem;
31
+ z-index: -1;
32
+ }
33
+
34
+ .topbar,
35
+ footer {
36
+ align-items: center;
37
+ display: flex;
38
+ justify-content: space-between;
39
+ margin: 0 auto;
40
+ max-width: 1180px;
41
+ padding: 1.4rem 2rem;
42
+ }
43
+ .topbar {
44
+ border-bottom: 1px solid var(--color-line);
45
+ }
46
+ .brand {
47
+ align-items: center;
48
+ color: var(--color-ink);
49
+ display: flex;
50
+ font-weight: 750;
51
+ gap: 0.72rem;
52
+ text-decoration: none;
53
+ }
54
+ .brand-mark {
55
+ align-items: center;
56
+ background: var(--color-brand);
57
+ border-radius: 10px;
58
+ color: white;
59
+ display: inline-flex;
60
+ height: 2rem;
61
+ justify-content: center;
62
+ width: 2rem;
63
+ }
64
+ .environment-label {
65
+ color: var(--color-muted);
66
+ font-size: 0.82rem;
67
+ font-weight: 650;
68
+ text-transform: uppercase;
69
+ letter-spacing: 0.08em;
70
+ }
71
+ .content {
72
+ margin: 0 auto;
73
+ max-width: 1180px;
74
+ padding: 6.5rem 2rem 5rem;
75
+ }
76
+ .hero {
77
+ align-items: flex-end;
78
+ display: flex;
79
+ gap: 2rem;
80
+ justify-content: space-between;
81
+ margin-bottom: 3.2rem;
82
+ }
83
+ .eyebrow {
84
+ color: var(--color-brand);
85
+ font-size: 0.76rem;
86
+ font-weight: 800;
87
+ letter-spacing: 0.13em;
88
+ margin: 0 0 0.7rem;
89
+ text-transform: uppercase;
90
+ }
91
+ h1 {
92
+ font-size: clamp(2.55rem, 6vw, 5.2rem);
93
+ letter-spacing: -0.055em;
94
+ line-height: 0.98;
95
+ margin: 0;
96
+ max-width: 820px;
97
+ }
98
+ .hero-copy {
99
+ color: var(--color-muted);
100
+ font-size: 1.05rem;
101
+ line-height: 1.7;
102
+ margin: 1.4rem 0 0;
103
+ max-width: 590px;
104
+ }
105
+ .dashboard-grid {
106
+ display: grid;
107
+ gap: 1.25rem;
108
+ grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.95fr);
109
+ }
110
+ .status-card {
111
+ box-shadow: var(--shadow-card);
112
+ padding: 1.7rem 1.8rem 0.5rem;
113
+ }
114
+ .card-heading {
115
+ align-items: flex-start;
116
+ display: flex;
117
+ justify-content: space-between;
118
+ margin-bottom: 1.5rem;
119
+ }
120
+ .card-heading h2 {
121
+ font-size: 1.45rem;
122
+ letter-spacing: -0.025em;
123
+ margin: 0;
124
+ }
125
+ .card-heading .eyebrow {
126
+ margin-bottom: 0.35rem;
127
+ }
128
+ .status-card dl {
129
+ margin: 0;
130
+ }
131
+ .version-chip {
132
+ background: var(--color-brand-deep);
133
+ border-radius: var(--radius-sm);
134
+ color: white;
135
+ font-family: ui-monospace, monospace;
136
+ font-size: 0.82rem;
137
+ padding: 0.5rem 0.65rem;
138
+ }
139
+ code {
140
+ background: var(--color-neutral-soft);
141
+ border-radius: 6px;
142
+ font-size: 0.8rem;
143
+ padding: 0.3rem 0.45rem;
144
+ word-break: break-all;
145
+ }
146
+ .state-panel {
147
+ align-items: center;
148
+ box-shadow: var(--shadow-card);
149
+ display: flex;
150
+ gap: 1rem;
151
+ min-height: 190px;
152
+ padding: 2rem;
153
+ }
154
+ .state-panel p {
155
+ color: var(--color-muted);
156
+ margin: 0.45rem 0 0;
157
+ }
158
+ .loading-line {
159
+ animation: pulse 1.2s ease-in-out infinite;
160
+ background: var(--color-brand);
161
+ border-radius: 999px;
162
+ height: 3rem;
163
+ opacity: 0.5;
164
+ width: 0.3rem;
165
+ }
166
+ .error-panel {
167
+ border-color: #e9c7c1;
168
+ }
169
+ .state-symbol {
170
+ align-items: center;
171
+ background: var(--color-danger-soft);
172
+ border-radius: 50%;
173
+ color: var(--color-danger);
174
+ display: flex;
175
+ font-weight: 800;
176
+ height: 2.5rem;
177
+ justify-content: center;
178
+ width: 2.5rem;
179
+ }
180
+ footer {
181
+ border-top: 1px solid var(--color-line);
182
+ color: var(--color-muted);
183
+ font-size: 0.8rem;
184
+ }
185
+ footer span:first-child {
186
+ color: var(--color-ink);
187
+ font-weight: 750;
188
+ }
189
+
190
+ @keyframes pulse {
191
+ 50% {
192
+ opacity: 1;
193
+ transform: scaleY(1.15);
194
+ }
195
+ }
196
+ @media (prefers-reduced-motion: reduce) {
197
+ .loading-line {
198
+ animation: none;
199
+ }
200
+ }
201
+ @media (max-width: 760px) {
202
+ .content {
203
+ padding-top: 4.2rem;
204
+ }
205
+ .hero {
206
+ align-items: flex-start;
207
+ flex-direction: column;
208
+ }
209
+ .dashboard-grid {
210
+ grid-template-columns: 1fr;
211
+ }
212
+ .environment-label,
213
+ footer span:last-child {
214
+ display: none;
215
+ }
216
+ .topbar,
217
+ footer,
218
+ .content {
219
+ padding-left: 1.15rem;
220
+ padding-right: 1.15rem;
221
+ }
222
+ .status-card {
223
+ padding-left: 1.2rem;
224
+ padding-right: 1.2rem;
225
+ }
226
+ .card-heading {
227
+ gap: 1rem;
228
+ }
229
+ }
apps/web/src/test/setup.ts ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ import "@testing-library/jest-dom/vitest";
2
+ import { afterEach } from "vitest";
3
+ import { cleanup } from "@testing-library/react";
4
+
5
+ afterEach(() => cleanup());
apps/web/tsconfig.json ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "jsx": "react-jsx",
5
+ "types": ["vite/client", "vitest/globals", "@testing-library/jest-dom"]
6
+ },
7
+ "include": ["src", "vite.config.ts"]
8
+ }
apps/web/vite.config.ts ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import react from "@vitejs/plugin-react";
2
+ import { defineConfig } from "vite";
3
+
4
+ export default defineConfig({
5
+ plugins: [react()],
6
+ server: {
7
+ proxy: { "/api": "http://localhost:7860" },
8
+ },
9
+ test: {
10
+ environment: "jsdom",
11
+ setupFiles: "./src/test/setup.ts",
12
+ css: true,
13
+ },
14
+ });
docs/architecture/DEPLOYMENT.md ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Deployment
2
+
3
+ ## Build artifact
4
+
5
+ The multi-stage Docker build installs the locked dependency graph, compiles contracts, design system, web, and server, then prunes development dependencies. The runtime stage contains only production dependencies and compiled artifacts. It runs as UID/GID `1000`, exposes `7860`, and includes an application health check.
6
+
7
+ Build identity enters the image through `APP_VERSION`, `GIT_COMMIT_SHA`, and `BUILD_TIMESTAMP` build arguments. Runtime configuration can override ordinary environment values, but production operators should keep build identity immutable.
8
+
9
+ ## GitHub to Hugging Face
10
+
11
+ The `develop` deployment workflow invokes the reusable validation workflow first. Formatting, lint, typecheck, unit, integration, frontend, production, browser, and Docker checks must all pass. The deployment job then pushes the exact validated Git commit to `phucsd/Oloka-Video-Dev` using the masked `HF_TOKEN` secret.
12
+
13
+ The Space reads Docker metadata from the YAML header in `README.md`. Persistent Hugging Face storage must be attached at `/data`; replacing that path with ephemeral storage loses SQLite state after rebuilds.
14
+
15
+ ## Observability
16
+
17
+ Fastify emits JSON logs to standard output. Use the Space Logs tab for build and container logs. Diagnose deployment layers separately: GitHub validation, repository sync, Docker build, container start, `/api/health`, then `/api/ready`.
18
+
19
+ ## Rollback
20
+
21
+ Revert the faulty GitHub commit on `develop`. A new workflow run validates and deploys the revert. Confirm `/api/version` returns the expected rollback SHA and then confirm `/api/ready` reports every dependency ready.
docs/architecture/FOUNDATION.md ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Foundation architecture
2
+
3
+ ## Purpose
4
+
5
+ This repository establishes the smallest online-safe base for Oloka Video. It deliberately contains no authentication, project management, uploads, AI integrations, editor, or rendering pipeline.
6
+
7
+ ## Module boundaries
8
+
9
+ The browser depends on `@oloka/contracts` and `@oloka/design-system`. The server depends on `@oloka/contracts`, application services, and infrastructure adapters. Routes translate HTTP to application services; they do not access SQLite or the filesystem directly.
10
+
11
+ The database boundary is `SystemDatabase`. `SqliteSystemDatabase` is the online-development adapter. A PostgreSQL adapter can later implement the same contract without changing system services or routes.
12
+
13
+ The persistent-data boundary is `ObjectStorage`. `FilesystemObjectStorage` owns all filesystem readiness work beneath `DATA_DIR`; future object-store implementations can replace it without changing HTTP handlers.
14
+
15
+ ## Request flow
16
+
17
+ 1. Configuration is parsed and validated before the listener opens.
18
+ 2. The database directory is created and migrations are applied idempotently.
19
+ 3. Persistent storage completes a real read/write startup probe.
20
+ 4. Fastify exposes system endpoints and serves the compiled React application.
21
+ 5. The frontend fetches all three system endpoints and validates responses with shared schemas.
22
+
23
+ ## Readiness semantics
24
+
25
+ `/api/health` is a liveness check only. `/api/ready` executes `SELECT 1`, checks storage through a fixed `.oloka-readiness` probe, and reports validated configuration. The probe is overwritten rather than multiplied and removed during graceful shutdown. A failed component returns HTTP `503` with per-component status.
26
+
27
+ ## Persistence schema
28
+
29
+ Migration 1 creates `schema_migrations` and `system_metadata`. There are no product-domain tables in the foundation phase. SQLite uses WAL mode for safer online-development concurrency.
docs/architecture/LOCAL_DEVELOPMENT.md ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Local development
2
+
3
+ ## Setup
4
+
5
+ Install Node.js 22 or later and run `npm ci`. Copy `.env.example` only when a local environment file is useful; never add `.env` to Git. Windows developers should set `DATA_DIR` and `DATABASE_URL` to a writable repository-local path because `/data` is the online Linux default.
6
+
7
+ `npm run dev` starts Fastify on `7860` and Vite on `5173`. Vite proxies API requests to Fastify. Production and browser checks use the single-port compiled build.
8
+
9
+ ## Quality commands
10
+
11
+ - `npm run format:check` verifies formatting.
12
+ - `npm run lint` applies typed ESLint rules.
13
+ - `npm run typecheck` checks every workspace and tool configuration.
14
+ - `npm run test:unit` verifies environment, health, and storage behavior.
15
+ - `npm run test:integration` exercises all system endpoints through Fastify injection.
16
+ - `npm run test:frontend` exercises connected and error states.
17
+ - `npm run build` produces all production artifacts.
18
+ - `npm run test:e2e` starts the compiled server and checks the page in Chromium.
19
+
20
+ ## Data cleanup
21
+
22
+ Repository-local `.data` and `.tmp` directories are ignored. Stop the server before removing a SQLite development database, especially on Windows where open files remain locked. Do not delete a mounted production `/data` volume as part of routine development.
docs/decisions/0001-modular-monolith.md ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ADR 0001: TypeScript modular monolith
2
+
3
+ - Status: Accepted
4
+ - Date: 2026-08-05
5
+
6
+ ## Context
7
+
8
+ Oloka Video needs a small deployable base while preserving explicit frontend, backend, contract, and UI boundaries.
9
+
10
+ ## Decision
11
+
12
+ Use npm workspaces with `apps/web`, `apps/server`, `packages/contracts`, and `packages/design-system`. Build one deployable process without combining source modules.
13
+
14
+ ## Consequences
15
+
16
+ One image and one runtime simplify operations. Workspace contracts prevent accidental coupling and leave modules separable if scale later justifies it.
docs/decisions/0002-single-hf-docker-space.md ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ADR 0002: Single Hugging Face Docker Space
2
+
3
+ - Status: Accepted
4
+ - Date: 2026-08-05
5
+
6
+ ## Context
7
+
8
+ The development environment needs to be online from the first commit with minimal operational surface.
9
+
10
+ ## Decision
11
+
12
+ Serve the compiled React application and API from one Fastify process on public port `7860` in `phucsd/Oloka-Video-Dev`.
13
+
14
+ ## Consequences
15
+
16
+ Frontend and API share an origin, eliminating CORS configuration and coordinating deployment. Independent scaling is deferred until product usage requires it.
docs/decisions/0003-storage-abstraction.md ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ADR 0003: Persistent storage abstraction
2
+
3
+ - Status: Accepted
4
+ - Date: 2026-08-05
5
+
6
+ ## Context
7
+
8
+ Online development uses Hugging Face persistent disk, while future production may use object storage.
9
+
10
+ ## Decision
11
+
12
+ Application services depend on `ObjectStorage`. The first adapter uses the filesystem below `/data` and owns its safe read/write readiness probe.
13
+
14
+ ## Consequences
15
+
16
+ Routes contain no direct filesystem operations. A cloud object-store adapter can replace the filesystem adapter without changing HTTP contracts.
docs/decisions/0004-database-abstraction.md ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ADR 0004: Database abstraction
2
+
3
+ - Status: Accepted
4
+ - Date: 2026-08-05
5
+
6
+ ## Context
7
+
8
+ SQLite minimizes online-development infrastructure, while expected production workloads may require PostgreSQL.
9
+
10
+ ## Decision
11
+
12
+ System services depend on `SystemDatabase`. Use Node's SQLite adapter for development and reserve `postgresql://` URLs for a future PostgreSQL implementation.
13
+
14
+ ## Consequences
15
+
16
+ Business and HTTP code do not know the active database engine. PostgreSQL activation requires a new adapter and focused contract tests, not route rewrites.
eslint.config.js ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import eslint from "@eslint/js";
2
+ import prettier from "eslint-config-prettier";
3
+ import globals from "globals";
4
+ import tseslint from "typescript-eslint";
5
+
6
+ export default tseslint.config(
7
+ {
8
+ ignores: [
9
+ "**/dist/**",
10
+ "**/coverage/**",
11
+ "playwright-report/**",
12
+ "test-results/**",
13
+ ],
14
+ },
15
+ eslint.configs.recommended,
16
+ ...tseslint.configs.recommendedTypeChecked.map((config) => ({
17
+ ...config,
18
+ files: ["**/*.ts", "**/*.tsx"],
19
+ })),
20
+ prettier,
21
+ {
22
+ files: ["**/*.ts", "**/*.tsx"],
23
+ languageOptions: {
24
+ parserOptions: {
25
+ projectService: {
26
+ allowDefaultProject: ["playwright.config.ts", "tests/e2e/*.ts"],
27
+ },
28
+ tsconfigRootDir: import.meta.dirname,
29
+ },
30
+ },
31
+ },
32
+ {
33
+ files: ["apps/server/**/*.ts", "*.ts", "*.js"],
34
+ languageOptions: { globals: globals.node },
35
+ },
36
+ {
37
+ files: ["apps/web/**/*.ts", "apps/web/**/*.tsx"],
38
+ languageOptions: { globals: globals.browser },
39
+ },
40
+ {
41
+ files: ["**/*.test.ts", "**/*.test.tsx", "tests/**/*.ts"],
42
+ rules: {
43
+ "@typescript-eslint/no-unsafe-assignment": "off",
44
+ "@typescript-eslint/no-unsafe-member-access": "off",
45
+ },
46
+ },
47
+ );
package-lock.json ADDED
The diff for this file is too large to render. See raw diff
 
package.json ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "oloka-video",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "description": "The online foundation for Oloka Video.",
6
+ "type": "module",
7
+ "workspaces": [
8
+ "apps/*",
9
+ "packages/*"
10
+ ],
11
+ "scripts": {
12
+ "build": "npm run build -w @oloka/contracts && npm run build -w @oloka/design-system && npm run build -w @oloka/web && npm run build -w @oloka/server",
13
+ "dev": "concurrently -n server,web -c blue,green \"npm run dev -w @oloka/server\" \"npm run dev -w @oloka/web\"",
14
+ "format": "prettier --write .",
15
+ "format:check": "prettier --check .",
16
+ "lint": "eslint . --max-warnings=0",
17
+ "typecheck": "npm run typecheck -w @oloka/contracts && npm run typecheck -w @oloka/design-system && npm run typecheck -w @oloka/server && npm run typecheck -w @oloka/web && tsc -p tsconfig.tools.json --noEmit",
18
+ "test": "npm run test -w @oloka/server && npm run test -w @oloka/web",
19
+ "test:unit": "npm run test:unit -w @oloka/server",
20
+ "test:integration": "npm run test:integration -w @oloka/server",
21
+ "test:frontend": "npm run test -w @oloka/web",
22
+ "test:e2e": "playwright test",
23
+ "start": "node apps/server/dist/index.js"
24
+ },
25
+ "engines": {
26
+ "node": ">=22.0.0"
27
+ },
28
+ "devDependencies": {
29
+ "@eslint/js": "9.32.0",
30
+ "@playwright/test": "1.62.1",
31
+ "concurrently": "9.2.0",
32
+ "eslint": "9.32.0",
33
+ "eslint-config-prettier": "10.1.8",
34
+ "globals": "16.3.0",
35
+ "prettier": "3.6.2",
36
+ "typescript": "5.9.2",
37
+ "typescript-eslint": "8.39.0"
38
+ }
39
+ }
packages/contracts/package.json ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "@oloka/contracts",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./src/index.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./src/index.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "scripts": {
15
+ "build": "tsc -p tsconfig.build.json",
16
+ "typecheck": "tsc -p tsconfig.json --noEmit"
17
+ },
18
+ "dependencies": {
19
+ "zod": "4.0.17"
20
+ }
21
+ }
packages/contracts/src/index.ts ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { z } from "zod";
2
+
3
+ export const healthResponseSchema = z.object({
4
+ status: z.literal("ok"),
5
+ timestamp: z.string(),
6
+ });
7
+
8
+ export const componentReadinessSchema = z.object({
9
+ status: z.enum(["ready", "not_ready"]),
10
+ message: z.string().optional(),
11
+ });
12
+
13
+ export const readyResponseSchema = z.object({
14
+ status: z.enum(["ready", "not_ready"]),
15
+ checks: z.object({
16
+ database: componentReadinessSchema,
17
+ storage: componentReadinessSchema,
18
+ configuration: componentReadinessSchema,
19
+ }),
20
+ });
21
+
22
+ export const versionResponseSchema = z.object({
23
+ name: z.literal("Oloka Video"),
24
+ version: z.string(),
25
+ environment: z.enum(["development", "test", "production"]),
26
+ gitCommitSha: z.string(),
27
+ buildTimestamp: z.string(),
28
+ });
29
+
30
+ export type HealthResponse = z.infer<typeof healthResponseSchema>;
31
+ export type ReadyResponse = z.infer<typeof readyResponseSchema>;
32
+ export type VersionResponse = z.infer<typeof versionResponseSchema>;
packages/contracts/tsconfig.build.json ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": { "declaration": true, "outDir": "dist" }
4
+ }
packages/contracts/tsconfig.json ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": { "rootDir": "src" },
4
+ "include": ["src"]
5
+ }
packages/design-system/package.json ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "@oloka/design-system",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./src/index.tsx",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./src/index.tsx",
11
+ "import": "./dist/index.js"
12
+ },
13
+ "./tokens.css": "./src/tokens.css"
14
+ },
15
+ "scripts": {
16
+ "build": "tsc -p tsconfig.build.json",
17
+ "typecheck": "tsc -p tsconfig.json --noEmit"
18
+ },
19
+ "peerDependencies": {
20
+ "react": ">=19"
21
+ },
22
+ "devDependencies": {
23
+ "@types/react": "19.1.10",
24
+ "react": "19.1.1"
25
+ }
26
+ }