Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 519aa35f1f | |||
| f8494a44ca |
@@ -2,146 +2,140 @@
|
||||
|
||||
## Project Layout
|
||||
|
||||
- Server: Go backend in the repository root, with documentation in `docs/server/`
|
||||
- Web: React client in `web/`, with documentation in `docs/web/`
|
||||
- Server: Go backend in the repository root
|
||||
- Web: React client in `web/`
|
||||
- Documentation index: `docs/README.md`
|
||||
|
||||
## Agent Workflow
|
||||
|
||||
1. Read the task and identify the affected project
|
||||
2. Read `docs/README.md` and the relevant project documentation for context
|
||||
3. Explore existing code before writing new code
|
||||
4. Implement following the relevant project conventions below
|
||||
5. Run the verification commands for every affected project
|
||||
6. Update the relevant project roadmap, decisions, architecture, or development documentation if anything changed
|
||||
1. Identify the affected project and required outcome.
|
||||
2. Read `docs/README.md` and the relevant project documents.
|
||||
3. Explore the existing code before making changes.
|
||||
4. Follow the project conventions below.
|
||||
5. Run the verification commands for every affected project.
|
||||
6. Update documentation when architecture, workflow, decisions, or feature status changes.
|
||||
|
||||
## Documentation Routing
|
||||
|
||||
| Change | Read | Update |
|
||||
|--------|------|--------|
|
||||
| Server packages or boundaries | `docs/server/architecture.md` | When the current architecture changes |
|
||||
| Server design or dependency decision | `docs/server/decisions.md` | When a decision is accepted or superseded |
|
||||
| Server build, test, review, or config workflow | `docs/server/development.md` | When the workflow changes |
|
||||
| Server feature scope or status | `docs/server/roadmap.md` | When capability status changes |
|
||||
| Web design or tooling decision | `docs/web/decisions.md` | When a decision is accepted or superseded |
|
||||
| Web feature scope or status | `docs/web/roadmap.md` | When capability status changes |
|
||||
| Repository documentation | `docs/writing-guide.md` | When documentation policy changes |
|
||||
|
||||
Use the relevant decision index before making a significant architecture or dependency decision. Read only the ADRs relevant to the change.
|
||||
|
||||
## Server — Go Backend
|
||||
|
||||
### Project Essentials
|
||||
### Essentials
|
||||
|
||||
- Module: `github.com/dhao2001/mygo`, Go 1.26.2
|
||||
- WebDisk (cloud drive) backend; roadmap in `docs/server/roadmap.md`
|
||||
- CLI framework: `github.com/spf13/cobra`
|
||||
- Go version pinned in `mise.toml`
|
||||
|
||||
### Repository Review
|
||||
|
||||
- Explicitly invoke `$mygo-api-repo-review` for repository-wide or scoped architecture, design, security, resilience, data-consistency, performance, and implementation audits.
|
||||
- Repository reviews are read-only unless the user separately requests fixes.
|
||||
- Use `diff`, `main`, or `full` review mode and optionally limit any mode with a target path, Go package, or logical component.
|
||||
- Module: `github.com/dhao2001/mygo`
|
||||
- Go: 1.26.2, pinned in `mise.toml`
|
||||
- CLI: Cobra
|
||||
- Application code: `internal/`
|
||||
- CLI composition: `cmd/`
|
||||
|
||||
### Go Conventions
|
||||
|
||||
- **Format**: `go fmt ./...` before every commit
|
||||
- **Imports**: stdlib / third-party / internal, blank-line separated
|
||||
- **Errors**: wrap with `fmt.Errorf("context: %w", err)`
|
||||
- **Context**: first param in I/O, storage, lifecycle funcs
|
||||
- **Exported names**: doc-commented
|
||||
- **`init()`**: only in cobra cmd files for flag registration
|
||||
- **`cmd/`** is thin; business logic goes in `internal/`
|
||||
- Separate imports into standard library, third-party, and internal groups.
|
||||
- Wrap errors with operation context: `fmt.Errorf("operation: %w", err)`.
|
||||
- Pass `context.Context` first in I/O, storage, and lifecycle functions.
|
||||
- Add doc comments to exported names.
|
||||
- Keep business logic in `internal/`; keep `cmd/` focused on CLI composition and startup.
|
||||
- Add required Go module dependencies before writing code that imports them.
|
||||
|
||||
### Documentation
|
||||
|
||||
| File | Read Before | Update After |
|
||||
|------|-------------|--------------|
|
||||
| `docs/server/architecture.md` | Adding new packages | Adding new packages |
|
||||
| `docs/server/decisions.md` | Making technical decisions | Making technical decisions |
|
||||
| `docs/server/roadmap.md` | Every server task | Completing a server feature |
|
||||
| `docs/server/development.md` | Build/test/debug setup | Changing server workflow |
|
||||
|
||||
### Commands
|
||||
### Verification
|
||||
|
||||
```bash
|
||||
go build ./... # build all packages
|
||||
go test ./... # all tests
|
||||
go vet ./... # static analysis
|
||||
go fmt ./... # format
|
||||
go mod tidy # clean deps after add/remove
|
||||
go fmt ./...
|
||||
go build ./...
|
||||
go test ./...
|
||||
go vet ./...
|
||||
```
|
||||
|
||||
### Server DO / DON'T
|
||||
Run `go mod tidy` after adding or removing imports.
|
||||
|
||||
- DO put business logic in `internal/`, keep `cmd/` thin
|
||||
- DO add all Go module dependencies **before** writing code that uses them
|
||||
- DON'T read `go.sum` entirely into context — use `grep` or other tools to search specific patterns if needed
|
||||
- DON'T skip `go vet ./...` before finishing server work
|
||||
- DON'T add, remove, or change Go module dependencies after debugging has started — ask for explicit permission first
|
||||
### Repository Review
|
||||
|
||||
Invoke `$mygo-api-repo-review` for repository-wide or scoped architecture, design, security, resilience, data-consistency, performance, and implementation audits.
|
||||
|
||||
- `diff`: compare the workspace, including relevant untracked files, with `HEAD`.
|
||||
- `main`: compare the workspace with the merge base of `HEAD` and local `main`.
|
||||
- `full`: review the complete repository.
|
||||
|
||||
Reviews are read-only unless the user also requests fixes. A mode can target a path, Go package, or logical component.
|
||||
|
||||
### Server Constraints
|
||||
|
||||
- Do not load the complete `go.sum` file into context. Use `rg` to find the required module or version entry.
|
||||
- Dependency changes made after debugging begins require explicit user approval.
|
||||
- Complete `go vet ./...` before finishing server code changes.
|
||||
|
||||
## Web — React Client
|
||||
|
||||
### Project Essentials
|
||||
### Essentials
|
||||
|
||||
- Source: `web/`
|
||||
- Pure client-side rendered application; roadmap in `docs/web/roadmap.md`
|
||||
- Stack: Node.js 24, Vite, React, TypeScript, React Router, TanStack Query, Tailwind CSS 4, and Ant Design
|
||||
- Node.js version pinned in `mise.toml`
|
||||
- Node.js: 24, pinned in `mise.toml`
|
||||
- Rendering: client-side Vite application
|
||||
- Stack: React, TypeScript, React Router, TanStack Query, Tailwind CSS 4, and Ant Design
|
||||
|
||||
### Documentation
|
||||
|
||||
| File | Read Before | Update After |
|
||||
|------|-------------|--------------|
|
||||
| `docs/web/decisions.md` | Making Web technical decisions | Making Web technical decisions |
|
||||
| `docs/web/roadmap.md` | Every Web task | Completing a Web feature or changing the Web plan |
|
||||
|
||||
### Commands
|
||||
### Verification
|
||||
|
||||
Run from `web/`:
|
||||
|
||||
```bash
|
||||
npm ci # install locked dependencies
|
||||
npm run dev # start the development server
|
||||
npm run check # lint, type-check, and build
|
||||
npm ci
|
||||
npm run check
|
||||
```
|
||||
|
||||
Use `$playwright-cli` for browser inspection and debugging. Run the repository wrapper from `web/`:
|
||||
|
||||
```bash
|
||||
mise exec -- npm run playwright:cli -- <command>
|
||||
```
|
||||
|
||||
Do not invoke Playwright through a global installation or a floating `npx` package.
|
||||
|
||||
## Git Version Control
|
||||
|
||||
- When using a worktree, create a new branch and place its worktree in `.worktree/`, naming safely (for example, `git worktree add -b feat/partly-upload .worktree/feat-partly-upload`).
|
||||
- DON'T create a commit unless the user explicitly asks for one.
|
||||
- Before any commit, verify the work with the required project checks. For code changes, run the checks for every affected project; for docs-only changes, run the most relevant non-mutating checks if available.
|
||||
- Create a commit only when all required checks pass and the current implementation area has no unresolved issues.
|
||||
- If a known failing check or unresolved issue belongs to another module and is outside the current task, report it clearly before asking for commit approval.
|
||||
- Before running `git commit`, write the complete commit message first, show it to the user, and ask for explicit approval. DON'T commit until the user approves that exact message.
|
||||
- Never include `Co-authored-by`, generated-tool signatures, or external attribution trailers unless the user explicitly asks for them.
|
||||
- Place worktrees under `.worktree/` and use a safe branch name, for example `git worktree add -b feat/partly-upload .worktree/feat-partly-upload`.
|
||||
- Create a commit only when the user explicitly requests one.
|
||||
- Run the required checks before proposing a commit. For documentation-only changes, run the relevant documentation checks.
|
||||
- Resolve implementation issues in the affected area before proposing a commit. Report unrelated known failures separately.
|
||||
- Write the complete commit message and obtain explicit approval for that exact message before running `git commit`.
|
||||
- Commit messages contain no `Co-authored-by`, generated-tool signatures, or attribution trailers unless the user requests them.
|
||||
|
||||
### Commit Message Format
|
||||
|
||||
Use Conventional Commits:
|
||||
|
||||
```text
|
||||
<type>[optional scope][optional !]: <description title>
|
||||
<type>[optional scope][optional !]: <description>
|
||||
|
||||
<body>
|
||||
```
|
||||
|
||||
- Choose the most accurate `type`: `fix`, `feat`, `build`, `docs`, `refactor`, or `test`.
|
||||
- Add `!` after `type` or `scope` when the change contains a breaking API change.
|
||||
- Keep the title to one concise sentence describing the main change.
|
||||
- Write the body as bullet points grouped by change category. Each bullet starts with a typed prefix such as `feat:`, `fix:`, `test:`, `docs:`, `refactor:`, or `build:`.
|
||||
- Use as few as possible bullets.
|
||||
- Use one blank line between the title and body.
|
||||
- DON'T paste full code sentences into the message body; summarize behavior and intent.
|
||||
- Use `fix`, `feat`, `build`, `docs`, `refactor`, or `test` as the type.
|
||||
- Add `!` for a breaking API change.
|
||||
- Keep the title concise.
|
||||
- Write the body as a small set of bullets with typed prefixes such as `feat:`, `fix:`, `test:`, or `docs:`.
|
||||
- Leave one blank line between the title and body.
|
||||
- Summarize behavior and intent instead of copying code statements.
|
||||
|
||||
Example:
|
||||
## Shared Constraints
|
||||
|
||||
```text
|
||||
feat(middleware): add AdminRequired authorization middleware
|
||||
|
||||
- feat: AdminRequired gates admin endpoints by checking user IsAdmin flag. Placed after AuthRequired; fetches user from repository, returns 403 for non-admins.
|
||||
- fix: soft-deleted users are rejected with 401 since FindByID excludes them.
|
||||
- test: admin passes, non-admin forbidden, soft-deleted admin rejected, missing user ID.
|
||||
```
|
||||
|
||||
## Shared DO / DON'T
|
||||
|
||||
- DO write all code, comments, and documentation in English
|
||||
- DON'T commit without following the Git Version Control rules above
|
||||
- Write code, comments, and repository documentation in English.
|
||||
- Follow the commit authorization workflow above.
|
||||
|
||||
## Debugging Principles
|
||||
|
||||
When a test failure occurs, follow this strict order:
|
||||
|
||||
1. **Examine the test first** — ensure the test code correctly expresses the intended program behavior
|
||||
2. **Fix the test if it's wrong** — if the test doesn't represent correct expected behavior, correct the test to match the intended behavior
|
||||
3. **Fix the implementation if the test is correct** — only after confirming the test is valid, locate and fix the bug in the implementation
|
||||
4. **Never weaken tests to gain passing status** — do not relax assertions, remove edge cases, or simplify test logic just to make tests pass. Tests exist to catch problems, not to produce a 100% pass rate
|
||||
5. **Escalate after 6 rounds** — if a problem remains unresolved after 6 debugging attempts, stop and report the current state to the user for further investigation
|
||||
1. Confirm that the test expresses the intended behavior.
|
||||
2. Correct the test when its expectation is wrong.
|
||||
3. Correct the implementation when the test is valid.
|
||||
4. Preserve intended assertions and edge-case coverage. Never weaken a valid test to make it pass.
|
||||
5. Stop after six unsuccessful debugging rounds and report the current evidence.
|
||||
|
||||
@@ -1,53 +1,33 @@
|
||||
# MyGO
|
||||
|
||||
MyGO is a WebDisk (cloud drive) server, written in Go.
|
||||
MyGO is a pre-alpha WebDisk server with a Go backend and a browser client.
|
||||
|
||||
**Current status**: pre-alpha — basic JWT authentication and file transfer flows
|
||||
are available while their APIs and large-file behavior continue to evolve.
|
||||
## Current Capabilities
|
||||
|
||||
---
|
||||
- JWT access and refresh authentication
|
||||
- Application passkeys
|
||||
- File and directory management through `/api/v1`
|
||||
- Local file storage with staged upload promotion
|
||||
- Administrator user listing, lookup, and deletion
|
||||
- React client with login, root file listing, upload, and download
|
||||
|
||||
## Roadmap
|
||||
|
||||
### v0
|
||||
|
||||
- [ ] CLI configuration management (`mygo config`)
|
||||
- [x] User authentication (JWT)
|
||||
- [x] Basic file upload / download / management (HTTP API)
|
||||
- [ ] Admin endpoints
|
||||
- [ ] WebDAV support
|
||||
|
||||
### Future
|
||||
|
||||
- [ ] Image server
|
||||
- [ ] Pastebin & code snippet editing in sharing
|
||||
- [ ] S3 storage backend
|
||||
- [ ] Nextcloud-compatible API
|
||||
|
||||
---
|
||||
APIs and large-file behavior continue to evolve.
|
||||
|
||||
## CLI
|
||||
|
||||
`mygo` is the backend entrypoint with these subcommands:
|
||||
The repository currently provides one server command:
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `mygo serve` | Start the backend server |
|
||||
| `mygo config` | Manage instance configuration |
|
||||
| `mygo status` | Show server status |
|
||||
| `mygo serve` | Load configuration and start the HTTP server |
|
||||
|
||||
---
|
||||
## Web Client
|
||||
|
||||
## Frontend
|
||||
The browser client lives in `web/` and uses the same REST API as other clients. See [`web/README.md`](web/README.md) for development, testing, and browser-debugging instructions.
|
||||
|
||||
The frontend uses React + TypeScript and communicates with the backend via HTTP
|
||||
API. Its first milestone supports login, a root file list, single-file upload,
|
||||
and authenticated download. Frontend source is maintained in `web/`; see
|
||||
[`web/README.md`](web/README.md) for development, testing, and browser-debugging
|
||||
instructions.
|
||||
## Documentation
|
||||
|
||||
---
|
||||
|
||||
## Development
|
||||
|
||||
See `docs/README.md` for the server and Web documentation index. See `AGENTS.md` for behavioral conventions.
|
||||
- [Documentation index](docs/README.md)
|
||||
- [Server roadmap](docs/server/roadmap.md)
|
||||
- [Web roadmap](docs/web/roadmap.md)
|
||||
- [Agent conventions](AGENTS.md)
|
||||
|
||||
+19
-12
@@ -1,19 +1,26 @@
|
||||
# Docs
|
||||
# Documentation
|
||||
|
||||
Documentation is organized by project. Use `server/` for the Go backend and `web/` for the browser client.
|
||||
Read the documents required by the current task. Current-state documents, roadmaps, and decision records serve different purposes.
|
||||
|
||||
## Shared
|
||||
|
||||
| Document | Purpose | Read when |
|
||||
|----------|---------|-----------|
|
||||
| [`writing-guide.md`](writing-guide.md) | Documentation roles and writing rules | Writing or reviewing repository documentation |
|
||||
|
||||
## Server
|
||||
|
||||
| File | Content |
|
||||
|------|---------|
|
||||
| `server/architecture.md` | Module layout, package boundaries |
|
||||
| `server/decisions.md` | Technical decisions (ADR) |
|
||||
| `server/roadmap.md` | Feature progress and status |
|
||||
| `server/development.md` | Build, test, debug workflow |
|
||||
| Document | Purpose | Read when |
|
||||
|----------|---------|-----------|
|
||||
| [`server/architecture.md`](server/architecture.md) | Current packages, responsibilities, and enforced boundaries | Changing server structure or cross-package behavior |
|
||||
| [`server/development.md`](server/development.md) | Build, test, review, and configuration workflow | Developing or debugging the server |
|
||||
| [`server/roadmap.md`](server/roadmap.md) | Available, planned, and future capabilities | Planning or completing server features |
|
||||
| [`server/decisions.md`](server/decisions.md) | Server ADR index | Making or revisiting a significant server decision |
|
||||
|
||||
## Web
|
||||
|
||||
| File | Content |
|
||||
|------|---------|
|
||||
| `web/decisions.md` | Technical decisions (ADR) |
|
||||
| `web/roadmap.md` | Product boundary, foundation, planned structure, and deferred dependencies |
|
||||
| Document | Purpose | Read when |
|
||||
|----------|---------|-----------|
|
||||
| [`../web/README.md`](../web/README.md) | Web development and browser workflow | Developing or debugging the Web client |
|
||||
| [`web/roadmap.md`](web/roadmap.md) | Available, planned, and future capabilities | Planning or completing Web features |
|
||||
| [`web/decisions.md`](web/decisions.md) | Web ADR index | Making or revisiting a significant Web decision |
|
||||
|
||||
+81
-101
@@ -1,106 +1,86 @@
|
||||
# Architecture
|
||||
# Server Architecture
|
||||
|
||||
## Layered Design
|
||||
## Request Flow
|
||||
|
||||
```
|
||||
Handler (Gin handlers) ← translates HTTP ↔ Service calls
|
||||
↓
|
||||
Service (business logic) ← orchestrates, authorizes, validates
|
||||
↓ ↓
|
||||
Repository (GORM data access) Storage (file I/O)
|
||||
↓ ↓
|
||||
[SQLite / PostgreSQL] [Local FS / S3]
|
||||
```text
|
||||
HTTP request
|
||||
-> middleware
|
||||
-> handler
|
||||
-> service
|
||||
-> repository and storage
|
||||
```
|
||||
|
||||
Rules:
|
||||
- Handler has no business logic — parse request, call service, write response.
|
||||
- Handler and middleware never access repositories directly; they depend on services.
|
||||
- Service has no HTTP awareness — operates on domain models and interfaces.
|
||||
## Layer Responsibilities
|
||||
|
||||
| Layer | Packages | Responsibility |
|
||||
|-------|----------|----------------|
|
||||
| CLI | `cmd` | Register Cobra commands and start the application |
|
||||
| Composition | `internal/app` | Open runtime resources and construct services |
|
||||
| HTTP | `internal/server`, `internal/handler`, `internal/middleware`, `internal/api` | Route requests, authenticate principals, map DTOs, and write HTTP responses |
|
||||
| Business | `internal/service`, `internal/model` | Validate operations, enforce authorization, and represent domain results and errors |
|
||||
| Data | `internal/repository`, `internal/storage` | Persist metadata and file content |
|
||||
| Infrastructure | `internal/config`, `internal/logging`, `internal/auth` | Load configuration, create loggers, and provide token and password primitives |
|
||||
| Test support | `internal/testutil` | Prepare privileged fixtures through test-only helpers |
|
||||
|
||||
Handlers define REST request and response DTOs. `internal/api` maps domain error kinds to HTTP status codes and error bodies.
|
||||
|
||||
Services use protocol-neutral domain types and errors. Each service constructor accepts the repository capabilities required by its use cases.
|
||||
|
||||
Repository query capabilities return domain records. Mutation capabilities use operation-specific parameter types, select writable columns explicitly, and apply the ownership and lifecycle predicates of the operation.
|
||||
|
||||
Storage backends manage staged and permanent file content. Repository records hold storage paths and file metadata.
|
||||
|
||||
## Runtime Composition
|
||||
|
||||
`app.Bootstrap` opens the configured database, runs migrations, creates repositories and local storage, constructs the services, and returns `app.WebApp`.
|
||||
|
||||
`server.NewRouter` creates a Gin engine and registers the public and protected route groups. `cmd/serve.go` loads configuration, initializes logging, bootstraps the application, and runs graceful shutdown.
|
||||
|
||||
`app.WebApp` owns the database lifecycle and exposes the services, storage backend, configuration, and build metadata required by HTTP setup.
|
||||
|
||||
## HTTP Boundaries
|
||||
|
||||
Global middleware runs in this order:
|
||||
|
||||
1. Request ID
|
||||
2. Gin access logger
|
||||
3. Gin recovery
|
||||
|
||||
Protected routes use `AuthRequired` to authenticate an access token and attach a service principal. Administrator routes add `AdminRequired` to verify the principal's administrator flag.
|
||||
|
||||
| Route group | Access | Capability |
|
||||
|-------------|--------|------------|
|
||||
| `/api/v1/version` | Public | Build metadata |
|
||||
| `/api/v1/auth` | Public | Registration, login, refresh, and logout |
|
||||
| `/api/v1/account` | Authenticated | Account and application passkeys |
|
||||
| `/api/v1/files` | Authenticated | File and directory operations |
|
||||
| `/api/v1/admin/users` | Administrator | User listing, lookup, and deletion |
|
||||
|
||||
The route declarations in `internal/server/routes_public.go` and `internal/server/routes_protected.go` are the source of truth for individual methods and paths.
|
||||
|
||||
## Persistence Commands
|
||||
|
||||
| Area | Mutation contract |
|
||||
|------|-------------------|
|
||||
| Users | Registration accepts `RegisteredUserParams` and creates an active non-admin user. Administration exposes a separate deletion command. |
|
||||
| Sessions | Refresh sessions are created, atomically consumed, revoked by token hash, or removed by maintenance operations. |
|
||||
| Files | Upload, directory creation, metadata update, and soft deletion use distinct parameter types and commands. |
|
||||
| Credentials | Application passkeys have explicit creation, usage-recording, and revocation operations. |
|
||||
|
||||
Metadata updates can write `name`, `parent_id`, and GORM-managed `updated_at`. File ownership and active-state predicates scope user mutations.
|
||||
|
||||
## Transaction Boundaries
|
||||
|
||||
File child creation, movement, and directory deletion use one hierarchy transaction protocol. PostgreSQL locks active owned source and parent rows in sorted ID order. SQLite adds `_txlock=immediate` to the configured DSN and reserves the write transaction before reading hierarchy state.
|
||||
|
||||
Refresh rotation locks, returns, and deletes the session in one transaction. A refresh session can issue one replacement token pair.
|
||||
|
||||
Uploads write to a staging path before promotion. The service promotes validated content before creating the active file record and attempts to remove promoted content when the database create fails.
|
||||
|
||||
## Enforced Architecture Constraints
|
||||
|
||||
`internal/architecture_test.go` enforces these boundaries:
|
||||
|
||||
- `internal/model` and `internal/service` do not import `net/http`, Gin, or `internal/api`.
|
||||
- JSON response DTOs live in HTTP packages. Domain and service result structs are not the public REST schema.
|
||||
- Domain models may use `json:"-"` for defensive redaction of sensitive fields; this is not a REST response contract.
|
||||
- Domain errors carry a protocol-neutral kind, a safe message, and an optional cause. HTTP status mapping happens only at the API boundary.
|
||||
- Repository query ports remain entity-oriented, while mutation ports expose business commands with operation-specific parameter types. Services never pass a mutable domain entity to a generic update or delete method.
|
||||
- Service constructors using split repository capabilities accept the smallest capability needed by that service. Their composition-time unions do not cross into business services.
|
||||
- Repository mutations use explicit columns and lifecycle/ownership predicates. Full-entity `Save` is prohibited so a write cannot accidentally change protected fields or revive a soft-deleted record.
|
||||
- Repository abstracts the database; Storage abstracts where bytes live, including staged upload promotion.
|
||||
- `internal/server` is the composition root — wires all dependencies together.
|
||||
|
||||
## Package Map
|
||||
|
||||
| Layer | Package | Purpose | Status |
|
||||
|-------|---------|---------|--------|
|
||||
| **CLI** | `cmd` | Cobra root command | 🛠 WIP |
|
||||
| | `cmd/serve.go` | `mygo serve` — wire deps, start HTTP | ✅ |
|
||||
| | `cmd/config.go` | `mygo config` — config subcommand | 🛠 WIP |
|
||||
| | `cmd/status.go` | `mygo status` — health check | 🛠 WIP |
|
||||
| **Config** | `internal/config` | Viper load (YAML + env + flags), typed Duration config via built-in decode hook | ✅ |
|
||||
| **App** | `internal/app` | Runtime dependency container and build metadata | 🛠 WIP |
|
||||
| **HTTP** | `internal/server` | Gin router init, route registration (public/protected split), graceful shutdown | ✅ |
|
||||
| | `internal/handler` | HTTP handlers (auth, file, admin, webdav...) | 🛠 WIP |
|
||||
| | `internal/middleware` | Gin middleware (logger, jwt, cors, auth) | 🛠 WIP |
|
||||
| **Business** | `internal/service` | Business logic: `AuthService`, `FileService`, `AdminService`; protocol-neutral results and errors | ✅ |
|
||||
| | `internal/model` | Domain types (User, File, Credential, Session), protocol-neutral error kinds | ✅ |
|
||||
| **Data** | `internal/repository` | Repository interfaces + GORM implementations (User, Session, File, Credential) | ✅ |
|
||||
| | `internal/storage` | Storage backend interface + local disk impl, with staging and promotion | 🛠 WIP |
|
||||
| **Util** | `internal/auth` | JWT sign/verify (HS256), token type discrimination (access/refresh), password hashing (bcrypt), app passkey tokens | ✅ |
|
||||
| | `internal/api` | Unified JSON error response helpers and REST error-kind mapping | ✅ |
|
||||
| | `internal/testutil` | Test-only fixture helpers for privileged setup that production service ports intentionally do not expose | ✅ |
|
||||
|
||||
## Persistence Write Boundaries
|
||||
|
||||
- User registration accepts `RegisteredUserParams`; the repository fixes new users to non-admin, active state. Administrative deletion is a separate capability.
|
||||
- File creation, directory creation, metadata changes, and soft deletion use distinct commands. Metadata updates can only write `name`, `parent_id`, and GORM-maintained `updated_at`.
|
||||
- Refresh sessions are created, atomically consumed, idempotently revoked, or removed by explicit maintenance operations. Refresh token rotation consumes a session at most once.
|
||||
- Passkey creation fixes the credential type to the app-passkey type. Usage recording and revocation include credential-type and ownership conditions where applicable.
|
||||
- `app.WebApp` contains runtime services, storage, database lifecycle, configuration, and metadata. Repository implementations remain local to bootstrap and are injected directly into the services that require them.
|
||||
|
||||
## File Hierarchy Transactions
|
||||
|
||||
File child creation, movement, and directory deletion share one parent-locking protocol. PostgreSQL locks active owned source and parent rows in sorted ID order with `SELECT ... FOR UPDATE`; directory deletion checks active children while holding the directory lock. SQLite connections add `_txlock=immediate` to the configured DSN so the transaction acquires its write reservation before reading hierarchy state. These rules prevent a concurrent mutation from creating an active child below a deleted directory.
|
||||
|
||||
## API Routes (v0)
|
||||
|
||||
```
|
||||
GET /api/v1/version
|
||||
|
||||
POST /api/v1/auth/register
|
||||
POST /api/v1/auth/login
|
||||
POST /api/v1/auth/refresh
|
||||
POST /api/v1/auth/logout
|
||||
|
||||
GET /api/v1/account
|
||||
GET /api/v1/account/passkeys
|
||||
POST /api/v1/account/passkeys
|
||||
DELETE /api/v1/account/passkeys/:id
|
||||
|
||||
GET /api/v1/files
|
||||
POST /api/v1/files # JSON creates directories; multipart uploads files. File upload parent_id is a query parameter.
|
||||
GET /api/v1/files/:id
|
||||
GET /api/v1/files/:id/content
|
||||
PUT /api/v1/files/:id
|
||||
DELETE /api/v1/files/:id
|
||||
|
||||
GET /api/v1/admin/users
|
||||
GET /api/v1/admin/users/:id
|
||||
PUT /api/v1/admin/users/:id
|
||||
DELETE /api/v1/admin/users/:id
|
||||
```
|
||||
|
||||
## Middleware Chain
|
||||
|
||||
Applied globally by `gin.Default()`: logger → recovery
|
||||
|
||||
Planned globally: cors
|
||||
|
||||
Applied to protected groups: auth (extract bearer token, delegate access-token authentication to `AuthService`, inject principal into gin.Context)
|
||||
|
||||
Applied to admin groups: admin (check authenticated principal from context)
|
||||
|
||||
## Server Responsibilities
|
||||
|
||||
- `cmd/serve.go` loads config, calls `app.Bootstrap` to initialize DB + services, builds the router, and starts the HTTP server.
|
||||
- `app.WebApp` carries runtime dependencies and build metadata needed to assemble handlers.
|
||||
- `internal/server` owns Gin router setup (`router.go`), route registration split into `routes_public.go` (public auth) and `routes_protected.go` (JWT-protected account).
|
||||
- Each route group creates its own handler instance: `routes_public.go` creates `AuthHandler`, `routes_protected.go` creates `AccountHandler` — no shared handler state between public and protected routes.
|
||||
- `RunWithGracefulShutdown` stops accepting new requests on termination and gives in-flight requests time to finish.
|
||||
- `internal/handler` and `internal/middleware` do not import `internal/repository`.
|
||||
- Repository implementations do not call GORM `Save`; mutation methods select writable columns explicitly.
|
||||
|
||||
+11
-146
@@ -1,148 +1,13 @@
|
||||
# Technical Decisions
|
||||
# Server Decisions
|
||||
|
||||
## 2026-04-25: v0 Tech Stack & Architecture
|
||||
Read the ADRs relevant to the current change. Accepted records define active decisions. Superseded records preserve history and link to current guidance.
|
||||
|
||||
**Context**: Project skeleton was created with only cobra CLI. We needed a concrete tech stack and package layout to begin implementation.
|
||||
|
||||
**Decisions**:
|
||||
|
||||
| Area | Choice | Rationale |
|
||||
|------|--------|-----------|
|
||||
| HTTP framework | Gin | Most widely adopted Go web framework, mature middleware ecosystem |
|
||||
| ORM | GORM | SQLite-first dev, PostgreSQL option later; GORM abstracts dialect differences |
|
||||
| Config management | Viper | YAML + env vars + CLI flags three-way merge, built for cobra integration |
|
||||
| Database | SQLite (v0) → PostgreSQL (future) | SQLite zero setup for dev; repo interface isolates the switch |
|
||||
| File storage | Local disk (v0) → S3 (future) | Backend interface (`internal/storage`) hides implementation |
|
||||
| File identity | UUID | Distributed-friendly, no coordination needed; cost is negligible for file metadata |
|
||||
| Token strategy | JWT, refresh token stored in DB | Enables server-side revocation (admin kick, logout-all-devices) |
|
||||
| Pagination | OFFSET/LIMIT | Simple, sufficient for v0; migrate to cursor-based if needed |
|
||||
| API response format | Direct JSON success bodies + unified error body | HTTP status codes carry request outcome; error body carries human-readable details |
|
||||
|
||||
**Architecture**: Four-layer model — Handler (Gin) → Service (business logic) → Repository (GORM data access) + Storage (file I/O). Each layer depends only on interfaces of the layer below.
|
||||
|
||||
**Consequences**:
|
||||
- Handler layer has no business logic; Service layer is reusable across REST API, WebDAV, and future Nextcloud API.
|
||||
- Repository interfaces keep DB swappable; future PostgreSQL implementation only needs a new package.
|
||||
- Refresh token in DB adds a `sessions` table and a `repository.SessionRepository` interface.
|
||||
- UUID dependency: `github.com/google/uuid` to be added.
|
||||
- Gin middleware chain: default logger/recovery → cors → auth (route-group-scoped).
|
||||
|
||||
## 2026-04-27: Web API Foundation
|
||||
|
||||
**Context**: The project needed the first HTTP slice that can validate Gin wiring and provide a stable shape for future auth, file, and admin APIs.
|
||||
|
||||
**Decisions**:
|
||||
|
||||
| Area | Choice | Guidance |
|
||||
|------|--------|----------|
|
||||
| API versioning | All REST routes under `/api/v1` | Keep future REST handlers under the versioned group. |
|
||||
| Initial public endpoint | `GET /api/v1/version` | Returns build metadata only; health/readiness endpoints need a separate security review. |
|
||||
| Success responses | Direct JSON resource bodies | Use HTTP status codes as the request outcome signal. |
|
||||
| Error responses | `{"error":{"message":"..."}}` | Add machine-readable error codes only when clients need stable branching behavior. |
|
||||
| App composition | `internal/app.WebApp` | `cmd/serve.go` creates the app from config and build metadata, then passes it to router setup. |
|
||||
| Router setup | `internal/server.NewRouter(*app.WebApp)` | Public routes (`routes_public.go`) and protected routes (`routes_protected.go`) split by auth boundary; `WebApp` serves as the unified dependency container. |
|
||||
| Server lifecycle | `RunWithGracefulShutdown` | Preserve graceful shutdown while keeping command startup linear. |
|
||||
| Default middleware | `gin.Default()` | Use default logger/recovery for the skeleton; add CORS/auth explicitly when their policies exist. |
|
||||
|
||||
**Consequences**:
|
||||
- Version is build metadata from `internal/app/version.go`, not a config-file field.
|
||||
- `app.WebApp` is the place to add future runtime services, storage, and app metadata incrementally; repository implementations stay inside composition setup.
|
||||
- Request ID middleware is not part of the current foundation; add it only with a logging/tracing/error-correlation design.
|
||||
|
||||
## 2026-04-29: Auth Refinements
|
||||
|
||||
**Context**: Auth layer had three structural weaknesses — handler duplication, indistinguishable token types, and fragile config duration parsing.
|
||||
|
||||
**Decisions**:
|
||||
|
||||
| Decision | Guidance |
|
||||
|----------|----------|
|
||||
| One handler per route group | `AuthHandler` owns `/auth/*` (public); `AccountHandler` owns `/account/*` (protected). A route group maps 1:1 to a handler type. |
|
||||
| JWT `type` claim | `Claims.Type` distinguishes access from refresh tokens. Middleware and service enforce the correct type at their respective boundaries. `ParseToken` does no type check — it verifies cryptographic validity only. |
|
||||
| Default JWT secret hardening | The development placeholder `jwt.secret` is replaced with an ephemeral runtime secret during config loading. Production and multi-instance deployments must set a stable secret. |
|
||||
| `time.Duration` in config structs | Config fields representing durations use `time.Duration` directly. Viper's built-in `StringToTimeDurationHookFunc` handles string→Duration conversion at unmarshal time. No accessor methods, no runtime parsing. Invalid values fail at startup via `Load()`. |
|
||||
|
||||
**Consequences**:
|
||||
- Handlers are independently extensible (caching, rate limiting scoped per handler).
|
||||
- Refresh tokens cannot authenticate API requests; access tokens cannot be used to issue new token pairs.
|
||||
- The placeholder JWT secret is safe for local startup, but tokens signed with it are invalidated on restart.
|
||||
- New duration config fields require zero boilerplate — declare as `time.Duration` in the struct.
|
||||
|
||||
## 2026-07-05: Staged File Uploads
|
||||
|
||||
**Context**: Multipart uploads previously used `ParseMultipartForm`, which parses the request before service-level size checks and may spill oversized requests to temporary disk. The file service also wrote directly to the long-term storage path and then attempted compensating deletes on upload failure.
|
||||
|
||||
**Decisions**:
|
||||
|
||||
| Decision | Guidance |
|
||||
|----------|----------|
|
||||
| Stream multipart requests | Handlers use `Request.MultipartReader()` instead of `ParseMultipartForm`/`FormFile`, so uploads are streamed into the service. |
|
||||
| Optional upload limits | `storage.max_upload_size = 0` means unlimited. Positive values enable both HTTP body limiting and service-level file content limiting. |
|
||||
| Staging before promotion | Storage backends write upload bytes to a staging path first, then promote the object to the long-term data path only after validation succeeds. |
|
||||
| Promote before DB create | The service promotes the object before creating the active file record, preventing visible DB rows from pointing at missing objects. If DB creation fails after promotion, the service best-effort deletes the promoted object. |
|
||||
| Upload parent location | Multipart upload `parent_id` is passed as a query parameter, keeping the multipart body focused on the file stream. |
|
||||
| Service-layer upload errors | HTTP transport errors are converted at the handler boundary; the file service only handles domain errors such as oversized uploads. |
|
||||
|
||||
**Consequences**:
|
||||
- Interrupted, malformed, and oversized uploads leave only staging objects, which are safe to clean by path prefix and age.
|
||||
- Local storage promotes with `os.Rename` and falls back to copy/delete on cross-device `EXDEV`; future S3 storage can implement promotion with copy/delete while keeping business visibility controlled by the DB row.
|
||||
- A DB failure after promotion can still leave a long-term orphan object, but it is not visible through the file API and can be cleaned independently.
|
||||
|
||||
## 2026-07-05: Protocol-Neutral Service Boundary
|
||||
|
||||
**Context**: The service and model layers carried HTTP status codes and JSON response tags, and some middleware and handlers accessed repositories directly. That made the business layer harder to reuse for future WebDAV and Nextcloud-compatible APIs.
|
||||
|
||||
**Decisions**:
|
||||
|
||||
| Decision | Guidance |
|
||||
|----------|----------|
|
||||
| Protocol-neutral errors | `model.AppError` uses an error `Kind`, safe message, and optional cause. REST status codes are mapped in `internal/api` only. |
|
||||
| API log references | REST error responses may include `error.log_id` for server-recorded errors: all 5xx responses and 4xx responses with internal causes. |
|
||||
| Service boundaries | Handlers and middleware depend on services, not repositories. `AuthService` authenticates access tokens into a principal; `AdminService` owns admin user operations. |
|
||||
| DTO ownership | Service and model structs are internal/domain data. HTTP handlers assemble response DTOs with JSON tags. |
|
||||
| Architecture enforcement | Package-level tests reject HTTP imports in model/service and repository imports in handlers/middleware. Targeted model tests verify defensive JSON redaction for sensitive fields. |
|
||||
|
||||
**Consequences**:
|
||||
- REST remains a handler/API concern, and future protocols can reuse services without HTTP leakage.
|
||||
- Error responses keep the same top-level shape, with optional `log_id` instead of embedding log references in the message.
|
||||
- Admin and auth middleware behavior is testable through service contracts rather than database access.
|
||||
|
||||
## 2026-07-15: Conceal File Resource Existence
|
||||
|
||||
**Context**: Returning permission-denied errors for cross-user file access allowed authenticated callers to distinguish another user's resource from a missing resource. Idempotent success for deleting missing files also conflicted with a consistent not-found concealment policy.
|
||||
|
||||
**Decisions**:
|
||||
|
||||
| Decision | Guidance |
|
||||
|----------|----------|
|
||||
| Conceal ownership | File and parent-directory ownership failures return the same not-found kind and safe message as missing or deleted resources. |
|
||||
| Strict delete result | Deleting an active owned file returns success once; missing, deleted, and cross-user targets return not found. |
|
||||
| Atomic soft delete | Repository deletes update only active rows and return `model.ErrNotFound` when no row changes. |
|
||||
|
||||
**Consequences**:
|
||||
- Authenticated file operations cannot use status codes or messages to determine whether another user's resource exists.
|
||||
- A successful first delete returns `204 No Content`; repeated deletes return `404 Not Found` while remaining idempotent in effect.
|
||||
- Unauthenticated requests remain `401 Unauthorized`, non-empty directory deletion remains `409 Conflict`, and permission-denied behavior outside the file boundary remains unchanged.
|
||||
|
||||
## 2026-07-16: Command-Scoped Persistence Mutations
|
||||
|
||||
**Context**: Generic `Repository.Update(*Entity)`, full-entity GORM `Save`, and broad delete methods exposed fields and lifecycle transitions that individual services were not authorized to perform. They also allowed a stale file entity to revive a soft-deleted row, left file parent checks vulnerable to concurrent deletion, and allowed two refresh requests to consume the same session.
|
||||
|
||||
**Decisions**:
|
||||
|
||||
| Decision | Guidance |
|
||||
|----------|----------|
|
||||
| Command-scoped writes | Every mutation has a use-case-specific method and parameter type. Mutable domain entities do not cross a service-to-repository write boundary. |
|
||||
| Capability interfaces | Auth and admin services accept only the query and command capabilities they require. Wider user and session interfaces exist only for construction and repository-focused consumers. |
|
||||
| Explicit mutation predicates | Updates name concrete columns and include ownership, credential type, and/or active-state predicates appropriate to the command. A zero-row guarded mutation maps to a domain not-found result. |
|
||||
| Transactional file hierarchy | Child creation, movement, and directory deletion revalidate hierarchy state in the same transaction. PostgreSQL locks relevant rows in sorted ID order; SQLite DSNs force `_txlock=immediate`. |
|
||||
| Atomic refresh consumption | Refresh rotates through one transaction that locks, returns, and deletes the session. A concurrent replay cannot issue a second token pair. Logout remains an idempotent revoke-by-hash command. |
|
||||
| Test-only privilege setup | Tests elevate users through `internal/testutil`; no production role mutation is added merely to prepare fixtures. |
|
||||
|
||||
**Consequences**:
|
||||
|
||||
- Protected user, file, session, and credential fields are absent from ordinary mutation inputs, reducing illegal write paths at compile time.
|
||||
- Deleted file rows cannot be revived by stale metadata updates, and successful hierarchy races cannot leave active orphan children.
|
||||
- PostgreSQL relies on row-level locking while SQLite serializes these hierarchy and session write transactions from their first database operation. This favors correctness over concurrent SQLite writers.
|
||||
- Adding a future profile, password, role, restore, or other mutation requires a named command and an explicit capability rather than extending a universal update method.
|
||||
- REST routes, payloads, status codes, database schema, and dependency set remain unchanged.
|
||||
| ADR | Date | Status | Decision |
|
||||
|-----|------|--------|----------|
|
||||
| [ADR-0001](decisions/0001-server-foundation.md) | 2026-04-25 | Accepted | Select the server foundation |
|
||||
| [ADR-0002](decisions/0002-rest-api-foundation.md) | 2026-04-27 | Accepted | Establish the REST API foundation |
|
||||
| [ADR-0003](decisions/0003-auth-contracts.md) | 2026-04-29 | Accepted | Define authentication contracts |
|
||||
| [ADR-0004](decisions/0004-staged-file-uploads.md) | 2026-07-05 | Accepted | Stage uploads before publication |
|
||||
| [ADR-0005](decisions/0005-protocol-neutral-services.md) | 2026-07-05 | Accepted | Keep service contracts protocol-neutral |
|
||||
| [ADR-0006](decisions/0006-conceal-file-resources.md) | 2026-07-15 | Accepted | Conceal file ownership boundaries |
|
||||
| [ADR-0007](decisions/0007-command-scoped-persistence.md) | 2026-07-16 | Accepted | Use command-scoped persistence mutations |
|
||||
|
||||
@@ -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.
|
||||
+55
-45
@@ -1,9 +1,13 @@
|
||||
# Development
|
||||
# Server Development
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Go 1.26.2 (pinned in `mise.toml`)
|
||||
- `mise` (https://mise.jdx.dev) — run `mise install` to install toolchain
|
||||
- Go 1.26.2, pinned in `mise.toml`
|
||||
- `mise` for installing the pinned toolchain
|
||||
|
||||
```bash
|
||||
mise install
|
||||
```
|
||||
|
||||
## Build
|
||||
|
||||
@@ -20,69 +24,75 @@ go test -v -run TestName ./internal/...
|
||||
go test -race ./internal/repository ./internal/service ./internal/server
|
||||
```
|
||||
|
||||
## Lint & Format
|
||||
## Format and Static Analysis
|
||||
|
||||
```bash
|
||||
go vet ./...
|
||||
go fmt ./...
|
||||
```
|
||||
|
||||
## Repository Review
|
||||
|
||||
Use the repository-scoped `$mygo-api-repo-review` skill for evidence-based architecture, design, security, resilience, data-consistency, performance, and implementation audits. Reviews are read-only unless fixes are requested separately.
|
||||
|
||||
Select one review mode:
|
||||
|
||||
- `diff` compares the current workspace, including relevant untracked files, with `HEAD`.
|
||||
- `main` compares the current workspace with the merge base of `HEAD` and the local `main` branch without fetching remote refs.
|
||||
- `full` reviews the current repository as a whole.
|
||||
|
||||
Optionally limit any mode to a target path, Go package, or logical component. For example:
|
||||
|
||||
```text
|
||||
Use $mygo-api-repo-review in main mode with target internal/storage.
|
||||
go vet ./...
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
|
||||
Run module cleanup after adding or removing imports:
|
||||
|
||||
```bash
|
||||
go mod tidy # after adding/removing imports
|
||||
go mod tidy
|
||||
```
|
||||
|
||||
## Config
|
||||
## Repository Review
|
||||
|
||||
Server config is loaded via viper from `config.yaml` (defaults in `internal/config/load.go`).
|
||||
Use `$mygo-api-repo-review` for evidence-based architecture, design, security, resilience, data-consistency, performance, and implementation audits.
|
||||
|
||||
For SQLite, the repository preserves configured DSN parameters and adds
|
||||
`_txlock=immediate`. Write transactions therefore reserve the database before
|
||||
performing hierarchy or refresh-session reads, matching the locking assumptions
|
||||
used by the repository commands. Keep this behavior when supplying a SQLite URI
|
||||
such as `file:mygo.db?cache=shared`.
|
||||
| Mode | Scope |
|
||||
|------|-------|
|
||||
| `diff` | Workspace, including relevant untracked files, compared with `HEAD` |
|
||||
| `main` | Workspace compared with the merge base of `HEAD` and local `main` |
|
||||
| `full` | Complete repository |
|
||||
|
||||
Reviews are read-only unless the user also requests fixes. A mode can target a path, Go package, or logical component.
|
||||
|
||||
## Configuration
|
||||
|
||||
The server reads `config.yaml` from the working directory when present. Environment variables use the `MYGO_` prefix and underscores, for example `MYGO_SERVER_PORT` and `MYGO_JWT_SECRET`.
|
||||
|
||||
```yaml
|
||||
server:
|
||||
host: 0.0.0.0
|
||||
port: 10086
|
||||
host: 0.0.0.0
|
||||
port: 10086
|
||||
|
||||
database:
|
||||
driver: sqlite3
|
||||
sqlite:
|
||||
path: data/mygo.db
|
||||
driver: sqlite3
|
||||
sqlite:
|
||||
path: data/mygo.db
|
||||
|
||||
storage:
|
||||
driver: local
|
||||
local:
|
||||
path: data/files
|
||||
driver: local
|
||||
max_upload_size: 0
|
||||
local:
|
||||
path: data/files
|
||||
|
||||
jwt:
|
||||
secret: dev-secret-do-not-use-in-production
|
||||
access_ttl: 15m
|
||||
refresh_ttl: 168h
|
||||
secret: dev-secret-do-not-use-in-production
|
||||
access_ttl: 15m
|
||||
refresh_ttl: 168h
|
||||
|
||||
log:
|
||||
level: info
|
||||
file_path: ""
|
||||
file_level: debug
|
||||
```
|
||||
|
||||
Environment variables use `MYGO_` prefix with underscore separators: `MYGO_SERVER_PORT=8080`, `MYGO_JWT_SECRET=...`
|
||||
| Setting | Behavior |
|
||||
|---------|----------|
|
||||
| `database.driver` | Selects `sqlite3` or `postgres` |
|
||||
| `storage.max_upload_size` | `0` allows any upload size; a positive byte count enables request and service limits |
|
||||
| `jwt.secret` | Signs access and refresh tokens |
|
||||
| `log.level` | Sets stderr logging to `debug`, `info`, `warn`, or `error` |
|
||||
| `log.file_path` | Enables an additional text log file when set |
|
||||
| `log.file_level` | Sets the file log level independently |
|
||||
|
||||
The default JWT secret is a development placeholder. At startup, MyGO replaces
|
||||
it with an ephemeral runtime secret; set a stable `jwt.secret` or
|
||||
`MYGO_JWT_SECRET` when tokens must survive restarts or when running multiple
|
||||
instances.
|
||||
PostgreSQL uses the `database.postgres` host, port, user, password, database name, and SSL mode fields defined in `internal/config`.
|
||||
|
||||
The SQLite connector preserves configured DSN parameters and sets `_txlock=immediate`. This setting establishes the transaction behavior required by hierarchy and refresh-session commands.
|
||||
|
||||
The default JWT secret is a development placeholder. Configuration loading replaces it with a random in-memory secret for the current process. Set a stable secret for multi-instance deployments and tokens that must survive a restart.
|
||||
|
||||
+22
-34
@@ -1,40 +1,28 @@
|
||||
# Roadmap
|
||||
# Server Roadmap
|
||||
|
||||
## v0
|
||||
## Available Capabilities
|
||||
|
||||
| Feature | Status | Notes |
|
||||
|---------|--------|-------|
|
||||
| CLI config management | ✅ | Viper YAML + env + flags, typed Duration config |
|
||||
| JWT authentication | ✅ | access + refresh tokens, atomic single-use refresh sessions, app passkey support |
|
||||
| Web API foundation | ✅ | WebApp composition, Gin router, graceful shutdown, `GET /api/v1/version` |
|
||||
| File upload/download/manage APIs | 🛠 WIP | REST API via Gin |
|
||||
| Admin endpoints | 🛠 WIP | user service boundary in place for superusers |
|
||||
| WebDAV | 🛠 WIP | future v0 or v1 |
|
||||
- YAML and environment configuration loading
|
||||
- SQLite and PostgreSQL metadata persistence
|
||||
- JWT access and single-use refresh tokens
|
||||
- Account and application-passkey APIs
|
||||
- File listing, upload, download, directory creation, metadata update, and soft deletion
|
||||
- Ownership concealment for file resources
|
||||
- Administrator user listing, lookup, and deletion
|
||||
- Local storage with staged upload promotion
|
||||
- Request IDs, structured logging, and graceful shutdown
|
||||
- Architecture, repository, service, handler, and route integration tests
|
||||
|
||||
## Implementation Tasks
|
||||
## Planned v0 Work
|
||||
|
||||
Package-level implementation order (each task includes unit tests):
|
||||
- Add `mygo config` for instance configuration management.
|
||||
- Add `mygo status` for server status inspection.
|
||||
- Complete the remaining administrator workflows.
|
||||
- Define the WebDAV delivery milestone and acceptance criteria.
|
||||
|
||||
1. `internal/config` — Viper loader, config struct ✅
|
||||
2. `internal/app` — runtime dependency container ✅
|
||||
3. `internal/model` — domain types, error codes ✅
|
||||
4. `internal/api` — protocol-neutral error kind to REST response mapping ✅
|
||||
5. `internal/auth` — JWT utils ✅
|
||||
6. `internal/storage` — backend interface + local fs with staged upload promotion
|
||||
7. `internal/repository` — command-scoped mutation capabilities + GORM/SQLite transaction protocol ✅
|
||||
8. `internal/service` — auth, file, admin services ✅
|
||||
9. `internal/middleware` — logger, cors, auth ✅ (auth done; principal boundary enforced)
|
||||
10. `internal/handler` — auth, account, file, admin handlers 🛠 (HTTP DTO mapping in place)
|
||||
11. `internal/server` — Gin router, route registration, graceful shutdown ✅
|
||||
12. `cmd/serve.go`, `cmd/config.go`, `cmd/status.go` ✅ (serve done)
|
||||
13. Integration tests 🛠 (authenticated register → login → upload → list → download route flow covered with a small file; authentication bypass, invalid token, concealed cross-user resources, strict delete, atomic refresh consumption, and file hierarchy race boundaries covered)
|
||||
14. Architecture boundary tests ✅
|
||||
## Future Candidates
|
||||
|
||||
## Future
|
||||
|
||||
| Feature | Status | Notes |
|
||||
|---------|--------|-------|
|
||||
| Image server | ⬜ plan | thumbnail generation |
|
||||
| Pastebin & code snippets | ⬜ plan | in sharing context |
|
||||
| S3 storage backend | ⬜ plan | new storage impl |
|
||||
| Nextcloud-compatible API | ⬜ plan | new handler layer on existing services |
|
||||
- S3 storage
|
||||
- Image thumbnails
|
||||
- Paste and code-snippet sharing
|
||||
- Nextcloud-compatible protocol adapters
|
||||
|
||||
+9
-100
@@ -1,102 +1,11 @@
|
||||
# Technical Decisions
|
||||
# Web Decisions
|
||||
|
||||
## 2026-07-14: Client-rendered Web Foundation
|
||||
Read the ADRs relevant to the current change. Accepted records define active decisions. Superseded records preserve history and link to current guidance.
|
||||
|
||||
**Context**: MyGO needs a browser client now and native clients later. The Web application must share the versioned REST API instead of introducing browser-only server logic.
|
||||
|
||||
**Decisions**:
|
||||
|
||||
| Area | Choice | Guidance |
|
||||
|------|--------|----------|
|
||||
| Rendering | Pure client-side rendered SPA | Vite emits static assets; do not introduce SSR, React Server Components, or a Node API server. |
|
||||
| Application stack | React, strict TypeScript, React Router, and TanStack Query | Keep routing and remote-data state explicit and client-side. |
|
||||
| UI system | Ant Design plus Tailwind CSS 4 | Ant Design owns reusable controls and theme tokens; Tailwind initially owns layout, spacing, and responsive utilities. |
|
||||
| Dependency policy | Install capabilities when their feature starts | Keep API generation, transfer, virtualization, drag-and-drop, test, and preview libraries deferred in `docs/web/roadmap.md`. |
|
||||
|
||||
**Consequences**:
|
||||
- The Web and future native clients consume the same client-neutral API contracts.
|
||||
- MyGO or a reverse proxy may host `web/dist` with an SPA fallback without changing the rendering model.
|
||||
- MyGO domain components own file-browser behavior and must not depend on Ant Design request behavior for business logic.
|
||||
|
||||
## 2026-07-14: Browser Authentication and Root File Workflow
|
||||
|
||||
**Context**: The first Web milestone needs to exercise the existing login, file
|
||||
list, upload, and download APIs without committing to the later directory,
|
||||
account, admin, or large-transfer designs.
|
||||
|
||||
**Decisions**:
|
||||
|
||||
| Area | Choice | Guidance |
|
||||
|------|--------|----------|
|
||||
| API topology | Same-origin `/api/v1` | Vite proxies `/api` to the local Go server. Production uses a same-origin reverse proxy; the milestone does not add CORS or Go static hosting. |
|
||||
| Browser session | Token pair in `sessionStorage` | Reloading the tab preserves the session, while closing the tab clears it. Do not add persistent login until the token transport design is revisited. |
|
||||
| Token refresh | Refresh once after a protected request returns 401 | Share one in-flight refresh across concurrent failures, store the rotated pair, and retry each request once. Clear the session if refresh or the retry fails. |
|
||||
| File scope | Root directory only | List root entries, upload one file to root, and download files. Show directories as non-interactive rows. |
|
||||
| Transfer model | Browser `FormData` upload and authenticated Blob download | This is intentionally limited to the small-file milestone; progress, streaming-to-disk, chunking, resume, and queues remain deferred. |
|
||||
|
||||
**Consequences**:
|
||||
- The API client owns bearer headers, error parsing, refresh coordination, and
|
||||
session invalidation; pages consume operation-specific functions.
|
||||
- TanStack Query caches are cleared whenever an authenticated session ends so
|
||||
one user cannot see another user's cached file metadata.
|
||||
- The file picker is independent of Ant Design upload request behavior, keeping
|
||||
transfer policy in MyGO code.
|
||||
- Handwritten TypeScript wire types remain temporary until an OpenAPI contract
|
||||
is available.
|
||||
|
||||
## 2026-07-14: Project-local Headless Browser Tooling
|
||||
|
||||
**Status**: Agent-debugging and browser-version decisions were superseded by
|
||||
the 2026-07-15 Playwright CLI decision below. The Playwright Test and
|
||||
project-local resource decisions remain active.
|
||||
|
||||
**Context**: Browser behavior needs deterministic E2E coverage and interactive
|
||||
agent debugging inside a long-lived headless Debian Incus container. Tooling
|
||||
should remain reproducible without placing browser binaries or generated
|
||||
artifacts in a developer's home directory.
|
||||
|
||||
**Decisions**:
|
||||
|
||||
| Area | Choice | Guidance |
|
||||
|------|--------|----------|
|
||||
| Regression tests | Playwright Test with bundled Chromium | Keep browser E2E tests separate from `npm run check` because browser installation is an explicit environment setup step. |
|
||||
| Agent debugging | Official Playwright MCP over project-scoped STDIO | Codex starts the locked local package from `.codex/config.toml`; do not use a global install, an `@latest` npx invocation, or a listening MCP service. |
|
||||
| Browser state | Headless, isolated, and sandboxed | Use bundled Chromium, discard the MCP profile after each session, and retain the Chromium sandbox supported by the Incus environment. |
|
||||
| Local resources | Repository `.cache/` and `.artifacts/` directories | `mise.toml` defines the portable npm and browser cache paths. Generated resources remain ignored by Git. |
|
||||
| Browser versions | Install both locked Playwright revisions | The stable test runner and current MCP package require different Chromium revisions; do not force either package onto an unsupported executable. |
|
||||
|
||||
**Consequences**:
|
||||
- Debian browser libraries are installed once in the Incus container root
|
||||
filesystem; npm packages, browsers, traces, screenshots, and MCP output stay
|
||||
project-scoped.
|
||||
- A fresh environment runs `npm ci`, the system dependency installer, and both
|
||||
browser install scripts before browser tests or MCP debugging.
|
||||
- Browser failures can retain traces, screenshots, and video under
|
||||
`.artifacts/playwright/` without adding generated files to source control.
|
||||
|
||||
## 2026-07-15: Stable Playwright CLI Agent Debugging
|
||||
|
||||
**Context**: The official Playwright MCP package pulled an alpha Playwright
|
||||
core alongside the stable test runner. That required a second Chromium
|
||||
revision and expanded the agent tool surface. Playwright 1.61.1 already
|
||||
provides the same browser-debugging command set through its embedded CLI.
|
||||
|
||||
**Decisions**:
|
||||
|
||||
| Area | Choice | Guidance |
|
||||
|------|--------|----------|
|
||||
| Agent entry point | Stable `playwright cli` behind the `playwright:cli` npm script | Run it from `web/` with `mise exec -- npm run playwright:cli --`; do not use a global install, standalone `@playwright/cli`, `playwright-cli`, or floating `npx`. |
|
||||
| Version ownership | `@playwright/test` is the only Playwright dependency | E2E tests and interactive debugging use Playwright 1.61.1 and Chromium revision 1228. |
|
||||
| Agent workflow | Repository Skill in `.agents/skills/playwright-cli` | Start from Playwright's generated Skill, then preserve the MyGO npm wrapper, headless workflow, artifact policy, and debugging principles when updating it. |
|
||||
| Browser policy | Bundled Chromium, headless, isolated, and sandboxed | `.playwright/cli.config.json` is the shared CLI configuration; do not silently disable the Chromium sandbox. |
|
||||
| Local resources | Repository `.cache/` and `.artifacts/` directories | `mise.toml` redirects XDG, npm, and browser caches; CLI output is limited to 100 MB under `.artifacts/playwright-cli/`. |
|
||||
|
||||
**Consequences**:
|
||||
- A fresh environment needs one locked npm install, the Debian browser
|
||||
libraries, and the stable Chromium install before E2E or CLI use.
|
||||
- Changing Playwright's CLI entry point only requires changing the npm script;
|
||||
the repository Skill and operator commands remain stable.
|
||||
- The official generated references remain available through progressive
|
||||
disclosure, while the main Skill carries only the MyGO-specific workflow.
|
||||
- The MCP server configuration, alpha Playwright core, and Chromium revision
|
||||
1232 are no longer required.
|
||||
| ADR | Date | Status | Decision |
|
||||
|-----|------|--------|----------|
|
||||
| [ADR-0001](decisions/0001-client-rendered-foundation.md) | 2026-07-14 | Accepted | Use a client-rendered Web foundation |
|
||||
| [ADR-0002](decisions/0002-browser-auth-root-files.md) | 2026-07-14 | Accepted | Implement browser auth and the root file workflow |
|
||||
| [ADR-0003](decisions/0003-project-local-browser-testing.md) | 2026-07-14 | Accepted | Keep browser testing resources project-local |
|
||||
| [ADR-0004](decisions/0004-playwright-mcp-debugging.md) | 2026-07-14 | Superseded | Use Playwright MCP for agent debugging |
|
||||
| [ADR-0005](decisions/0005-playwright-cli-debugging.md) | 2026-07-15 | Accepted | Use the pinned Playwright CLI for agent debugging |
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
# ADR-0001: Use a Client-Rendered Web Foundation
|
||||
|
||||
Status: Accepted
|
||||
Date: 2026-07-14
|
||||
|
||||
## Context
|
||||
|
||||
MyGO needs a browser client and expects native clients to consume the same application API.
|
||||
|
||||
## Decision
|
||||
|
||||
- Build a client-side application with Vite, React, and strict TypeScript.
|
||||
- Use React Router for navigation and TanStack Query for remote state.
|
||||
- Use Ant Design for reusable controls and theme tokens.
|
||||
- Use Tailwind CSS for layout, spacing, and responsive utilities.
|
||||
- Keep business API contracts client-neutral.
|
||||
- Select additional dependencies when their feature is designed.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Vite emits static assets for same-origin deployment.
|
||||
- The Web project contains no SSR, React Server Components, or Node application server.
|
||||
- Browser and native clients share the versioned REST API.
|
||||
- MyGO components own file-browser and transfer behavior; Ant Design supplies presentation controls.
|
||||
@@ -0,0 +1,26 @@
|
||||
# ADR-0002: Implement Browser Auth and the Root File Workflow
|
||||
|
||||
Status: Accepted
|
||||
Date: 2026-07-14
|
||||
|
||||
## Context
|
||||
|
||||
The first Web milestone needed an authenticated workflow over the existing login, file listing, upload, and download APIs.
|
||||
|
||||
## Decision
|
||||
|
||||
- Use same-origin `/api/v1` requests.
|
||||
- Store the access and refresh token pair in `sessionStorage`.
|
||||
- Coordinate one refresh request across concurrent `401` responses.
|
||||
- Retry each failed protected request once after refresh.
|
||||
- Clear the session when refresh or the retry fails.
|
||||
- List root entries and upload one file at a time to the root directory.
|
||||
- Download authenticated file responses as browser blobs.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Reloading the tab preserves the session; closing the tab clears it.
|
||||
- Query caches clear when the authenticated session ends.
|
||||
- Directories appear as non-interactive root rows in the first milestone.
|
||||
- Handwritten wire types remain until a shared OpenAPI contract is available.
|
||||
- Directory management and large-transfer workflows require later feature decisions.
|
||||
@@ -0,0 +1,22 @@
|
||||
# ADR-0003: Keep Browser Testing Resources Project-Local
|
||||
|
||||
Status: Accepted
|
||||
Date: 2026-07-14
|
||||
|
||||
## Context
|
||||
|
||||
Browser tests and diagnostics run in a headless Debian development container. The toolchain needs repeatable versions and repository-scoped resources.
|
||||
|
||||
## Decision
|
||||
|
||||
- Use Playwright Test with bundled Chromium for repeatable E2E tests.
|
||||
- Keep E2E tests separate from `npm run check` because browser installation is an explicit setup step.
|
||||
- Run Chromium headlessly with profile isolation and sandboxing.
|
||||
- Store npm and browser caches under `.cache/`.
|
||||
- Store traces, screenshots, videos, and CLI output under `.artifacts/`.
|
||||
|
||||
## Consequences
|
||||
|
||||
- A fresh environment installs npm dependencies, browser libraries, and the pinned Chromium revision before browser work.
|
||||
- Browser failures can retain diagnostics without adding generated files to source control.
|
||||
- Repository configuration controls browser versions and artifact locations.
|
||||
@@ -0,0 +1,12 @@
|
||||
# ADR-0004: Use Playwright MCP for Agent Debugging
|
||||
|
||||
Status: Superseded by [ADR-0005](0005-playwright-cli-debugging.md)
|
||||
Date: 2026-07-14
|
||||
|
||||
## Historical Decision
|
||||
|
||||
The project initially selected a repository-scoped Playwright MCP process for interactive agent debugging.
|
||||
|
||||
## Current Guidance
|
||||
|
||||
[ADR-0005](0005-playwright-cli-debugging.md) defines the current browser-debugging entry point. [ADR-0003](0003-project-local-browser-testing.md) defines the browser test and resource policy that remains active.
|
||||
@@ -0,0 +1,23 @@
|
||||
# ADR-0005: Use the Pinned Playwright CLI for Agent Debugging
|
||||
|
||||
Status: Accepted
|
||||
Date: 2026-07-15
|
||||
|
||||
## Context
|
||||
|
||||
The Playwright MCP package introduced a second Playwright core, a second Chromium revision, and a larger agent tool surface. The pinned test runner provides the required interactive commands through its CLI.
|
||||
|
||||
## Decision
|
||||
|
||||
- Use the `playwright:cli` npm script as the browser-debugging entry point.
|
||||
- Use `@playwright/test` as the Playwright dependency for tests and diagnostics.
|
||||
- Use the bundled Chromium revision selected by the pinned package.
|
||||
- Load `.playwright/cli.config.json` for headless, isolated, and sandboxed execution.
|
||||
- Use the repository-scoped `$playwright-cli` skill for agent browser work.
|
||||
- Limit CLI output under `.artifacts/playwright-cli/`.
|
||||
|
||||
## Consequences
|
||||
|
||||
- E2E tests and interactive diagnostics share one Playwright core and Chromium revision.
|
||||
- The npm script isolates operators and agents from upstream CLI entry-point changes.
|
||||
- The repository skill keeps the main workflow concise and exposes detailed references on demand.
|
||||
+26
-51
@@ -1,65 +1,40 @@
|
||||
# Web Roadmap
|
||||
|
||||
## Product Boundary
|
||||
## Product Model
|
||||
|
||||
- The Web client is a pure client-side rendered single-page application.
|
||||
- Vite produces static assets only. The project does not use SSR, React Server Components, a Node API server, or a browser-specific business backend.
|
||||
- The Web client consumes the same versioned REST API as future Android and other native clients.
|
||||
- Production may serve `web/dist` from MyGO or a reverse proxy.
|
||||
- Shared API contracts should remain client-neutral and eventually be described by OpenAPI.
|
||||
MyGO Web is a client-side application built by Vite. It consumes the same `/api/v1` REST API as native clients. Production can serve `web/dist` from MyGO or a same-origin reverse proxy.
|
||||
|
||||
## Current Milestone
|
||||
## Available Capabilities
|
||||
|
||||
The browser client now provides the first authenticated file workflow:
|
||||
- Email and password login
|
||||
- Access and refresh tokens stored for the browser session
|
||||
- One coordinated refresh retry after an authenticated request returns `401`
|
||||
- Protected application shell
|
||||
- Root file listing with 50 items per page
|
||||
- Single-file upload to the root directory
|
||||
- Authenticated file download
|
||||
- Responsive Ant Design interface with Tailwind layout utilities
|
||||
- Unit tests for the API client and session behavior
|
||||
- Playwright smoke tests and repository-scoped browser diagnostics
|
||||
|
||||
- Email/password login through the shared REST API
|
||||
- Session-scoped access and refresh tokens with one automatic refresh retry
|
||||
- Protected application shell and root file list
|
||||
- Single-file upload to the root directory and authenticated download
|
||||
- Server-side pagination with 50 items per page
|
||||
|
||||
Directory navigation and management, multi-file queues, transfer progress,
|
||||
resumable or chunked transfers, account/profile/settings screens, and admin
|
||||
screens remain deferred.
|
||||
|
||||
## Foundation
|
||||
|
||||
The project foundation contains:
|
||||
|
||||
- Node.js 24 and npm
|
||||
- Vite
|
||||
- React
|
||||
- TypeScript in strict mode
|
||||
- TanStack Query provider
|
||||
- Tailwind CSS 4 through its Vite plugin
|
||||
- Ant Design provider and components
|
||||
- Ant Design icons for application and file actions
|
||||
- Oxlint from the Vite template
|
||||
- Vitest for framework-independent client and session tests
|
||||
- Playwright Test with a headless Chromium smoke test
|
||||
- Project-scoped Playwright CLI and repository Skill for interactive browser debugging
|
||||
|
||||
Ant Design owns reusable UI components and theme tokens. Tailwind CSS is initially limited to application layout, spacing, and responsive utilities.
|
||||
|
||||
## Planned Structure
|
||||
## Current Structure
|
||||
|
||||
```text
|
||||
web/src/
|
||||
├── app/ # Router, providers, and application composition
|
||||
├── api/ # API client and generated contracts
|
||||
├── api/ # API client, session storage, and wire types
|
||||
├── app/ # Router, providers, and authentication guard
|
||||
├── components/ # Shared presentation components
|
||||
├── features/ # Auth, files, account, and admin features
|
||||
├── pages/ # Route entry points
|
||||
├── lib/ # Framework-independent utilities
|
||||
└── test/ # Shared test setup and fixtures
|
||||
├── features/ # Feature hooks and state
|
||||
└── pages/ # Route entry points
|
||||
```
|
||||
|
||||
## Deferred Dependencies
|
||||
## Planned Capabilities
|
||||
|
||||
Some dependencies are suggested for future implementation. Refer these only when the corresponding feature is implemented and propose better choices if any:
|
||||
- Directory navigation and management
|
||||
- Account, settings, and administrator screens
|
||||
- Shared OpenAPI contracts and generated client types
|
||||
- Multi-file transfer queues and progress reporting
|
||||
- Resumable large-file transfers
|
||||
- Document preview
|
||||
|
||||
- `openapi-typescript`: Generate TypeScript API types from the shared OpenAPI document.
|
||||
- `openapi-fetch`: Provide a small type-safe Fetch client based on generated OpenAPI types.
|
||||
- `openapi-react-query`: Connect generated OpenAPI operations to TanStack Query if handwritten query adapters become repetitive.
|
||||
- `zustand`: Manage a cross-route upload queue, bulk selection, or other complex client-only state if React state is insufficient.
|
||||
- `pdfjs-dist`: Preview PDF files in the browser when document preview is implemented.
|
||||
Select dependencies when designing the capability that requires them. Record significant choices in the Web decision log.
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
# Documentation Writing Guide
|
||||
|
||||
## Goals
|
||||
|
||||
Repository documentation must be accurate, concise, and useful to both people and coding agents.
|
||||
|
||||
## Document Roles
|
||||
|
||||
| Document type | Content |
|
||||
|---------------|---------|
|
||||
| README | Project entry point and runnable paths |
|
||||
| Architecture | Current structure, responsibilities, and enforced boundaries |
|
||||
| Development | Commands and operational procedures |
|
||||
| Roadmap | Available, planned, and future capabilities |
|
||||
| Architecture Decision Record (ADR) | One technical decision, its context, and its consequences |
|
||||
| `AGENTS.md` | Durable agent workflow, conventions, verification, and constraints |
|
||||
|
||||
Keep each fact in its canonical document. Link to that document from other locations.
|
||||
|
||||
## Current-State Writing
|
||||
|
||||
- Use English, active voice, and present tense.
|
||||
- Name the component that performs each action.
|
||||
- Put one fact or instruction in each sentence or list item.
|
||||
- Prefer short sections, lists, and tables.
|
||||
- State the supported path before optional details.
|
||||
- Use `must` for requirements, `can` for optional behavior, and present tense for existing behavior.
|
||||
- Use current commands, symbols, routes, and examples.
|
||||
|
||||
## Constraints and Anti-Patterns
|
||||
|
||||
Keep a negative rule **only when** it prevents a current and plausible mistake with a meaningful cost. This includes:
|
||||
|
||||
- Tooling traps that reduce reproducibility or pollute context
|
||||
- Architecture boundaries enforced by tests
|
||||
- Security, authorization, and destructive-action limits
|
||||
- Commit and dependency-change authorization
|
||||
|
||||
Place the rule near the affected workflow and provide the supported path. Keep one canonical copy when the same audience reads multiple documents.
|
||||
|
||||
Remove or move a negative statement when it describes a hypothetical design, repeats a positive contract, or explains an obsolete implementation. Preserve material history in the relevant ADR instead.
|
||||
|
||||
## Decision Records
|
||||
|
||||
- Record one logical decision per ADR.
|
||||
- Put the status before the body.
|
||||
- Describe requirements and forces in Context.
|
||||
- State the selected design in Decision.
|
||||
- Record current trade-offs in Consequences.
|
||||
- Link superseded records to their replacement.
|
||||
- Use past tense for historical implementation details.
|
||||
|
||||
## Review Checklist
|
||||
|
||||
1. Confirm the document type and audience.
|
||||
2. Verify facts against code and configuration.
|
||||
3. Remove duplicated current-state information.
|
||||
4. Check every negative statement for a current action or decision.
|
||||
5. Verify commands, examples, and local links.
|
||||
6. Run `git diff --check`.
|
||||
|
||||
## References
|
||||
|
||||
- [OpenAI Codex best practices](https://learn.chatgpt.com/guides/best-practices)
|
||||
- [Architectural Decision Records](https://adr.github.io/)
|
||||
@@ -6,3 +6,16 @@ node = "24"
|
||||
XDG_CACHE_HOME = "{{config_root}}/.cache"
|
||||
NPM_CONFIG_CACHE = "{{config_root}}/.cache/npm"
|
||||
PLAYWRIGHT_BROWSERS_PATH = "{{config_root}}/.cache/ms-playwright"
|
||||
|
||||
[tasks."dev:api"]
|
||||
description = "Start the MyGO API server"
|
||||
run = "go run . serve"
|
||||
|
||||
[tasks."dev:web"]
|
||||
description = "Start the Vite development server"
|
||||
dir = "{{config_root}}/web"
|
||||
run = "npm run dev -- --host ${MYGO_WEB_HOST:-127.0.0.1}"
|
||||
|
||||
[tasks.dev]
|
||||
description = "Start the MyGO API and Web development servers"
|
||||
depends = ["dev:api", "dev:web"]
|
||||
|
||||
+33
-30
@@ -1,22 +1,30 @@
|
||||
# MyGO Web
|
||||
|
||||
MyGO's browser client is a pure client-side rendered application built with React, TypeScript, and Vite.
|
||||
MyGO Web is a client-side React application built with TypeScript and Vite.
|
||||
|
||||
## Development
|
||||
## Setup
|
||||
|
||||
Install the repository-pinned Go and Node.js versions from the repository root:
|
||||
Install the pinned toolchains from the repository root:
|
||||
|
||||
```bash
|
||||
mise install
|
||||
```
|
||||
|
||||
Start the Go API from the repository root:
|
||||
Install Web dependencies from `web/`:
|
||||
|
||||
```bash
|
||||
mise exec -- npm ci
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
Start the API from the repository root:
|
||||
|
||||
```bash
|
||||
mise exec -- go run . serve
|
||||
```
|
||||
|
||||
Create a development account through the existing public API if needed:
|
||||
Create a development account when needed:
|
||||
|
||||
```bash
|
||||
curl --request POST http://127.0.0.1:10086/api/v1/auth/register \
|
||||
@@ -24,42 +32,47 @@ curl --request POST http://127.0.0.1:10086/api/v1/auth/register \
|
||||
--data '{"username":"web-user","email":"web@example.com","password":"password123"}'
|
||||
```
|
||||
|
||||
Then start the browser client from `web/`:
|
||||
Start Vite from `web/`:
|
||||
|
||||
```bash
|
||||
mise exec -- npm ci
|
||||
mise exec -- npm run dev
|
||||
```
|
||||
|
||||
Vite proxies `/api` to `http://127.0.0.1:10086`. Production deployments must serve the static build and `/api/v1` from the same origin, normally through a reverse proxy.
|
||||
Vite proxies `/api` to `http://127.0.0.1:10086`. Production deployments serve the static build and `/api/v1` from the same origin.
|
||||
|
||||
## Browser Testing and Debugging
|
||||
## Checks
|
||||
|
||||
The Web project uses the Playwright `@playwright/test` for both repeatable E2E tests and interactive browser debugging. The npm cache, browser binaries, CLI daemon state, screenshots, traces, and videos stay under the repository-level ignored `.cache/` and `.artifacts/` directories.
|
||||
Run the Web checks from `web/`:
|
||||
|
||||
Install the Debian browser dependencies once per development container:
|
||||
```bash
|
||||
mise exec -- npm run check
|
||||
```
|
||||
|
||||
The command runs linting, unit tests, type checking, and the production build.
|
||||
|
||||
## Browser Testing
|
||||
|
||||
Install the Debian browser libraries once per development container:
|
||||
|
||||
```bash
|
||||
mise exec -- npm run playwright:install:deps
|
||||
```
|
||||
|
||||
Install the matching bundled Chromium revision:
|
||||
Install the repository-pinned Chromium revision:
|
||||
|
||||
```bash
|
||||
mise exec -- npm run playwright:install
|
||||
```
|
||||
|
||||
E2E tests and interactive debugging intentionally share the same stable Playwright core and browser revision.
|
||||
|
||||
Currently Playwright's version is locked by the only `@playwright/test` dependency. Standalone `@playwright/cli` or `@playwright/mcp` won't be added unless additional requirement.
|
||||
|
||||
Run the headless browser tests with Chromium sandboxing enabled:
|
||||
Run the headless E2E tests:
|
||||
|
||||
```bash
|
||||
mise exec -- npm run test:e2e
|
||||
```
|
||||
|
||||
Run interactive commands through the repository wrapper:
|
||||
## Browser Debugging
|
||||
|
||||
Use the repository wrapper for interactive diagnostics:
|
||||
|
||||
```bash
|
||||
mise exec -- npm run playwright:cli -- open http://127.0.0.1:5173/login
|
||||
@@ -70,16 +83,6 @@ mise exec -- npm run playwright:cli -- screenshot --filename=.artifacts/playwrig
|
||||
mise exec -- npm run playwright:cli -- close
|
||||
```
|
||||
|
||||
The wrapper changes to the repository root before invoking the stable `playwright cli` bundled with the locked test runner. This loads `.playwright/cli.config.json`, which selects bundled Chromium in headless, isolated, sandboxed mode and limits generated output under `.artifacts/playwright-cli/` to 100 MB.
|
||||
The wrapper uses the pinned `@playwright/test` package, bundled Chromium, and `.playwright/cli.config.json`. Browser caches and generated artifacts stay under the repository-level `.cache/` and `.artifacts/` directories.
|
||||
|
||||
Do not invoke `playwright-cli`, a global Playwright installation, or a floating `npx` package.
|
||||
|
||||
If the upstream CLI entry point changes, update only the `playwright:cli` npm script. Agents should follow the repository-scoped `$playwright-cli` Skill in `.agents/skills/playwright-cli/` for snapshots, sessions, network diagnosis, test debugging, and artifact handling.
|
||||
|
||||
## Checks
|
||||
|
||||
```bash
|
||||
mise exec -- npm run check
|
||||
```
|
||||
|
||||
The production build is emitted to `dist/` as static assets. Browser E2E tests are run separately because they require the browser setup above. See `docs/web/roadmap.md` at the repository root for architecture boundaries and planned dependencies.
|
||||
Do not invoke Playwright through a global installation or a floating `npx` package. Agents use the repository-scoped `$playwright-cli` skill for browser work.
|
||||
|
||||
Reference in New Issue
Block a user