package handlers import ( "net/http" "net/url" "strings" "time" "amnezia-share/internal/models" "amnezia-share/internal/share" "amnezia-share/internal/web" ) func flashFromQuery(r *http.Request) (ok, errText string) { return r.URL.Query().Get("ok"), r.URL.Query().Get("err") } func redirectWithFlash(w http.ResponseWriter, r *http.Request, a *web.App, path string, ok, errText string) { u, _ := url.Parse(a.Cfg.AppURL(path)) q := u.Query() if ok != "" { q.Set("ok", ok) } if errText != "" { q.Set("err", errText) } u.RawQuery = q.Encode() http.Redirect(w, r, u.String(), http.StatusSeeOther) } const adminPerPage = 20 type linkRow struct { models.ShareLink Status share.LinkStatus ActiveConfigs int } type adminLinksView struct { adminLayout Links []linkRow StatusFilter string Page int Total int PerPage int Detail *models.ShareLink DetailStatus share.LinkStatus Creations []models.ShareCreation DeletedCreations []models.ShareCreation AllowedDurations []int AllowedServerIDs []int AllServers []models.ServerInfo } // AdminLinksGet lists share links (paginated, filterable by status) and, when // ?id= is present, shows one link's detail with its creations (share_links.php). func AdminLinksGet(a *web.App) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() ok, errText := flashFromQuery(r) view := adminLinksView{ adminLayout: newAdminLayout(a, r, "links"), StatusFilter: r.URL.Query().Get("status"), Page: queryInt(r, "page", 1), PerPage: adminPerPage, AllowedDurations: share.AllowedDurations, } view.OK, view.Error = ok, errText if view.Page < 1 { view.Page = 1 } links, err := a.Share.ListLinks(ctx, view.Page, view.PerPage, view.StatusFilter) if err != nil { view.Error = err.Error() } view.Total, _ = a.Share.CountLinks(ctx, view.StatusFilter) for _, l := range links { creations, _ := a.Share.ListCreationsForLink(ctx, l.ID) view.Links = append(view.Links, linkRow{ ShareLink: l, Status: share.ClassifyLinkStatus(l, len(creations) > 0), ActiveConfigs: len(creations), }) } if id := queryInt(r, "id", 0); id > 0 { if link, err := a.Share.LinkByID(ctx, id); err == nil && link != nil { view.Detail = link creations, _ := a.Share.ListCreationsForLink(ctx, id) view.DetailStatus = share.ClassifyLinkStatus(*link, len(creations) > 0) view.Creations = creations view.DeletedCreations, _ = a.Share.ListDeletedCreationsForLink(ctx, id) view.AllowedServerIDs = share.ParseAllowedServerIDs(link.AllowedServerIDs) view.AllServers = share.BuildGuestServers(ctx, a.Settings, a.Settings.ServerLabels(ctx)) } } a.Render(w, r, "page_admin_links", view) } } func parseFormDate(v string) *time.Time { v = strings.TrimSpace(v) if v == "" { return nil } layouts := []string{"2006-01-02T15:04", "2006-01-02", time.RFC3339} for _, layout := range layouts { if t, err := time.Parse(layout, v); err == nil { return &t } } return nil } // AdminLinksPost handles link create/update/delete/extend/allowed-servers and // per-creation deletion. func AdminLinksPost(a *web.App) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() action := r.FormValue("action") back := "admin/links" if id := r.FormValue("back_id"); id != "" { back += "?id=" + url.QueryEscape(id) } var err error var okMsg string switch action { case "create": days := formInt(r, "days", 30) maxUses := formInt(r, "max_uses", 5) subUntil := parseFormDate(r.FormValue("subscription_until")) link, cerr := a.Share.CreateLink(ctx, days, maxUses, subUntil) err = cerr if err == nil { okMsg = "Ссылка создана: #" + itoaHelper(link.ID) } case "update": linkID := formInt(r, "link_id", 0) validityDays := formInt(r, "validity_days", 30) floating := r.FormValue("floating_expiry") == "1" expiresAt := parseFormDate(r.FormValue("expires_at")) subUntil := parseFormDate(r.FormValue("subscription_until")) maxUses := formInt(r, "max_uses", 5) err = a.Share.UpdateLink(ctx, linkID, validityDays, floating, expiresAt, subUntil, maxUses) if err == nil { okMsg = "Срок ссылки обновлён." } case "delete": linkID := formInt(r, "link_id", 0) err = a.Share.DeleteLink(ctx, linkID) back = "admin/links" if err == nil { okMsg = "Ссылка удалена." } case "extend": linkID := formInt(r, "link_id", 0) days := formInt(r, "days", 30) err = a.Share.ExtendLinkByDays(ctx, linkID, days) if err == nil { okMsg = "Ссылка продлена." } case "set_allowed_servers": linkID := formInt(r, "link_id", 0) _ = r.ParseForm() var ids []int for _, v := range r.Form["server_ids[]"] { if n := formIntFromString(v, -1); n >= 0 { ids = append(ids, n) } } err = a.Share.SetAllowedServerIDs(ctx, linkID, ids) if err == nil { okMsg = "Список серверов обновлён." } case "delete_creation": linkID := formInt(r, "link_id", 0) creationID := formInt64(r, "creation_id", 0) err = a.Share.SoftDeleteCreation(ctx, a.Panel, a.Settings, linkID, creationID) if err == nil { okMsg = "Конфигурация удалена." } default: err = errUnknownAction } errText := "" if err != nil { errText = err.Error() } redirectWithFlash(w, r, a, back, okMsg, errText) } }