♻️ Move realm system to Passport

This commit is contained in:
2024-05-04 22:22:58 +08:00
parent 1181b1e5ce
commit f78a1447d5
20 changed files with 344 additions and 428 deletions

View File

@ -8,7 +8,6 @@ import (
var DatabaseAutoActionRange = []any{
&models.Account{},
&models.Realm{},
&models.RealmMember{},
&models.Category{},
&models.Tag{},
&models.Moment{},

View File

@ -8,6 +8,7 @@ import (
"google.golang.org/grpc"
)
var Realms idpb.RealmsClient
var Friendships idpb.FriendshipsClient
var Notify idpb.NotifyClient
var Auth idpb.AuthClient
@ -17,6 +18,7 @@ func ConnectPassport() error {
if conn, err := grpc.Dial(addr, grpc.WithTransportCredentials(insecure.NewCredentials())); err != nil {
return err
} else {
Realms = idpb.NewRealmsClient(conn)
Friendships = idpb.NewFriendshipsClient(conn)
Notify = idpb.NewNotifyClient(conn)
Auth = idpb.NewAuthClient(conn)

View File

@ -2,26 +2,24 @@ package models
import "time"
// Account profiles basically fetched from Hydrogen.Identity
// Account profiles basically fetched from Hydrogen.Passport
// But cache at here for better usage
// At the same time this model can make relations between local models
type Account struct {
BaseModel
Name string `json:"name"`
Nick string `json:"nick"`
Avatar string `json:"avatar"`
Banner string `json:"banner"`
Description string `json:"description"`
EmailAddress string `json:"email_address"`
PowerLevel int `json:"power_level"`
Moments []Moment `json:"moments" gorm:"foreignKey:AuthorID"`
Articles []Article `json:"articles" gorm:"foreignKey:AuthorID"`
Attachments []Attachment `json:"attachments" gorm:"foreignKey:AuthorID"`
Reactions []Reaction `json:"reactions"`
RealmIdentities []RealmMember `json:"identities"`
Realms []Realm `json:"realms"`
ExternalID uint `json:"external_id"`
Name string `json:"name"`
Nick string `json:"nick"`
Avatar string `json:"avatar"`
Banner string `json:"banner"`
Description string `json:"description"`
EmailAddress string `json:"email_address"`
PowerLevel int `json:"power_level"`
Moments []Moment `json:"moments" gorm:"foreignKey:AuthorID"`
Articles []Article `json:"articles" gorm:"foreignKey:AuthorID"`
Attachments []Attachment `json:"attachments" gorm:"foreignKey:AuthorID"`
Reactions []Reaction `json:"reactions"`
ExternalID uint `json:"external_id"`
}
type AccountMembership struct {

View File

@ -1,30 +1,16 @@
package models
type RealmType = int
const (
RealmTypePublic = RealmType(iota)
RealmTypeRestricted
RealmTypePrivate
)
// Realm profiles basically fetched from Hydrogen.Passport
// But cache at here for better usage and database relations
type Realm struct {
BaseModel
Name string `json:"name"`
Description string `json:"description"`
Articles []Article `json:"article"`
Moments []Moment `json:"moments"`
Members []RealmMember `json:"members"`
RealmType RealmType `json:"realm_type"`
AccountID uint `json:"account_id"`
}
type RealmMember struct {
BaseModel
RealmID uint `json:"realm_id"`
AccountID uint `json:"account_id"`
Realm Realm `json:"realm"`
Account Account `json:"account"`
Alias string `json:"alias"`
Name string `json:"name"`
Description string `json:"description"`
Articles []Article `json:"article"`
Moments []Moment `json:"moments"`
IsPublic bool `json:"is_public"`
IsCommunity bool `json:"is_community"`
ExternalID uint `json:"external_id"`
}

View File

@ -1,7 +1,6 @@
package server
import (
"git.solsynth.dev/hydrogen/interactive/pkg/security"
"git.solsynth.dev/hydrogen/interactive/pkg/services"
"github.com/gofiber/fiber/v2"
"strings"
@ -9,7 +8,7 @@ import (
func authMiddleware(c *fiber.Ctx) error {
var token string
if cookie := c.Cookies(security.CookieAccessKey); len(cookie) > 0 {
if cookie := c.Cookies(services.CookieAccessKey); len(cookie) > 0 {
token = cookie
}
if header := c.Get(fiber.HeaderAuthorization); len(header) > 0 {
@ -38,10 +37,10 @@ func authFunc(c *fiber.Ctx, overrides ...string) error {
}
}
rtk := c.Cookies(security.CookieRefreshKey)
rtk := c.Cookies(services.CookieRefreshKey)
if user, atk, rtk, err := services.Authenticate(token, rtk); err == nil {
if atk != token {
security.SetJwtCookieSet(c, atk, rtk)
services.SetJwtCookieSet(c, atk, rtk)
}
c.Locals("principal", user)
return nil

View File

@ -2,6 +2,7 @@ package server
import (
"fmt"
"git.solsynth.dev/hydrogen/passport/pkg/services"
"strings"
"git.solsynth.dev/hydrogen/interactive/pkg/database"
@ -19,7 +20,7 @@ const (
func listFeed(c *fiber.Ctx) error {
take := c.QueryInt("take", 0)
offset := c.QueryInt("offset", 0)
realmId := c.QueryInt("realmId", 0)
realmAlias := c.Query("realm")
if take > 20 {
take = 20
@ -27,10 +28,12 @@ func listFeed(c *fiber.Ctx) error {
var whereConditions []string
if realmId < 0 {
whereConditions = append(whereConditions, "feed.realm_id IS NULL")
} else if realmId > 0 {
whereConditions = append(whereConditions, fmt.Sprintf("feed.realm_id = %d", realmId))
if len(realmAlias) > 0 {
realm, err := services.GetRealmWithAlias(realmAlias)
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("related realm was not found: %v", err))
}
whereConditions = append(whereConditions, fmt.Sprintf("feed.realm_id = %d", realm.ID))
}
var author models.Account

View File

@ -1,113 +0,0 @@
package server
import (
"git.solsynth.dev/hydrogen/interactive/pkg/database"
"git.solsynth.dev/hydrogen/interactive/pkg/models"
"git.solsynth.dev/hydrogen/interactive/pkg/services"
"github.com/gofiber/fiber/v2"
)
func listRealmMembers(c *fiber.Ctx) error {
realmId, _ := c.ParamsInt("realmId", 0)
if members, err := services.ListRealmMember(uint(realmId)); err != nil {
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
} else {
return c.JSON(members)
}
}
func inviteRealm(c *fiber.Ctx) error {
user := c.Locals("principal").(models.Account)
realmId, _ := c.ParamsInt("realmId", 0)
var data struct {
AccountName string `json:"account_name" validate:"required"`
}
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(realmId)},
AccountID: user.ID,
}).First(&realm).Error; err != nil {
return fiber.NewError(fiber.StatusNotFound, err.Error())
}
var account models.Account
if err := database.C.Where(&models.Account{
Name: data.AccountName,
}).First(&account).Error; err != nil {
return fiber.NewError(fiber.StatusNotFound, err.Error())
}
if err := services.InviteRealmMember(account, realm); err != nil {
return fiber.NewError(fiber.StatusBadRequest, err.Error())
} else {
return c.SendStatus(fiber.StatusOK)
}
}
func kickRealm(c *fiber.Ctx) error {
user := c.Locals("principal").(models.Account)
realmId, _ := c.ParamsInt("realmId", 0)
var data struct {
AccountName string `json:"account_name" validate:"required"`
}
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(realmId)},
AccountID: user.ID,
}).First(&realm).Error; err != nil {
return fiber.NewError(fiber.StatusNotFound, err.Error())
}
var account models.Account
if err := database.C.Where(&models.Account{
Name: data.AccountName,
}).First(&account).Error; err != nil {
return fiber.NewError(fiber.StatusNotFound, err.Error())
}
if err := services.KickRealmMember(account, realm); err != nil {
return fiber.NewError(fiber.StatusBadRequest, err.Error())
} else {
return c.SendStatus(fiber.StatusOK)
}
}
func leaveRealm(c *fiber.Ctx) error {
user := c.Locals("principal").(models.Account)
realmId, _ := c.ParamsInt("realmId", 0)
var realm models.Realm
if err := database.C.Where(&models.Realm{
BaseModel: models.BaseModel{ID: uint(realmId)},
}).First(&realm).Error; err != nil {
return fiber.NewError(fiber.StatusNotFound, err.Error())
} else if user.ID == realm.AccountID {
return fiber.NewError(fiber.StatusBadRequest, "you cannot leave your own realm")
}
var account models.Account
if err := database.C.Where(&models.Account{
BaseModel: models.BaseModel{ID: user.ID},
}).First(&account).Error; err != nil {
return fiber.NewError(fiber.StatusNotFound, err.Error())
}
if err := services.KickRealmMember(account, realm); err != nil {
return fiber.NewError(fiber.StatusBadRequest, err.Error())
} else {
return c.SendStatus(fiber.StatusOK)
}
}

View File

@ -1,125 +0,0 @@
package server
import (
"git.solsynth.dev/hydrogen/interactive/pkg/database"
"git.solsynth.dev/hydrogen/interactive/pkg/models"
"git.solsynth.dev/hydrogen/interactive/pkg/services"
"github.com/gofiber/fiber/v2"
)
func getRealm(c *fiber.Ctx) error {
id, _ := c.ParamsInt("realmId", 0)
var realm models.Realm
if err := database.C.Where(&models.Realm{
BaseModel: models.BaseModel{ID: uint(id)},
}).First(&realm).Error; err != nil {
return fiber.NewError(fiber.StatusNotFound, err.Error())
}
return c.JSON(realm)
}
func listRealm(c *fiber.Ctx) error {
realms, err := services.ListRealm()
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, err.Error())
}
return c.JSON(realms)
}
func listOwnedRealm(c *fiber.Ctx) error {
user := c.Locals("principal").(models.Account)
realms, err := services.ListRealmWithUser(user)
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, err.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 {
return fiber.NewError(fiber.StatusForbidden, "require power level 10 to create realms")
}
var data struct {
Name string `json:"name" validate:"required"`
Description string `json:"description"`
RealmType int `json:"realm_type"`
}
if err := BindAndValidate(c, &data); err != nil {
return err
}
realm, err := services.NewRealm(user, data.Name, data.Description, data.RealmType)
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"`
RealmType int `json:"realm_type"`
}
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, data.RealmType)
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)
}

View File

@ -104,21 +104,6 @@ func NewServer() {
api.Post("/categories", authMiddleware, newCategory)
api.Put("/categories/:categoryId", authMiddleware, editCategory)
api.Delete("/categories/:categoryId", authMiddleware, deleteCategory)
realms := api.Group("/realms").Name("Realms API")
{
realms.Get("/", listRealm)
realms.Get("/me", authMiddleware, listOwnedRealm)
realms.Get("/me/available", authMiddleware, listAvailableRealm)
realms.Get("/:realmId", getRealm)
realms.Get("/:realmId/members", listRealmMembers)
realms.Post("/", authMiddleware, createRealm)
realms.Post("/:realmId/invite", authMiddleware, inviteRealm)
realms.Post("/:realmId/kick", authMiddleware, kickRealm)
realms.Post("/:realmId/leave", authMiddleware, kickRealm)
realms.Put("/:realmId", authMiddleware, editRealm)
realms.Delete("/:realmId", authMiddleware, deleteRealm)
}
}
A.Use(favicon.New(favicon.Config{

View File

@ -1,4 +1,4 @@
package security
package services
import "golang.org/x/crypto/bcrypt"

View File

@ -1,4 +1,4 @@
package security
package services
import (
"fmt"

View File

@ -3,6 +3,7 @@ package services
import (
"errors"
"fmt"
"git.solsynth.dev/hydrogen/passport/pkg/services"
"time"
"git.solsynth.dev/hydrogen/interactive/pkg/database"
@ -296,14 +297,9 @@ func NewPost[T models.PostInterface](item T) (T, error) {
}
if item.GetRealm() != nil {
if item.GetRealm().RealmType != models.RealmTypePublic {
var member models.RealmMember
if err := database.C.Where(&models.RealmMember{
RealmID: item.GetRealm().ID,
AccountID: item.GetAuthor().ID,
}).First(&member).Error; err != nil {
return item, fmt.Errorf("you aren't a part of that realm")
}
_, err := services.GetRealmMember(item.GetRealm().ID, item.GetAuthor().ID)
if err != nil {
return item, fmt.Errorf("you aren't a part of that realm: %v", err)
}
}

View File

@ -1,118 +1,82 @@
package services
import (
"context"
"errors"
"fmt"
"git.solsynth.dev/hydrogen/interactive/pkg/database"
"git.solsynth.dev/hydrogen/interactive/pkg/grpc"
"git.solsynth.dev/hydrogen/interactive/pkg/models"
"git.solsynth.dev/hydrogen/passport/pkg/grpc/proto"
"github.com/samber/lo"
"gorm.io/gorm"
)
func ListRealm() ([]models.Realm, error) {
var realms []models.Realm
if err := database.C.Find(&realms).Error; err != nil {
return realms, err
}
return realms, nil
}
func ListRealmWithUser(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 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
func GetRealm(id uint) (models.Realm, error) {
var realm models.Realm
response, err := grpc.Realms.GetRealm(context.Background(), &proto.RealmLookupRequest{
Id: lo.ToPtr(uint64(id)),
})
if err != nil {
return realm, err
}
return LinkRealm(response)
}
func GetRealmWithAlias(alias string) (models.Realm, error) {
var realm models.Realm
response, err := grpc.Realms.GetRealm(context.Background(), &proto.RealmLookupRequest{
Alias: &alias,
})
if err != nil {
return realm, err
}
return LinkRealm(response)
}
func GetRealmMember(realmId uint, userId uint) (*proto.RealmMemberResponse, error) {
response, err := grpc.Realms.GetRealmMember(context.Background(), &proto.RealmMemberLookupRequest{
RealmId: uint64(realmId),
UserId: lo.ToPtr(uint64(userId)),
})
if err != nil {
return nil, err
} else {
return response, nil
}
}
func ListRealmMember(realmId uint) ([]*proto.RealmMemberResponse, error) {
response, err := grpc.Realms.ListRealmMember(context.Background(), &proto.RealmMemberLookupRequest{
RealmId: uint64(realmId),
})
if err != nil {
return nil, err
} else {
return response.Data, nil
}
}
func LinkRealm(info *proto.RealmResponse) (models.Realm, error) {
var realm models.Realm
if info == nil {
return realm, fmt.Errorf("remote realm info was not found")
}
if err := database.C.Where(&models.Realm{
RealmType: models.RealmTypePublic,
}).Or("id IN ?", idx).Find(&realms).Error; err != nil {
return realms, err
ExternalID: uint(info.Id),
}).First(&realm).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
realm = models.Realm{
Alias: info.Alias,
Name: info.Name,
Description: info.Description,
IsPublic: info.IsPublic,
IsCommunity: info.IsCommunity,
ExternalID: uint(info.Id),
}
return realm, database.C.Save(&realm).Error
}
return realm, err
}
return realms, nil
}
func NewRealm(user models.Account, name, description string, realmType int) (models.Realm, error) {
realm := models.Realm{
Name: name,
Description: description,
AccountID: user.ID,
RealmType: realmType,
Members: []models.RealmMember{
{AccountID: user.ID},
},
}
err := database.C.Save(&realm).Error
return realm, err
}
func ListRealmMember(realmId uint) ([]models.RealmMember, error) {
var members []models.RealmMember
if err := database.C.
Where(&models.RealmMember{RealmID: realmId}).
Preload("Account").
Find(&members).Error; err != nil {
return members, err
}
return members, nil
}
func InviteRealmMember(user models.Account, target models.Realm) error {
if _, err := GetAccountFriend(user.ID, target.AccountID, 1); err != nil {
return fmt.Errorf("you only can invite your friends to your realm")
}
member := models.RealmMember{
RealmID: target.ID,
AccountID: user.ID,
}
err := database.C.Save(&member).Error
return err
}
func KickRealmMember(user models.Account, target models.Realm) error {
var member models.RealmMember
if err := database.C.Where(&models.RealmMember{
RealmID: target.ID,
AccountID: user.ID,
}).First(&member).Error; err != nil {
return err
}
return database.C.Delete(&member).Error
}
func EditRealm(realm models.Realm, name, description string, realmType int) (models.Realm, error) {
realm.Name = name
realm.Description = description
realm.RealmType = realmType
err := database.C.Save(&realm).Error
return realm, err
}
func DeleteRealm(realm models.Realm) error {
return database.C.Delete(&realm).Error
return realm, nil
}