From a5d6071bef7962c2cdefab68e0511d28a8572091 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Sun, 11 Feb 2024 13:12:37 +0800 Subject: [PATCH] :sparkles: Creator hub --- .DS_Store | Bin 0 -> 6148 bytes .idea/codeStyles/Project.xml | 57 ++++ .idea/codeStyles/codeStyleConfig.xml | 5 + .idea/dataSources.xml | 12 + .idea/inspectionProfiles/Project_Default.xml | 11 + .idea/sqldialects.xml | 6 + pkg/.DS_Store | Bin 0 -> 6148 bytes pkg/models/posts.go | 1 + pkg/server/creators_api.go | 95 ++++++ pkg/server/posts_api.go | 2 +- pkg/server/realms_api.go | 11 + pkg/server/startup.go | 4 + pkg/services/realms.go | 23 ++ pkg/view/package.json | 1 + pkg/view/src/components/LoadingAnimation.tsx | 8 + .../{ => posts}/PostAttachments.module.css | 0 .../{ => posts}/PostAttachments.tsx | 0 .../PostEditActions.tsx} | 304 ++++-------------- pkg/view/src/components/posts/PostEditor.tsx | 210 ++++++++++++ .../src/components/{ => posts}/PostItem.tsx | 2 +- .../{ => posts}/PostList.module.css | 0 .../src/components/{ => posts}/PostList.tsx | 8 +- .../{ => posts}/PostPublish.module.css | 0 pkg/view/src/components/posts/PostPublish.tsx | 212 ++++++++++++ pkg/view/src/index.css | 14 + pkg/view/src/index.tsx | 10 +- pkg/view/src/layouts/RootLayout.tsx | 2 +- pkg/view/src/layouts/shared/Navbar.tsx | 1 + pkg/view/src/pages/account.tsx | 5 +- pkg/view/src/pages/creators/edit.tsx | 57 ++++ pkg/view/src/pages/creators/index.tsx | 120 +++++++ pkg/view/src/pages/creators/publish.tsx | 40 +++ pkg/view/src/pages/creators/view.module.css | 13 + pkg/view/src/pages/creators/view.tsx | 16 + pkg/view/src/pages/global.tsx | 4 +- pkg/view/src/pages/post.tsx | 6 +- pkg/view/src/pages/realms/realm.tsx | 4 +- pkg/view/src/pages/search.tsx | 4 +- pkg/view/src/pages/view.tsx | 4 +- 39 files changed, 1015 insertions(+), 257 deletions(-) create mode 100644 .DS_Store create mode 100644 .idea/codeStyles/Project.xml create mode 100644 .idea/codeStyles/codeStyleConfig.xml create mode 100644 .idea/dataSources.xml create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 .idea/sqldialects.xml create mode 100644 pkg/.DS_Store create mode 100644 pkg/server/creators_api.go create mode 100644 pkg/view/src/components/LoadingAnimation.tsx rename pkg/view/src/components/{ => posts}/PostAttachments.module.css (100%) rename pkg/view/src/components/{ => posts}/PostAttachments.tsx (100%) rename pkg/view/src/components/{PostPublish.tsx => posts/PostEditActions.tsx} (52%) create mode 100644 pkg/view/src/components/posts/PostEditor.tsx rename pkg/view/src/components/{ => posts}/PostItem.tsx (99%) rename pkg/view/src/components/{ => posts}/PostList.module.css (100%) rename pkg/view/src/components/{ => posts}/PostList.tsx (91%) rename pkg/view/src/components/{ => posts}/PostPublish.module.css (100%) create mode 100644 pkg/view/src/components/posts/PostPublish.tsx create mode 100644 pkg/view/src/pages/creators/edit.tsx create mode 100644 pkg/view/src/pages/creators/index.tsx create mode 100644 pkg/view/src/pages/creators/publish.tsx create mode 100644 pkg/view/src/pages/creators/view.module.css create mode 100644 pkg/view/src/pages/creators/view.tsx diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..c22897c38d69da32a0bbb3c183862a37d7faf041 GIT binary patch literal 6148 zcmeHK%}T>S5T0$TO({YT3Oz1(E!ZC^ikDF93mDOZN=-87tCSksWogDxWooUzrr~rV2N|-C*@Qu(u>4xO2rGO~t8R;m9Re-Cv zV6@@QhQG)FeY-r2U z2g|u1Tu1#`ud;uxl8GNB{jm;+!alm(-9$;KW*s$6!c@n4X2U5trCy~vpEpn2HQ738 zENXIo+N{^)ajU&pl$?XZqqEE2Fn&tZi=j~9-=<~H;u2nQVzYVn#!0M_0b0nr2vf{5 z1_eHZkZwt?Ygw)3ykv!u8DIvOfprG#De9EhH+anqFa!Ud0XiQfDxqsJGpLUa9CQnS zNY_X$IHz8MVx&daVrCF`P=pCZG@-(_7{Y|3U0OfaVrI~UgRsqqux}Q&LlOGz_ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..79ee123 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml new file mode 100644 index 0000000..b6f0309 --- /dev/null +++ b/.idea/dataSources.xml @@ -0,0 +1,12 @@ + + + + + postgresql + true + org.postgresql.Driver + jdbc:postgresql://localhost:5432/hy_interactive + $ProjectFileDir$ + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..8029dc3 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,11 @@ + + + + \ No newline at end of file diff --git a/.idea/sqldialects.xml b/.idea/sqldialects.xml new file mode 100644 index 0000000..ad50001 --- /dev/null +++ b/.idea/sqldialects.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/pkg/.DS_Store b/pkg/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..7a0810886d55587af36646b9626118305611eab2 GIT binary patch literal 6148 zcmeH~K?=e^3`G;|qTr@Wm$UHz-e3?tK`-D!5kV>l>UNIqPbLUf*CO%*$)98o3FUJRDH7`XtWU8s5ohKej0(KmsH{0wh2J zKSaRpZCE?2%18nvKmtz!_I*fj(;Ql=`lkcIM*wJpvK!VuOF)wqpgFWuMFplYJ!rJ5 zk0Dm~c4&%oIkZ%*?V>S!XgpbMih*fu7fncDnq3%3fCNSarZw;E{@=hq&HtkorX)ZD ze?~x?b-!NWrQ&XVdp)afqiX8{hx&1Zm!AM6b`{UzZrD$@facIr6%`nN1RMhc34E2n E1Dk&m&Hw-a literal 0 HcmV?d00001 diff --git a/pkg/models/posts.go b/pkg/models/posts.go index 035dbbb..02cd576 100644 --- a/pkg/models/posts.go +++ b/pkg/models/posts.go @@ -5,6 +5,7 @@ import "time" type Post struct { BaseModel + // TODO Introduce thumbnail Alias string `json:"alias" gorm:"uniqueIndex"` Title string `json:"title"` Content string `json:"content"` diff --git a/pkg/server/creators_api.go b/pkg/server/creators_api.go new file mode 100644 index 0000000..47f2fbe --- /dev/null +++ b/pkg/server/creators_api.go @@ -0,0 +1,95 @@ +package server + +import ( + "code.smartsheep.studio/hydrogen/interactive/pkg/database" + "code.smartsheep.studio/hydrogen/interactive/pkg/models" + "code.smartsheep.studio/hydrogen/interactive/pkg/services" + "github.com/gofiber/fiber/v2" + "github.com/samber/lo" + "time" +) + +func getOwnPost(c *fiber.Ctx) error { + user := c.Locals("principal").(models.Account) + + id := c.Params("postId") + take := c.QueryInt("take", 0) + offset := c.QueryInt("offset", 0) + + tx := database.C.Where(&models.Post{ + Alias: id, + AuthorID: user.ID, + }) + + post, err := services.GetPost(tx) + if err != nil { + return fiber.NewError(fiber.StatusNotFound, err.Error()) + } + + tx = database.C. + Where(&models.Post{ReplyID: &post.ID}). + Where("published_at <= ? OR published_at IS NULL", time.Now()). + Order("created_at desc") + + var count int64 + if err := tx. + Model(&models.Post{}). + Count(&count).Error; err != nil { + return fiber.NewError(fiber.StatusInternalServerError, err.Error()) + } + + posts, err := services.ListPost(tx, take, offset) + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } + + return c.JSON(fiber.Map{ + "data": post, + "count": count, + "related": posts, + }) +} + +func listOwnPost(c *fiber.Ctx) error { + take := c.QueryInt("take", 0) + offset := c.QueryInt("offset", 0) + realmId := c.QueryInt("realmId", 0) + + user := c.Locals("principal").(models.Account) + + tx := database.C. + Where(&models.Post{AuthorID: user.ID}). + Where("published_at <= ? OR published_at IS NULL", time.Now()). + Order("created_at desc") + + if realmId > 0 { + tx = tx.Where(&models.Post{RealmID: lo.ToPtr(uint(realmId))}) + } else { + tx = tx.Where("realm_id IS NULL") + } + + if len(c.Query("category")) > 0 { + tx = services.FilterPostWithCategory(tx, c.Query("category")) + } + + if len(c.Query("tag")) > 0 { + tx = services.FilterPostWithTag(tx, c.Query("tag")) + } + + var count int64 + if err := tx. + Model(&models.Post{}). + Count(&count).Error; err != nil { + return fiber.NewError(fiber.StatusInternalServerError, err.Error()) + } + + posts, err := services.ListPost(tx, take, offset) + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } + + return c.JSON(fiber.Map{ + "count": count, + "data": posts, + }) +} diff --git a/pkg/server/posts_api.go b/pkg/server/posts_api.go index 5759d2d..6b45ddf 100644 --- a/pkg/server/posts_api.go +++ b/pkg/server/posts_api.go @@ -19,7 +19,7 @@ func getPost(c *fiber.Ctx) error { tx := database.C.Where(&models.Post{ Alias: id, - }) + }).Where("published_at <= ? OR published_at IS NULL", time.Now()) post, err := services.GetPost(tx) if err != nil { diff --git a/pkg/server/realms_api.go b/pkg/server/realms_api.go index dee15ec..a269d23 100644 --- a/pkg/server/realms_api.go +++ b/pkg/server/realms_api.go @@ -40,6 +40,17 @@ func listOwnedRealm(c *fiber.Ctx) error { return c.JSON(realms) } +func listAvailableRealm(c *fiber.Ctx) error { + user := c.Locals("principal").(models.Account) + + realms, err := services.ListRealmIsAvailable(user) + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } + + return c.JSON(realms) +} + func createRealm(c *fiber.Ctx) error { user := c.Locals("principal").(models.Account) if user.PowerLevel < 10 { diff --git a/pkg/server/startup.go b/pkg/server/startup.go index 9d56f0f..800351e 100644 --- a/pkg/server/startup.go +++ b/pkg/server/startup.go @@ -76,8 +76,12 @@ func NewServer() { api.Put("/posts/:postId", auth, editPost) api.Delete("/posts/:postId", auth, deletePost) + api.Get("/creators/posts", auth, listOwnPost) + api.Get("/creators/posts/:postId", auth, getOwnPost) + api.Get("/realms", listRealm) api.Get("/realms/me", auth, listOwnedRealm) + api.Get("/realms/me/available", auth, listAvailableRealm) api.Get("/realms/:realmId", getRealm) api.Post("/realms", auth, createRealm) api.Post("/realms/:realmId/invite", auth, inviteRealm) diff --git a/pkg/services/realms.go b/pkg/services/realms.go index 43e2be7..3f74d2b 100644 --- a/pkg/services/realms.go +++ b/pkg/services/realms.go @@ -3,6 +3,7 @@ package services import ( "code.smartsheep.studio/hydrogen/interactive/pkg/database" "code.smartsheep.studio/hydrogen/interactive/pkg/models" + "github.com/samber/lo" ) func ListRealm() ([]models.Realm, error) { @@ -23,6 +24,28 @@ func ListRealmWithUser(user models.Account) ([]models.Realm, error) { return realms, nil } +func ListRealmIsAvailable(user models.Account) ([]models.Realm, error) { + var realms []models.Realm + var members []models.RealmMember + if err := database.C.Where(&models.RealmMember{ + AccountID: user.ID, + }).Find(&members).Error; err != nil { + return realms, err + } + + idx := lo.Map(members, func(item models.RealmMember, index int) uint { + return item.RealmID + }) + + if err := database.C.Where(&models.Realm{ + IsPublic: true, + }).Or("id IN ?", idx).Find(&realms).Error; err != nil { + return realms, err + } + + return realms, nil +} + func NewRealm(user models.Account, name, description string, isPublic bool) (models.Realm, error) { realm := models.Realm{ Name: name, diff --git a/pkg/view/package.json b/pkg/view/package.json index 62e620c..112e107 100644 --- a/pkg/view/package.json +++ b/pkg/view/package.json @@ -12,6 +12,7 @@ "@fortawesome/fontawesome-free": "^6.5.1", "@solidjs/router": "^0.10.10", "artplayer": "^5.1.1", + "cherry-markdown": "^0.8.38", "dompurify": "^3.0.8", "flv.js": "^1.6.2", "hls.js": "^1.5.3", diff --git a/pkg/view/src/components/LoadingAnimation.tsx b/pkg/view/src/components/LoadingAnimation.tsx new file mode 100644 index 0000000..ea2dd8f --- /dev/null +++ b/pkg/view/src/components/LoadingAnimation.tsx @@ -0,0 +1,8 @@ +export default function LoadingAnimation() { + return ( +
+

+

Listening to the latest news...

+
+ ) +} \ No newline at end of file diff --git a/pkg/view/src/components/PostAttachments.module.css b/pkg/view/src/components/posts/PostAttachments.module.css similarity index 100% rename from pkg/view/src/components/PostAttachments.module.css rename to pkg/view/src/components/posts/PostAttachments.module.css diff --git a/pkg/view/src/components/PostAttachments.tsx b/pkg/view/src/components/posts/PostAttachments.tsx similarity index 100% rename from pkg/view/src/components/PostAttachments.tsx rename to pkg/view/src/components/posts/PostAttachments.tsx diff --git a/pkg/view/src/components/PostPublish.tsx b/pkg/view/src/components/posts/PostEditActions.tsx similarity index 52% rename from pkg/view/src/components/PostPublish.tsx rename to pkg/view/src/components/posts/PostEditActions.tsx index 8ee090c..c914073 100644 --- a/pkg/view/src/components/PostPublish.tsx +++ b/pkg/view/src/components/posts/PostEditActions.tsx @@ -1,119 +1,28 @@ -import { createEffect, createSignal, For, Match, Show, Switch } from "solid-js"; -import { getAtk, useUserinfo } from "../stores/userinfo.tsx"; +import { closeModel, openModel } from "../../scripts/modals.ts"; +import { createSignal, For, Match, Show, Switch } from "solid-js"; +import { getAtk, useUserinfo } from "../../stores/userinfo.tsx"; import styles from "./PostPublish.module.css"; -import { closeModel, openModel } from "../scripts/modals.ts"; -export default function PostPublish(props: { - replying?: any, - reposting?: any, +export default function PostEditActions(props: { editing?: any, - realmId?: number, - onReset: () => void, + 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, - onPost: () => void }) { - const userinfo = useUserinfo(); + const userinfo = useUserinfo() - if (!userinfo?.isLoggedIn) { - return ( -
-
-

Login!

-

Or keep silent.

-
-
- ); - } - - const [submitting, setSubmitting] = createSignal(false); const [uploading, setUploading] = createSignal(false); - const [attachments, setAttachments] = createSignal([]); - const [categories, setCategories] = createSignal<{ alias: string, name: string }[]>([]); - const [tags, setTags] = createSignal<{ alias: string, name: string }[]>([]); + 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 [attachmentMode, setAttachmentMode] = createSignal(0); - createEffect(() => { - setAttachments(props.editing?.attachments ?? []); - setCategories(props.editing?.categories ?? []); - setTags(props.editing?.tags ?? []); - }, [props.editing]); - - async function doPost(evt: SubmitEvent) { - evt.preventDefault(); - - const form = evt.target as HTMLFormElement; - const data = Object.fromEntries(new FormData(form)); - if (!data.content) return; - - setSubmitting(true); - const res = await fetch("/api/posts", { - method: "POST", - headers: { - "Content-Type": "application/json", - "Authorization": `Bearer ${getAtk()}` - }, - body: JSON.stringify({ - alias: data.alias ?? crypto.randomUUID().replace(/-/g, ""), - title: data.title, - content: data.content, - attachments: attachments(), - categories: categories(), - tags: tags(), - realm_id: data.publish_in_realm ? props.realmId : undefined, - published_at: data.published_at ? new Date(data.published_at as string) : new Date(), - repost_to: props.reposting?.id, - reply_to: props.replying?.id - }) - }); - if (res.status !== 200) { - props.onError(await res.text()); - } else { - form.reset(); - props.onError(null); - props.onPost(); - } - setSubmitting(false); - } - - async function doEdit(evt: SubmitEvent) { - evt.preventDefault(); - - const form = evt.target as HTMLFormElement; - const data = Object.fromEntries(new FormData(form)); - if (!data.content) return; - if (uploading()) return; - - setSubmitting(true); - const res = await fetch(`/api/posts/${props.editing?.id}`, { - method: "PUT", - headers: { - "Content-Type": "application/json", - "Authorization": `Bearer ${getAtk()}` - }, - body: JSON.stringify({ - alias: data.alias ?? crypto.randomUUID().replace(/-/g, ""), - title: data.title, - content: data.content, - attachments: attachments(), - categories: categories(), - tags: tags(), - realm_id: props.realmId, - published_at: data.published_at ? new Date(data.published_at as string) : new Date() - }) - }); - if (res.status !== 200) { - props.onError(await res.text()); - } else { - form.reset(); - props.onError(null); - props.onPost(); - } - setSubmitting(false); - } - async function uploadAttachment(evt: SubmitEvent) { evt.preventDefault(); @@ -148,6 +57,7 @@ export default function PostPublish(props: { ...data, author_id: userinfo?.profiles?.id }])); + props.onInputCategories(categories()) form.reset(); } @@ -160,6 +70,7 @@ export default function PostPublish(props: { if (!data.name) return; setCategories(categories().concat([data as any])); + props.onInputCategories(categories()) form.reset(); } @@ -176,156 +87,83 @@ export default function PostPublish(props: { if (!data.name) return; setTags(tags().concat([data as any])); + props.onInputTags(tags()) form.reset(); } function removeTag(target: any) { setTags(tags().filter(item => item.alias !== target.alias)); - } - - function resetForm() { - setAttachments([]); - setCategories([]); - setTags([]); - props.onReset(); + props.onInputTags(tags()) } return ( <> -
(props.editing ? doEdit : doPost)(evt)} onReset={() => resetForm()}> -
-
-
- {userinfo?.displayName.substring(0, 1)}}> - avatar - +
+ + + + +
+ + + -
- -
-
- - - - - - - - - - - - -
-
- -
-
-
- -