diff --git a/frontend/index.html b/frontend/index.html index 1b22057..000b63d 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -36,6 +36,8 @@ window.serverName = "{{SiteName}}"; window.cloudflareTurnstileSiteKey = "{{CFTurnstileSiteKey}}"; window.siteInfo = `{{SiteInfo}}`; + window.uploadPrompt = `{{UploadPrompt}}`; + window.allowNormalUserUpload = `{{AllowNormalUserUpload}}`;
diff --git a/frontend/src/app.ts b/frontend/src/app.ts index 9237f1a..6773036 100644 --- a/frontend/src/app.ts +++ b/frontend/src/app.ts @@ -4,6 +4,8 @@ interface MyWindow extends Window { serverName?: string; cloudflareTurnstileSiteKey?: string; siteInfo?: string; + uploadPrompt?: string; + allowNormalUserUpload?: string; } class App { @@ -17,6 +19,10 @@ class App { siteInfo = ""; + uploadPrompt = ""; + + allowNormalUserUpload = true; + constructor() { this.init(); } @@ -37,6 +43,8 @@ class App { this.cloudflareTurnstileSiteKey = null; // Placeholder value, set to null if not configured } this.siteInfo = (window as MyWindow).siteInfo || ""; + this.uploadPrompt = (window as MyWindow).uploadPrompt || ""; + // this.allowNormalUserUpload = (window as MyWindow).allowNormalUserUpload === "true"; } saveData() { diff --git a/frontend/src/network/models.ts b/frontend/src/network/models.ts index 64d0379..35361c6 100644 --- a/frontend/src/network/models.ts +++ b/frontend/src/network/models.ts @@ -93,6 +93,7 @@ export interface Storage { maxSize: number; currentSize: number; createdAt: string; + isDefault: boolean; } export interface RFile { @@ -157,6 +158,9 @@ export interface ServerConfig { server_name: string; server_description: string; site_info: string; + allow_normal_user_upload: boolean; + max_normal_user_upload_size_in_mb: number; + upload_prompt: string; } export enum RSort { diff --git a/frontend/src/network/network.ts b/frontend/src/network/network.ts index 8870ba0..46c2224 100644 --- a/frontend/src/network/network.ts +++ b/frontend/src/network/network.ts @@ -460,6 +460,12 @@ class Network { ); } + async setDefaultStorage(id: number): Promise> { + return this._callApi(() => + axios.put(`${this.apiBaseUrl}/storage/${id}/default`), + ); + } + async initFileUpload( filename: string, description: string, diff --git a/frontend/src/pages/manage_server_config_page.tsx b/frontend/src/pages/manage_server_config_page.tsx index 1bf3835..5db90bb 100644 --- a/frontend/src/pages/manage_server_config_page.tsx +++ b/frontend/src/pages/manage_server_config_page.tsx @@ -108,7 +108,7 @@ export default function ManageServerConfigPage() { }} >
- Allow register + Allow registration +
+ Allow normal user upload + { + setConfig({ ...config, allow_normal_user_upload: e.target.checked }); + }} + /> +
+ { + setConfig({ + ...config, + max_normal_user_upload_size_in_mb: parseInt(e.target.value), + }); + }} + > + { + setConfig({ ...config, upload_prompt: e.target.value }); + }} + > { + if (loadingId != null) { + return; + } + setLoadingId(id); + const response = await network.setDefaultStorage(id); + if (response.success) { + showToast({ + message: t("Storage set as default successfully"), + }); + updateStorages(); + } else { + showToast({ + message: response.message, + type: "error", + }); + } + setLoadingId(null); + }; + return ( <>
{ return ( - {s.name} + + {s.name} + {s.isDefault && {t("Default")}} + {new Date(s.createdAt).toLocaleString()} {(s.currentSize / 1024 / 1024).toFixed(2)} /{" "} @@ -129,13 +154,39 @@ export default function StorageView() { { - console.log(props.children); if ( typeof props.children === "object" && (props.children as ReactElement).type === "strong" @@ -722,7 +721,7 @@ function Files({ files, resourceID }: { files: RFile[]; resourceID: number }) { return ; })}
- {app.canUpload() && ( + {app.canUpload() || app.allowNormalUserUpload && (
@@ -876,6 +875,12 @@ function CreateFileDialog({ resourceId }: { resourceId: number }) { showToast({ message: res.message, type: "error" }); } else { storages.current = res.data!; + let defaultStorage = storages.current.find((s) => s.isDefault); + if (!defaultStorage && storages.current.length > 0) { + defaultStorage = storages.current[0]; + } + console.log("defaultStorage", defaultStorage); + setStorage(defaultStorage || null); setLoading(false); const dialog = document.getElementById( "upload_dialog", @@ -902,6 +907,10 @@ function CreateFileDialog({ resourceId }: { resourceId: number }) {

{t("Create File")}

+ {app.uploadPrompt && ( +

{app.uploadPrompt}

+ )} +

{t("Type")}

{ const id = parseInt(e.target.value); if (isNaN(id)) { diff --git a/server/api/storage.go b/server/api/storage.go index 90f8f6f..9ab8a2c 100644 --- a/server/api/storage.go +++ b/server/api/storage.go @@ -109,10 +109,34 @@ func handleDeleteStorage(c fiber.Ctx) error { }) } +func handleSetDefaultStorage(c fiber.Ctx) error { + idStr := c.Params("id") + id, err := strconv.ParseUint(idStr, 10, 32) + if err != nil { + return model.NewRequestError("Invalid storage ID") + } + + uid, ok := c.Locals("uid").(uint) + if !ok { + return model.NewUnAuthorizedError("You are not authorized to perform this action") + } + + err = service.SetDefaultStorage(uid, uint(id)) + if err != nil { + return err + } + + return c.Status(fiber.StatusOK).JSON(model.Response[any]{ + Success: true, + Message: "Default storage set successfully", + }) +} + func AddStorageRoutes(r fiber.Router) { s := r.Group("storage") s.Post("/s3", handleCreateS3Storage) s.Post("/local", handleCreateLocalStorage) s.Get("/", handleListStorages) s.Delete("/:id", handleDeleteStorage) + s.Put("/:id/default", handleSetDefaultStorage) } diff --git a/server/config/config.go b/server/config/config.go index 02b4148..4b2ef6c 100644 --- a/server/config/config.go +++ b/server/config/config.go @@ -28,6 +28,12 @@ type ServerConfig struct { ServerDescription string `json:"server_description"` // SiteInfo is an article that describes the site. It will be displayed on the home page. Markdown format. SiteInfo string `json:"site_info"` + // AllowNormalUserUpload indicates whether normal users are allowed to upload files. + AllowNormalUserUpload bool `json:"allow_normal_user_upload"` + // MaxNormalUserUploadSizeInMB is the maximum size of files that normal users can upload. + MaxNormalUserUploadSizeInMB int `json:"max_normal_user_upload_size_in_mb"` + // Prompt for upload page + UploadPrompt string `json:"upload_prompt"` } func init() { @@ -42,6 +48,9 @@ func init() { CloudflareTurnstileSecretKey: "", ServerName: "Nysoure", ServerDescription: "Nysoure is a file sharing service.", + AllowNormalUserUpload: true, + MaxNormalUserUploadSizeInMB: 16, + UploadPrompt: "You can upload your files here.", } } else { data, err := os.ReadFile(p) @@ -106,3 +115,15 @@ func CloudflareTurnstileSecretKey() string { func SiteInfo() string { return config.SiteInfo } + +func AllowNormalUserUpload() bool { + return config.AllowNormalUserUpload +} + +func MaxNormalUserUploadSize() int64 { + return int64(config.MaxNormalUserUploadSizeInMB) * 1024 * 1024 +} + +func UploadPrompt() string { + return config.UploadPrompt +} diff --git a/server/dao/storage.go b/server/dao/storage.go index 4bad3bf..254ef44 100644 --- a/server/dao/storage.go +++ b/server/dao/storage.go @@ -1,9 +1,10 @@ package dao import ( + "nysoure/server/model" + "gorm.io/gorm" "gorm.io/gorm/clause" - "nysoure/server/model" ) func CreateStorage(s model.Storage) (model.Storage, error) { @@ -37,3 +38,12 @@ func AddStorageUsage(id uint, offset int64) error { return tx.Model(&model.Storage{}).Where("id = ?", id).Update("current_size", storage.CurrentSize+offset).Error }) } + +func SetDefaultStorage(id uint) error { + return db.Transaction(func(tx *gorm.DB) error { + if err := tx.Model(&model.Storage{}).Where("is_default = ?", true).Update("is_default", false).Error; err != nil { + return err + } + return tx.Model(&model.Storage{}).Where("id = ?", id).Update("is_default", true).Error + }) +} diff --git a/server/middleware/frontend_middleware.go b/server/middleware/frontend_middleware.go index 858b45d..3f4e9f6 100644 --- a/server/middleware/frontend_middleware.go +++ b/server/middleware/frontend_middleware.go @@ -128,6 +128,8 @@ func serveIndexHtml(c fiber.Ctx) error { content = strings.ReplaceAll(content, "{{Url}}", url) content = strings.ReplaceAll(content, "{{CFTurnstileSiteKey}}", cfTurnstileSiteKey) content = strings.ReplaceAll(content, "{{SiteInfo}}", siteInfo) + content = strings.ReplaceAll(content, "{{UploadPrompt}}", config.UploadPrompt()) + content = strings.ReplaceAll(content, "{{AllowNormalUserUpload}}", strconv.FormatBool(config.AllowNormalUserUpload())) c.Set("Content-Type", "text/html; charset=utf-8") return c.SendString(content) diff --git a/server/model/storage.go b/server/model/storage.go index 2c6f3a1..9305a20 100644 --- a/server/model/storage.go +++ b/server/model/storage.go @@ -1,8 +1,9 @@ package model import ( - "gorm.io/gorm" "time" + + "gorm.io/gorm" ) type Storage struct { @@ -12,6 +13,7 @@ type Storage struct { Config string MaxSize int64 CurrentSize int64 + IsDefault bool } type StorageView struct { @@ -21,6 +23,7 @@ type StorageView struct { MaxSize int64 `json:"maxSize"` CurrentSize int64 `json:"currentSize"` CreatedAt time.Time `json:"createdAt"` + IsDefault bool `json:"isDefault"` } func (s *Storage) ToView() StorageView { @@ -31,5 +34,6 @@ func (s *Storage) ToView() StorageView { MaxSize: s.MaxSize, CurrentSize: s.CurrentSize, CreatedAt: s.CreatedAt, + IsDefault: s.IsDefault, } } diff --git a/server/service/file.go b/server/service/file.go index 7bee262..a6b0cb7 100644 --- a/server/service/file.go +++ b/server/service/file.go @@ -80,7 +80,9 @@ func CreateUploadingFile(uid uint, filename string, description string, fileSize return nil, model.NewInternalServerError("failed to check user permission") } if !canUpload { - return nil, model.NewUnAuthorizedError("user cannot upload file") + if !config.AllowNormalUserUpload() || fileSize > config.MaxNormalUserUploadSize()*1024*1024 { + return nil, model.NewUnAuthorizedError("user cannot upload file") + } } if fileSize > config.MaxFileSize() { @@ -300,7 +302,7 @@ func CreateRedirectFile(uid uint, filename string, description string, resourceI log.Error("failed to check user permission: ", err) return nil, model.NewInternalServerError("failed to check user permission") } - if !canUpload { + if !canUpload && !config.AllowNormalUserUpload() { return nil, model.NewUnAuthorizedError("user cannot upload file") } diff --git a/server/service/storage.go b/server/service/storage.go index 3433f8e..67a2340 100644 --- a/server/service/storage.go +++ b/server/service/storage.go @@ -105,3 +105,19 @@ func DeleteStorage(uid, id uint) error { } return nil } + +func SetDefaultStorage(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 set default storage") + } + err = dao.SetDefaultStorage(id) + if err != nil { + return err + } + return nil +}