docs: restructure server and web documentation into ADR files and a

routing table
This commit is contained in:
2026-07-17 22:07:55 +08:00
parent f8494a44ca
commit 519aa35f1f
23 changed files with 710 additions and 654 deletions
@@ -0,0 +1,27 @@
# ADR-0001: Select the Server Foundation
Status: Accepted
Date: 2026-04-25
## Context
MyGO needed a server stack that supports local development, relational metadata, configurable deployment, and multiple file storage backends.
## Decision
- Use Gin for HTTP routing and middleware.
- Use GORM with SQLite and PostgreSQL drivers.
- Use Viper for YAML and environment configuration, with a Cobra flag for the config file path.
- Use local file storage first and expose storage through `internal/storage`.
- Use UUIDs for file and domain identities.
- Use JWT access tokens and database-backed refresh sessions.
- Use offset and limit pagination for the initial API.
- Return direct JSON success bodies and a shared JSON error shape.
- Organize the server as Handler -> Service -> Repository and Storage.
## Consequences
- SQLite supports a zero-service local setup; PostgreSQL supports multi-instance deployments.
- Repository and storage interfaces isolate infrastructure choices from services.
- Database-backed refresh sessions support rotation and revocation.
- Offset pagination remains suitable for the current dataset size and API scope.
@@ -0,0 +1,24 @@
# ADR-0002: Establish the REST API Foundation
Status: Accepted
Date: 2026-04-27
## Context
The first HTTP slice needed stable versioning, response conventions, dependency composition, and server lifecycle behavior.
## Decision
- Place REST routes under `/api/v1`.
- Expose build metadata through `GET /api/v1/version`.
- Return resource bodies directly for successful requests.
- Return errors as `{"error":{"message":"..."}}` with optional diagnostic fields.
- Use `internal/app.Bootstrap` to construct runtime dependencies and return `app.WebApp`.
- Register public and protected routes in separate server functions.
- Stop accepting new requests during shutdown and allow in-flight requests to finish.
## Consequences
- Future REST endpoints share one version boundary and response convention.
- `app.WebApp` provides the services and metadata required by route setup.
- HTTP lifecycle behavior stays in `internal/server`.
@@ -0,0 +1,24 @@
# ADR-0003: Define Authentication Contracts
Status: Accepted
Date: 2026-04-29
## Context
Authentication requires distinct access and refresh behavior, stable duration parsing, and safe development defaults.
## Decision
- Use `AuthHandler` for public authentication routes and `AccountHandler` for authenticated account routes.
- Add a JWT `type` claim that distinguishes access and refresh tokens.
- Verify token type at the boundary that consumes the token.
- Store configuration durations as `time.Duration` values.
- Replace known development JWT placeholders with a random runtime secret during configuration loading.
## Consequences
- Access tokens authenticate protected requests.
- Refresh tokens rotate token pairs through database sessions.
- Invalid duration values fail during startup.
- Tokens signed with the generated development secret expire when the process restarts.
- Multi-instance deployments require a stable configured secret.
@@ -0,0 +1,24 @@
# ADR-0004: Stage Uploads Before Publication
Status: Accepted
Date: 2026-07-05
## Context
File uploads need streaming size enforcement and a clear publication boundary between stored content and visible metadata.
## Decision
- Stream multipart bodies with `Request.MultipartReader`.
- Interpret `storage.max_upload_size = 0` as unlimited and positive values as a byte limit.
- Write upload content to a staging path.
- Validate and promote staged content before creating the active database record.
- Pass multipart `parent_id` as a query parameter.
- Convert HTTP body errors in the handler and domain upload errors in the service.
## Consequences
- Upload size checks apply while the request streams.
- Interrupted and rejected uploads remain in the staging namespace.
- Local storage promotes with rename and uses copy and delete across filesystems.
- A failed database create can leave a promoted object; the service attempts cleanup and the object remains invisible to the file API.
@@ -0,0 +1,23 @@
# ADR-0005: Keep Service Contracts Protocol-Neutral
Status: Accepted
Date: 2026-07-05
## Context
MyGO exposes REST today and is designed to support additional protocols. Business services need contracts that transport adapters can reuse.
## Decision
- Services return domain results and `model.AppError` values.
- HTTP handlers own REST response DTOs.
- `internal/api` maps domain error kinds to REST responses.
- HTTP packages obtain application behavior through service contracts and authenticated principals.
- Architecture tests enforce the package dependency boundaries.
## Consequences
- Additional protocol adapters can reuse the existing services.
- Each protocol adapter defines its own response mapping.
- Service tests exercise business behavior without constructing HTTP requests.
- Error responses can include `error.log_id` for failures recorded by the server.
@@ -0,0 +1,22 @@
# ADR-0006: Conceal File Ownership Boundaries
Status: Accepted
Date: 2026-07-15
## Context
Authenticated callers must not infer the existence of files owned by another user from status codes or response messages.
## Decision
- Map missing, deleted, and cross-user file targets to the same not-found result.
- Apply the same rule to parent-directory lookups.
- Return success for one deletion of an active owned file.
- Return not found for repeated deletion and cross-user deletion.
- Guard soft deletion with ownership and active-state predicates.
## Consequences
- File responses conceal resource ownership from other authenticated users.
- The first successful deletion returns `204 No Content`; later attempts return `404 Not Found`.
- Non-empty directory deletion continues to return a conflict.
@@ -0,0 +1,29 @@
# ADR-0007: Use Command-Scoped Persistence Mutations
Status: Accepted
Date: 2026-07-16
## Context
Each business operation needs a persistence contract that exposes its writable fields, authorization predicates, and transaction boundary.
## Decision
- Give each mutation a use-case-specific method and parameter type.
- Inject the query and mutation capabilities required by each service.
- Select mutation columns explicitly.
- Include ownership, credential type, and active-state predicates where the operation requires them.
- Revalidate file hierarchy state inside the mutation transaction.
- Lock PostgreSQL hierarchy rows in sorted order.
- Start SQLite write transactions with `_txlock=immediate`.
- Consume a refresh session in one lock, read, and delete transaction.
- Use `internal/testutil` for privileged fixture setup.
## Consequences
- Mutation inputs expose the fields writable by the use case.
- Guarded zero-row updates map to domain not-found results.
- File hierarchy mutations preserve active parent-child relationships under concurrency.
- One refresh session issues one replacement token pair.
- SQLite serializes these write transactions from their first database operation.
- Future mutations require a named command and an explicit capability.