Initial import

This commit is contained in:
sirily
2026-03-10 14:03:52 +03:00
commit 6c0ca4e28b
102 changed files with 6598 additions and 0 deletions

13
docs/AGENTS.md Normal file
View File

@@ -0,0 +1,13 @@
# AGENTS.md
## Scope
Applies within `docs/`.
## Purpose
Documentation in this directory is operational source-of-truth, not marketing copy.
## Rules
- Keep docs aligned with actual architecture decisions.
- Prefer short, decision-complete documents over long narrative text.
- When architecture changes, update the relevant document in the same change.
- Do not store secrets or live credentials here.

View File

@@ -0,0 +1,52 @@
# Repository Layout
## Tree
```text
.
|- apps/
| |- web/
| |- worker/
| |- bot/
| `- cli/
|- packages/
| |- config/
| |- db/
| |- domain/
| `- providers/
|- docs/
| |- plan/
| |- architecture/
| `- ops/
|- infra/
| |- compose/
| `- caddy/
`- scripts/
```
## Directory responsibilities
### `apps/web`
Owns the browser-facing product and HTTP API entrypoints. It should not own core business rules.
### `apps/worker`
Owns asynchronous and scheduled work. It is the execution surface for image-generation jobs, cleanup, and health polling.
### `apps/bot`
Owns Telegram admin interaction only. Business decisions still belong to `packages/domain`.
### `apps/cli`
Owns operator-facing CLI commands such as `nproxy pair`, `nproxy pair list`, and `nproxy pair revoke`.
### `packages/config`
Owns typed environment contracts and config normalization.
### `packages/db`
Owns database schema, migrations, and data-access utilities.
### `packages/domain`
Owns subscription logic, quota logic, key state transitions, and orchestration rules.
### `packages/providers`
Owns provider-specific adapters and low-level HTTP calls. It should not decide business policy.
### `infra`
Owns deployment templates and reverse-proxy configuration for the single-VPS Docker Compose target.

View File

@@ -0,0 +1,34 @@
# System Overview
## Runtime components
- `apps/web`: public site, user dashboard, admin UI, HTTP API handlers
- `apps/worker`: background jobs for generation execution, reconciliation, cleanup, and health checks
- `apps/bot`: Telegram admin bot runtime
- `apps/cli`: operator commands executed on the server
## Shared packages
- `packages/config`: environment parsing and config contracts
- `packages/db`: Prisma schema, migrations, data access helpers
- `packages/domain`: business rules and state machines
- `packages/providers`: external adapters for model APIs, payment processor, storage, email, and Telegram
## Core request flow
1. User submits a generation request from the chat UI.
2. The web app validates auth, subscription, quota, and request shape.
3. The app stores a `GenerationRequest` and enqueues work.
4. The worker runs provider routing through the key pool.
5. The worker persists `GenerationAttempt` rows for each key-level attempt.
6. On the first success, the worker stores assets, marks the request succeeded, and consumes quota.
7. The web app exposes polling endpoints until the result is ready.
## Data boundaries
- User-visible request lifecycle lives in `GenerationRequest`.
- Key-level retries live in `GenerationAttempt`.
- Quota accounting lives in `UsageLedgerEntry`.
- Provider key health lives in `ProviderKey` plus status-event history.
## Failure handling
- Retryable provider failures are hidden from the user while eligible keys remain.
- User-caused provider failures are terminal for that request.
- Balance or quota exhaustion removes a key from active rotation.
- Provider-key state transitions must be audited.

48
docs/ops/deployment.md Normal file
View File

@@ -0,0 +1,48 @@
# Deployment Plan
## Chosen target
Deploy on one VPS with Docker Compose.
## Why this target
- The system has multiple long-lived components: web, worker, bot, database, and reverse proxy.
- Compose gives predictable service boundaries, easier upgrades, and easier recovery than manually managed host processes.
- It keeps the path open for later separation of web, worker, and bot without reworking the repository layout.
## Expected services
- `migrate`: one-shot schema bootstrap job run before app services start
- `web`: Next.js app serving the site, dashboard, admin UI, and API routes
- `worker`: background job processor
- `bot`: Telegram admin bot runtime
- `postgres`: primary database
- `caddy`: TLS termination and reverse proxy
- optional `minio`: self-hosted object storage for single-server deployments
## Deployment notes
- Run one Compose project on a single server.
- Keep persistent data in named volumes or external storage.
- Keep secrets in server-side environment files or a secret manager.
- Back up PostgreSQL and object storage separately.
- Prefer Telegram long polling in MVP to avoid an extra public webhook surface for the bot.
## Upgrade strategy
- Build new images.
- Run the one-shot database schema job.
- Restart `web`, `worker`, and `bot` in the same Compose project.
- Roll back by redeploying the previous image set if schema changes are backward compatible.
## Current database bootstrap state
- The current Compose template runs a `migrate` service before `web`, `worker`, and `bot`.
- The job runs `prisma migrate deploy` from the committed migration history.
- The same bootstrap job also ensures the default MVP `SubscriptionPlan` row exists after migrations.
- Schema changes must land with a new committed Prisma migration before deployment.
## Initial operational checklist
- provision VPS
- install Docker and Compose plugin
- provision DNS and TLS
- provision PostgreSQL storage
- provision S3-compatible storage or enable local MinIO
- create `.env`
- deploy Compose stack
- run database migration job
- verify web health, worker job loop, and bot polling

View File

@@ -0,0 +1,67 @@
# Provider Key Pool
## Purpose
Route generation traffic through multiple provider API keys while hiding transient failures from end users.
## Key selection
- Only keys in `active` state are eligible for first-pass routing.
- Requests start from the next active key by round robin.
- A single request must not attempt the same key twice.
## Optional proxy behavior
- A key may have one optional proxy attached.
- If a proxy exists, the first attempt uses the proxy.
- If the proxy path fails with a transport error, retry the same key directly.
- Direct fallback does not bypass other business checks.
- Current runtime policy reads cooldown and manual-review thresholds from environment:
- `KEY_COOLDOWN_MINUTES`
- `KEY_FAILURES_BEFORE_MANUAL_REVIEW`
## Retry rules
Retry on the next key only for:
- network errors
- connection failures
- timeouts
- provider `5xx`
Do not retry on the next key for:
- validation errors
- unsupported inputs
- policy rejections
- other user-caused provider `4xx`
## States
- `active`
- `cooldown`
- `out_of_funds`
- `manual_review`
- `disabled`
## Transitions
- `active -> cooldown` on retryable failures
- `cooldown -> active` after successful automatic recheck
- `cooldown -> manual_review` after more than 10 consecutive retryable failures across recovery cycles
- `active|cooldown -> out_of_funds` on confirmed insufficient funds
- `out_of_funds -> active` only by manual admin action
- `manual_review -> active` only by manual admin action
- `active -> disabled` by manual admin action
## Current runtime note
- The current worker implementation already applies proxy-first then direct fallback within one provider-key attempt.
- The current worker implementation writes `GenerationAttempt.usedProxy` and `GenerationAttempt.directFallbackUsed` for auditability.
- The current worker implementation also runs a background cooldown-recovery sweep and returns keys to `active` after `cooldownUntil` passes.
## Balance tracking
- Primary source of truth is the provider balance API.
- Balance refresh runs periodically and also after relevant failures.
- Telegram admin output must show per-key balance snapshots and the count of keys in `out_of_funds`.
## Admin expectations
Web admin and Telegram admin must both be able to:
- inspect key state
- inspect last error category and code
- inspect balance snapshot and refresh time
- enable or disable a key
- return a key from `manual_review`
- return a key from `out_of_funds`
- add a new key

View File

@@ -0,0 +1,48 @@
# Telegram Pairing Flow
## Goal
Allow a new Telegram admin to be approved from the server console without editing the database manually.
## Runtime behavior
### Unpaired user
1. A user opens the Telegram bot.
2. The bot checks whether `telegram_user_id` is present in the allowlist.
3. If not present, the bot creates a pending pairing record with:
- Telegram user ID
- Telegram username and display name snapshot
- pairing code hash
- expiration timestamp
- status `pending`
4. The bot replies with a message telling the user to run `nproxy pair <code>` on the server.
Current runtime note:
- The current bot runtime uses Telegram long polling.
- On each message from an unpaired user, the bot rotates any previous pending code and issues a fresh pairing code.
- Pending pairing creation writes an audit-log entry with actor type `system`.
### Pair completion
1. An operator runs `nproxy pair <code>` on the server.
2. The CLI looks up the pending pairing by code.
3. The CLI prints the target Telegram identity and asks for confirmation.
4. On confirmation, the CLI adds the Telegram user to the allowlist.
5. The CLI marks the pending pairing record as `completed`.
6. The CLI writes an admin action log entry.
## Required CLI commands
- `nproxy pair <code>`
- `nproxy pair list`
- `nproxy pair revoke <telegram-user-id>`
- `nproxy pair cleanup`
## Current CLI behavior
- `nproxy pair <code>` prints the Telegram identity and requires explicit confirmation unless `--yes` is provided.
- `nproxy pair list` prints active allowlist entries and pending pairing records.
- `nproxy pair revoke <telegram-user-id>` requires explicit confirmation unless `--yes` is provided.
- `nproxy pair cleanup` marks expired pending pairing records as `expired` and writes an audit log entry.
## Security rules
- Pairing codes expire.
- Pairing codes are stored hashed, not in plaintext.
- Only the server-side CLI can complete a pairing.
- Telegram bot access is denied until allowlist membership exists.
- Every pairing and revocation action is auditable.

View File

@@ -0,0 +1,103 @@
# MVP System Plan
## Summary
Build `nproxy`, a B2C web product for image generation through external model APIs. The first model is `nano_banana`. Users register with `email + password`, pay a monthly crypto subscription, receive a monthly request limit, and use a chat-style interface for `text-to-image` and `image-to-image` generation.
The service hides provider-key failures behind a routed key pool. A user request is attempted against one provider key at a time. Retryable failures move execution to the next eligible key. The user sees an error only after all eligible keys have been exhausted or the request fails for a terminal user-caused reason.
## Confirmed MVP decisions
- One B2C website.
- One monthly subscription plan.
- Crypto checkout through a payment processor.
- Manual renewal in MVP.
- Text-to-image and image-to-image.
- User-facing synchronous experience implemented with polling over background execution.
- Approximate quota buckets only: `100/80/60/40/20/0`.
- Storage in S3-compatible object storage.
- One VPS deployment with Docker Compose.
- Web admin plus Telegram admin bot.
- Telegram admin onboarding through pairing on the server console.
- Multiple provider API keys with round-robin routing, cooldown, balance tracking, optional per-key proxy, and transparent failover.
## Core product surfaces
### Public web
- landing page
- register / login / password reset
- dashboard with subscription state and approximate quota
- chat UI
- billing / checkout pages
### Admin surfaces
- web admin for users, subscriptions, payments, generations, provider keys, proxies, and health
- Telegram bot for alerts and low-friction admin actions
- CLI for server-side operational commands, including Telegram pairing
## Main backend domains
- auth
- billing
- subscriptions
- quota ledger
- conversations and generations
- provider routing
- provider key pool health
- asset storage
- admin audit
- notifications
## Billing rules
- One active plan in MVP.
- Each user has an individual billing cycle based on successful activation timestamp.
- Limit resets on each successful cycle activation.
- One successful generation consumes one request.
- Failed generations do not consume quota.
## Quota display contract
Backend tracks exact usage. Normal users see only an approximate bucket:
- `81-100%` remaining -> `100%`
- `61-80%` remaining -> `80%`
- `41-60%` remaining -> `60%`
- `21-40%` remaining -> `40%`
- `1-20%` remaining -> `20%`
- `0%` remaining -> `0%`
## Generation controls in MVP
- mode: `text_to_image` or `image_to_image`
- resolution preset
- batch size
- image strength for `image_to_image`
## Key pool behavior
- Start from the next `active` key by round robin.
- Use the key-specific proxy first if configured.
- If the proxy path fails with a transport error, retry the same key directly.
- Retry on the next key only for retryable failures: network, timeout, provider `5xx`.
- Do not retry on the next key for validation, policy, or other user-caused `4xx` errors.
- Move a key to `cooldown` on retryable failures.
- Default cooldown is `5 minutes`.
- After more than `10` consecutive retryable failures across cooldown recoveries, move the key to `manual_review`.
- Move a key to `out_of_funds` when the provider balance API or provider response shows insufficient funds.
- `out_of_funds` and `manual_review` keys return to service only through a manual admin action.
## Telegram pairing
1. A Telegram user opens the bot.
2. If the user is not in the allowlist, the bot generates a short pairing code and stores a pending pairing record.
3. The bot tells the user to run `nproxy pair <code>` on the server.
4. The server-side CLI confirms the target user and adds the Telegram ID to the allowlist.
5. The pairing record is marked complete and the user gains bot access.
## Deployment target
Single VPS with Docker Compose, expected services:
- `web`
- `worker`
- `bot`
- `postgres`
- `caddy` or `nginx`
- optional `minio` when object storage is self-hosted
## Future-compatible boundaries
The codebase should be able to add later:
- more image providers
- more billing methods
- more subscription plans
- internal balance wallet
- recurring billing if the payment processor supports it natively