2026-05-30 15:42:35 +03:00
|
|
|
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: row.Box.ExpiresAt.Format("Jan 2 15:04"),
|
|
|
|
|
URL: "/d/" + row.Box.ID,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-30 17:23:20 +03:00
|
|
|
a.renderPage(w, r, http.StatusOK, "dashboard.html", web.PageData{
|
2026-05-30 15:42:35 +03:00
|
|
|
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)
|
2026-05-31 02:14:10 +03:00
|
|
|
if !ok || !a.validateCSRF(w, r) {
|
2026-05-30 15:42:35 +03:00
|
|
|
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())
|
|
|
|
|
}
|
|
|
|
|
http.Redirect(w, r, "/app", http.StatusSeeOther)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (a *App) RenameUserBox(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
user, ok := a.requireUser(w, r)
|
2026-05-31 02:14:10 +03:00
|
|
|
if !ok || !a.validateCSRF(w, r) {
|
2026-05-30 15:42:35 +03:00
|
|
|
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.handleUserBoxError(w, r, err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
http.Redirect(w, r, "/app", http.StatusSeeOther)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (a *App) MoveUserBox(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
user, ok := a.requireUser(w, r)
|
2026-05-31 02:14:10 +03:00
|
|
|
if !ok || !a.validateCSRF(w, r) {
|
2026-05-30 15:42:35 +03:00
|
|
|
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) {
|
|
|
|
|
http.Error(w, "collection not found", http.StatusForbidden)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if err := a.uploadService.MoveOwnedBox(r.PathValue("boxID"), user.ID, collectionID); err != nil {
|
|
|
|
|
a.handleUserBoxError(w, r, err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
http.Redirect(w, r, "/app", http.StatusSeeOther)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (a *App) DeleteUserBox(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
user, ok := a.requireUser(w, r)
|
2026-05-31 02:14:10 +03:00
|
|
|
if !ok || !a.validateCSRF(w, r) {
|
2026-05-30 15:42:35 +03:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if err := a.uploadService.DeleteOwnedBox(r.PathValue("boxID"), user.ID); err != nil {
|
|
|
|
|
a.handleUserBoxError(w, r, err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
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)
|
|
|
|
|
}
|