Initial commit

This commit is contained in:
2025-05-11 20:32:14 +08:00
commit d97247159f
80 changed files with 13013 additions and 0 deletions

320
server/service/file.go Normal file
View File

@@ -0,0 +1,320 @@
package service
import (
"github.com/gofiber/fiber/v3/log"
"github.com/google/uuid"
"nysoure/server/dao"
"nysoure/server/model"
"nysoure/server/storage"
"nysoure/server/utils"
"os"
"path/filepath"
"strconv"
"time"
)
const (
blockSize = 4 * 1024 * 1024 // 4MB
)
var (
maxUploadingSize = int64(1024 * 1024 * 1024 * 20) // TODO: make this configurable
maxFileSize = int64(1024 * 1024 * 1024 * 8) // TODO: make this configurable
)
func getUploadingSize(uid uint) int64 {
return dao.GetStatistic("uploading_size")
}
func updateUploadingSize(offset int64) {
c := dao.GetStatistic("uploading_size")
c += offset
_ = dao.SetStatistic("uploading_size", c)
}
func getTempDir() (string, error) {
name := uuid.NewString()
path := filepath.Join(utils.GetStoragePath(), "uploading", name)
if err := os.MkdirAll(path, os.ModePerm); err != nil {
return "", err
}
return path, nil
}
func init() {
go func() {
// Wait for 1 minute to ensure the database is ready
time.Sleep(time.Minute)
for {
oneDayAgo := time.Now().Add(-24 * time.Hour)
oldFiles, err := dao.GetUploadingFilesOlderThan(oneDayAgo)
if err != nil {
log.Error("failed to get old uploading files: ", err)
} else {
for _, file := range oldFiles {
if err := os.RemoveAll(file.TempPath); err != nil {
log.Error("failed to remove temp dir: ", err)
}
if err := dao.DeleteUploadingFile(file.ID); err != nil {
log.Error("failed to delete uploading file: ", err)
}
updateUploadingSize(-file.TotalSize)
}
}
// Sleep for 1 hour
time.Sleep(1 * time.Hour)
}
}()
}
func CreateUploadingFile(uid uint, filename string, description string, fileSize int64, resourceID, storageID uint) (*model.UploadingFileView, error) {
if filename == "" {
return nil, model.NewRequestError("filename is empty")
}
canUpload, err := checkUserCanUpload(uid)
if err != nil {
log.Error("failed to check user permission: ", err)
return nil, model.NewInternalServerError("failed to check user permission")
}
if !canUpload {
return nil, model.NewUnAuthorizedError("user cannot upload file")
}
if fileSize > maxFileSize {
return nil, model.NewRequestError("file size exceeds the limit")
}
currentUploadingSize := getUploadingSize(uid)
if currentUploadingSize+fileSize > maxUploadingSize {
log.Info("A new uploading file is rejected due to max uploading size limit")
return nil, model.NewRequestError("server is busy, please try again later")
}
tempPath, err := getTempDir()
if err != nil {
log.Error("failed to create temp dir: ", err)
return nil, model.NewInternalServerError("failed to create temp dir")
}
uploadingFile, err := dao.CreateUploadingFile(filename, description, fileSize, blockSize, tempPath, resourceID, storageID, uid)
if err != nil {
log.Error("failed to create uploading file: ", err)
_ = os.Remove(tempPath)
return nil, model.NewInternalServerError("failed to create uploading file")
}
updateUploadingSize(fileSize)
return uploadingFile.ToView(), nil
}
func UploadBlock(uid uint, fid uint, index int, data []byte) error {
uploadingFile, err := dao.GetUploadingFile(fid)
if err != nil {
log.Error("failed to get uploading file: ", err)
return model.NewNotFoundError("file not found")
}
if uploadingFile.UserID != uid {
return model.NewUnAuthorizedError("user cannot upload file")
}
if len(data) > int(uploadingFile.BlockSize) {
return model.NewRequestError("block size exceeds the limit")
}
if index != uploadingFile.BlocksCount()-1 && len(data) != int(uploadingFile.BlockSize) {
return model.NewRequestError("block size is not correct")
}
if index < 0 || index >= uploadingFile.BlocksCount() {
return model.NewRequestError("block index is not correct")
}
if uploadingFile.Blocks[index] {
return model.NewRequestError("block already uploaded")
}
path := filepath.Join(uploadingFile.TempPath, strconv.Itoa(index))
if err := os.WriteFile(path, data, os.ModePerm); err != nil {
log.Error("failed to write block file: ", err)
return model.NewInternalServerError("failed to write block file")
}
uploadingFile.Blocks[index] = true
if err := dao.UpdateUploadingBlock(fid, index); err != nil {
log.Error("failed to update uploading file: ", err)
_ = os.Remove(path)
return model.NewInternalServerError("failed to update uploading file")
}
return nil
}
func FinishUploadingFile(uid uint, fid uint) (*model.FileView, error) {
uploadingFile, err := dao.GetUploadingFile(fid)
if err != nil {
log.Error("failed to get uploading file: ", err)
return nil, model.NewNotFoundError("file not found")
}
if uploadingFile.UserID != uid {
return nil, model.NewUnAuthorizedError("user cannot upload file")
}
for i := 0; i < uploadingFile.BlocksCount(); i++ {
if !uploadingFile.Blocks[i] {
return nil, model.NewRequestError("file is not completely uploaded")
}
}
tempRemoved := false
defer func() {
if !tempRemoved {
if err := os.RemoveAll(uploadingFile.TempPath); err != nil {
log.Error("failed to remove temp dir: ", err)
}
}
if err := dao.DeleteUploadingFile(fid); err != nil {
log.Error("failed to delete uploading file: ", err)
}
updateUploadingSize(-uploadingFile.TotalSize)
}()
resultFilePath := filepath.Join(utils.GetStoragePath(), uuid.NewString())
file, err := os.OpenFile(resultFilePath, os.O_CREATE|os.O_WRONLY, os.ModePerm)
if err != nil {
log.Error("failed to open result file: ", err)
return nil, model.NewInternalServerError("failed to finish uploading file. please re-upload")
}
defer func() {
_ = os.Remove(resultFilePath)
}()
for i := 0; i < uploadingFile.BlocksCount(); i++ {
blockPath := filepath.Join(uploadingFile.TempPath, strconv.Itoa(i))
data, err := os.ReadFile(blockPath)
if err != nil {
log.Error("failed to read block file: ", err)
_ = file.Close()
return nil, model.NewInternalServerError("failed to finish uploading file. please re-upload")
}
if _, err := file.Write(data); err != nil {
log.Error("failed to write result file: ", err)
_ = file.Close()
return nil, model.NewInternalServerError("failed to finish uploading file. please re-upload")
}
}
_ = file.Close()
_ = os.RemoveAll(uploadingFile.TempPath)
tempRemoved = true
s, err := dao.GetStorage(uploadingFile.TargetStorageID)
if err != nil {
log.Error("failed to get storage: ", err)
return nil, model.NewInternalServerError("failed to finish uploading file. please re-upload")
}
iStorage := storage.NewStorage(s)
if iStorage == nil {
log.Error("failed to find storage: ", err)
return nil, model.NewInternalServerError("failed to finish uploading file. please re-upload")
}
storageKey, err := iStorage.Upload(resultFilePath)
if err != nil {
log.Error("failed to upload file: ", err)
return nil, model.NewInternalServerError("failed to finish uploading file. please re-upload")
}
dbFile, err := dao.CreateFile(uploadingFile.Filename, uploadingFile.Description, uploadingFile.TargetResourceID, &uploadingFile.TargetStorageID, storageKey, "")
if err != nil {
log.Error("failed to create file in db: ", err)
_ = iStorage.Delete(storageKey)
return nil, model.NewInternalServerError("failed to finish uploading file. please re-upload")
}
return dbFile.ToView(), nil
}
func CreateRedirectFile(uid uint, filename string, description string, resourceID uint, redirectUrl string) (*model.FileView, error) {
canUpload, err := checkUserCanUpload(uid)
if err != nil {
log.Error("failed to check user permission: ", err)
return nil, model.NewInternalServerError("failed to check user permission")
}
if !canUpload {
return nil, model.NewUnAuthorizedError("user cannot upload file")
}
file, err := dao.CreateFile(filename, description, resourceID, nil, "", redirectUrl)
if err != nil {
log.Error("failed to create file in db: ", err)
return nil, model.NewInternalServerError("failed to create file in db")
}
return file.ToView(), nil
}
func DeleteFile(uid uint, fid uint) error {
file, err := dao.GetFile(fid)
if err != nil {
log.Error("failed to get file: ", err)
return model.NewNotFoundError("file not found")
}
isAdmin, err := checkUserIsAdmin(uid)
if err != nil {
log.Error("failed to check user permission: ", err)
return model.NewInternalServerError("failed to check user permission")
}
if !isAdmin && file.UserID != uid {
return model.NewUnAuthorizedError("user cannot delete file")
}
iStorage := storage.NewStorage(file.Storage)
if iStorage == nil {
log.Error("failed to find storage: ", err)
return model.NewInternalServerError("failed to find storage")
}
if err := iStorage.Delete(file.StorageKey); err != nil {
log.Error("failed to delete file from storage: ", err)
return model.NewInternalServerError("failed to delete file from storage")
}
if err := dao.DeleteFile(fid); err != nil {
log.Error("failed to delete file from db: ", err)
return model.NewInternalServerError("failed to delete file from db")
}
return nil
}
func UpdateFile(uid uint, fid uint, filename string, description string) (*model.FileView, error) {
file, err := dao.GetFile(fid)
if err != nil {
log.Error("failed to get file: ", err)
return nil, model.NewNotFoundError("file not found")
}
isAdmin, err := checkUserIsAdmin(uid)
if err != nil {
log.Error("failed to check user permission: ", err)
return nil, model.NewInternalServerError("failed to check user permission")
}
if !isAdmin && file.UserID != uid {
return nil, model.NewUnAuthorizedError("user cannot update file")
}
file, err = dao.UpdateFile(fid, filename, description)
if err != nil {
log.Error("failed to update file in db: ", err)
return nil, model.NewInternalServerError("failed to update file in db")
}
return file.ToView(), nil
}
func GetFile(fid uint) (*model.FileView, error) {
file, err := dao.GetFile(fid)
if err != nil {
log.Error("failed to get file: ", err)
return nil, model.NewNotFoundError("file not found")
}
return file.ToView(), nil
}

129
server/service/image.go Normal file
View File

@@ -0,0 +1,129 @@
package service
import (
"bytes"
"errors"
"github.com/gofiber/fiber/v3/log"
"github.com/google/uuid"
"image"
"net/http"
"nysoure/server/dao"
"nysoure/server/model"
"nysoure/server/utils"
"os"
"time"
_ "image/gif"
_ "image/jpeg"
_ "image/png"
_ "golang.org/x/image/webp"
"github.com/chai2010/webp"
)
func init() {
// Start a goroutine to delete unused images every hour
go func() {
// Wait for 1 minute to ensure the database is ready
time.Sleep(time.Minute)
for {
images, err := dao.GetUnusedImages()
if err != nil {
log.Errorf("Failed to get unused images: %v", err)
}
if len(images) > 0 {
for _, i := range images {
err := DeleteImage(i.ID)
if err != nil {
log.Errorf("Failed to delete unused image %d: %v", i.ID, err)
}
}
}
time.Sleep(time.Hour)
}
}()
}
func CreateImage(data []byte) (uint, error) {
if len(data) == 0 {
return 0, model.NewRequestError("Image data is empty")
} else if len(data) > 1024*1024*5 {
return 0, model.NewRequestError("Image data is too large")
}
imageDir := utils.GetStoragePath() + "/images/"
if _, err := os.Stat(imageDir); os.IsNotExist(err) {
if err := os.MkdirAll(imageDir, 0755); err != nil {
return 0, err
}
}
contentType := http.DetectContentType(data)
if contentType != "image/jpeg" && contentType != "image/png" && contentType != "image/gif" && contentType != "image/webp" {
return 0, model.NewRequestError("Invalid image format")
}
// Reformat the image data to webp format if necessary
img, _, err := image.Decode(bytes.NewReader(data))
if err != nil {
return 0, errors.New("failed to decode image data")
}
if img.Bounds().Dx() == 0 || img.Bounds().Dy() == 0 {
return 0, errors.New("invalid image dimensions")
}
if contentType != "image/webp" {
buf := new(bytes.Buffer)
if err := webp.Encode(buf, img, &webp.Options{Quality: 80}); err != nil {
return 0, errors.New("failed to encode image data to webp format")
}
data = buf.Bytes()
contentType = "image/webp"
}
filename := uuid.New().String()
if err := os.WriteFile(imageDir+filename, data, 0644); err != nil {
return 0, errors.New("failed to save image file")
}
i, err := dao.CreateImage(filename, img.Bounds().Dx(), img.Bounds().Dy())
if err != nil {
_ = os.Remove(imageDir + filename)
return 0, err
}
return i.ID, nil
}
func GetImage(id uint) ([]byte, error) {
i, err := dao.GetImageByID(id)
if err != nil {
return nil, err
}
imageDir := utils.GetStoragePath() + "/images/"
if _, err := os.Stat(imageDir); os.IsNotExist(err) {
return nil, model.NewNotFoundError("Image not found")
}
data, err := os.ReadFile(imageDir + i.FileName)
if err != nil {
return nil, errors.New("Failed to read image file")
}
return data, nil
}
func DeleteImage(id uint) error {
i, err := dao.GetImageByID(id)
if err != nil {
return err
}
imageDir := utils.GetStoragePath() + "/images/"
_ = os.Remove(imageDir + i.FileName)
if err := dao.DeleteImage(id); err != nil {
return err
}
return nil
}

129
server/service/resource.go Normal file
View File

@@ -0,0 +1,129 @@
package service
import (
"nysoure/server/dao"
"nysoure/server/model"
"gorm.io/gorm"
)
const (
pageSize = 20
)
type ResourceCreateParams struct {
Title string `json:"title" binding:"required"`
AlternativeTitles []string `json:"alternative_titles"`
Tags []uint `json:"tags"`
Article string `json:"article"`
Images []uint `json:"images"`
}
func CreateResource(uid uint, params *ResourceCreateParams) (uint, error) {
canUpload, err := checkUserCanUpload(uid)
if err != nil {
return 0, err
}
if !canUpload {
return 0, model.NewUnAuthorizedError("You have not permission to upload resources")
}
images := make([]model.Image, len(params.Images))
for i, id := range params.Images {
images[i] = model.Image{
Model: gorm.Model{
ID: id,
},
}
}
tags := make([]model.Tag, len(params.Tags))
for i, id := range params.Tags {
tags[i] = model.Tag{
Model: gorm.Model{
ID: id,
},
}
}
r := model.Resource{
Title: params.Title,
AlternativeTitles: params.AlternativeTitles,
Article: params.Article,
Images: images,
Tags: tags,
UserID: uid,
}
if r, err = dao.CreateResource(r); err != nil {
return 0, err
}
return r.ID, nil
}
func GetResource(id uint) (*model.ResourceDetailView, error) {
r, err := dao.GetResourceByID(id)
if err != nil {
return nil, err
}
v := r.ToDetailView()
return &v, nil
}
func GetResourceList(page int) ([]model.ResourceView, int, error) {
resources, totalPages, err := dao.GetResourceList(page, pageSize)
if err != nil {
return nil, 0, err
}
var views []model.ResourceView
for _, r := range resources {
views = append(views, r.ToView())
}
return views, totalPages, nil
}
func SearchResource(keyword string, page int) ([]model.ResourceView, int, error) {
resources, totalPages, err := dao.Search(keyword, page, pageSize)
if err != nil {
return nil, 0, err
}
var views []model.ResourceView
for _, r := range resources {
views = append(views, r.ToView())
}
return views, totalPages, nil
}
func DeleteResource(uid, id uint) error {
isAdmin, err := checkUserIsAdmin(uid)
if err != nil {
return err
}
if !isAdmin {
r, err := dao.GetResourceByID(id)
if err != nil {
return err
}
if r.UserID != uid {
return model.NewUnAuthorizedError("You have not permission to delete this resource")
}
}
if err := dao.DeleteResource(id); err != nil {
return err
}
return nil
}
func GetResourcesWithTag(tag string, page int) ([]model.ResourceView, int, error) {
t, err := dao.GetTagByName(tag)
if err != nil {
return nil, 0, err
}
tagID := t.ID
resources, totalPages, err := dao.GetResourceByTag(tagID, page, pageSize)
if err != nil {
return nil, 0, err
}
var views []model.ResourceView
for _, r := range resources {
views = append(views, r.ToView())
}
return views, totalPages, nil
}

104
server/service/storage.go Normal file
View File

@@ -0,0 +1,104 @@
package service
import (
"github.com/gofiber/fiber/v3/log"
"nysoure/server/dao"
"nysoure/server/model"
"nysoure/server/storage"
"os"
)
type CreateS3StorageParams struct {
Name string `json:"name"`
EndPoint string `json:"endPoint"`
AccessKeyID string `json:"accessKeyID"`
SecretAccessKey string `json:"secretAccessKey"`
BucketName string `json:"bucketName"`
MaxSizeInMB uint `json:"maxSizeInMB"`
}
func CreateS3Storage(uid uint, params CreateS3StorageParams) error {
isAdmin, err := checkUserIsAdmin(uid)
if err != nil {
log.Errorf("check user is admin failed: %s", err)
return model.NewInternalServerError("check user is admin failed")
}
if !isAdmin {
return model.NewUnAuthorizedError("only admin can create s3 storage")
}
s3 := storage.S3Storage{
EndPoint: params.EndPoint,
AccessKeyID: params.AccessKeyID,
SecretAccessKey: params.SecretAccessKey,
BucketName: params.BucketName,
}
s := model.Storage{
Name: params.Name,
Type: s3.Type(),
Config: s3.ToString(),
MaxSize: int64(params.MaxSizeInMB) * 1024 * 1024,
}
_, err = dao.CreateStorage(s)
return err
}
type CreateLocalStorageParams struct {
Name string `json:"name"`
Path string `json:"path"`
MaxSizeInMB uint `json:"maxSizeInMB"`
}
func CreateLocalStorage(uid uint, params CreateLocalStorageParams) error {
isAdmin, err := checkUserIsAdmin(uid)
if err != nil {
log.Errorf("check user is admin failed: %s", err)
return model.NewInternalServerError("check user is admin failed")
}
if !isAdmin {
return model.NewUnAuthorizedError("only admin can create local storage")
}
local := storage.LocalStorage{
Path: params.Path,
}
err = os.MkdirAll(params.Path, os.ModePerm)
if err != nil {
log.Errorf("create local storage dir failed: %s", err)
return model.NewInternalServerError("create local storage dir failed")
}
s := model.Storage{
Name: params.Name,
Type: local.Type(),
Config: local.ToString(),
MaxSize: int64(params.MaxSizeInMB) * 1024 * 1024,
}
_, err = dao.CreateStorage(s)
return err
}
func ListStorages() ([]model.StorageView, error) {
storages, err := dao.GetStorages()
if err != nil {
return nil, err
}
var result []model.StorageView
for _, s := range storages {
result = append(result, s.ToView())
}
return result, nil
}
func DeleteStorage(uid, id uint) error {
isAdmin, err := checkUserIsAdmin(uid)
if err != nil {
log.Errorf("check user is admin failed: %s", err)
return model.NewInternalServerError("check user is admin failed")
}
if !isAdmin {
return model.NewUnAuthorizedError("only admin can delete storage")
}
err = dao.DeleteStorage(id)
if err != nil {
return err
}
return nil
}

38
server/service/tag.go Normal file
View File

@@ -0,0 +1,38 @@
package service
import (
"nysoure/server/dao"
"nysoure/server/model"
)
func CreateTag(name string) (*model.TagView, error) {
t, err := dao.CreateTag(name)
if err != nil {
return nil, err
}
return t.ToView(), nil
}
func GetTag(id uint) (*model.TagView, error) {
t, err := dao.GetTagByID(id)
if err != nil {
return nil, err
}
return t.ToView(), nil
}
func SearchTag(name string) ([]model.TagView, error) {
tags, err := dao.SearchTag(name)
if err != nil {
return nil, err
}
var tagViews []model.TagView
for _, t := range tags {
tagViews = append(tagViews, *t.ToView())
}
return tagViews, nil
}
func DeleteTag(id uint) error {
return dao.DeleteTag(id)
}

263
server/service/user.go Normal file
View File

@@ -0,0 +1,263 @@
package service
import (
"errors"
"fmt"
"nysoure/server/dao"
"nysoure/server/model"
"nysoure/server/static"
"nysoure/server/utils"
"os"
"strconv"
"golang.org/x/crypto/bcrypt"
)
const (
embedAvatarCount = 1
)
func CreateUser(username, password string) (model.UserViewWithToken, error) {
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return model.UserViewWithToken{}, err
}
user, err := dao.CreateUser(username, hashedPassword)
if err != nil {
return model.UserViewWithToken{}, err
}
token, err := utils.GenerateToken(user.ID)
if err != nil {
return model.UserViewWithToken{}, err
}
return user.ToView().WithToken(token), nil
}
func Login(username, password string) (model.UserViewWithToken, error) {
user, err := dao.GetUserByUsername(username)
if err != nil {
if model.IsNotFoundError(err) {
return model.UserViewWithToken{}, model.NewRequestError("User not found")
}
return model.UserViewWithToken{}, err
}
if err := bcrypt.CompareHashAndPassword(user.PasswordHash, []byte(password)); err != nil {
return model.UserViewWithToken{}, model.NewRequestError("Invalid password")
}
token, err := utils.GenerateToken(user.ID)
if err != nil {
return model.UserViewWithToken{}, err
}
return user.ToView().WithToken(token), nil
}
func ChangePassword(id uint, oldPassword, newPassword string) (model.UserViewWithToken, error) {
user, err := dao.GetUserByID(id)
if err != nil {
return model.UserViewWithToken{}, err
}
if err := bcrypt.CompareHashAndPassword(user.PasswordHash, []byte(oldPassword)); err != nil {
return model.UserViewWithToken{}, err
}
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.DefaultCost)
if err != nil {
return model.UserViewWithToken{}, err
}
user.PasswordHash = hashedPassword
if err := dao.UpdateUser(user); err != nil {
return model.UserViewWithToken{}, err
}
token, err := utils.GenerateToken(user.ID)
if err != nil {
return model.UserViewWithToken{}, err
}
return user.ToView().WithToken(token), nil
}
func ChangeAvatar(id uint, image []byte) (model.UserView, error) {
user, err := dao.GetUserByID(id)
if err != nil {
return model.UserView{}, err
}
if len(image) > 4*1024*1024 {
return model.UserView{}, errors.New("image size is too large")
}
avatarDir := utils.GetStoragePath() + "/avatar"
if _, err := os.Stat(avatarDir); os.IsNotExist(err) {
if err := os.MkdirAll(avatarDir, os.ModePerm); err != nil {
return model.UserView{}, errors.New("failed to create avatar directory")
}
}
avatarPath := avatarDir + "/" + strconv.Itoa(int(user.ID))
if err := os.WriteFile(avatarPath, image, 0644); err != nil {
return model.UserView{}, errors.New("failed to save avatar")
}
user.AvatarVersion++
if err := dao.UpdateUser(user); err != nil {
return model.UserView{}, err
}
return user.ToView(), nil
}
func GetAvatar(id uint) ([]byte, error) {
avatarPath := utils.GetStoragePath() + "/avatar/" + strconv.Itoa(int(id))
if _, err := os.Stat(avatarPath); os.IsNotExist(err) {
return getEmbedAvatar(id)
}
image, err := os.ReadFile(avatarPath)
if err != nil {
return nil, errors.New("failed to read avatar")
}
return image, nil
}
func getEmbedAvatar(id uint) ([]byte, error) {
fileIndex := id%embedAvatarCount + 1
fileName := fmt.Sprintf("avatars/%d.png", fileIndex)
return static.Static.ReadFile(fileName)
}
func HavePermissionToUpload(id uint) error {
user, err := dao.GetUserByID(id)
if err != nil {
return err
}
if !user.IsAdmin && !user.CanUpload {
return model.NewUnAuthorizedError("User does not have permission to upload")
}
return nil
}
func SetUserAdmin(adminID uint, targetUserID uint, isAdmin bool) (model.UserView, error) {
if adminID == targetUserID {
return model.UserView{}, model.NewRequestError("You cannot modify your own admin status")
}
adminUser, err := dao.GetUserByID(adminID)
if err != nil {
return model.UserView{}, err
}
if !adminUser.IsAdmin {
return model.UserView{}, model.NewUnAuthorizedError("Only administrators can modify admin status")
}
targetUser, err := dao.GetUserByID(targetUserID)
if err != nil {
return model.UserView{}, err
}
targetUser.IsAdmin = isAdmin
if err := dao.UpdateUser(targetUser); err != nil {
return model.UserView{}, err
}
return targetUser.ToView(), nil
}
func SetUserUploadPermission(adminID uint, targetUserID uint, canUpload bool) (model.UserView, error) {
adminUser, err := dao.GetUserByID(adminID)
if err != nil {
return model.UserView{}, err
}
if !adminUser.IsAdmin {
return model.UserView{}, model.NewUnAuthorizedError("Only administrators can modify upload permissions")
}
targetUser, err := dao.GetUserByID(targetUserID)
if err != nil {
return model.UserView{}, err
}
targetUser.CanUpload = canUpload
if err := dao.UpdateUser(targetUser); err != nil {
return model.UserView{}, err
}
return targetUser.ToView(), nil
}
func ListUsers(adminID uint, page int) ([]model.UserView, int, error) {
admin, err := dao.GetUserByID(adminID)
if err != nil {
return nil, 0, err
}
if !admin.IsAdmin {
return nil, 0, model.NewUnAuthorizedError("Only administrators can list users")
}
if page < 1 {
page = 1
}
pageSize := 10
users, total, err := dao.ListUsers(page, pageSize)
if err != nil {
return nil, 0, err
}
userViews := make([]model.UserView, len(users))
for i, user := range users {
userViews[i] = user.ToView()
}
totalPages := int((total + int64(pageSize) - 1) / int64(pageSize))
return userViews, totalPages, nil
}
func SearchUsers(adminID uint, username string, page int) ([]model.UserView, int, error) {
admin, err := dao.GetUserByID(adminID)
if err != nil {
return nil, 0, err
}
if !admin.IsAdmin {
return nil, 0, model.NewUnAuthorizedError("Only administrators can search users")
}
if page < 1 {
page = 1
}
pageSize := 10
users, total, err := dao.SearchUsersByUsername(username, page, pageSize)
if err != nil {
return nil, 0, err
}
userViews := make([]model.UserView, len(users))
for i, user := range users {
userViews[i] = user.ToView()
}
totalPages := int((total + int64(pageSize) - 1) / int64(pageSize))
return userViews, totalPages, nil
}
func DeleteUser(adminID uint, targetUserID uint) error {
admin, err := dao.GetUserByID(adminID)
if err != nil {
return err
}
if !admin.IsAdmin {
return model.NewUnAuthorizedError("Only administrators can delete users")
}
// Check if user is trying to delete themselves
if adminID == targetUserID {
return model.NewRequestError("You cannot delete your own account")
}
// Check if target user exists
_, err = dao.GetUserByID(targetUserID)
if err != nil {
return err
}
return dao.DeleteUser(targetUserID)
}

19
server/service/utils.go Normal file
View File

@@ -0,0 +1,19 @@
package service
import "nysoure/server/dao"
func checkUserCanUpload(uid uint) (bool, error) {
user, err := dao.GetUserByID(uid)
if err != nil {
return false, err
}
return user.IsAdmin || user.CanUpload, nil
}
func checkUserIsAdmin(uid uint) (bool, error) {
user, err := dao.GetUserByID(uid)
if err != nil {
return false, err
}
return user.IsAdmin, nil
}