refactor: split sqlite.go and server.go by domain (FLAW-20)
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m31s
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m31s
Pure moves, no behaviour change: the storage layer now has users, apikeys, thoughts, tasklists, documents, folders, tags, lists, teams and system files next to sqlite.go (store, schema, migrations); the API has handlers_*.go per area plus realtime.go and alarms.go next to server.go (server, router, middleware). Declarations were moved verbatim with an AST-based tool; imports pruned with goimports. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
7bba174454
commit
74c394efaa
26 files changed
+7702
-7467
No files matched your search
@@ -0,0 +1,340 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/sandrodach/mindsift/internal/auth"
|
||||
"github.com/sandrodach/mindsift/internal/domain"
|
||||
)
|
||||
|
||||
// --- ORGANIZATIONS & TEAMS ---
|
||||
|
||||
func (s *Server) handleAdminGetOrganizations(w http.ResponseWriter, r *http.Request) {
|
||||
orgs, err := s.store.GetOrganizations()
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, orgs)
|
||||
}
|
||||
|
||||
func (s *Server) handleAdminCreateOrganization(w http.ResponseWriter, r *http.Request) {
|
||||
var req struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeError(w, http.StatusBadRequest, "Ungültiges JSON")
|
||||
return
|
||||
}
|
||||
org, err := s.store.CreateOrganization(req.Name)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusCreated, org)
|
||||
}
|
||||
|
||||
func (s *Server) handleAdminRenameOrganization(w http.ResponseWriter, r *http.Request) {
|
||||
var req struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeError(w, http.StatusBadRequest, "Ungültiges JSON")
|
||||
return
|
||||
}
|
||||
if err := s.store.RenameOrganization(chi.URLParam(r, "id"), req.Name); err != nil {
|
||||
writeError(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, map[string]bool{"updated": true})
|
||||
}
|
||||
|
||||
func (s *Server) handleAdminDeleteOrganization(w http.ResponseWriter, r *http.Request) {
|
||||
if err := s.store.DeleteOrganization(chi.URLParam(r, "id")); err != nil {
|
||||
writeError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, map[string]bool{"deleted": true})
|
||||
}
|
||||
|
||||
func (s *Server) handleAdminSetUserOrganization(w http.ResponseWriter, r *http.Request) {
|
||||
var req struct {
|
||||
OrganizationID string `json:"organizationId"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeError(w, http.StatusBadRequest, "Ungültiges JSON")
|
||||
return
|
||||
}
|
||||
if err := s.store.SetUserOrganization(chi.URLParam(r, "id"), req.OrganizationID); err != nil {
|
||||
writeError(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, map[string]bool{"updated": true})
|
||||
}
|
||||
|
||||
// handleAdminSharingImpact counts the shares and group memberships a switch to ?mode= would remove.
|
||||
func (s *Server) handleAdminSharingImpact(w http.ResponseWriter, r *http.Request) {
|
||||
n, err := s.store.PruneSharing(r.URL.Query().Get("mode"), true)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, map[string]int64{"affected": n})
|
||||
}
|
||||
|
||||
// --- ADMIN & STAFF PANEL ---
|
||||
|
||||
func (s *Server) handleAdminGetUsers(w http.ResponseWriter, r *http.Request) {
|
||||
users, err := s.store.GetAllUsers()
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, users)
|
||||
}
|
||||
|
||||
func (s *Server) handleAdminCreateUser(w http.ResponseWriter, r *http.Request) {
|
||||
callerID := auth.GetUserID(r.Context())
|
||||
caller, _ := s.store.GetUserByID(callerID)
|
||||
|
||||
var req struct {
|
||||
Email string `json:"email"`
|
||||
Password string `json:"password"`
|
||||
DisplayName string `json:"displayName"`
|
||||
Role string `json:"role"`
|
||||
OrganizationID string `json:"organizationId"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeError(w, http.StatusBadRequest, "Ungültiges JSON")
|
||||
return
|
||||
}
|
||||
if req.Email == "" || req.Password == "" {
|
||||
writeError(w, http.StatusBadRequest, "E-Mail und Passwort erforderlich")
|
||||
return
|
||||
}
|
||||
// Only full server admin can create another server admin
|
||||
if req.Role == "admin" && (caller == nil || caller.Role != "admin") {
|
||||
req.Role = "member"
|
||||
}
|
||||
if strings.TrimSpace(req.DisplayName) == "" {
|
||||
req.DisplayName = strings.Split(req.Email, "@")[0]
|
||||
}
|
||||
|
||||
hash, err := auth.HashPassword(req.Password)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, "Passwort-Hash fehlgeschlagen")
|
||||
return
|
||||
}
|
||||
u, err := s.store.CreateUser(req.Email, hash, req.DisplayName, req.Role)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
if req.OrganizationID != "" {
|
||||
if err := s.store.SetUserOrganization(u.ID, req.OrganizationID); err == nil {
|
||||
u.OrganizationID = req.OrganizationID
|
||||
}
|
||||
}
|
||||
writeJSON(w, http.StatusCreated, u)
|
||||
}
|
||||
|
||||
func (s *Server) handleAdminUpdateUser(w http.ResponseWriter, r *http.Request) {
|
||||
callerID := auth.GetUserID(r.Context())
|
||||
caller, _ := s.store.GetUserByID(callerID)
|
||||
targetUserID := chi.URLParam(r, "id")
|
||||
targetUser, err := s.store.GetUserByID(targetUserID)
|
||||
if err != nil || targetUser == nil {
|
||||
writeError(w, http.StatusNotFound, "Benutzer nicht gefunden")
|
||||
return
|
||||
}
|
||||
|
||||
// Team-Admins cannot modify Server-Admins
|
||||
if caller != nil && caller.Role != "admin" && targetUser.Role == "admin" {
|
||||
writeError(w, http.StatusForbidden, "Nur Server-Administratoren können Server-Admin-Konten bearbeiten")
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
DisplayName *string `json:"displayName"`
|
||||
Email *string `json:"email"`
|
||||
Role *string `json:"role"`
|
||||
IsActive *bool `json:"isActive"`
|
||||
DiscordWebhookURL *string `json:"discordWebhookUrl"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeError(w, http.StatusBadRequest, "Ungültiges JSON")
|
||||
return
|
||||
}
|
||||
if req.Role != nil && *req.Role == "admin" && (caller == nil || caller.Role != "admin") {
|
||||
writeError(w, http.StatusForbidden, "Nur Server-Administratoren können die Rolle Server-Admin vergeben")
|
||||
return
|
||||
}
|
||||
|
||||
if req.DiscordWebhookURL != nil {
|
||||
if err := validateWebhookURL(*req.DiscordWebhookURL, true, s.allowPrivateWebhooks()); err != nil {
|
||||
writeError(w, http.StatusBadRequest, "Discord-Webhook: "+err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
u, err := s.store.UpdateUser(targetUserID, req.DisplayName, req.Email, req.Role, req.IsActive, req.DiscordWebhookURL, nil)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, u)
|
||||
}
|
||||
|
||||
func (s *Server) handleAdminResetPassword(w http.ResponseWriter, r *http.Request) {
|
||||
callerID := auth.GetUserID(r.Context())
|
||||
caller, _ := s.store.GetUserByID(callerID)
|
||||
targetUserID := chi.URLParam(r, "id")
|
||||
targetUser, _ := s.store.GetUserByID(targetUserID)
|
||||
if caller != nil && caller.Role != "admin" && targetUser != nil && targetUser.Role == "admin" {
|
||||
writeError(w, http.StatusForbidden, "Keine Berechtigung")
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
Password string `json:"password"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil || strings.TrimSpace(req.Password) == "" {
|
||||
writeError(w, http.StatusBadRequest, "Neues Passwort erforderlich")
|
||||
return
|
||||
}
|
||||
hash, err := auth.HashPassword(req.Password)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, "Passwort-Hash fehlgeschlagen")
|
||||
return
|
||||
}
|
||||
if err := s.store.UpdateUserPassword(targetUserID, hash); err != nil {
|
||||
writeError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, map[string]bool{"success": true})
|
||||
}
|
||||
|
||||
func (s *Server) handleAdminDeleteUser(w http.ResponseWriter, r *http.Request) {
|
||||
targetUserID := chi.URLParam(r, "id")
|
||||
currUserID := auth.GetUserID(r.Context())
|
||||
if targetUserID == currUserID {
|
||||
writeError(w, http.StatusBadRequest, "Sie können Ihren eigenen Benutzer nicht löschen")
|
||||
return
|
||||
}
|
||||
caller, _ := s.store.GetUserByID(currUserID)
|
||||
targetUser, _ := s.store.GetUserByID(targetUserID)
|
||||
if caller != nil && caller.Role != "admin" && targetUser != nil && targetUser.Role == "admin" {
|
||||
writeError(w, http.StatusForbidden, "Keine Berechtigung zum Löschen eines Server-Admins")
|
||||
return
|
||||
}
|
||||
if err := s.store.DeleteUser(targetUserID); err != nil {
|
||||
writeError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, map[string]bool{"deleted": true})
|
||||
}
|
||||
|
||||
// --- INVITES MANAGEMENT ---
|
||||
|
||||
func (s *Server) handleAdminGetInvites(w http.ResponseWriter, r *http.Request) {
|
||||
invites, err := s.store.GetUserInvites()
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, invites)
|
||||
}
|
||||
|
||||
func (s *Server) handleAdminCreateInvite(w http.ResponseWriter, r *http.Request) {
|
||||
callerID := auth.GetUserID(r.Context())
|
||||
caller, _ := s.store.GetUserByID(callerID)
|
||||
|
||||
var req struct {
|
||||
Email string `json:"email"`
|
||||
Role string `json:"role"`
|
||||
OrganizationID string `json:"organizationId"`
|
||||
ValidDays int `json:"validDays"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeError(w, http.StatusBadRequest, "Ungültiges JSON")
|
||||
return
|
||||
}
|
||||
if req.Role == "admin" && (caller == nil || caller.Role != "admin") {
|
||||
req.Role = "member"
|
||||
}
|
||||
inv, err := s.store.CreateUserInvite(callerID, req.Email, req.Role, req.OrganizationID, req.ValidDays)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusCreated, inv)
|
||||
}
|
||||
|
||||
func (s *Server) handleAdminDeleteInvite(w http.ResponseWriter, r *http.Request) {
|
||||
inviteID := chi.URLParam(r, "id")
|
||||
if err := s.store.DeleteUserInvite(inviteID); err != nil {
|
||||
writeError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, map[string]bool{"deleted": true})
|
||||
}
|
||||
|
||||
// --- SERVER SETTINGS & SMTP ---
|
||||
|
||||
func (s *Server) handleAdminGetSettings(w http.ResponseWriter, r *http.Request) {
|
||||
settings, err := s.store.GetSystemSettings()
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
settings.SMTPConfiguredViaEnv = strings.TrimSpace(s.cfg.SMTPHost) != ""
|
||||
writeJSON(w, http.StatusOK, settings)
|
||||
}
|
||||
|
||||
func (s *Server) handleAdminUpdateSettings(w http.ResponseWriter, r *http.Request) {
|
||||
var settings domain.SystemSettings
|
||||
if err := json.NewDecoder(r.Body).Decode(&settings); err != nil {
|
||||
writeError(w, http.StatusBadRequest, "Ungültiges JSON")
|
||||
return
|
||||
}
|
||||
previousMode := s.store.SharingMode()
|
||||
if err := s.store.UpdateSystemSettings(&settings); err != nil {
|
||||
writeError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
// A stricter mode takes away what is no longer allowed (shares, group memberships, assignments)
|
||||
if settings.SharingMode != previousMode {
|
||||
_, _ = s.store.PruneSharing(settings.SharingMode, false)
|
||||
}
|
||||
settings.SMTPConfiguredViaEnv = strings.TrimSpace(s.cfg.SMTPHost) != ""
|
||||
writeJSON(w, http.StatusOK, settings)
|
||||
}
|
||||
|
||||
func (s *Server) handleAdminTestSMTP(w http.ResponseWriter, r *http.Request) {
|
||||
userID := auth.GetUserID(r.Context())
|
||||
user, _ := s.store.GetUserByID(userID)
|
||||
|
||||
var req struct {
|
||||
ToEmail string `json:"toEmail"`
|
||||
}
|
||||
_ = json.NewDecoder(r.Body).Decode(&req)
|
||||
toEmail := strings.TrimSpace(req.ToEmail)
|
||||
if toEmail == "" && user != nil {
|
||||
toEmail = user.Email
|
||||
}
|
||||
if toEmail == "" {
|
||||
writeError(w, http.StatusBadRequest, "Ziel-E-Mail-Adresse erforderlich")
|
||||
return
|
||||
}
|
||||
|
||||
if err := s.sendEmailNotification(toEmail, "MindSift SMTP-Server Test", "Die SMTP-Konfiguration deiner MindSift-Instanz funktioniert einwandfrei!"); err != nil {
|
||||
writeError(w, http.StatusBadGateway, "SMTP-Fehler: "+err.Error())
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, map[string]interface{}{
|
||||
"sent": true,
|
||||
"toEmail": toEmail,
|
||||
})
|
||||
}
|
||||
Reference in new issue
Block a user