- Update user policy and user update handlers to accept -1 as an unlimited value for MaxDays, DailyBoxes, ActiveBoxes, and ShortWindowRequests. - Introduce `optionalIntAllowUnlimited` helper and update `optionalMBAllowZero` to support -1. - Use `boxExpiryLabel` helper across admin, dashboard, and download handlers to properly format expiration dates, supporting boxes that never expire.
189 lines
6.3 KiB
Go
189 lines
6.3 KiB
Go
package handlers
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"os"
|
|
|
|
"warpbox.dev/backend/libs/helpers"
|
|
"warpbox.dev/backend/libs/services"
|
|
"warpbox.dev/backend/libs/web"
|
|
)
|
|
|
|
type dashboardData struct {
|
|
User services.PublicUser
|
|
Collections []collectionView
|
|
Boxes []userBoxView
|
|
StorageUsed string
|
|
MaxUploadSize string
|
|
Selected string
|
|
LastInviteURL string
|
|
}
|
|
|
|
type collectionView struct {
|
|
ID string
|
|
Name string
|
|
}
|
|
|
|
type userBoxView struct {
|
|
ID string
|
|
Title string
|
|
CollectionID string
|
|
CollectionName string
|
|
FileCount int
|
|
Size string
|
|
CreatedAt string
|
|
ExpiresAt string
|
|
URL string
|
|
}
|
|
|
|
func (a *App) Dashboard(w http.ResponseWriter, r *http.Request) {
|
|
user, ok := a.requireUser(w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
collections, err := a.authService.ListCollections(user.ID)
|
|
if err != nil {
|
|
http.Error(w, "unable to load collections", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
collectionNames := map[string]string{}
|
|
collectionViews := make([]collectionView, 0, len(collections))
|
|
for _, collection := range collections {
|
|
collectionNames[collection.ID] = collection.Name
|
|
collectionViews = append(collectionViews, collectionView{ID: collection.ID, Name: collection.Name})
|
|
}
|
|
boxes, err := a.uploadService.UserBoxes(user.ID, collectionNames)
|
|
if err != nil {
|
|
http.Error(w, "unable to load boxes", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
storageUsed, err := a.uploadService.UserStorageUsed(user.ID)
|
|
if err != nil {
|
|
http.Error(w, "unable to load storage usage", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
selected := r.URL.Query().Get("collection")
|
|
boxViews := make([]userBoxView, 0, len(boxes))
|
|
for _, row := range boxes {
|
|
if selected != "" && row.Box.CollectionID != selected {
|
|
continue
|
|
}
|
|
title := row.Box.Title
|
|
if title == "" {
|
|
title = fmt.Sprintf("%d file upload", len(row.Box.Files))
|
|
}
|
|
boxViews = append(boxViews, userBoxView{
|
|
ID: row.Box.ID,
|
|
Title: title,
|
|
CollectionID: row.Box.CollectionID,
|
|
CollectionName: row.CollectionName,
|
|
FileCount: len(row.Box.Files),
|
|
Size: row.TotalSizeLabel,
|
|
CreatedAt: row.Box.CreatedAt.Format("Jan 2 15:04"),
|
|
ExpiresAt: boxExpiryLabel(row.Box.ExpiresAt, "Jan 2 15:04"),
|
|
URL: "/d/" + row.Box.ID,
|
|
})
|
|
}
|
|
|
|
a.renderPage(w, r, http.StatusOK, "dashboard.html", web.PageData{
|
|
Title: "My files",
|
|
Description: "Your Warpbox personal file space.",
|
|
CurrentUser: a.authService.PublicUser(user),
|
|
Data: dashboardData{
|
|
User: a.authService.PublicUser(user),
|
|
Collections: collectionViews,
|
|
Boxes: boxViews,
|
|
StorageUsed: helpers.FormatBytes(storageUsed),
|
|
MaxUploadSize: a.uploadService.MaxUploadSizeLabel(),
|
|
Selected: selected,
|
|
},
|
|
})
|
|
}
|
|
|
|
func (a *App) CreateCollection(w http.ResponseWriter, r *http.Request) {
|
|
user, ok := a.requireUser(w, r)
|
|
if !ok || !a.validateCSRF(w, r) {
|
|
return
|
|
}
|
|
if err := r.ParseForm(); err != nil {
|
|
http.Redirect(w, r, "/app", http.StatusSeeOther)
|
|
return
|
|
}
|
|
if _, err := a.authService.CreateCollection(user.ID, r.FormValue("name")); err != nil {
|
|
a.logger.Warn("collection create failed", "source", "user_activity", "severity", "warn", "code", 4410, "user_id", user.ID, "error", err.Error())
|
|
} else {
|
|
a.logger.Info("collection created", "source", "user_activity", "severity", "user_activity", "code", 2410, "user_id", user.ID, "name", r.FormValue("name"))
|
|
}
|
|
http.Redirect(w, r, "/app", http.StatusSeeOther)
|
|
}
|
|
|
|
func (a *App) RenameUserBox(w http.ResponseWriter, r *http.Request) {
|
|
user, ok := a.requireUser(w, r)
|
|
if !ok || !a.validateCSRF(w, r) {
|
|
return
|
|
}
|
|
if err := r.ParseForm(); err != nil {
|
|
http.Redirect(w, r, "/app", http.StatusSeeOther)
|
|
return
|
|
}
|
|
if err := a.uploadService.RenameOwnedBox(r.PathValue("boxID"), user.ID, r.FormValue("title")); err != nil {
|
|
a.logger.Warn("owned box rename failed", "source", "user_activity", "severity", "warn", "code", 4411, "user_id", user.ID, "box_id", r.PathValue("boxID"), "error", err.Error())
|
|
a.handleUserBoxError(w, r, err)
|
|
return
|
|
}
|
|
a.logger.Info("owned box renamed", "source", "user_activity", "severity", "user_activity", "code", 2411, "user_id", user.ID, "box_id", r.PathValue("boxID"))
|
|
http.Redirect(w, r, "/app", http.StatusSeeOther)
|
|
}
|
|
|
|
func (a *App) MoveUserBox(w http.ResponseWriter, r *http.Request) {
|
|
user, ok := a.requireUser(w, r)
|
|
if !ok || !a.validateCSRF(w, r) {
|
|
return
|
|
}
|
|
if err := r.ParseForm(); err != nil {
|
|
http.Redirect(w, r, "/app", http.StatusSeeOther)
|
|
return
|
|
}
|
|
collectionID := r.FormValue("collection_id")
|
|
if !a.authService.CollectionOwnedBy(collectionID, user.ID) {
|
|
a.logger.Warn("owned box move invalid collection", "source", "user_activity", "severity", "warn", "code", 4412, "user_id", user.ID, "box_id", r.PathValue("boxID"), "collection_id", collectionID)
|
|
http.Error(w, "collection not found", http.StatusForbidden)
|
|
return
|
|
}
|
|
if err := a.uploadService.MoveOwnedBox(r.PathValue("boxID"), user.ID, collectionID); err != nil {
|
|
a.logger.Warn("owned box move failed", "source", "user_activity", "severity", "warn", "code", 4413, "user_id", user.ID, "box_id", r.PathValue("boxID"), "error", err.Error())
|
|
a.handleUserBoxError(w, r, err)
|
|
return
|
|
}
|
|
a.logger.Info("owned box moved", "source", "user_activity", "severity", "user_activity", "code", 2412, "user_id", user.ID, "box_id", r.PathValue("boxID"), "collection_id", collectionID)
|
|
http.Redirect(w, r, "/app", http.StatusSeeOther)
|
|
}
|
|
|
|
func (a *App) DeleteUserBox(w http.ResponseWriter, r *http.Request) {
|
|
user, ok := a.requireUser(w, r)
|
|
if !ok || !a.validateCSRF(w, r) {
|
|
return
|
|
}
|
|
if err := a.uploadService.DeleteOwnedBox(r.PathValue("boxID"), user.ID); err != nil {
|
|
a.logger.Warn("owned box delete failed", "source", "user_activity", "severity", "warn", "code", 4414, "user_id", user.ID, "box_id", r.PathValue("boxID"), "error", err.Error())
|
|
a.handleUserBoxError(w, r, err)
|
|
return
|
|
}
|
|
a.logger.Info("owned box deleted", "source", "user_activity", "severity", "user_activity", "code", 2413, "user_id", user.ID, "box_id", r.PathValue("boxID"))
|
|
http.Redirect(w, r, "/app", http.StatusSeeOther)
|
|
}
|
|
|
|
func (a *App) handleUserBoxError(w http.ResponseWriter, r *http.Request, err error) {
|
|
if os.IsPermission(err) {
|
|
http.Error(w, "not allowed", http.StatusForbidden)
|
|
return
|
|
}
|
|
if os.IsNotExist(err) {
|
|
http.NotFound(w, r)
|
|
return
|
|
}
|
|
http.Error(w, "unable to update box", http.StatusInternalServerError)
|
|
}
|