Add Go rewrite with Postgres 17 and Dokploy Docker Compose

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
orohi
2026-07-27 08:21:21 +03:00
co-authored by Cursor
parent bac78dd3fc
commit 99a27f00be
67 changed files with 17864 additions and 0 deletions
+252
View File
@@ -0,0 +1,252 @@
package handlers
import (
"errors"
"fmt"
"net/http"
"strings"
"amnezia-share/internal/i18n"
"amnezia-share/internal/member"
"amnezia-share/internal/models"
"amnezia-share/internal/share"
"amnezia-share/internal/web"
)
func registerCabinet(a *web.App, mux *http.ServeMux) {
mux.HandleFunc("GET /cabinet", a.RequireMember(CabinetGet(a)))
mux.HandleFunc("POST /cabinet", a.RequireMember(a.CSRFProtect(CabinetPost(a))))
mux.HandleFunc("GET /cabinet/login", CabinetLoginForm(a))
mux.HandleFunc("POST /cabinet/login", a.CSRFProtect(CabinetLoginSubmit(a)))
mux.HandleFunc("GET /cabinet/register", CabinetRegisterForm(a))
mux.HandleFunc("POST /cabinet/register", a.CSRFProtect(CabinetRegisterSubmit(a)))
mux.HandleFunc("GET /cabinet/logout", CabinetLogout(a))
mux.HandleFunc("GET /cabinet/servers", CabinetServers(a))
mux.HandleFunc("GET /cabinet/download", CabinetDownload(a))
}
type cabinetView struct {
Member *models.Member
Sub *models.MemberSubscription
SubLabel string
ConfigsRemaining int
Servers []models.ServerInfo
Bundles []share.Bundle
Maintenance bool
Expired bool
LimitReached bool
OK string
Error string
}
func renderCabinet(a *web.App, w http.ResponseWriter, r *http.Request, okMsg, errText string) {
ctx := r.Context()
m := a.CurrentMember(r)
sub, err := a.Member.EnsureSubscription(ctx, a.Settings, m.ID)
if err != nil {
errText = err.Error()
}
bundles, _ := a.Member.Bundles(ctx, m.ID)
servers := share.BuildGuestServers(ctx, a.Settings, a.Settings.ServerLabels(ctx))
view := cabinetView{
Member: m,
Sub: sub,
Servers: servers,
Bundles: bundles,
Maintenance: a.Settings.Maintenance(ctx),
OK: okMsg,
Error: errText,
}
if sub != nil {
view.Expired = member.SubscriptionExpired(*sub)
view.LimitReached = sub.ConfigCount >= sub.MaxConfigs
view.SubLabel = member.SubscriptionExpiresLabel(*sub)
view.ConfigsRemaining = sub.MaxConfigs - sub.ConfigCount
}
a.Render(w, r, "page_cabinet", view)
}
// CabinetGet renders the member portal dashboard (cabinet.php GET).
func CabinetGet(a *web.App) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
renderCabinet(a, w, r, "", "")
}
}
// CabinetPost handles member actions: create / migrate / renew / delete
// (cabinet.php POST).
func CabinetPost(a *web.App) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
m := a.CurrentMember(r)
lang := i18n.Resolve(r, a.Sessions)
maintenance := a.Settings.Maintenance(ctx)
var actionErr error
var okMsg string
switch r.FormValue("action") {
case "create":
if maintenance {
actionErr = errors.New(i18n.T(lang, "err_maintenance_mode", nil))
break
}
serverID := formInt(r, "server_id", -1)
protocol := strings.TrimSpace(r.FormValue("protocol"))
_, err := a.Member.TryAddConfig(ctx, a.Panel, a.Settings, m.ID, serverID, protocol)
if err != nil {
actionErr = err
break
}
okMsg = "Готово! Конфигурация создана."
case "migrate":
if maintenance {
actionErr = errors.New(i18n.T(lang, "err_maintenance_mode", nil))
break
}
configID := formInt64(r, "config_id", 0)
newServerID := formInt(r, "new_server_id", -1)
newProtocol := strings.TrimSpace(r.FormValue("new_protocol"))
if err := a.Member.TryMigrate(ctx, a.Panel, a.Settings, m.ID, configID, newServerID, newProtocol); err != nil {
actionErr = err
break
}
okMsg = i18n.T(lang, "ok_migrated", nil)
case "renew":
code := r.FormValue("code")
res, err := a.Member.TryRedeemRenewal(ctx, m.ID, code)
if err != nil {
actionErr = err
break
}
until := ""
if res.Sub != nil {
until = member.SubscriptionExpiresLabel(*res.Sub)
}
okMsg = i18n.T(lang, "ok_renewed", map[string]string{"days": fmt.Sprint(res.AddDays), "until": until})
case "delete":
configID := formInt64(r, "config_id", 0)
if err := a.Member.SoftDelete(ctx, a.Panel, a.Settings, m.ID, configID); err != nil {
actionErr = err
break
}
okMsg = "Конфигурация удалена."
default:
actionErr = errors.New("Неизвестное действие.")
}
errText := ""
if actionErr != nil {
errText = i18n.TranslateGuestError(lang, actionErr.Error())
}
if wantsJSON(r) {
if errText != "" {
writeJSONError(w, http.StatusBadRequest, errText)
return
}
writeJSONOK(w, map[string]any{"message": okMsg})
return
}
renderCabinet(a, w, r, okMsg, errText)
}
}
type cabinetAuthView struct {
Error string
}
// CabinetLoginForm renders the member login form.
func CabinetLoginForm(a *web.App) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if a.CurrentMember(r) != nil {
redirectTo(w, r, a, "cabinet")
return
}
a.Render(w, r, "page_cabinet_auth", cabinetAuthView{})
}
}
// CabinetLoginSubmit verifies member credentials and signs them in.
func CabinetLoginSubmit(a *web.App) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
username := strings.TrimSpace(r.FormValue("username"))
password := r.FormValue("password")
m, err := a.Member.Login(ctx, username, password)
if err != nil {
a.Render(w, r, "page_cabinet_auth", cabinetAuthView{Error: err.Error()})
return
}
_ = a.Sessions.RenewToken(ctx)
a.Sessions.Put(ctx, web.SessionMemberID, m.ID)
redirectTo(w, r, a, "cabinet")
}
}
// CabinetRegisterForm renders the member self-registration form.
func CabinetRegisterForm(a *web.App) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if a.CurrentMember(r) != nil {
redirectTo(w, r, a, "cabinet")
return
}
a.Render(w, r, "page_cabinet_auth", cabinetAuthView{})
}
}
// CabinetRegisterSubmit creates a new member account and signs them in.
func CabinetRegisterSubmit(a *web.App) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
username := strings.TrimSpace(r.FormValue("username"))
password := r.FormValue("password")
email := strings.TrimSpace(r.FormValue("email"))
m, err := a.Member.Register(ctx, username, password, email)
if err != nil {
a.Render(w, r, "page_cabinet_auth", cabinetAuthView{Error: err.Error()})
return
}
if _, err := a.Member.EnsureSubscription(ctx, a.Settings, m.ID); err != nil {
a.Render(w, r, "page_cabinet_auth", cabinetAuthView{Error: err.Error()})
return
}
_ = a.Sessions.RenewToken(ctx)
a.Sessions.Put(ctx, web.SessionMemberID, m.ID)
redirectTo(w, r, a, "cabinet")
}
}
// CabinetLogout clears the member session.
func CabinetLogout(a *web.App) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
a.Sessions.Remove(r.Context(), web.SessionMemberID)
_ = a.Sessions.RenewToken(r.Context())
redirectTo(w, r, a, "cabinet/login")
}
}
// CabinetServers returns the server catalog as JSON for the member portal.
func CabinetServers(a *web.App) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
servers := share.BuildGuestServers(ctx, a.Settings, a.Settings.ServerLabels(ctx))
writeJSONOK(w, map[string]any{"servers": servers})
}
}
// CabinetDownload streams a single downloadable artifact for one of the
// member's configs.
func CabinetDownload(a *web.App) http.HandlerFunc {
return a.RequireMember(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
m := a.CurrentMember(r)
configID := int64(queryInt(r, "cre", 0))
part := r.URL.Query().Get("part")
fp, err := a.Member.DownloadPayload(ctx, m.ID, configID, part)
if err != nil || fp == nil {
http.Error(w, "Файл не найден.", http.StatusNotFound)
return
}
serveFilePart(w, fp)
})
}