feat: M0 monorepo skeleton with services, clients, deploy and CI
ci / contract (сгенерированный код совпадает с openapi.yaml) (push) Successful in 3m55s
ci / api (push) Successful in 51s
ci / mobile (Metro-бандл Android) (push) Canceled after 0s
release / images (services/api/Dockerfile, storage-agent, storage-agent) (push) Canceled after 0s
release / images (services/recommender/Dockerfile, recommender, ) (push) Canceled after 0s
ci / recommender (push) Successful in 1m11s
ci / ts (packages + web) (push) Canceled after 3s
release / verify (push) Successful in 1m16s
release / images (apps/web/Dockerfile, web, ) (push) Failing after 2m40s
release / android + release (push) Skipped
release / images (services/api/Dockerfile, api, api) (push) Canceled after 1s

- api/openapi.yaml: contract with /healthz, /readyz, /api/v1/server-info
- services/api (Go): api with oapi-codegen strict server, goose migrations,
  readiness checks for Postgres/Redis; storage-agent stub with internal token
- services/recommender (Python, uv): FastAPI health/readiness/internal status,
  `pipu-rec serve` and `pipu-rec analyze` entrypoints
- packages: generated api-client, core (server URL + server check),
  i18n (ru/en + dictionary check), design-tokens (CSS vars + TS from tokens.json)
- apps/web: Vite + React shell showing server status, language switch
- apps/mobile: Expo (SDK 53) Android app with server connect screen
- deploy: Dockerfiles, compose (prod + dev), nginx template, install/update scripts
- .gitea/workflows: ci.yml and tag-based release.yml (images, signed APK, release)
- scripts/version.mjs: version and Android versionCode from git tag
- tests/fixtures/music/generate.sh: synthetic test library via ffmpeg
- docs: versionCode formula, OpenAPI 3.0.3, dev instructions in README

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
mrleo1nidandClaude Opus 5.5 committed 2026-10-19 23:55:11 +03:00
1 parent 9eb1c25147
commit ebf296542c
98 files changed
+12130 -4

No files matched your search

+17
View File
@@ -0,0 +1,17 @@
{
"name": "@pipu/api-client",
"version": "0.0.0",
"private": true,
"type": "module",
"exports": { ".": "./src/index.ts" },
"scripts": {
"generate": "openapi-typescript ../../api/openapi.yaml -o src/generated/schema.ts",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"openapi-fetch": "^0.14.0"
},
"devDependencies": {
"openapi-typescript": "^7.8.0"
}
}
+202
View File
@@ -0,0 +1,202 @@
/**
* This file was auto-generated by openapi-typescript.
* Do not make direct changes to the file.
*/
export interface paths {
"/healthz": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
/** Живость процесса */
get: operations["getHealthz"];
put?: never;
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/readyz": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
/** Готовность обслуживать запросы (зависимости доступны) */
get: operations["getReadyz"];
put?: never;
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/v1/server-info": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
/**
* Сведения о сервере для клиентов
* @description Вызывается без аутентификации: веб и Android проверяют версию сервера,
* совместимость клиента и поддерживаемые языки (экран подключения).
*/
get: operations["getServerInfo"];
put?: never;
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
}
export type webhooks = Record<string, never>;
export interface components {
schemas: {
HealthStatus: {
/** @enum {string} */
status: "ok";
};
ReadyStatus: {
/** @enum {string} */
status: "ok" | "degraded";
/** @description Результат проверки каждой зависимости */
checks: {
[key: string]: string;
};
};
ServerInfo: {
/** @example pipu-music */
name: string;
/**
* @description Версия сервера (SemVer)
* @example 0.1.0
*/
version: string;
/** @example v1 */
api_version: string;
/**
* @example [
* "ru",
* "en"
* ]
*/
supported_locales: string[];
/**
* @description Минимальный versionCode Android-приложения, которое поддерживает сервер
* @example 10099
*/
min_android_version_code: number;
};
Problem: {
/** @default about:blank */
type: string;
status: number;
/** @description Машинный код ошибки для i18n, например `auth.invalid_credentials` */
code: string;
/** @description Пояснение для логов (не для показа пользователю) */
detail?: string;
params?: {
[key: string]: unknown;
};
};
};
responses: {
/** @description Ошибка (RFC 9457) */
Problem: {
headers: {
[name: string]: unknown;
};
content: {
"application/problem+json": components["schemas"]["Problem"];
};
};
};
parameters: never;
requestBodies: never;
headers: never;
pathItems: never;
}
export type $defs = Record<string, never>;
export interface operations {
getHealthz: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Процесс жив */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HealthStatus"];
};
};
};
};
getReadyz: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Все зависимости доступны */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ReadyStatus"];
};
};
/** @description Одна или несколько зависимостей недоступны */
503: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ReadyStatus"];
};
};
};
};
getServerInfo: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Сведения о сервере */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ServerInfo"];
};
};
default: components["responses"]["Problem"];
};
};
}
+19
View File
@@ -0,0 +1,19 @@
import createClient, { type Client } from 'openapi-fetch';
import type { components, paths } from './generated/schema';
export type { components, paths };
export type ServerInfo = components['schemas']['ServerInfo'];
export type Problem = components['schemas']['Problem'];
export type ApiClient = Client<paths>;
export interface ApiClientOptions {
/** Адрес сервера без завершающего слеша; пусто — тот же origin (веб). */
baseUrl?: string;
fetch?: typeof globalThis.fetch;
}
/** Создаёт типизированный клиент публичного API (контракт — api/openapi.yaml). */
export function createApiClient(opts: ApiClientOptions = {}): ApiClient {
return createClient<paths>({ baseUrl: opts.baseUrl ?? '', fetch: opts.fetch });
}
+1
View File
@@ -0,0 +1 @@
{ "extends": "../../tsconfig.base.json", "compilerOptions": { "lib": ["ES2022", "DOM"] }, "include": ["src"] }