diff --git a/.DS_Store b/.DS_Store
new file mode 100644
index 0000000..c22897c
Binary files /dev/null and b/.DS_Store differ
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
new file mode 100644
index 0000000..60dc64b
--- /dev/null
+++ b/.idea/codeStyles/Project.xml
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ 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 0000000..7a08108
Binary files /dev/null and b/pkg/.DS_Store differ
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 (
<>
-