✨ Realms
This commit is contained in:
		| @@ -9,6 +9,7 @@ type Account struct { | |||||||
| 	BaseModel | 	BaseModel | ||||||
|  |  | ||||||
| 	Name          string        `json:"name"` | 	Name          string        `json:"name"` | ||||||
|  | 	Nick          string        `json:"nick"` | ||||||
| 	Avatar        string        `json:"avatar"` | 	Avatar        string        `json:"avatar"` | ||||||
| 	Description   string        `json:"description"` | 	Description   string        `json:"description"` | ||||||
| 	EmailAddress  string        `json:"email_address"` | 	EmailAddress  string        `json:"email_address"` | ||||||
|   | |||||||
							
								
								
									
										126
									
								
								pkg/server/realms_api.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										126
									
								
								pkg/server/realms_api.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,126 @@ | |||||||
|  | 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 listRealms(c *fiber.Ctx) error { | ||||||
|  | 	user := c.Locals("principal").(models.Account) | ||||||
|  |  | ||||||
|  | 	realms, err := services.ListRealms(user) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return fiber.NewError(fiber.StatusBadRequest, err.Error()) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return c.JSON(realms) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func listPostInRealm(c *fiber.Ctx) error { | ||||||
|  | 	take := c.QueryInt("take", 0) | ||||||
|  | 	offset := c.QueryInt("offset", 0) | ||||||
|  | 	authorId := c.QueryInt("authorId", 0) | ||||||
|  |  | ||||||
|  | 	realmId, _ := c.ParamsInt("realmId", 0) | ||||||
|  |  | ||||||
|  | 	tx := database.C. | ||||||
|  | 		Where(&models.Post{RealmID: lo.ToPtr(uint(realmId))}). | ||||||
|  | 		Where("published_at <= ? OR published_at IS NULL", time.Now()). | ||||||
|  | 		Order("created_at desc") | ||||||
|  |  | ||||||
|  | 	if authorId > 0 { | ||||||
|  | 		tx = tx.Where(&models.Post{AuthorID: uint(authorId)}) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	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, | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | 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") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	var data struct { | ||||||
|  | 		Name        string `json:"name" validate:"required"` | ||||||
|  | 		Description string `json:"description"` | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if err := BindAndValidate(c, &data); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	realm, err := services.NewRealm(user, data.Name, data.Description) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return fiber.NewError(fiber.StatusBadRequest, err.Error()) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return c.JSON(realm) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func editRealm(c *fiber.Ctx) error { | ||||||
|  | 	user := c.Locals("principal").(models.Account) | ||||||
|  | 	id, _ := c.ParamsInt("realmId", 0) | ||||||
|  |  | ||||||
|  | 	var data struct { | ||||||
|  | 		Name        string `json:"name" validate:"required"` | ||||||
|  | 		Description string `json:"description"` | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if err := BindAndValidate(c, &data); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	var realm models.Realm | ||||||
|  | 	if err := database.C.Where(&models.Realm{ | ||||||
|  | 		BaseModel: models.BaseModel{ID: uint(id)}, | ||||||
|  | 		AccountID: user.ID, | ||||||
|  | 	}).First(&realm).Error; err != nil { | ||||||
|  | 		return fiber.NewError(fiber.StatusNotFound, err.Error()) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	realm, err := services.EditRealm(realm, data.Name, data.Description) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return fiber.NewError(fiber.StatusBadRequest, err.Error()) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return c.JSON(realm) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func deleteRealm(c *fiber.Ctx) error { | ||||||
|  | 	user := c.Locals("principal").(models.Account) | ||||||
|  | 	id, _ := c.ParamsInt("realmId", 0) | ||||||
|  |  | ||||||
|  | 	var realm models.Realm | ||||||
|  | 	if err := database.C.Where(&models.Realm{ | ||||||
|  | 		BaseModel: models.BaseModel{ID: uint(id)}, | ||||||
|  | 		AccountID: user.ID, | ||||||
|  | 	}).First(&realm).Error; err != nil { | ||||||
|  | 		return fiber.NewError(fiber.StatusNotFound, err.Error()) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if err := services.DeleteRealm(realm); err != nil { | ||||||
|  | 		return fiber.NewError(fiber.StatusBadRequest, err.Error()) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return c.SendStatus(fiber.StatusOK) | ||||||
|  | } | ||||||
| @@ -73,6 +73,12 @@ func NewServer() { | |||||||
| 		api.Post("/posts/:postId/react/:reactType", auth, reactPost) | 		api.Post("/posts/:postId/react/:reactType", auth, reactPost) | ||||||
| 		api.Put("/posts/:postId", auth, editPost) | 		api.Put("/posts/:postId", auth, editPost) | ||||||
| 		api.Delete("/posts/:postId", auth, deletePost) | 		api.Delete("/posts/:postId", auth, deletePost) | ||||||
|  |  | ||||||
|  | 		api.Get("/realms", auth, listRealms) | ||||||
|  | 		api.Get("/realms/:realmId/posts", listPostInRealm) | ||||||
|  | 		api.Post("/realms", auth, createRealm) | ||||||
|  | 		api.Put("/realms/:realmId", auth, editRealm) | ||||||
|  | 		api.Delete("/realms/:realmId", auth, deleteRealm) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	A.Use("/", cache.New(cache.Config{ | 	A.Use("/", cache.New(cache.Config{ | ||||||
|   | |||||||
| @@ -21,11 +21,11 @@ func getUserinfo(c *fiber.Ctx) error { | |||||||
| } | } | ||||||
|  |  | ||||||
| func getOthersInfo(c *fiber.Ctx) error { | func getOthersInfo(c *fiber.Ctx) error { | ||||||
| 	accountId, _ := c.ParamsInt("accountId", 0) | 	accountId := c.Params("accountId") | ||||||
|  |  | ||||||
| 	var data models.Account | 	var data models.Account | ||||||
| 	if err := database.C. | 	if err := database.C. | ||||||
| 		Where(&models.Account{BaseModel: models.BaseModel{ID: uint(accountId)}}). | 		Where(&models.Account{Name: accountId}). | ||||||
| 		First(&data).Error; err != nil { | 		First(&data).Error; err != nil { | ||||||
| 		return fiber.NewError(fiber.StatusInternalServerError, err.Error()) | 		return fiber.NewError(fiber.StatusInternalServerError, err.Error()) | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -14,6 +14,7 @@ import ( | |||||||
|  |  | ||||||
| type PassportUserinfo struct { | type PassportUserinfo struct { | ||||||
| 	Sub               string `json:"sub"` | 	Sub               string `json:"sub"` | ||||||
|  | 	Name              string `json:"name"` | ||||||
| 	Email             string `json:"email"` | 	Email             string `json:"email"` | ||||||
| 	Picture           string `json:"picture"` | 	Picture           string `json:"picture"` | ||||||
| 	PreferredUsername string `json:"preferred_username"` | 	PreferredUsername string `json:"preferred_username"` | ||||||
| @@ -28,7 +29,8 @@ func LinkAccount(userinfo PassportUserinfo) (models.Account, error) { | |||||||
| 	}).First(&account).Error; err != nil { | 	}).First(&account).Error; err != nil { | ||||||
| 		if errors.Is(err, gorm.ErrRecordNotFound) { | 		if errors.Is(err, gorm.ErrRecordNotFound) { | ||||||
| 			account = models.Account{ | 			account = models.Account{ | ||||||
| 				Name:         userinfo.PreferredUsername, | 				Name:         userinfo.Name, | ||||||
|  | 				Nick:         userinfo.PreferredUsername, | ||||||
| 				Avatar:       userinfo.Picture, | 				Avatar:       userinfo.Picture, | ||||||
| 				EmailAddress: userinfo.Email, | 				EmailAddress: userinfo.Email, | ||||||
| 				PowerLevel:   0, | 				PowerLevel:   0, | ||||||
| @@ -39,7 +41,14 @@ func LinkAccount(userinfo PassportUserinfo) (models.Account, error) { | |||||||
| 		return account, err | 		return account, err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return account, nil | 	account.Name = userinfo.Name | ||||||
|  | 	account.Nick = userinfo.PreferredUsername | ||||||
|  | 	account.Avatar = userinfo.Picture | ||||||
|  | 	account.EmailAddress = userinfo.Email | ||||||
|  |  | ||||||
|  | 	err := database.C.Save(&account).Error | ||||||
|  |  | ||||||
|  | 	return account, err | ||||||
| } | } | ||||||
|  |  | ||||||
| func GetToken(account models.Account) (string, string, error) { | func GetToken(account models.Account) (string, string, error) { | ||||||
|   | |||||||
| @@ -12,11 +12,8 @@ import ( | |||||||
| 	"gorm.io/gorm" | 	"gorm.io/gorm" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func ListPost(tx *gorm.DB, take int, offset int) ([]*models.Post, error) { | func PreloadRelatedPost(tx *gorm.DB) *gorm.DB { | ||||||
| 	var posts []*models.Post | 	return tx. | ||||||
| 	if err := tx. |  | ||||||
| 		Limit(take). |  | ||||||
| 		Offset(offset). |  | ||||||
| 		Preload("Author"). | 		Preload("Author"). | ||||||
| 		Preload("Attachments"). | 		Preload("Attachments"). | ||||||
| 		Preload("Categories"). | 		Preload("Categories"). | ||||||
| @@ -30,7 +27,14 @@ func ListPost(tx *gorm.DB, take int, offset int) ([]*models.Post, error) { | |||||||
| 		Preload("RepostTo.Categories"). | 		Preload("RepostTo.Categories"). | ||||||
| 		Preload("ReplyTo.Categories"). | 		Preload("ReplyTo.Categories"). | ||||||
| 		Preload("RepostTo.Tags"). | 		Preload("RepostTo.Tags"). | ||||||
| 		Preload("ReplyTo.Tags"). | 		Preload("ReplyTo.Tags") | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func ListPost(tx *gorm.DB, take int, offset int) ([]*models.Post, error) { | ||||||
|  | 	var posts []*models.Post | ||||||
|  | 	if err := PreloadRelatedPost(tx). | ||||||
|  | 		Limit(take). | ||||||
|  | 		Offset(offset). | ||||||
| 		Find(&posts).Error; err != nil { | 		Find(&posts).Error; err != nil { | ||||||
| 		return posts, err | 		return posts, err | ||||||
| 	} | 	} | ||||||
|   | |||||||
							
								
								
									
										40
									
								
								pkg/services/realms.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								pkg/services/realms.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | |||||||
|  | package services | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"code.smartsheep.studio/hydrogen/interactive/pkg/database" | ||||||
|  | 	"code.smartsheep.studio/hydrogen/interactive/pkg/models" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func ListRealms(user models.Account) ([]models.Realm, error) { | ||||||
|  | 	var realms []models.Realm | ||||||
|  | 	if err := database.C.Where(&models.Realm{AccountID: user.ID}).Find(&realms).Error; err != nil { | ||||||
|  | 		return realms, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return realms, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func NewRealm(user models.Account, name, description string) (models.Realm, error) { | ||||||
|  | 	realm := models.Realm{ | ||||||
|  | 		Name:        name, | ||||||
|  | 		Description: description, | ||||||
|  | 		AccountID:   user.ID, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	err := database.C.Save(&realm).Error | ||||||
|  |  | ||||||
|  | 	return realm, err | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func EditRealm(realm models.Realm, name, description string) (models.Realm, error) { | ||||||
|  | 	realm.Name = name | ||||||
|  | 	realm.Description = description | ||||||
|  |  | ||||||
|  | 	err := database.C.Save(&realm).Error | ||||||
|  |  | ||||||
|  | 	return realm, err | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func DeleteRealm(realm models.Realm) error { | ||||||
|  | 	return database.C.Delete(&realm).Error | ||||||
|  | } | ||||||
| @@ -35,7 +35,7 @@ export default function PostItem(props: { | |||||||
|   return ( |   return ( | ||||||
|     <div class="post-item"> |     <div class="post-item"> | ||||||
|       <Show when={!props.noAuthor}> |       <Show when={!props.noAuthor}> | ||||||
|         <a href={`/accounts/${props.post.author.id}`}> |         <a href={`/accounts/${props.post.author.name}`}> | ||||||
|           <div class="flex bg-base-200"> |           <div class="flex bg-base-200"> | ||||||
|             <div class="avatar pl-[20px]"> |             <div class="avatar pl-[20px]"> | ||||||
|               <div class="w-12"> |               <div class="w-12"> | ||||||
| @@ -47,7 +47,7 @@ export default function PostItem(props: { | |||||||
|             </div> |             </div> | ||||||
|             <div class="flex items-center px-5"> |             <div class="flex items-center px-5"> | ||||||
|               <div> |               <div> | ||||||
|                 <h3 class="font-bold text-sm">{props.post.author.name}</h3> |                 <h3 class="font-bold text-sm">{props.post.author.nick}</h3> | ||||||
|                 <p class="text-xs">{props.post.author.description}</p> |                 <p class="text-xs">{props.post.author.description}</p> | ||||||
|               </div> |               </div> | ||||||
|             </div> |             </div> | ||||||
| @@ -61,12 +61,12 @@ export default function PostItem(props: { | |||||||
|  |  | ||||||
|         <div class="mt-2 flex gap-2"> |         <div class="mt-2 flex gap-2"> | ||||||
|           <For each={props.post.categories}> |           <For each={props.post.categories}> | ||||||
|             {item => <a href={`/categories/${item.alias}`} class="link link-primary"> |             {item => <a class="link link-primary"> | ||||||
|               #{item.name} |               #{item.name} | ||||||
|             </a>} |             </a>} | ||||||
|           </For> |           </For> | ||||||
|           <For each={props.post.tags}> |           <For each={props.post.tags}> | ||||||
|             {item => <a href={`/tags/${item.alias}`} class="link link-primary"> |             {item => <a class="link link-primary"> | ||||||
|               #{item.name} |               #{item.name} | ||||||
|             </a>} |             </a>} | ||||||
|           </For> |           </For> | ||||||
|   | |||||||
| @@ -184,7 +184,7 @@ export default function PostPublish(props: { | |||||||
|           <div role="alert" class="bg-base-200 flex justify-between"> |           <div role="alert" class="bg-base-200 flex justify-between"> | ||||||
|             <div class="px-5 py-3"> |             <div class="px-5 py-3"> | ||||||
|               <i class="fa-solid fa-circle-info me-3"></i> |               <i class="fa-solid fa-circle-info me-3"></i> | ||||||
|               You are reposting a post from <b>{props.reposting?.author?.name}</b> |               You are reposting a post from <b>{props.reposting?.author?.nick}</b> | ||||||
|             </div> |             </div> | ||||||
|             <button type="reset" class="btn btn-ghost w-12" disabled={submitting()}> |             <button type="reset" class="btn btn-ghost w-12" disabled={submitting()}> | ||||||
|               <i class="fa-solid fa-xmark"></i> |               <i class="fa-solid fa-xmark"></i> | ||||||
| @@ -195,7 +195,7 @@ export default function PostPublish(props: { | |||||||
|           <div role="alert" class="bg-base-200 flex justify-between"> |           <div role="alert" class="bg-base-200 flex justify-between"> | ||||||
|             <div class="px-5 py-3"> |             <div class="px-5 py-3"> | ||||||
|               <i class="fa-solid fa-circle-info me-3"></i> |               <i class="fa-solid fa-circle-info me-3"></i> | ||||||
|               You are replying a post from <b>{props.replying?.author?.name}</b> |               You are replying a post from <b>{props.replying?.author?.nick}</b> | ||||||
|             </div> |             </div> | ||||||
|             <button type="reset" class="btn btn-ghost w-12" disabled={submitting()}> |             <button type="reset" class="btn btn-ghost w-12" disabled={submitting()}> | ||||||
|               <i class="fa-solid fa-xmark"></i> |               <i class="fa-solid fa-xmark"></i> | ||||||
|   | |||||||
| @@ -11,7 +11,8 @@ interface MenuItem { | |||||||
|  |  | ||||||
| export default function Navbar() { | export default function Navbar() { | ||||||
|   const nav: MenuItem[] = [ |   const nav: MenuItem[] = [ | ||||||
|     { label: "Feed", href: "/" } |     { label: "Feed", href: "/" }, | ||||||
|  |     { label: "Realms", href: "/realms" } | ||||||
|   ]; |   ]; | ||||||
|  |  | ||||||
|   const wellKnown = useWellKnown(); |   const wellKnown = useWellKnown(); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user