|
| 1 | +# PlainNAS Development Rules |
| 2 | + |
| 3 | +## Project Overview |
| 4 | +PlainNAS is a Go-based NAS (Network Attached Storage) system with a Vue 3 web frontend. The backend provides API, file system, media, and watcher services, while the frontend (in `web/`) offers a modern UI for user interaction. |
| 5 | + |
| 6 | +## Architecture & Key Components |
| 7 | + |
| 8 | +### Backend (Go) |
| 9 | +- **Entrypoint**: `main.go`, with commands in `cmd/` (e.g., `run.go`, `install.go`) |
| 10 | +- **Services**: `internal/services/` (API, watcher), `internal/media/`, `internal/db/`, `internal/cache/`, `internal/config/` |
| 11 | +- **GraphQL**: Defined in `internal/graph/schema.graphql`, resolvers in `internal/graph/schema.resolvers.go`, generated code in `internal/graph/generated/` |
| 12 | +- **Config**: TOML files in `cmd/install/config.toml` |
| 13 | +- **Systemd integration**: `cmd/install/plainnas.service` |
| 14 | + |
| 15 | +### Frontend (Vue 3) |
| 16 | +- **Location**: `web/` |
| 17 | +- **Main entry**: `web/src/main.ts`, root component: `web/src/App.vue` |
| 18 | +- **Components**: `web/src/components/`, assets: `web/src/assets/` |
| 19 | +- **Build tools**: Vite (`vite.config.ts`), TypeScript (`tsconfig.json`) |
| 20 | + |
| 21 | +## Developer Workflows |
| 22 | + |
| 23 | +### Install & Initialize |
| 24 | +```bash |
| 25 | +sudo go run main.go install # Run once to set up packages, DB, config |
| 26 | +``` |
| 27 | + |
| 28 | +### Run (Dev) |
| 29 | +```bash |
| 30 | +sudo go run main.go run |
| 31 | +``` |
| 32 | + |
| 33 | +### GraphQL Codegen |
| 34 | +```bash |
| 35 | +go env -w GOFLAGS=-mod=mod |
| 36 | +go mod tidy |
| 37 | +go generate ./internal/graph |
| 38 | +``` |
| 39 | + |
| 40 | +### Production Build |
| 41 | +```bash |
| 42 | +go build |
| 43 | +sudo mv ./plainnas /usr/local/bin/ |
| 44 | +sudo systemctl start plainnas |
| 45 | +``` |
| 46 | + |
| 47 | +### Frontend Build |
| 48 | +```bash |
| 49 | +cd web/ |
| 50 | +npm install && npm run build |
| 51 | +``` |
| 52 | + |
| 53 | +## Logging |
| 54 | + |
| 55 | +Always use the project's logging system located at `internal/pkg/log/log.go`. |
| 56 | + |
| 57 | +### Available Log Functions |
| 58 | + |
| 59 | +```go |
| 60 | +import "ismartcoding/plainnas/internal/pkg/log" |
| 61 | + |
| 62 | +// Debug logging |
| 63 | +log.Debug("message") |
| 64 | +log.Debugf("format %s", value) |
| 65 | + |
| 66 | +// Info logging |
| 67 | +log.Info("message") |
| 68 | +log.Infof("format %s", value) |
| 69 | + |
| 70 | +// Error logging |
| 71 | +log.Error(err) |
| 72 | +log.Errorf("format %s", value) |
| 73 | + |
| 74 | +// Trace logging |
| 75 | +log.Trace("message") |
| 76 | +log.Tracef("format %s", value) |
| 77 | + |
| 78 | +// Panic logging |
| 79 | +log.Panic(err) |
| 80 | +log.Panicf("format %s", value) |
| 81 | +``` |
| 82 | + |
| 83 | +### DO NOT Use |
| 84 | + |
| 85 | +❌ `log.Printf()` from standard library |
| 86 | +❌ `fmt.Println()` for logging |
| 87 | +❌ Any other logging libraries |
| 88 | + |
| 89 | +### Examples |
| 90 | + |
| 91 | +```go |
| 92 | +// ✅ Correct |
| 93 | +log.Debugf("[FunctionName] operation completed: count=%d", count) |
| 94 | +log.Infof("[ModuleName] started processing: id=%s", id) |
| 95 | +log.Error(err) |
| 96 | + |
| 97 | +// ❌ Wrong |
| 98 | +log.Printf("operation completed: count=%d", count) |
| 99 | +fmt.Println("started processing") |
| 100 | +``` |
| 101 | + |
| 102 | +## Core Development Principles |
| 103 | + |
| 104 | +### Code Quality |
| 105 | +- Prioritize **simple, readable, minimal code** (less code is better) |
| 106 | +- **Do not reduce features or logic**: behavior must remain correct |
| 107 | +- **Do not regress performance**: avoid full scans, avoid N+1 patterns, preserve index-backed fast paths |
| 108 | +- **Avoid duplication**: extract shared helpers instead of repeating parsing/filtering/iteration logic |
| 109 | +- **Split oversized files**: keep files focused by responsibility (e.g., store vs indexes vs helpers) |
| 110 | + |
| 111 | +### Compatibility & Migration |
| 112 | +- **Avoid compatibility/migration/backfill code** unless explicitly requested |
| 113 | +- Development workflow: it is acceptable to **delete the DB and rebuild** |
| 114 | +- Avoid persistent index version machinery unless explicitly requested |
| 115 | + |
| 116 | +### Documentation |
| 117 | +- If logic/behavior changes, update relevant Markdown docs in `README.md` and/or `docs/` |
| 118 | +- Keep docs in sync with code |
| 119 | + |
| 120 | +## Platform Support (Linux Only) |
| 121 | + |
| 122 | +PlainNAS is **Linux-only**. Design and implementation should assume Linux (systemd, `/proc`, `/sys`, `mount`, `lsblk`, etc.). |
| 123 | + |
| 124 | +- Do **not** add Windows/macOS fallbacks, multi-platform abstractions, or non-Linux build stubs unless explicitly requested |
| 125 | +- It is acceptable for non-Linux builds to fail; correctness on Linux takes priority |
| 126 | + |
| 127 | +## Backend Patterns & Conventions |
| 128 | + |
| 129 | +### Service Boundaries |
| 130 | +- API, watcher, media, cache, DB, config are separated by directory |
| 131 | +- Always check for service boundaries before making changes |
| 132 | + |
| 133 | +### GraphQL Development |
| 134 | +- Schema-driven development: Update `schema.graphql` and run codegen |
| 135 | +- Keep `internal/graph/schema.resolvers.go` lightweight |
| 136 | +- Resolvers should only validate/translate inputs and delegate to focused files (e.g. `internal/graph/*_api.go`) |
| 137 | +- Do not put substantial business logic in `schema.resolvers.go` |
| 138 | + |
| 139 | +### Example: Adding a GraphQL Field |
| 140 | +1. Update `internal/graph/schema.graphql` |
| 141 | +2. Run `go generate ./internal/graph` |
| 142 | +3. Implement resolver in `internal/graph/schema.resolvers.go` |
| 143 | + |
| 144 | +### Error Handling |
| 145 | +Always handle errors appropriately: |
| 146 | +```go |
| 147 | +if err != nil { |
| 148 | + log.Error(err) |
| 149 | + return nil, err |
| 150 | +} |
| 151 | +``` |
| 152 | + |
| 153 | +### Root-Cause Fixes |
| 154 | +- Prefer root-cause fixes over workaround logic |
| 155 | +- Avoid extra guards/dedup that mask bugs |
| 156 | +- If an invariant is broken, fix the source and let issues surface during development |
| 157 | + |
| 158 | +## Frontend Patterns & Conventions |
| 159 | + |
| 160 | +### Styling Rules |
| 161 | +- **Avoid copy-pasting the same scoped CSS** across pages/components |
| 162 | +- If a style pattern is used in more than one place, extract it into: |
| 163 | + - A shared stylesheet under `web/src/styles/` |
| 164 | + - A component-level style |
| 165 | + - A small reusable UI component |
| 166 | +- Reuse via classes/components instead of duplicating blocks in each `.vue` file |
| 167 | +- Keep `scoped` styles for truly view-specific tweaks only |
| 168 | + |
| 169 | +### Forms & Validation (Vue + Vuetify) |
| 170 | +- Prefer `vee-validate` `handleSubmit` + schema (see `SettingsBasicView`) for submit-time validation |
| 171 | +- Show errors inline via each input's `error`/`error-text` gated by `dirty` and/or `submitAttempted` |
| 172 | +- **Do not show extra error toasts** for client-side validation failures when the input already shows an inline error |
| 173 | +- Reserve toasts for success and genuine server/network errors |
| 174 | + |
| 175 | +### GraphQL (Vue Apollo) |
| 176 | +- Prefer the existing wrappers `initMutation` / `initQuery` / `initLazyQuery` in `web/src/lib/api/*` |
| 177 | +- Follow their patterns |
| 178 | + |
| 179 | +#### Mutations (Callback-Driven) |
| 180 | +```typescript |
| 181 | +// ✅ Correct |
| 182 | +const { mutate, loading, onDone, onError } = initMutation({ document: myMutation }) |
| 183 | + |
| 184 | +onDone((result) => { |
| 185 | + // handle success |
| 186 | +}) |
| 187 | + |
| 188 | +onError((error) => { |
| 189 | + // handle error |
| 190 | +}) |
| 191 | + |
| 192 | +// ❌ Wrong - do not use try/catch |
| 193 | +try { |
| 194 | + await mutate() |
| 195 | +} catch (error) { |
| 196 | + // GraphQL errors are not thrown as exceptions |
| 197 | +} |
| 198 | +``` |
| 199 | + |
| 200 | +#### Queries (Result-Driven) |
| 201 | +```typescript |
| 202 | +// ✅ Correct |
| 203 | +const { loading, onResult } = initQuery({ |
| 204 | + document: myQuery, |
| 205 | + handle: (data, error) => { |
| 206 | + if (error) { |
| 207 | + // handle error string |
| 208 | + } else { |
| 209 | + // handle data |
| 210 | + } |
| 211 | + } |
| 212 | +}) |
| 213 | + |
| 214 | +// ❌ Wrong - do not use try/catch for GraphQL errors |
| 215 | +``` |
| 216 | + |
| 217 | +### Yarn Commands |
| 218 | +- Yarn v4+ (Berry) does **not** support `yarn -s` / `--silent` |
| 219 | +- Run scripts as `yarn <script>` (e.g. `yarn typecheck`) without `-s` |
| 220 | + |
| 221 | +## Testing |
| 222 | + |
| 223 | +- Write unit tests for new functionality |
| 224 | +- Use table-driven tests when appropriate |
| 225 | +- Mock external dependencies |
| 226 | + |
| 227 | +## Integration Points |
| 228 | + |
| 229 | +- **API**: Go backend exposes GraphQL and REST endpoints (see `internal/services/api/`) |
| 230 | +- **Watcher**: Monitors file changes (see `internal/services/watcher/`) |
| 231 | +- **Media**: Handles media scanning/storage (see `internal/media/`) |
| 232 | +- **Frontend-backend communication**: Via GraphQL and REST APIs |
| 233 | + |
| 234 | +## External Dependencies |
| 235 | + |
| 236 | +- Go modules (see `go.mod`) |
| 237 | +- Node.js packages for frontend (see `web/package.json`) |
| 238 | +- Systemd for service management |
| 239 | + |
| 240 | +## For AI Agents |
| 241 | + |
| 242 | +- Always check for service boundaries before making changes |
| 243 | +- Use provided scripts and commands for setup/builds |
| 244 | +- Reference key files for patterns (see above) |
| 245 | +- Ask for clarification if workflow or integration is unclear |
| 246 | +- Prioritize simple, readable, minimal code |
| 247 | +- Avoid unnecessary abstractions and duplication |
| 248 | +- Keep documentation in sync with code changes |
0 commit comments