34 Commits

Author SHA1 Message Date
27d8215a0a feat(frontend): add header, filter bar, and sorting store for files page
- sorting.ts: per-section sort store (sort field + order) persisted to localStorage
- dsl.ts: build/parse DSL filter strings ({t=uuid,&,|,!,...})
- Header.svelte: sort dropdown, asc/desc toggle, filter toggle button
- FilterBar.svelte: tag token picker with operator buttons, search, apply/reset
- files/+page.svelte: wired header + filter bar, resets pagination on sort/filter change
- vite-mock-plugin.ts: added 5 mock tags for filter bar development

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 12:47:18 +03:00
e72d4822e9 feat(frontend): implement file gallery page with infinite scroll
Adds InfiniteScroll component (IntersectionObserver, 300px margin,
CSS spinner). Adds FileCard component (fetch thumbnail with JWT auth
header, blob URL, shimmer placeholder). Adds files/+page.svelte with
160×160 flex-wrap grid and cursor pagination. Updates mock plugin with
75 sample files, cursor pagination, and colored SVG thumbnail handler.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 03:34:33 +03:00
9e341a0fc6 feat(frontend): add dev mock API plugin
Adds a Vite dev-only middleware that intercepts /api/v1/* requests
and returns mock responses for auth, users, files, tags, categories,
and pools. Login with any username and password "password".

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 03:26:03 +03:00
7770960cbf feat(frontend): add root layout with auth guard and bottom navbar
Adds +layout.ts auth guard (redirects to /login when no token).
Adds bottom navbar with inline SVGs for Categories/Tags/Files/Pools/
Settings, active-route highlight (#343249), muted-to-bright color
transition. Adds theme store (dark/light, persisted to localStorage,
applies data-theme attribute). Hides navbar on /login route.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 03:21:00 +03:00
e21d0ef67b feat(frontend): implement auth store and login page
Rewrites auth store with typed AuthUser shape (id, name, isAdmin) and
localStorage persistence. Adds login page with tanabata decorative
images, centered form, purple primary button matching the reference
design.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 03:06:32 +03:00
fde8672bb1 feat(frontend): implement API client and auth module
Adds the base fetch wrapper (client.ts) with JWT auth headers,
automatic token refresh on 401 with request deduplication, and
typed ApiError. Adds auth.ts with login/logout/refresh/listSessions/
terminateSession. Adds authStore (stores/auth.ts) persisted to
localStorage.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 03:02:35 +03:00
071829a79e fix(backend): fix file upload and integration test suite
- Make data.files.exif column nullable (was NOT NULL but service passes nil
  for files without EXIF data, causing a constraint violation on upload)
- FileRepo.Create: include id in INSERT so disk storage path and DB record
  share the same UUID (previously DB generated its own UUID, causing a mismatch)
- Integration test: use correct filter DSL format {t=<uuid>} instead of tag:<uuid>

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 02:56:04 +03:00
0784605267 feat(backend): add integration tests with testcontainers-go
Add internal/integration/server_test.go covering the full happy-path
flow (admin login, user create, upload, tag assign, tag filter, ACL
grant, pool create/add/reorder, trash/restore/permanent-delete, audit
log). Also add targeted tests for blocked-user login prevention, pool
reorder, and tag auto-rules. Uses a disposable postgres:16-alpine
container via testcontainers-go.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 02:34:16 +03:00
e767b07b23 feat(backend): implement user, ACL, and audit stacks
Add UserService (GetMe, UpdateMe, admin CRUD with block/unblock),
UserHandler (/users, /users/me), ACLHandler (GET/PUT /acl/:type/:id),
AuditHandler (GET /audit with all filters). Fix UserRepo.Update to
include is_blocked. Wire all remaining routes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 02:25:16 +03:00
3a49036507 feat(backend): implement pool stack
Add pool repo (gap-based position ordering, cursor pagination, add/remove/reorder
files), service, handler, and wire all /pools endpoints including
/pools/:id/files, /pools/:id/files/remove, and /pools/:id/files/reorder.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 22:04:27 +03:00
21debf626d feat(backend): implement category stack
Add category repo, service, handler, and wire all /categories endpoints
including list, create, get, update, delete, and list-tags.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 21:50:57 +03:00
04d2dfa16e docs(project): document scoped commit naming convention in CLAUDE.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 21:41:22 +03:00
595b8fa671 feat(backend): implement full tag stack (repo, service, handler, routes)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 21:29:20 +03:00
5050dbea3c feat(backend): implement file handler and wire all /files endpoints
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 18:40:04 +03:00
99508cdbf8 feat(backend): implement file service with upload, CRUD, ACL, and audit
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 18:28:59 +03:00
0ae8b81a0b feat(backend): seed MIME types and support all image/video formats
007_seed_data.sql: insert 10 MIME types (4 image, 6 video) with their
canonical extensions into core.mime_types.

disk.go: register golang.org/x/image/webp decoder so imaging.Open
handles WebP still images. Videos (mp4, mov, avi, webm, 3gp, m4v)
continue to go through the ffmpeg frame-extraction path.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 18:21:27 +03:00
fae87ad05c feat(backend): implement DiskStorage with on-demand thumbnail/preview cache
Files stored as {files_path}/{id} (no extension). The ext parameter
is removed from Save/Read/Delete in both the port interface and
the implementation.

Thumbnail and Preview both use imaging.Thumbnail (fit within
configured max bounds, never upscale, never crop) — the config
values THUMB_WIDTH/HEIGHT and PREVIEW_WIDTH/HEIGHT are upper limits,
not forced dimensions.

Non-decodable files (video, etc.) receive a #444455 placeholder.
Cache writes use atomic temp→rename; on cache failure the generated
image is served from memory so the request still succeeds.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 18:11:54 +03:00
1a873949f4 feat(backend): implement FileRepo and filter DSL parser
filter_parser.go — recursive-descent parser for the {token,...} DSL.
Tokens: t=UUID (tag), m=INT (MIME exact), m~PATTERN (MIME LIKE),
operators & | ! ( ) with standard NOT>AND>OR precedence.
All values go through pgx parameters ($N) — SQL injection impossible.

file_repo.go — full FileRepo:
- Create/GetByID/Update via CTE RETURNING with JOIN for one round-trip
- SoftDelete/Restore/DeletePermanent with RowsAffected guards
- SetTags: full replace (DELETE + INSERT per tag)
- ListTags: delegates to loadTagsBatch (single query for N files)
- List: keyset cursor pagination (bidirectional), anchor mode,
  filter DSL, search ILIKE, trash flag, 4 sort columns.
  Cursor is base64url(JSON) encoding sort position; backward
  pagination fetches in reversed ORDER BY then reverses the slice.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 16:55:23 +03:00
0724892e29 feat(backend): implement audit repo and service
AuditRepo.Log resolves action_type_id/object_type_id via SQL subqueries.
AuditRepo.List supports dynamic filtering by user, action, object type/ID,
and date range with COUNT(*) OVER() for total count.
AuditService.Log reads user from context, marshals details to JSON,
and delegates to the repo.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 01:19:24 +03:00
559f891d8d feat(backend): implement ACL repo and service
Add postgres ACLRepo (List/Get/Set) and ACLService with CanView/CanEdit
checks (admin bypass, public flag, creator shortcut, explicit grants)
and GetPermissions/SetPermissions for the /acl endpoints.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 01:13:21 +03:00
5a617af22c fix(backend): wire handler layer in main.go and fix migration issues
cmd/server/main.go: replace stub router with full wiring —
  UserRepo, SessionRepo, AuthService, AuthMiddleware, AuthHandler,
  NewRouter; use postgres.NewPool instead of pgxpool.New directly.

migrations/001_init_schemas.sql: wrap uuid_v7 and uuid_extract_timestamp
  function bodies with goose StatementBegin/End so semicolons inside
  dollar-quoted strings are not treated as statement separators.

migrations/007_seed_data.sql: add seed admin user (admin/admin,
  bcrypt cost 10, is_admin=true, can_create=true) for manual testing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 00:54:54 +03:00
1766dc2b3c feat(backend): implement auth handler, middleware, and router
domain/context.go: extend WithUser/UserFromContext with session ID

handler/response.go: respondJSON/respondError with domain→HTTP status
  mapping (404/403/401/409/400/415/500)
handler/middleware.go: Bearer JWT extraction, ParseAccessToken,
  domain.WithUser injection; aborts with 401 JSON on failure
handler/auth_handler.go: Login, Refresh, Logout, ListSessions,
  TerminateSession
handler/router.go: /health, /api/v1/auth routes; login and refresh
  are public, session routes protected by AuthMiddleware

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 00:43:41 +03:00
277f42035c feat(backend): implement auth service with JWT and session management
Login: bcrypt credential validation, session creation, JWT pair issuance.
Logout/TerminateSession: soft-delete session (is_active = false).
Refresh: token rotation — deactivate old session, issue new pair.
ListSessions: marks IsCurrent by comparing session IDs.
ParseAccessToken: for use by auth middleware.

Claims carry uid (int16), adm (bool), sid (int). Refresh tokens are
stored as SHA-256 hashes; raw tokens never reach the database.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 00:38:21 +03:00
0e9b4637b0 feat(backend): implement db helpers and postgres pool/transactor
- Add is_blocked to core.users (002_core_tables.sql)
- Add is_active to activity.sessions for soft deletes (005_activity_tables.sql)
- Implement UserRepo: List, GetByID, GetByName, Create, Update, Delete
- Implement MimeRepo: List, GetByID, GetByName
- Implement SessionRepo: Create, GetByTokenHash, ListByUser,
  UpdateLastActivity, Delete, DeleteByUserID
- Session deletes are soft (SET is_active = false); is_active is a
  SQL-only filter, not mapped to the domain type

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 00:34:45 +03:00
2c83073903 feat(backend): implement db helpers and postgres pool/transactor
db/db.go: TxFromContext/ContextWithTx for transaction propagation,
Querier interface (QueryRow/Query/Exec), ScanRow generic helper,
ClampLimit/ClampOffset pagination guards.

db/postgres/postgres.go: NewPool with ping validation, Transactor
backed by pgxpool (BeginTx → fn → commit/rollback), connOrTx helper
that returns the active transaction from context or falls back to pool.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 00:15:17 +03:00
83fda85bea feat(backend): implement port interfaces (repository and storage)
Define all repository interfaces in port/repository.go:
FileRepo, TagRepo, TagRuleRepo, CategoryRepo, PoolRepo, UserRepo,
SessionRepo, ACLRepo, AuditRepo, MimeRepo, and Transactor.
Add OffsetParams and PoolFileListParams as shared parameter structs.

Define FileStorage interface in port/storage.go with Save, Read,
Delete, Thumbnail, and Preview methods.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 00:11:06 +03:00
1e2a2a61de refactor(backend): strengthen domain layer types and add missing page types
- DomainError struct with Code() string method replaces plain errors.New
  sentinels; errors.Is() still works via pointer equality
- UUIDCreatedAt(uuid.UUID) time.Time helper extracts timestamp from UUID v7
- Add TagOffsetPage, CategoryOffsetPage, PoolOffsetPage
- FileListParams fields grouped with comments matching openapi.yaml params
- Fix mismatched comment on UserPage

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 00:06:44 +03:00
36d9488f21 feat(frontend): initialize SvelteKit frontend with Tailwind and OpenAPI types
- SvelteKit SPA mode with adapter-static (index.html fallback)
- Tailwind CSS v4 via @tailwindcss/vite with custom color palette
- CSS custom properties for dark/light theme (dark is default)
- Epilogue variable font with preload
- openapi-typescript generates src/lib/api/schema.ts from openapi.yaml
- Friendly domain type aliases in src/lib/api/types.ts

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 00:00:26 +03:00
8565bf9200 feat(backend): config, migrations embed, and server entrypoint
- internal/config: typed Config struct loaded from env vars via godotenv;
  all fields from docs (listen addr, JWT, DB, storage, thumbs, import)
- migrations/embed.go: embed FS so goose SQL files are baked into the binary
- cmd/server/main.go: load config → connect pgxpool → goose migrations
  (embedded) → Gin server with GET /health returning 200 OK
- .env.example: documents all required and optional env vars
- go.mod: bump to Go 1.26, add gin/pgx/goose/godotenv as direct deps

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 18:57:17 +03:00
ecad017274 refactor(backend): split monolithic migration into 7 goose files
001_init_schemas  — extensions, schemas, uuid_v7 functions
002_core_tables   — core.users, mime_types, object_types
003_data_tables   — data.categories, tags, tag_rules, files, file_tag, pools, file_pool
004_acl_tables    — acl.permissions
005_activity_tables — activity.action_types, sessions, file_views, pool_views, tag_uses, audit_log
006_indexes       — all indexes across all schemas
007_seed_data     — object_types and action_types reference rows

Each file has -- +goose Up / Down annotations; downs drop in reverse
dependency order.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 18:40:36 +03:00
a2823337b6 docs(project): add reference Python/Flask implementation
Previous version of Tanabata used as visual and functional reference
for the new Go + SvelteKit rewrite.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 18:36:05 +03:00
4c10553549 chore(project): add .gitignore and .gitattributes
.gitignore covers env/secrets, OS files, IDE, Go build artifacts,
frontend build output, data dirs, and vendored reference libs.
.gitattributes enforces LF line endings, marks binaries, configures
diff drivers per language, and sets Linguist hints for repo stats.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 18:35:22 +03:00
1d341eef24 feat(backend): initialize Go module and implement domain layer
- Add go.mod (module tanabata/backend, Go 1.21) with uuid dependency
- Implement internal/domain: File, Tag, TagRule, Category, Pool, PoolFile,
  User, Session, Permission, ObjectType, AuditEntry + all pagination types
- Add domain error sentinels (ErrNotFound, ErrForbidden, etc.)
- Add context helpers WithUser/UserFromContext for JWT propagation
- Fix migration: remove redundant DEFAULT on exif jsonb column

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 18:28:33 +03:00
dbdc80b3a0 chore(project): initial project structure 2026-04-01 16:17:37 +03:00