diff --git a/backend/cmd/server/main.go b/backend/cmd/server/main.go index 650292d..a6df5d5 100644 --- a/backend/cmd/server/main.go +++ b/backend/cmd/server/main.go @@ -59,17 +59,17 @@ func main() { } // Repositories - userRepo := postgres.NewUserRepo(pool) - sessionRepo := postgres.NewSessionRepo(pool) - fileRepo := postgres.NewFileRepo(pool) - mimeRepo := postgres.NewMimeRepo(pool) - aclRepo := postgres.NewACLRepo(pool) - auditRepo := postgres.NewAuditRepo(pool) - tagRepo := postgres.NewTagRepo(pool) - tagRuleRepo := postgres.NewTagRuleRepo(pool) + userRepo := postgres.NewUserRepo(pool) + sessionRepo := postgres.NewSessionRepo(pool) + fileRepo := postgres.NewFileRepo(pool) + mimeRepo := postgres.NewMimeRepo(pool) + aclRepo := postgres.NewACLRepo(pool) + auditRepo := postgres.NewAuditRepo(pool) + tagRepo := postgres.NewTagRepo(pool) + tagRuleRepo := postgres.NewTagRuleRepo(pool) categoryRepo := postgres.NewCategoryRepo(pool) - poolRepo := postgres.NewPoolRepo(pool) - transactor := postgres.NewTransactor(pool) + poolRepo := postgres.NewPoolRepo(pool) + transactor := postgres.NewTransactor(pool) // Services authSvc := service.NewAuthService( @@ -79,12 +79,12 @@ func main() { cfg.JWTAccessTTL, cfg.JWTRefreshTTL, ) - aclSvc := service.NewACLService(aclRepo, fileRepo, tagRepo, categoryRepo, poolRepo, transactor) - auditSvc := service.NewAuditService(auditRepo) - tagSvc := service.NewTagService(tagRepo, tagRuleRepo, aclSvc, auditSvc, transactor) + aclSvc := service.NewACLService(aclRepo, fileRepo, tagRepo, categoryRepo, poolRepo, transactor) + auditSvc := service.NewAuditService(auditRepo) + tagSvc := service.NewTagService(tagRepo, tagRuleRepo, aclSvc, auditSvc, transactor) categorySvc := service.NewCategoryService(categoryRepo, tagRepo, aclSvc, auditSvc) - poolSvc := service.NewPoolService(poolRepo, aclSvc, auditSvc) - fileSvc := service.NewFileService( + poolSvc := service.NewPoolService(poolRepo, aclSvc, auditSvc) + fileSvc := service.NewFileService( fileRepo, mimeRepo, diskStorage, @@ -103,15 +103,15 @@ func main() { } // Handlers - authMiddleware := handler.NewAuthMiddleware(authSvc) - authHandler := handler.NewAuthHandler(authSvc) - fileHandler := handler.NewFileHandler(fileSvc, tagSvc, cfg.MaxUploadBytes) - tagHandler := handler.NewTagHandler(tagSvc, fileSvc) + authMiddleware := handler.NewAuthMiddleware(authSvc) + authHandler := handler.NewAuthHandler(authSvc) + fileHandler := handler.NewFileHandler(fileSvc, tagSvc, cfg.MaxUploadBytes) + tagHandler := handler.NewTagHandler(tagSvc, fileSvc) categoryHandler := handler.NewCategoryHandler(categorySvc) - poolHandler := handler.NewPoolHandler(poolSvc) - userHandler := handler.NewUserHandler(userSvc) - aclHandler := handler.NewACLHandler(aclSvc) - auditHandler := handler.NewAuditHandler(auditSvc) + poolHandler := handler.NewPoolHandler(poolSvc) + userHandler := handler.NewUserHandler(userSvc) + aclHandler := handler.NewACLHandler(aclSvc) + auditHandler := handler.NewAuditHandler(auditSvc) r := handler.NewRouter( authMiddleware, authHandler, diff --git a/backend/internal/db/postgres/category_repo.go b/backend/internal/db/postgres/category_repo.go index 509ac2a..e4325c2 100644 --- a/backend/internal/db/postgres/category_repo.go +++ b/backend/internal/db/postgres/category_repo.go @@ -300,4 +300,4 @@ func (r *CategoryRepo) Delete(ctx context.Context, id uuid.UUID) error { return domain.ErrNotFound } return nil -} \ No newline at end of file +} diff --git a/backend/internal/db/postgres/file_repo.go b/backend/internal/db/postgres/file_repo.go index 3a227c9..816d90f 100644 --- a/backend/internal/db/postgres/file_repo.go +++ b/backend/internal/db/postgres/file_repo.go @@ -142,7 +142,7 @@ func makeCursor(r fileRow, sort, order string) fileCursor { } case "mime": val = r.MIMEType - // "created": val is empty; f.id is the sort key. + // "created": val is empty; f.id is the sort key. } return fileCursor{Sort: sort, Order: order, ID: r.ID.String(), Val: val} } @@ -569,7 +569,7 @@ func (r *FileRepo) List(ctx context.Context, params domain.FileListParams) (*dom cursorVal = av.OriginalName case "mime": cursorVal = av.MIMEType - // "created": cursorVal stays ""; cursorID is the sort key. + // "created": cursorVal stays ""; cursorID is the sort key. } hasCursor = true isAnchor = true diff --git a/backend/internal/db/postgres/filter_parser.go b/backend/internal/db/postgres/filter_parser.go index f3f87f8..a3adf50 100644 --- a/backend/internal/db/postgres/filter_parser.go +++ b/backend/internal/db/postgres/filter_parser.go @@ -15,7 +15,7 @@ import ( type filterTokenKind int const ( - ftkAnd filterTokenKind = iota + ftkAnd filterTokenKind = iota ftkOr ftkNot ftkLParen @@ -44,9 +44,9 @@ type filterNode interface { toSQL(n int, args []any) (string, int, []any) } -type andNode struct{ left, right filterNode } -type orNode struct{ left, right filterNode } -type notNode struct{ child filterNode } +type andNode struct{ left, right filterNode } +type orNode struct{ left, right filterNode } +type notNode struct{ child filterNode } type leafNode struct{ tok filterToken } func (a *andNode) toSQL(n int, args []any) (string, int, []any) { diff --git a/backend/internal/db/postgres/pool_repo.go b/backend/internal/db/postgres/pool_repo.go index e1fc848..c49ed99 100644 --- a/backend/internal/db/postgres/pool_repo.go +++ b/backend/internal/db/postgres/pool_repo.go @@ -712,4 +712,4 @@ func (r *PoolRepo) Reorder(ctx context.Context, poolID uuid.UUID, fileIDs []uuid } return r.reassignPositions(ctx, q, poolID, ordered) -} \ No newline at end of file +} diff --git a/backend/internal/db/postgres/tag_repo.go b/backend/internal/db/postgres/tag_repo.go index 742e552..838b4b2 100644 --- a/backend/internal/db/postgres/tag_repo.go +++ b/backend/internal/db/postgres/tag_repo.go @@ -23,16 +23,16 @@ import ( type tagRow struct { ID uuid.UUID `db:"id"` - Name string `db:"name"` - Notes *string `db:"notes"` - Color *string `db:"color"` + Name string `db:"name"` + Notes *string `db:"notes"` + Color *string `db:"color"` CategoryID *uuid.UUID `db:"category_id"` - CategoryName *string `db:"category_name"` - CategoryColor *string `db:"category_color"` - Metadata []byte `db:"metadata"` - CreatorID int16 `db:"creator_id"` - CreatorName string `db:"creator_name"` - IsPublic bool `db:"is_public"` + CategoryName *string `db:"category_name"` + CategoryColor *string `db:"category_color"` + Metadata []byte `db:"metadata"` + CreatorID int16 `db:"creator_id"` + CreatorName string `db:"creator_name"` + IsPublic bool `db:"is_public"` } type tagRowWithTotal struct { @@ -43,8 +43,8 @@ type tagRowWithTotal struct { type tagRuleRow struct { WhenTagID uuid.UUID `db:"when_tag_id"` ThenTagID uuid.UUID `db:"then_tag_id"` - ThenTagName string `db:"then_tag_name"` - IsActive bool `db:"is_active"` + ThenTagName string `db:"then_tag_name"` + IsActive bool `db:"is_active"` } // --------------------------------------------------------------------------- @@ -637,4 +637,4 @@ WHERE when_tag_id = $1 AND then_tag_id = $2` return domain.ErrNotFound } return nil -} \ No newline at end of file +} diff --git a/backend/internal/domain/acl.go b/backend/internal/domain/acl.go index 0a660d8..7ca2a9c 100644 --- a/backend/internal/domain/acl.go +++ b/backend/internal/domain/acl.go @@ -10,10 +10,10 @@ type ObjectType struct { // Permission represents a per-object access entry for a user. type Permission struct { - UserID int16 - UserName string // denormalized - ObjectTypeID int16 - ObjectID uuid.UUID - CanView bool - CanEdit bool + UserID int16 + UserName string // denormalized + ObjectTypeID int16 + ObjectID uuid.UUID + CanView bool + CanEdit bool } diff --git a/backend/internal/domain/audit.go b/backend/internal/domain/audit.go index faeb469..896624e 100644 --- a/backend/internal/domain/audit.go +++ b/backend/internal/domain/audit.go @@ -15,14 +15,14 @@ type ActionType struct { // AuditEntry is a single audit log record. type AuditEntry struct { - ID int64 - UserID int16 - UserName string // denormalized - Action string // action type name, e.g. "file_create" - ObjectType *string - ObjectID *uuid.UUID - Details json.RawMessage - PerformedAt time.Time + ID int64 + UserID int16 + UserName string // denormalized + Action string // action type name, e.g. "file_create" + ObjectType *string + ObjectID *uuid.UUID + Details json.RawMessage + PerformedAt time.Time } // AuditPage is an offset-based page of audit log entries. diff --git a/backend/internal/handler/acl_handler.go b/backend/internal/handler/acl_handler.go index 5f2aa1f..19512be 100644 --- a/backend/internal/handler/acl_handler.go +++ b/backend/internal/handler/acl_handler.go @@ -143,4 +143,4 @@ func (h *ACLHandler) SetPermissions(c *gin.Context) { out[i] = toPermissionJSON(p) } respondJSON(c, http.StatusOK, out) -} \ No newline at end of file +} diff --git a/backend/internal/handler/audit_handler.go b/backend/internal/handler/audit_handler.go index 6dad2fc..3ce9e47 100644 --- a/backend/internal/handler/audit_handler.go +++ b/backend/internal/handler/audit_handler.go @@ -117,4 +117,4 @@ func (h *AuditHandler) List(c *gin.Context) { "offset": page.Offset, "limit": page.Limit, }) -} \ No newline at end of file +} diff --git a/backend/internal/handler/category_handler.go b/backend/internal/handler/category_handler.go index 46f0f30..d3b42f7 100644 --- a/backend/internal/handler/category_handler.go +++ b/backend/internal/handler/category_handler.go @@ -232,4 +232,4 @@ func (h *CategoryHandler) ListTags(c *gin.Context) { "offset": page.Offset, "limit": page.Limit, }) -} \ No newline at end of file +} diff --git a/backend/internal/handler/file_handler.go b/backend/internal/handler/file_handler.go index e273402..9b3bb77 100644 --- a/backend/internal/handler/file_handler.go +++ b/backend/internal/handler/file_handler.go @@ -89,16 +89,16 @@ type fileJSON struct { func toTagJSON(t domain.Tag) tagJSON { j := tagJSON{ - ID: t.ID.String(), - Name: t.Name, - Notes: t.Notes, - Color: t.Color, + ID: t.ID.String(), + Name: t.Name, + Notes: t.Notes, + Color: t.Color, CategoryName: t.CategoryName, CategoryColor: t.CategoryColor, - CreatorID: t.CreatorID, - CreatorName: t.CreatorName, - IsPublic: t.IsPublic, - CreatedAt: t.CreatedAt.Format(time.RFC3339), + CreatorID: t.CreatorID, + CreatorName: t.CreatorName, + IsPublic: t.IsPublic, + CreatedAt: t.CreatedAt.Format(time.RFC3339), } if t.CategoryID != nil { s := t.CategoryID.String() @@ -672,4 +672,4 @@ func parseUUIDs(strs []string) ([]uuid.UUID, error) { ids = append(ids, id) } return ids, nil -} \ No newline at end of file +} diff --git a/backend/internal/handler/pool_handler.go b/backend/internal/handler/pool_handler.go index b9722e1..c55593e 100644 --- a/backend/internal/handler/pool_handler.go +++ b/backend/internal/handler/pool_handler.go @@ -353,4 +353,4 @@ func (h *PoolHandler) Reorder(c *gin.Context) { return } c.Status(http.StatusNoContent) -} \ No newline at end of file +} diff --git a/backend/internal/handler/tag_handler.go b/backend/internal/handler/tag_handler.go index d25a9e9..eb44eca 100644 --- a/backend/internal/handler/tag_handler.go +++ b/backend/internal/handler/tag_handler.go @@ -106,11 +106,11 @@ func (h *TagHandler) List(c *gin.Context) { func (h *TagHandler) Create(c *gin.Context) { var body struct { - Name string `json:"name" binding:"required"` - Notes *string `json:"notes"` - Color *string `json:"color"` - CategoryID *string `json:"category_id"` - IsPublic *bool `json:"is_public"` + Name string `json:"name" binding:"required"` + Notes *string `json:"notes"` + Color *string `json:"color"` + CategoryID *string `json:"category_id"` + IsPublic *bool `json:"is_public"` } if err := c.ShouldBindJSON(&body); err != nil { respondError(c, domain.ErrValidation) @@ -549,4 +549,4 @@ func (h *TagHandler) FileRemoveTag(c *gin.Context) { // Helpers // --------------------------------------------------------------------------- -func ptr(s string) *string { return &s } \ No newline at end of file +func ptr(s string) *string { return &s } diff --git a/backend/internal/handler/user_handler.go b/backend/internal/handler/user_handler.go index 18d4580..b7eb7a6 100644 --- a/backend/internal/handler/user_handler.go +++ b/backend/internal/handler/user_handler.go @@ -255,4 +255,4 @@ func (h *UserHandler) Delete(c *gin.Context) { return } c.Status(http.StatusNoContent) -} \ No newline at end of file +} diff --git a/backend/internal/integration/server_test.go b/backend/internal/integration/server_test.go index eb58397..08326dd 100644 --- a/backend/internal/integration/server_test.go +++ b/backend/internal/integration/server_test.go @@ -111,42 +111,42 @@ func setupSuite(t *testing.T) *harness { require.NoError(t, err) // --- Repositories -------------------------------------------------------- - userRepo := postgres.NewUserRepo(pool) - sessionRepo := postgres.NewSessionRepo(pool) - fileRepo := postgres.NewFileRepo(pool) - mimeRepo := postgres.NewMimeRepo(pool) - aclRepo := postgres.NewACLRepo(pool) - auditRepo := postgres.NewAuditRepo(pool) - tagRepo := postgres.NewTagRepo(pool) - tagRuleRepo := postgres.NewTagRuleRepo(pool) + userRepo := postgres.NewUserRepo(pool) + sessionRepo := postgres.NewSessionRepo(pool) + fileRepo := postgres.NewFileRepo(pool) + mimeRepo := postgres.NewMimeRepo(pool) + aclRepo := postgres.NewACLRepo(pool) + auditRepo := postgres.NewAuditRepo(pool) + tagRepo := postgres.NewTagRepo(pool) + tagRuleRepo := postgres.NewTagRuleRepo(pool) categoryRepo := postgres.NewCategoryRepo(pool) - poolRepo := postgres.NewPoolRepo(pool) - transactor := postgres.NewTransactor(pool) + poolRepo := postgres.NewPoolRepo(pool) + transactor := postgres.NewTransactor(pool) // --- Services ------------------------------------------------------------ - authSvc := service.NewAuthService(userRepo, sessionRepo, "test-secret", 15*time.Minute, 720*time.Hour) - aclSvc := service.NewACLService(aclRepo, fileRepo, tagRepo, categoryRepo, poolRepo, transactor) - auditSvc := service.NewAuditService(auditRepo) - tagSvc := service.NewTagService(tagRepo, tagRuleRepo, aclSvc, auditSvc, transactor) + authSvc := service.NewAuthService(userRepo, sessionRepo, "test-secret", 15*time.Minute, 720*time.Hour) + aclSvc := service.NewACLService(aclRepo, fileRepo, tagRepo, categoryRepo, poolRepo, transactor) + auditSvc := service.NewAuditService(auditRepo) + tagSvc := service.NewTagService(tagRepo, tagRuleRepo, aclSvc, auditSvc, transactor) categorySvc := service.NewCategoryService(categoryRepo, tagRepo, aclSvc, auditSvc) - poolSvc := service.NewPoolService(poolRepo, aclSvc, auditSvc) - fileSvc := service.NewFileService(fileRepo, mimeRepo, diskStorage, aclSvc, auditSvc, tagSvc, transactor, filesDir) - userSvc := service.NewUserService(userRepo, sessionRepo, auditSvc) + poolSvc := service.NewPoolService(poolRepo, aclSvc, auditSvc) + fileSvc := service.NewFileService(fileRepo, mimeRepo, diskStorage, aclSvc, auditSvc, tagSvc, transactor, filesDir) + userSvc := service.NewUserService(userRepo, sessionRepo, auditSvc) // Bootstrap the admin account the suite logs in with (replaces the old // hardcoded seed credentials). require.NoError(t, userSvc.EnsureAdmin(ctx, "admin", "admin")) // --- Handlers ------------------------------------------------------------ - authMiddleware := handler.NewAuthMiddleware(authSvc) - authHandler := handler.NewAuthHandler(authSvc) - fileHandler := handler.NewFileHandler(fileSvc, tagSvc, 500<<20) - tagHandler := handler.NewTagHandler(tagSvc, fileSvc) + authMiddleware := handler.NewAuthMiddleware(authSvc) + authHandler := handler.NewAuthHandler(authSvc) + fileHandler := handler.NewFileHandler(fileSvc, tagSvc, 500<<20) + tagHandler := handler.NewTagHandler(tagSvc, fileSvc) categoryHandler := handler.NewCategoryHandler(categorySvc) - poolHandler := handler.NewPoolHandler(poolSvc) - userHandler := handler.NewUserHandler(userSvc) - aclHandler := handler.NewACLHandler(aclSvc) - auditHandler := handler.NewAuditHandler(auditSvc) + poolHandler := handler.NewPoolHandler(poolSvc) + userHandler := handler.NewUserHandler(userSvc) + aclHandler := handler.NewACLHandler(aclSvc) + auditHandler := handler.NewAuditHandler(auditSvc) r := handler.NewRouter( authMiddleware, authHandler, @@ -289,7 +289,7 @@ func TestFullFlow(t *testing.T) { // 3. Log in as alice // ========================================================================= aliceToken := h.login("alice", "alicepass") - bobToken := h.login("bob", "bobpass") + bobToken := h.login("bob", "bobpass") // ========================================================================= // 4. Alice uploads a private JPEG @@ -619,7 +619,7 @@ func TestTagRuleActivateApplyToExisting(t *testing.T) { // Activate A→B WITHOUT apply_to_existing — existing file must not change. resp = h.doJSON("PATCH", "/tags/"+tagA+"/rules/"+tagB, map[string]any{ - "is_active": true, + "is_active": true, "apply_to_existing": false, }, tok) require.Equal(t, http.StatusOK, resp.StatusCode, resp.String()) @@ -634,7 +634,7 @@ func TestTagRuleActivateApplyToExisting(t *testing.T) { // Activate A→B WITH apply_to_existing=true. // Expectation: file gets B directly, and C transitively via the active B→C rule. resp = h.doJSON("PATCH", "/tags/"+tagA+"/rules/"+tagB, map[string]any{ - "is_active": true, + "is_active": true, "apply_to_existing": true, }, tok) require.Equal(t, http.StatusOK, resp.StatusCode, resp.String()) @@ -1023,4 +1023,4 @@ func writeFile(t *testing.T, dir, name string, content []byte) string { var ( _ = freePort _ = writeFile -) \ No newline at end of file +) diff --git a/backend/internal/service/category_service.go b/backend/internal/service/category_service.go index 31c9809..9a20667 100644 --- a/backend/internal/service/category_service.go +++ b/backend/internal/service/category_service.go @@ -10,14 +10,14 @@ import ( "tanabata/backend/internal/port" ) -const categoryObjectType = "category" -const categoryObjectTypeID int16 = 3 // third row in 007_seed_data.sql object_types +const categoryObjectType = "category" +const categoryObjectTypeID int16 = 3 // third row in 007_seed_data.sql object_types // CategoryParams holds the fields for creating or patching a category. type CategoryParams struct { Name string Notes *string - Color *string // nil = no change; pointer to empty string = clear + Color *string // nil = no change; pointer to empty string = clear Metadata json.RawMessage IsPublic *bool } @@ -176,4 +176,4 @@ func (s *CategoryService) Delete(ctx context.Context, id uuid.UUID) error { func (s *CategoryService) ListTags(ctx context.Context, categoryID uuid.UUID, params port.OffsetParams) (*domain.TagOffsetPage, error) { params.ViewerID, params.ViewerIsAdmin, _ = domain.UserFromContext(ctx) return s.tags.ListByCategory(ctx, categoryID, params) -} \ No newline at end of file +} diff --git a/backend/internal/service/file_service.go b/backend/internal/service/file_service.go index 05acd10..35ba5ed 100644 --- a/backend/internal/service/file_service.go +++ b/backend/internal/service/file_service.go @@ -627,4 +627,4 @@ func extractEXIFWithDatetime(data []byte) (json.RawMessage, *time.Time) { dt = &t } return json.RawMessage(b), dt -} \ No newline at end of file +} diff --git a/backend/internal/service/pool_service.go b/backend/internal/service/pool_service.go index d1890f0..850e26f 100644 --- a/backend/internal/service/pool_service.go +++ b/backend/internal/service/pool_service.go @@ -10,7 +10,7 @@ import ( "tanabata/backend/internal/port" ) -const poolObjectType = "pool" +const poolObjectType = "pool" const poolObjectTypeID int16 = 4 // fourth row in 007_seed_data.sql object_types // PoolParams holds the fields for creating or patching a pool. @@ -238,4 +238,4 @@ func (s *PoolService) Reorder(ctx context.Context, poolID uuid.UUID, fileIDs []u return err } return s.pools.Reorder(ctx, poolID, fileIDs) -} \ No newline at end of file +} diff --git a/backend/internal/service/tag_service.go b/backend/internal/service/tag_service.go index 195d6fd..61bffee 100644 --- a/backend/internal/service/tag_service.go +++ b/backend/internal/service/tag_service.go @@ -10,15 +10,15 @@ import ( "tanabata/backend/internal/port" ) -const tagObjectType = "tag" -const tagObjectTypeID int16 = 2 // second row in 007_seed_data.sql object_types +const tagObjectType = "tag" +const tagObjectTypeID int16 = 2 // second row in 007_seed_data.sql object_types // TagParams holds the fields for creating or patching a tag. type TagParams struct { Name string Notes *string - Color *string // nil = no change; pointer to empty string = clear - CategoryID *uuid.UUID // nil = no change; Nil UUID = unassign + Color *string // nil = no change; pointer to empty string = clear + CategoryID *uuid.UUID // nil = no change; Nil UUID = unassign Metadata json.RawMessage IsPublic *bool } @@ -422,4 +422,4 @@ func (s *TagService) expandTagSet(ctx context.Context, seeds []uuid.UUID) ([]uui } return queue, nil -} \ No newline at end of file +} diff --git a/backend/internal/service/user_service.go b/backend/internal/service/user_service.go index 0123888..28c2020 100644 --- a/backend/internal/service/user_service.go +++ b/backend/internal/service/user_service.go @@ -189,4 +189,4 @@ func (s *UserService) Delete(ctx context.Context, id int16) error { } _ = s.audit.Log(ctx, "user_delete", nil, nil, map[string]any{"target_user_id": id}) return nil -} \ No newline at end of file +} diff --git a/backend/internal/storage/disk.go b/backend/internal/storage/disk.go index 346a577..8cb5c07 100644 --- a/backend/internal/storage/disk.go +++ b/backend/internal/storage/disk.go @@ -5,12 +5,12 @@ import ( "bytes" "context" "fmt" + _ "golang.org/x/image/webp" // register WebP decoder "image" "image/color" + _ "image/gif" // register GIF decoder "image/jpeg" - _ "image/gif" // register GIF decoder - _ "image/png" // register PNG decoder - _ "golang.org/x/image/webp" // register WebP decoder + _ "image/png" // register PNG decoder "io" "os" "os/exec" diff --git a/frontend/.prettierignore b/frontend/.prettierignore new file mode 100644 index 0000000..b9d37c8 --- /dev/null +++ b/frontend/.prettierignore @@ -0,0 +1,11 @@ +# Build output and dependencies +.svelte-kit +build +node_modules +package-lock.json + +# Generated from openapi.yaml — formatting would be overwritten on regen +src/lib/api/schema.ts + +# Static assets (fonts, icons, manifest, robots) +static diff --git a/frontend/.prettierrc b/frontend/.prettierrc new file mode 100644 index 0000000..3f7802c --- /dev/null +++ b/frontend/.prettierrc @@ -0,0 +1,15 @@ +{ + "useTabs": true, + "singleQuote": true, + "trailingComma": "none", + "printWidth": 100, + "plugins": ["prettier-plugin-svelte"], + "overrides": [ + { + "files": "*.svelte", + "options": { + "parser": "svelte" + } + } + ] +} diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 3b222a2..47c0d29 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -15,6 +15,8 @@ "@tailwindcss/vite": "^4.2.2", "@types/node": "^25.5.2", "openapi-typescript": "^7.13.0", + "prettier": "^3.8.4", + "prettier-plugin-svelte": "^4.1.0", "svelte": "^5.54.0", "svelte-check": "^4.4.2", "tailwindcss": "^4.2.2", @@ -2210,6 +2212,36 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/prettier": { + "version": "3.8.4", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.4.tgz", + "integrity": "sha512-N2MylSdi48+5N/6S5j+maeHbUSIzzZ5uOcX5Hm4QpV8Dkb1HFjfAKTKX6yNPJQD9AhcT3ifHNB66tWTTJDi11Q==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-plugin-svelte": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/prettier-plugin-svelte/-/prettier-plugin-svelte-4.1.0.tgz", + "integrity": "sha512-YZkhA2Q9oOerFFG9tq+2f98WYT7Z2JgrybJrAyrB78jpsH9i/DdgplXemehuFPgsldetFNCcR/yCcYlDjPy94Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "prettier": "^3.0.0", + "svelte": "^5.0.0" + } + }, "node_modules/readdirp": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", diff --git a/frontend/package.json b/frontend/package.json index 36f00ee..88bba31 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -10,7 +10,9 @@ "preview": "vite preview", "prepare": "svelte-kit sync || echo ''", "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", - "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch" + "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", + "format": "prettier --write .", + "format:check": "prettier --check ." }, "devDependencies": { "@sveltejs/adapter-auto": "^7.0.0", @@ -20,6 +22,8 @@ "@tailwindcss/vite": "^4.2.2", "@types/node": "^25.5.2", "openapi-typescript": "^7.13.0", + "prettier": "^3.8.4", + "prettier-plugin-svelte": "^4.1.0", "svelte": "^5.54.0", "svelte-check": "^4.4.2", "tailwindcss": "^4.2.2", diff --git a/frontend/src/app.css b/frontend/src/app.css index 29fbb88..b6e8988 100644 --- a/frontend/src/app.css +++ b/frontend/src/app.css @@ -1,42 +1,42 @@ @import 'tailwindcss'; @theme { - --color-bg-primary: #312F45; - --color-bg-secondary: #181721; - --color-bg-elevated: #111118; - --color-accent: #9592B5; - --color-accent-hover: #7D7AA4; - --color-text-primary: #f0f0f0; - --color-text-muted: #9999AD; - --color-danger: #DB6060; - --color-info: #4DC7ED; - --color-warning: #F5E872; - --color-tag-default: #444455; - --color-nav-bg: rgba(0, 0, 0, 0.45); - --color-nav-active: rgba(52, 50, 73, 0.72); + --color-bg-primary: #312f45; + --color-bg-secondary: #181721; + --color-bg-elevated: #111118; + --color-accent: #9592b5; + --color-accent-hover: #7d7aa4; + --color-text-primary: #f0f0f0; + --color-text-muted: #9999ad; + --color-danger: #db6060; + --color-info: #4dc7ed; + --color-warning: #f5e872; + --color-tag-default: #444455; + --color-nav-bg: rgba(0, 0, 0, 0.45); + --color-nav-active: rgba(52, 50, 73, 0.72); - --font-sans: 'Epilogue', sans-serif; + --font-sans: 'Epilogue', sans-serif; } -:root[data-theme="light"] { - /* Muted, faintly lavender-tinted surfaces — not a glaring near-white, the same +:root[data-theme='light'] { + /* Muted, faintly lavender-tinted surfaces — not a glaring near-white, the same way the dark theme's background isn't pure black. Page sits on the dimmest surface; sheets are brighter to pop, chips a touch darker for definition. */ - --color-bg-primary: #e4e2ec; - --color-bg-secondary: #f2f1f6; - --color-bg-elevated: #d8d6e2; - --color-accent: #6B68A0; - --color-accent-hover: #5A578F; - --color-text-primary: #111118; - --color-text-muted: #555566; - --color-tag-default: #cbcad9; - --color-nav-bg: rgba(228, 226, 236, 0.85); - --color-nav-active: rgba(90, 87, 143, 0.22); + --color-bg-primary: #e4e2ec; + --color-bg-secondary: #f2f1f6; + --color-bg-elevated: #d8d6e2; + --color-accent: #6b68a0; + --color-accent-hover: #5a578f; + --color-text-primary: #111118; + --color-text-muted: #555566; + --color-tag-default: #cbcad9; + --color-nav-bg: rgba(228, 226, 236, 0.85); + --color-nav-active: rgba(90, 87, 143, 0.22); } @font-face { - font-family: 'Epilogue'; - src: url('/fonts/Epilogue-VariableFont_wght.ttf') format('truetype'); - font-weight: 100 900; - font-display: swap; + font-family: 'Epilogue'; + src: url('/fonts/Epilogue-VariableFont_wght.ttf') format('truetype'); + font-weight: 100 900; + font-display: swap; } diff --git a/frontend/src/app.html b/frontend/src/app.html index 8ed6dab..eb0bf1c 100644 --- a/frontend/src/app.html +++ b/frontend/src/app.html @@ -16,16 +16,22 @@ - - - - + + + + - + %sveltekit.head%
diff --git a/frontend/src/lib/api/auth.ts b/frontend/src/lib/api/auth.ts index 530a870..b461956 100644 --- a/frontend/src/lib/api/auth.ts +++ b/frontend/src/lib/api/auth.ts @@ -8,7 +8,7 @@ export async function login(name: string, password: string): Promise{error}
{:else}| Time | -User | -Action | -Object | -ID | -|||||
|---|---|---|---|---|---|---|---|---|---|
| {formatTs(e.performed_at)} | -{e.user_name ?? '—'} | -- - {actionLabel(e.action)} - - | -{e.object_type ?? '—'} | -{shortId(e.object_id)} | +Time | +User | +Action | +Object | +ID |
|---|---|---|---|---|---|---|---|---|---|
| {formatTs(e.performed_at)} | +{e.user_name ?? '—'} | ++ + {actionLabel(e.action)} + + | +{e.object_type ?? '—'} | +{shortId(e.object_id)} | +|||||
| - - | -|||||||||
| + + | +|||||||||
| No entries match the current filters. | -|||||||||
{createError}
{/if}Can upload files and create tags, pools, categories.
Blocked users cannot log in.
{saveError}
{/if} -