From 31a09a90746ad339363dffbe617537411c98f6cc Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Wed, 14 Feb 2024 22:03:45 +0800 Subject: [PATCH] :sparkles: Better categories --- pkg/server/categories_api.go | 89 ++++++++ pkg/server/realms_api.go | 2 +- pkg/server/startup.go | 12 +- pkg/services/categories.go | 59 ++++- pkg/services/posts.go | 11 +- .../src/components/posts/PostEditActions.tsx | 202 +++++++++++------- settings.toml | 4 +- 7 files changed, 283 insertions(+), 96 deletions(-) create mode 100644 pkg/server/categories_api.go diff --git a/pkg/server/categories_api.go b/pkg/server/categories_api.go new file mode 100644 index 0000000..c5c00db --- /dev/null +++ b/pkg/server/categories_api.go @@ -0,0 +1,89 @@ +package server + +import ( + "code.smartsheep.studio/hydrogen/interactive/pkg/models" + "code.smartsheep.studio/hydrogen/interactive/pkg/services" + "github.com/gofiber/fiber/v2" +) + +func listCategroies(c *fiber.Ctx) error { + categories, err := services.ListCategory() + if err != nil { + return fiber.NewError(fiber.StatusNotFound, err.Error()) + } + + return c.JSON(categories) +} + +func newCategory(c *fiber.Ctx) error { + user := c.Locals("principal").(models.Account) + if user.PowerLevel <= 55 { + return fiber.NewError(fiber.StatusForbidden, "require power level 55 to create categories") + } + + var data struct { + Alias string `json:"alias" validate:"required"` + Name string `json:"name" validate:"required"` + Description string `json:"description"` + } + + if err := BindAndValidate(c, &data); err != nil { + return err + } + + category, err := services.NewCategory(data.Alias, data.Name, data.Description) + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } + + return c.JSON(category) +} + +func editCategory(c *fiber.Ctx) error { + user := c.Locals("principal").(models.Account) + if user.PowerLevel <= 55 { + return fiber.NewError(fiber.StatusForbidden, "require power level 55 to edit categories") + } + + id, _ := c.ParamsInt("categoryId", 0) + category, err := services.GetCategoryWithID(uint(id)) + if err != nil { + return fiber.NewError(fiber.StatusNotFound, err.Error()) + } + + var data struct { + Alias string `json:"alias" validate:"required"` + Name string `json:"name" validate:"required"` + Description string `json:"description"` + } + + if err := BindAndValidate(c, &data); err != nil { + return err + } + + category, err = services.EditCategory(category, data.Alias, data.Name, data.Description) + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } + + return c.JSON(category) +} + +func deleteCategory(c *fiber.Ctx) error { + user := c.Locals("principal").(models.Account) + if user.PowerLevel <= 55 { + return fiber.NewError(fiber.StatusForbidden, "require power level 55 to delete categories") + } + + id, _ := c.ParamsInt("categoryId", 0) + category, err := services.GetCategoryWithID(uint(id)) + if err != nil { + return fiber.NewError(fiber.StatusNotFound, err.Error()) + } + + if err := services.DeleteCategory(category); err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } + + return c.JSON(category) +} diff --git a/pkg/server/realms_api.go b/pkg/server/realms_api.go index a269d23..649f4f8 100644 --- a/pkg/server/realms_api.go +++ b/pkg/server/realms_api.go @@ -54,7 +54,7 @@ func listAvailableRealm(c *fiber.Ctx) error { func createRealm(c *fiber.Ctx) error { user := c.Locals("principal").(models.Account) if user.PowerLevel < 10 { - return fiber.NewError(fiber.StatusForbidden, "require power level 10 to create realm") + return fiber.NewError(fiber.StatusForbidden, "require power level 10 to create realms") } var data struct { diff --git a/pkg/server/startup.go b/pkg/server/startup.go index 800351e..9f1b9e9 100644 --- a/pkg/server/startup.go +++ b/pkg/server/startup.go @@ -1,6 +1,10 @@ package server import ( + "net/http" + "strings" + "time" + "code.smartsheep.studio/hydrogen/interactive/pkg/view" "github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2/middleware/cache" @@ -11,9 +15,6 @@ import ( jsoniter "github.com/json-iterator/go" "github.com/rs/zerolog/log" "github.com/spf13/viper" - "net/http" - "strings" - "time" ) var A *fiber.App @@ -76,6 +77,11 @@ func NewServer() { api.Put("/posts/:postId", auth, editPost) api.Delete("/posts/:postId", auth, deletePost) + api.Get("/categories", listCategroies) + api.Post("/categories", auth, newCategory) + api.Put("/categories/:categoryId", auth, editCategory) + api.Delete("/categories/:categoryId", auth, deleteCategory) + api.Get("/creators/posts", auth, listOwnPost) api.Get("/creators/posts/:postId", auth, getOwnPost) diff --git a/pkg/services/categories.go b/pkg/services/categories.go index 416d6fa..a87bbe5 100644 --- a/pkg/services/categories.go +++ b/pkg/services/categories.go @@ -1,29 +1,66 @@ package services import ( + "errors" + "code.smartsheep.studio/hydrogen/interactive/pkg/database" "code.smartsheep.studio/hydrogen/interactive/pkg/models" - "errors" "gorm.io/gorm" ) -func GetCategory(alias, name string) (models.Category, error) { +func ListCategory() ([]models.Category, error) { + var categories []models.Category + + err := database.C.Find(&categories).Error + + return categories, err +} + +func GetCategory(alias string) (models.Category, error) { var category models.Category if err := database.C.Where(models.Category{Alias: alias}).First(&category).Error; err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - category = models.Category{ - Alias: alias, - Name: name, - } - err := database.C.Save(&category).Error - return category, err - } return category, err } return category, nil } -func GetTag(alias, name string) (models.Tag, error) { +func GetCategoryWithID(id uint) (models.Category, error) { + var category models.Category + if err := database.C.Where(models.Category{ + BaseModel: models.BaseModel{ID: id}, + }).First(&category).Error; err != nil { + return category, err + } + return category, nil +} + +func NewCategory(alias, name, description string) (models.Category, error) { + category := models.Category{ + Alias: alias, + Name: name, + Description: description, + } + + err := database.C.Save(&category).Error + + return category, err +} + +func EditCategory(category models.Category, alias, name, description string) (models.Category, error) { + category.Alias = alias + category.Name = name + category.Description = description + + err := database.C.Save(&category).Error + + return category, err +} + +func DeleteCategory(category models.Category) error { + return database.C.Delete(category).Error +} + +func GetTagOrCreate(alias, name string) (models.Tag, error) { var tag models.Tag if err := database.C.Where(models.Category{Alias: alias}).First(&tag).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { diff --git a/pkg/services/posts.go b/pkg/services/posts.go index 25a66e0..d525424 100644 --- a/pkg/services/posts.go +++ b/pkg/services/posts.go @@ -3,9 +3,10 @@ package services import ( "errors" "fmt" + "time" + "github.com/gofiber/fiber/v2" "github.com/rs/zerolog/log" - "time" "code.smartsheep.studio/hydrogen/interactive/pkg/database" "code.smartsheep.studio/hydrogen/interactive/pkg/models" @@ -141,13 +142,13 @@ func NewPost( var err error var post models.Post for idx, category := range categories { - categories[idx], err = GetCategory(category.Alias, category.Name) + categories[idx], err = GetCategory(category.Alias) if err != nil { return post, err } } for idx, tag := range tags { - tags[idx], err = GetTag(tag.Alias, tag.Name) + tags[idx], err = GetTagOrCreate(tag.Alias, tag.Name) if err != nil { return post, err } @@ -248,13 +249,13 @@ func EditPost( ) (models.Post, error) { var err error for idx, category := range categories { - categories[idx], err = GetCategory(category.Alias, category.Name) + categories[idx], err = GetCategory(category.Alias) if err != nil { return post, err } } for idx, tag := range tags { - tags[idx], err = GetTag(tag.Alias, tag.Name) + tags[idx], err = GetTagOrCreate(tag.Alias, tag.Name) if err != nil { return post, err } diff --git a/pkg/view/src/components/posts/PostEditActions.tsx b/pkg/view/src/components/posts/PostEditActions.tsx index 205ced6..a9bb6b2 100644 --- a/pkg/view/src/components/posts/PostEditActions.tsx +++ b/pkg/view/src/components/posts/PostEditActions.tsx @@ -5,24 +5,34 @@ import { getAtk, useUserinfo } from "../../stores/userinfo.tsx"; import styles from "./PostPublish.module.css"; export default function PostEditActions(props: { - editing?: any, - onInputAlias: (value: string) => void, - onInputPublish: (value: string) => void, - onInputAttachments: (value: any[]) => void, - onInputCategories: (categories: any[]) => void, - onInputTags: (tags: any[]) => void, - onError: (message: string | null) => void, + editing?: any; + onInputAlias: (value: string) => void; + onInputPublish: (value: string) => void; + onInputAttachments: (value: any[]) => void; + onInputCategories: (categories: any[]) => void; + onInputTags: (tags: any[]) => void; + onError: (message: string | null) => void; }) { const userinfo = useUserinfo(); const [uploading, setUploading] = createSignal(false); const [attachments, setAttachments] = createSignal(props.editing?.attachments ?? []); - const [categories, setCategories] = createSignal<{ alias: string, name: string }[]>(props.editing?.categories ?? []); - const [tags, setTags] = createSignal<{ alias: string, name: string }[]>(props.editing?.tags ?? []); + const [categories, setCategories] = createSignal<{ alias: string; name: string }[]>(props.editing?.categories ?? []); + const [tags, setTags] = createSignal<{ alias: string; name: string }[]>(props.editing?.tags ?? []); + const [availableCategories, setAvailableCategories] = createSignal([]); const [attachmentMode, setAttachmentMode] = createSignal(0); + async function readCategories() { + const res = await fetch("/api/categories"); + if (res.status === 200) { + setAvailableCategories(await res.json()); + } + } + + readCategories(); + async function uploadAttachment(evt: SubmitEvent) { evt.preventDefault(); @@ -33,8 +43,8 @@ export default function PostEditActions(props: { setUploading(true); const res = await fetch("/api/attachments", { method: "POST", - headers: { "Authorization": `Bearer ${getAtk()}` }, - body: data + headers: { Authorization: `Bearer ${getAtk()}` }, + body: data, }); if (res.status !== 200) { props.onError(await res.text()); @@ -54,10 +64,14 @@ export default function PostEditActions(props: { const form = evt.target as HTMLFormElement; const data = Object.fromEntries(new FormData(form)); - setAttachments(attachments().concat([{ - ...data, - author_id: userinfo?.profiles?.id - }])); + setAttachments( + attachments().concat([ + { + ...data, + author_id: userinfo?.profiles?.id, + }, + ]), + ); props.onInputAttachments(attachments()); form.reset(); } @@ -74,10 +88,11 @@ export default function PostEditActions(props: { const form = evt.target as HTMLFormElement; const data = Object.fromEntries(new FormData(form)); - if (!data.alias) data.alias = crypto.randomUUID().replace(/-/g, ""); - if (!data.name) return; + if (!data.category) return; - setCategories(categories().concat([data as any])); + const item = availableCategories().find((item) => item.alias === data.category); + + setCategories(categories().concat([item])); props.onInputCategories(categories()); form.reset(); } @@ -134,19 +149,21 @@ export default function PostEditActions(props: { Alias props.onInputAlias(evt.target.value)} />
- - Leave blank to generate a random string. - + Leave blank to generate a random string.
@@ -159,7 +176,8 @@ export default function PostEditActions(props: { Published At
- Before this time, your post will not be visible for everyone. - You can modify this plan on Creator Hub. + Before this time, your post will not be visible for everyone. You can modify this plan on Creator Hub.
@@ -183,10 +202,24 @@ export default function PostEditActions(props: {

Attachments

- setAttachmentMode(0)} /> - setAttachmentMode(1)} /> + setAttachmentMode(0)} + /> + setAttachmentMode(1)} + />
@@ -197,8 +230,12 @@ export default function PostEditActions(props: { Pick a file
- + @@ -216,14 +253,29 @@ export default function PostEditActions(props: { Attach an external file
- - + +
- + @@ -240,19 +292,23 @@ export default function PostEditActions(props: {

Attachment list

    - {(item, idx) =>
  1. - - {item.filename} - -
  2. } + {(item, idx) => ( +
  3. + + {item.filename} + +
  4. + )}
@@ -266,18 +322,13 @@ export default function PostEditActions(props: { Add a category
- - +
-
- - Alias is the url key of this category. Lowercase only, required length 4-24. - Leave blank for auto generate. - -
@@ -285,13 +336,15 @@ export default function PostEditActions(props: {

Category list

    - {(item, idx) =>
  1. - - {item.name} #{item.alias} - -
  2. } + {(item, idx) => ( +
  3. + + {item.name} #{item.alias} + +
  4. + )}
@@ -310,8 +363,7 @@ export default function PostEditActions(props: {
- Alias is the url key of this tag. Lowercase only, required length 4-24. - Leave blank for auto generate. + Alias is the url key of this tag. Lowercase only, required length 4-24. Leave blank for auto generate.
@@ -321,22 +373,26 @@ export default function PostEditActions(props: {

Category list

    - {(item, idx) =>
  1. - - {item.name} #{item.alias} - -
  2. } + {(item, idx) => ( +
  3. + + {item.name} #{item.alias} + +
  4. + )}
); -} \ No newline at end of file +} diff --git a/settings.toml b/settings.toml index 9795e14..793715d 100644 --- a/settings.toml +++ b/settings.toml @@ -1,4 +1,4 @@ -debug = true +debug = false name = "Goatplaza" maintainer = "SmartSheep Studio" @@ -9,8 +9,6 @@ secret = "LtTjzAGFLshwXhN4ZD4nG5KlMv1MWcsvfv03TSZYnT1VhiAnLIZFTnHUwR0XhGgi" content = "uploads" -everyone_postable = false - [passport] client_id = "goatplaza" client_secret = "Z9k9AFTj^p"