Address spec review feedback for categories design
Adds updated_at field, transaction requirement, slug collision handling, authorization checks, explicit query filters, pubmodel signatures, and template registration notes. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
847e8e76d0
commit
9a02a2f8af
|
|
@ -17,6 +17,7 @@ CREATE TABLE categories (
|
||||||
slug TEXT NOT NULL,
|
slug TEXT NOT NULL,
|
||||||
description TEXT NOT NULL DEFAULT '',
|
description TEXT NOT NULL DEFAULT '',
|
||||||
created_at INTEGER NOT NULL,
|
created_at INTEGER NOT NULL,
|
||||||
|
updated_at INTEGER NOT NULL,
|
||||||
FOREIGN KEY (site_id) REFERENCES sites (id) ON DELETE CASCADE
|
FOREIGN KEY (site_id) REFERENCES sites (id) ON DELETE CASCADE
|
||||||
);
|
);
|
||||||
CREATE INDEX idx_categories_site ON categories (site_id);
|
CREATE INDEX idx_categories_site ON categories (site_id);
|
||||||
|
|
@ -44,11 +45,13 @@ type Category struct {
|
||||||
Slug string `json:"slug"`
|
Slug string `json:"slug"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- `slug` is auto-generated from `name` (e.g. "Go Programming" -> `go-programming`), editable by the user.
|
- `slug` is auto-generated from `name` (e.g. "Go Programming" -> `go-programming`), editable by the user.
|
||||||
- `description` is Markdown, rendered on the category archive page. Defaults to empty string.
|
- `description` is Markdown, rendered on the category archive page. Defaults to empty string.
|
||||||
|
- DB provider must use the existing `timeToInt()`/`time.Unix()` helpers for timestamp conversion, consistent with how posts are handled.
|
||||||
|
|
||||||
## Admin UI
|
## Admin UI
|
||||||
|
|
||||||
|
|
@ -66,7 +69,7 @@ Templates: `views/categories/index.html`, `views/categories/edit.html`.
|
||||||
|
|
||||||
### Post Edit Form Changes
|
### Post Edit Form Changes
|
||||||
|
|
||||||
- A multi-select checkbox list of all available categories, displayed in a **right sidebar** alongside the main title/body editing area on the left.
|
- A multi-select checkbox list of all available categories (sorted alphabetically by name), displayed in a **right sidebar** alongside the main title/body editing area on the left.
|
||||||
- Selected category IDs sent with the form submission.
|
- Selected category IDs sent with the form submission.
|
||||||
- `CreatePostParams` gains `CategoryIDs []int64`.
|
- `CreatePostParams` gains `CategoryIDs []int64`.
|
||||||
|
|
||||||
|
|
@ -114,8 +117,8 @@ New file: `sql/queries/categories.sql`
|
||||||
- `SelectCategory` — single category by ID
|
- `SelectCategory` — single category by ID
|
||||||
- `SelectCategoryByGUID` — single category by GUID
|
- `SelectCategoryByGUID` — single category by GUID
|
||||||
- `SelectCategoriesOfPost` — categories for a given post (via join table)
|
- `SelectCategoriesOfPost` — categories for a given post (via join table)
|
||||||
- `SelectPostsOfCategory` — published, non-deleted posts in a category, ordered by `published_at` desc
|
- `SelectPostsOfCategory` — published, non-deleted posts in a category (`state = 0 AND deleted_at = 0`), ordered by `published_at` desc
|
||||||
- `CountPostsOfCategory` — count of published posts per category
|
- `CountPostsOfCategory` — count of published posts per category (same `state = 0 AND deleted_at = 0` filter)
|
||||||
- `InsertCategory` / `UpdateCategory` / `DeleteCategory` — CRUD
|
- `InsertCategory` / `UpdateCategory` / `DeleteCategory` — CRUD
|
||||||
- `InsertPostCategory` / `DeletePostCategory` — manage the join table
|
- `InsertPostCategory` / `DeletePostCategory` — manage the join table
|
||||||
- `DeletePostCategoriesByPost` — clear all categories for a post (delete-then-reinsert on save)
|
- `DeletePostCategoriesByPost` — clear all categories for a post (delete-then-reinsert on save)
|
||||||
|
|
@ -128,25 +131,28 @@ New file: `sql/queries/categories.sql`
|
||||||
|
|
||||||
- `ListCategories(ctx) ([]Category, error)` — all categories for the current site (from context)
|
- `ListCategories(ctx) ([]Category, error)` — all categories for the current site (from context)
|
||||||
- `GetCategory(ctx, id) (*Category, error)`
|
- `GetCategory(ctx, id) (*Category, error)`
|
||||||
- `CreateCategory(ctx, params) (*Category, error)` — auto-generates slug from name
|
- `CreateCategory(ctx, params) (*Category, error)` — auto-generates slug from name. If the slug collides with an existing one for the same site, return a validation error.
|
||||||
- `UpdateCategory(ctx, params) (*Category, error)`
|
- `UpdateCategory(ctx, params) (*Category, error)` — same slug collision check on update.
|
||||||
- `DeleteCategory(ctx, id) error` — deletes category and post associations, queues site rebuild
|
- `DeleteCategory(ctx, id) error` — deletes category and post associations, queues site rebuild
|
||||||
|
|
||||||
|
All mutation methods verify site ownership (same pattern as post service authorization checks).
|
||||||
|
|
||||||
### Changes to `services/posts`
|
### Changes to `services/posts`
|
||||||
|
|
||||||
- `UpdatePost` — after saving the post, deletes existing `post_categories` rows and re-inserts for the selected category IDs
|
- `UpdatePost` — after saving the post, deletes existing `post_categories` rows and re-inserts for the selected category IDs. The post save and category reassignment must run within a single database transaction to ensure atomicity.
|
||||||
- `GetPost` / `ListPosts` — loads each post's categories for admin display
|
- `GetPost` / `ListPosts` — loads each post's categories for admin display
|
||||||
|
|
||||||
### Changes to Publishing Pipeline
|
### Changes to Publishing Pipeline
|
||||||
|
|
||||||
- `pubmodel.Site` gains new fields:
|
- `pubmodel.Site` gains new fields:
|
||||||
- Category list (with post counts and description excerpts for the index page)
|
- `Categories []CategoryWithCount` — category list with post counts and description excerpts for the index page
|
||||||
- A function to iterate published posts by category
|
- `PostIterByCategory func(ctx context.Context, categoryID int64) iter.Seq[models.Maybe[*models.Post]]` — iterator for posts in a specific category
|
||||||
- `sitebuilder.Builder.BuildSite` gains additional goroutines for:
|
- `sitebuilder.Builder.BuildSite` gains additional goroutines for:
|
||||||
- Rendering the category index page
|
- Rendering the category index page
|
||||||
- Rendering each category archive page
|
- Rendering each category archive page
|
||||||
- Rendering per-category feeds
|
- Rendering per-category feeds
|
||||||
- New templates: `tmplNameCategoryList`, `tmplNameCategorySingle`
|
- New templates: `tmplNameCategoryList`, `tmplNameCategorySingle` (must be added to the `ParseFS` call in `sitebuilder.New()`)
|
||||||
|
- `postSingleData` gains a `Categories []Category` field so post templates can render category links
|
||||||
|
|
||||||
### Rebuild Triggers
|
### Rebuild Triggers
|
||||||
|
|
||||||
|
|
@ -155,3 +161,9 @@ Saving or deleting a category queues a site rebuild, same as post state changes.
|
||||||
## DB Provider
|
## DB Provider
|
||||||
|
|
||||||
`providers/db/` gains wrapper methods for all new sqlc queries, following the same pattern as existing post methods (e.g. `SaveCategory`, `SelectCategoriesOfPost`, etc.).
|
`providers/db/` gains wrapper methods for all new sqlc queries, following the same pattern as existing post methods (e.g. `SaveCategory`, `SelectCategoriesOfPost`, etc.).
|
||||||
|
|
||||||
|
## Design Decisions
|
||||||
|
|
||||||
|
- **Hard delete for categories** — unlike posts which use soft-delete, categories are hard-deleted. They are simpler entities and don't need a trash/restore workflow.
|
||||||
|
- **No sort_order column** — categories are sorted alphabetically by name. Manual ordering can be added later if needed.
|
||||||
|
- **Existing microblog-crosspost feed** — kept as-is. Per-category feeds are a separate, additive feature.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue