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

79
server/model/error.go Normal file
View File

@@ -0,0 +1,79 @@
package model
import (
"errors"
)
type RequestError struct {
Message string `json:"message"`
}
func (e *RequestError) Error() string {
return e.Message
}
func NewRequestError(message string) *RequestError {
return &RequestError{
Message: message,
}
}
func IsRequestError(err error) bool {
var requestError *RequestError
ok := errors.As(err, &requestError)
return ok
}
type UnAuthorizedError struct {
Message string `json:"message"`
}
func (e *UnAuthorizedError) Error() string {
return e.Message
}
func NewUnAuthorizedError(message string) *UnAuthorizedError {
return &UnAuthorizedError{
Message: message,
}
}
func IsUnAuthorizedError(err error) bool {
var unAuthorizedError *UnAuthorizedError
ok := errors.As(err, &unAuthorizedError)
return ok
}
type NotFoundError struct {
Message string `json:"message"`
}
func (e *NotFoundError) Error() string {
return e.Message
}
func NewNotFoundError(message string) *NotFoundError {
return &NotFoundError{
Message: message,
}
}
func IsNotFoundError(err error) bool {
var notFoundError *NotFoundError
ok := errors.As(err, &notFoundError)
return ok
}
type InternalServerError struct {
Message string `json:"message"`
}
func (e *InternalServerError) Error() string {
return e.Message
}
func NewInternalServerError(message string) *InternalServerError {
return &InternalServerError{
Message: message,
}
}

33
server/model/file.go Normal file
View File

@@ -0,0 +1,33 @@
package model
import (
"gorm.io/gorm"
)
type File struct {
gorm.Model
Filename string
Description string
StorageKey string
StorageID *uint `gorm:"default:null"`
Storage Storage
ResourceID uint
RedirectUrl string
Resource Resource `gorm:"foreignKey:ResourceID"`
UserID uint
User User `gorm:"foreignKey:UserID"`
}
type FileView struct {
ID uint `json:"id"`
Filename string `json:"filename"`
Description string `json:"description"`
}
func (f *File) ToView() *FileView {
return &FileView{
ID: f.ID,
Filename: f.Filename,
Description: f.Description,
}
}

26
server/model/image.go Normal file
View File

@@ -0,0 +1,26 @@
package model
import "gorm.io/gorm"
type Image struct {
gorm.Model
FileName string
Width int
Height int
// An image can only belong to one resource, or it doesn't belong to any resource and is waiting for usage.
Resource []Resource `gorm:"many2many:resource_images;"`
}
type ImageView struct {
ID uint `json:"id"`
Width int `json:"width"`
Height int `json:"height"`
}
func (i *Image) ToView() ImageView {
return ImageView{
ID: i.ID,
Width: i.Width,
Height: i.Height,
}
}

89
server/model/resource.go Normal file
View File

@@ -0,0 +1,89 @@
package model
import (
"time"
"gorm.io/gorm"
)
type Resource struct {
gorm.Model
Title string
AlternativeTitles []string `gorm:"serializer:json"`
Article string
Images []Image `gorm:"many2many:resource_images;"`
Tags []Tag `gorm:"many2many:resource_tags;"`
Files []File `gorm:"foreignKey:ResourceID"`
UserID uint
User User
}
type ResourceView struct {
ID uint `json:"id"`
Title string `json:"title"`
CreatedAt time.Time `json:"created_at"`
Tags []TagView `json:"tags"`
Image *ImageView `json:"image"`
Author UserView `json:"author"`
}
type ResourceDetailView struct {
ID uint `json:"id"`
Title string `json:"title"`
AlternativeTitles []string `json:"alternativeTitles"`
Article string `json:"article"`
CreatedAt time.Time `json:"createdAt"`
Tags []TagView `json:"tags"`
Images []ImageView `json:"images"`
Files []FileView `json:"files"`
Author UserView `json:"author"`
}
func (r *Resource) ToView() ResourceView {
tags := make([]TagView, len(r.Tags))
for i, tag := range r.Tags {
tags[i] = *tag.ToView()
}
var image *ImageView
if len(r.Images) > 0 {
v := r.Images[0].ToView()
image = &v
}
return ResourceView{
ID: r.ID,
Title: r.Title,
CreatedAt: r.CreatedAt,
Tags: tags,
Image: image,
Author: r.User.ToView(),
}
}
func (r *Resource) ToDetailView() ResourceDetailView {
images := make([]ImageView, len(r.Images))
for i, image := range r.Images {
images[i] = image.ToView()
}
tags := make([]TagView, len(r.Tags))
for i, tag := range r.Tags {
tags[i] = *tag.ToView()
}
files := make([]FileView, len(r.Files))
for i, file := range r.Files {
files[i] = *file.ToView()
}
return ResourceDetailView{
ID: r.ID,
Title: r.Title,
AlternativeTitles: r.AlternativeTitles,
Article: r.Article,
CreatedAt: r.CreatedAt,
Tags: tags,
Images: images,
Files: files,
Author: r.User.ToView(),
}
}

14
server/model/response.go Normal file
View File

@@ -0,0 +1,14 @@
package model
type Response[T any] struct {
Success bool `json:"success"`
Data T `json:"data,omitempty"`
Message string `json:"message,omitempty"`
}
type PageResponse[T any] struct {
Success bool `json:"success"`
TotalPages int `json:"totalPages"`
Data []T `json:"data"`
Message string `json:"message,omitempty"`
}

View File

@@ -0,0 +1,6 @@
package model
type Statistic struct {
Key string `gorm:"primaryKey"`
Value int64
}

35
server/model/storage.go Normal file
View File

@@ -0,0 +1,35 @@
package model
import (
"gorm.io/gorm"
"time"
)
type Storage struct {
gorm.Model
Name string
Type string
Config string
MaxSize int64
CurrentSize int64
}
type StorageView struct {
ID uint `json:"id"`
Name string `json:"name"`
Type string `json:"type"`
MaxSize int64 `json:"maxSize"`
CurrentSize int64 `json:"currentSize"`
CreatedAt time.Time `json:"createdAt"`
}
func (s *Storage) ToView() StorageView {
return StorageView{
ID: s.ID,
Name: s.Name,
Type: s.Type,
MaxSize: s.MaxSize,
CurrentSize: s.CurrentSize,
CreatedAt: s.CreatedAt,
}
}

21
server/model/tag.go Normal file
View File

@@ -0,0 +1,21 @@
package model
import "gorm.io/gorm"
type Tag struct {
gorm.Model
Name string `gorm:"unique"`
Resources []Resource `gorm:"many2many:resource_tags;"`
}
type TagView struct {
ID uint `json:"id"`
Name string `json:"name"`
}
func (t *Tag) ToView() *TagView {
return &TagView{
ID: t.ID,
Name: t.Name,
}
}

View File

@@ -0,0 +1,77 @@
package model
import (
"context"
"gorm.io/gorm"
"gorm.io/gorm/schema"
"reflect"
)
type UploadingFile struct {
gorm.Model
Filename string
Description string
TargetResourceID uint
TargetStorageID uint
UserID uint
BlockSize int64
TotalSize int64
Blocks UploadingFileBlocks `gorm:"type:blob"`
TempPath string
Resource Resource `gorm:"foreignKey:TargetResourceID"`
Storage Storage `gorm:"foreignKey:TargetStorageID"`
}
func (uf *UploadingFile) BlocksCount() int {
return int((uf.TotalSize + uf.BlockSize - 1) / uf.BlockSize)
}
type UploadingFileBlocks []bool
func (ufb *UploadingFileBlocks) Scan(ctx context.Context, field *schema.Field, dst reflect.Value, dbValue interface{}) (err error) {
data, ok := dbValue.([]byte)
if !ok {
return nil
}
*ufb = make([]bool, len(data)*8)
for i, b := range data {
for j := 0; j < 8; j++ {
(*ufb)[i*8+j] = (b>>j)&1 == 1
}
}
return nil
}
func (ufb UploadingFileBlocks) Value(ctx context.Context, field *schema.Field, dbValue reflect.Value) (value interface{}, err error) {
data := make([]byte, (len(ufb)+7)/8)
for i, b := range ufb {
if b {
data[i/8] |= 1 << (i % 8)
}
}
return data, nil
}
type UploadingFileView struct {
ID uint `json:"id"`
Filename string `json:"filename"`
Description string `json:"description"`
TotalSize int64 `json:"totalSize"`
BlockSize int64 `json:"blockSize"`
BlocksCount int `json:"blocksCount"`
StorageID uint `json:"storageId"`
ResourceID uint `json:"resourceId"`
}
func (uf *UploadingFile) ToView() *UploadingFileView {
return &UploadingFileView{
ID: uf.ID,
Filename: uf.Filename,
Description: uf.Description,
TotalSize: uf.TotalSize,
BlockSize: uf.BlockSize,
BlocksCount: uf.BlocksCount(),
StorageID: uf.TargetStorageID,
ResourceID: uf.TargetResourceID,
}
}

49
server/model/user.go Normal file
View File

@@ -0,0 +1,49 @@
package model
import (
"fmt"
"gorm.io/gorm"
"time"
)
type User struct {
gorm.Model
Username string
PasswordHash []byte
IsAdmin bool
CanUpload bool
AvatarVersion int
Resources []Resource `gorm:"foreignKey:UserID"`
}
type UserView struct {
ID uint `json:"id"`
Username string `json:"username"`
CreatedAt time.Time `json:"created_at"`
AvatarPath string `json:"avatar_path"`
IsAdmin bool `json:"is_admin"`
CanUpload bool `json:"can_upload"`
}
type UserViewWithToken struct {
UserView
Token string `json:"token"`
}
func (u User) ToView() UserView {
return UserView{
ID: u.ID,
Username: u.Username,
CreatedAt: u.CreatedAt,
AvatarPath: fmt.Sprintf("/api/user/avatar/%d?v=%d", u.ID, u.AvatarVersion),
IsAdmin: u.IsAdmin,
CanUpload: u.CanUpload || u.IsAdmin,
}
}
func (u UserView) WithToken(token string) UserViewWithToken {
return UserViewWithToken{
UserView: u,
Token: token,
}
}