This commit is contained in:
@@ -5,8 +5,12 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"gitea.avt.data-center.id/othman.suseno/atlas/internal/auth"
|
||||
"gitea.avt.data-center.id/othman.suseno/atlas/internal/models"
|
||||
"gitea.avt.data-center.id/othman.suseno/atlas/internal/storage"
|
||||
)
|
||||
|
||||
// pathParam is now in router_helpers.go
|
||||
@@ -453,87 +457,496 @@ func (a *App) handleDeleteSnapshotPolicy(w http.ResponseWriter, r *http.Request)
|
||||
|
||||
// SMB Share Handlers
|
||||
func (a *App) handleListSMBShares(w http.ResponseWriter, r *http.Request) {
|
||||
shares := []models.SMBShare{} // Stub
|
||||
shares := a.smbStore.List()
|
||||
writeJSON(w, http.StatusOK, shares)
|
||||
}
|
||||
|
||||
func (a *App) handleCreateSMBShare(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(w, http.StatusNotImplemented, map[string]string{"error": "not implemented"})
|
||||
var req struct {
|
||||
Name string `json:"name"`
|
||||
Path string `json:"path"`
|
||||
Dataset string `json:"dataset"`
|
||||
Description string `json:"description"`
|
||||
ReadOnly bool `json:"read_only"`
|
||||
GuestOK bool `json:"guest_ok"`
|
||||
ValidUsers []string `json:"valid_users"`
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid request body"})
|
||||
return
|
||||
}
|
||||
|
||||
if req.Name == "" || req.Dataset == "" {
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "name and dataset are required"})
|
||||
return
|
||||
}
|
||||
|
||||
// Validate dataset exists
|
||||
datasets, err := a.zfs.ListDatasets("")
|
||||
if err != nil {
|
||||
log.Printf("list datasets error: %v", err)
|
||||
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": "failed to validate dataset"})
|
||||
return
|
||||
}
|
||||
|
||||
datasetExists := false
|
||||
for _, ds := range datasets {
|
||||
if ds.Name == req.Dataset {
|
||||
datasetExists = true
|
||||
if req.Path == "" {
|
||||
req.Path = ds.Mountpoint
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !datasetExists {
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "dataset not found"})
|
||||
return
|
||||
}
|
||||
|
||||
share, err := a.smbStore.Create(req.Name, req.Path, req.Dataset, req.Description, req.ReadOnly, req.GuestOK, req.ValidUsers)
|
||||
if err != nil {
|
||||
if err == storage.ErrSMBShareExists {
|
||||
writeJSON(w, http.StatusConflict, map[string]string{"error": "share name already exists"})
|
||||
return
|
||||
}
|
||||
log.Printf("create SMB share error: %v", err)
|
||||
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusCreated, share)
|
||||
}
|
||||
|
||||
func (a *App) handleGetSMBShare(w http.ResponseWriter, r *http.Request) {
|
||||
id := pathParam(r, "/api/v1/shares/smb/")
|
||||
writeJSON(w, http.StatusNotImplemented, map[string]string{"error": "not implemented", "id": id})
|
||||
if id == "" {
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "share id required"})
|
||||
return
|
||||
}
|
||||
|
||||
share, err := a.smbStore.Get(id)
|
||||
if err != nil {
|
||||
if err == storage.ErrSMBShareNotFound {
|
||||
writeJSON(w, http.StatusNotFound, map[string]string{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, share)
|
||||
}
|
||||
|
||||
func (a *App) handleUpdateSMBShare(w http.ResponseWriter, r *http.Request) {
|
||||
id := pathParam(r, "/api/v1/shares/smb/")
|
||||
writeJSON(w, http.StatusNotImplemented, map[string]string{"error": "not implemented", "id": id})
|
||||
if id == "" {
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "share id required"})
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
Description string `json:"description"`
|
||||
ReadOnly bool `json:"read_only"`
|
||||
GuestOK bool `json:"guest_ok"`
|
||||
ValidUsers []string `json:"valid_users"`
|
||||
Enabled bool `json:"enabled"`
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid request body"})
|
||||
return
|
||||
}
|
||||
|
||||
if err := a.smbStore.Update(id, req.Description, req.ReadOnly, req.GuestOK, req.Enabled, req.ValidUsers); err != nil {
|
||||
if err == storage.ErrSMBShareNotFound {
|
||||
writeJSON(w, http.StatusNotFound, map[string]string{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
log.Printf("update SMB share error: %v", err)
|
||||
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
share, _ := a.smbStore.Get(id)
|
||||
writeJSON(w, http.StatusOK, share)
|
||||
}
|
||||
|
||||
func (a *App) handleDeleteSMBShare(w http.ResponseWriter, r *http.Request) {
|
||||
id := pathParam(r, "/api/v1/shares/smb/")
|
||||
writeJSON(w, http.StatusNotImplemented, map[string]string{"error": "not implemented", "id": id})
|
||||
if id == "" {
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "share id required"})
|
||||
return
|
||||
}
|
||||
|
||||
if err := a.smbStore.Delete(id); err != nil {
|
||||
if err == storage.ErrSMBShareNotFound {
|
||||
writeJSON(w, http.StatusNotFound, map[string]string{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
log.Printf("delete SMB share error: %v", err)
|
||||
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, map[string]string{"message": "share deleted", "id": id})
|
||||
}
|
||||
|
||||
// NFS Export Handlers
|
||||
func (a *App) handleListNFSExports(w http.ResponseWriter, r *http.Request) {
|
||||
exports := []models.NFSExport{} // Stub
|
||||
exports := a.nfsStore.List()
|
||||
writeJSON(w, http.StatusOK, exports)
|
||||
}
|
||||
|
||||
func (a *App) handleCreateNFSExport(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(w, http.StatusNotImplemented, map[string]string{"error": "not implemented"})
|
||||
var req struct {
|
||||
Path string `json:"path"`
|
||||
Dataset string `json:"dataset"`
|
||||
Clients []string `json:"clients"`
|
||||
ReadOnly bool `json:"read_only"`
|
||||
RootSquash bool `json:"root_squash"`
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid request body"})
|
||||
return
|
||||
}
|
||||
|
||||
if req.Dataset == "" {
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "dataset is required"})
|
||||
return
|
||||
}
|
||||
|
||||
// Validate dataset exists
|
||||
datasets, err := a.zfs.ListDatasets("")
|
||||
if err != nil {
|
||||
log.Printf("list datasets error: %v", err)
|
||||
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": "failed to validate dataset"})
|
||||
return
|
||||
}
|
||||
|
||||
datasetExists := false
|
||||
for _, ds := range datasets {
|
||||
if ds.Name == req.Dataset {
|
||||
datasetExists = true
|
||||
if req.Path == "" {
|
||||
req.Path = ds.Mountpoint
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !datasetExists {
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "dataset not found"})
|
||||
return
|
||||
}
|
||||
|
||||
// Default clients to "*" (all) if not specified
|
||||
if req.Clients == nil || len(req.Clients) == 0 {
|
||||
req.Clients = []string{"*"}
|
||||
}
|
||||
|
||||
export, err := a.nfsStore.Create(req.Path, req.Dataset, req.Clients, req.ReadOnly, req.RootSquash)
|
||||
if err != nil {
|
||||
if err == storage.ErrNFSExportExists {
|
||||
writeJSON(w, http.StatusConflict, map[string]string{"error": "export for this path already exists"})
|
||||
return
|
||||
}
|
||||
log.Printf("create NFS export error: %v", err)
|
||||
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusCreated, export)
|
||||
}
|
||||
|
||||
func (a *App) handleGetNFSExport(w http.ResponseWriter, r *http.Request) {
|
||||
id := pathParam(r, "/api/v1/exports/nfs/")
|
||||
writeJSON(w, http.StatusNotImplemented, map[string]string{"error": "not implemented", "id": id})
|
||||
if id == "" {
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "export id required"})
|
||||
return
|
||||
}
|
||||
|
||||
export, err := a.nfsStore.Get(id)
|
||||
if err != nil {
|
||||
if err == storage.ErrNFSExportNotFound {
|
||||
writeJSON(w, http.StatusNotFound, map[string]string{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, export)
|
||||
}
|
||||
|
||||
func (a *App) handleUpdateNFSExport(w http.ResponseWriter, r *http.Request) {
|
||||
id := pathParam(r, "/api/v1/exports/nfs/")
|
||||
writeJSON(w, http.StatusNotImplemented, map[string]string{"error": "not implemented", "id": id})
|
||||
if id == "" {
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "export id required"})
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
Clients []string `json:"clients"`
|
||||
ReadOnly bool `json:"read_only"`
|
||||
RootSquash bool `json:"root_squash"`
|
||||
Enabled bool `json:"enabled"`
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid request body"})
|
||||
return
|
||||
}
|
||||
|
||||
if err := a.nfsStore.Update(id, req.Clients, req.ReadOnly, req.RootSquash, req.Enabled); err != nil {
|
||||
if err == storage.ErrNFSExportNotFound {
|
||||
writeJSON(w, http.StatusNotFound, map[string]string{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
log.Printf("update NFS export error: %v", err)
|
||||
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
export, _ := a.nfsStore.Get(id)
|
||||
writeJSON(w, http.StatusOK, export)
|
||||
}
|
||||
|
||||
func (a *App) handleDeleteNFSExport(w http.ResponseWriter, r *http.Request) {
|
||||
id := pathParam(r, "/api/v1/exports/nfs/")
|
||||
writeJSON(w, http.StatusNotImplemented, map[string]string{"error": "not implemented", "id": id})
|
||||
if id == "" {
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "export id required"})
|
||||
return
|
||||
}
|
||||
|
||||
if err := a.nfsStore.Delete(id); err != nil {
|
||||
if err == storage.ErrNFSExportNotFound {
|
||||
writeJSON(w, http.StatusNotFound, map[string]string{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
log.Printf("delete NFS export error: %v", err)
|
||||
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, map[string]string{"message": "export deleted", "id": id})
|
||||
}
|
||||
|
||||
// iSCSI Handlers
|
||||
func (a *App) handleListISCSITargets(w http.ResponseWriter, r *http.Request) {
|
||||
targets := []models.ISCSITarget{} // Stub
|
||||
targets := a.iscsiStore.List()
|
||||
writeJSON(w, http.StatusOK, targets)
|
||||
}
|
||||
|
||||
func (a *App) handleCreateISCSITarget(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(w, http.StatusNotImplemented, map[string]string{"error": "not implemented"})
|
||||
var req struct {
|
||||
IQN string `json:"iqn"`
|
||||
Initiators []string `json:"initiators"`
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid request body"})
|
||||
return
|
||||
}
|
||||
|
||||
if req.IQN == "" {
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "iqn is required"})
|
||||
return
|
||||
}
|
||||
|
||||
// Basic IQN format validation (iqn.yyyy-mm.reversed.domain:identifier)
|
||||
if !strings.HasPrefix(req.IQN, "iqn.") {
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid IQN format (must start with 'iqn.')"})
|
||||
return
|
||||
}
|
||||
|
||||
target, err := a.iscsiStore.Create(req.IQN, req.Initiators)
|
||||
if err != nil {
|
||||
if err == storage.ErrISCSITargetExists {
|
||||
writeJSON(w, http.StatusConflict, map[string]string{"error": "target with this IQN already exists"})
|
||||
return
|
||||
}
|
||||
log.Printf("create iSCSI target error: %v", err)
|
||||
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusCreated, target)
|
||||
}
|
||||
|
||||
func (a *App) handleGetISCSITarget(w http.ResponseWriter, r *http.Request) {
|
||||
id := pathParam(r, "/api/v1/iscsi/targets/")
|
||||
writeJSON(w, http.StatusNotImplemented, map[string]string{"error": "not implemented", "id": id})
|
||||
if id == "" {
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "target id required"})
|
||||
return
|
||||
}
|
||||
|
||||
target, err := a.iscsiStore.Get(id)
|
||||
if err != nil {
|
||||
if err == storage.ErrISCSITargetNotFound {
|
||||
writeJSON(w, http.StatusNotFound, map[string]string{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, target)
|
||||
}
|
||||
|
||||
func (a *App) handleUpdateISCSITarget(w http.ResponseWriter, r *http.Request) {
|
||||
id := pathParam(r, "/api/v1/iscsi/targets/")
|
||||
writeJSON(w, http.StatusNotImplemented, map[string]string{"error": "not implemented", "id": id})
|
||||
if id == "" {
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "target id required"})
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
Initiators []string `json:"initiators"`
|
||||
Enabled bool `json:"enabled"`
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid request body"})
|
||||
return
|
||||
}
|
||||
|
||||
if err := a.iscsiStore.Update(id, req.Initiators, req.Enabled); err != nil {
|
||||
if err == storage.ErrISCSITargetNotFound {
|
||||
writeJSON(w, http.StatusNotFound, map[string]string{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
log.Printf("update iSCSI target error: %v", err)
|
||||
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
target, _ := a.iscsiStore.Get(id)
|
||||
writeJSON(w, http.StatusOK, target)
|
||||
}
|
||||
|
||||
func (a *App) handleDeleteISCSITarget(w http.ResponseWriter, r *http.Request) {
|
||||
id := pathParam(r, "/api/v1/iscsi/targets/")
|
||||
writeJSON(w, http.StatusNotImplemented, map[string]string{"error": "not implemented", "id": id})
|
||||
if id == "" {
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "target id required"})
|
||||
return
|
||||
}
|
||||
|
||||
if err := a.iscsiStore.Delete(id); err != nil {
|
||||
if err == storage.ErrISCSITargetNotFound {
|
||||
writeJSON(w, http.StatusNotFound, map[string]string{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
log.Printf("delete iSCSI target error: %v", err)
|
||||
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, map[string]string{"message": "target deleted", "id": id})
|
||||
}
|
||||
|
||||
func (a *App) handleAddLUN(w http.ResponseWriter, r *http.Request) {
|
||||
id := pathParam(r, "/api/v1/iscsi/targets/")
|
||||
writeJSON(w, http.StatusNotImplemented, map[string]string{"error": "not implemented", "id": id})
|
||||
// Extract target ID from path like /api/v1/iscsi/targets/{id}/luns
|
||||
path := strings.TrimPrefix(r.URL.Path, "/api/v1/iscsi/targets/")
|
||||
parts := strings.Split(path, "/")
|
||||
if len(parts) == 0 || parts[0] == "" {
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "target id required"})
|
||||
return
|
||||
}
|
||||
id := parts[0]
|
||||
|
||||
var req struct {
|
||||
ZVOL string `json:"zvol"`
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid request body"})
|
||||
return
|
||||
}
|
||||
|
||||
if req.ZVOL == "" {
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "zvol is required"})
|
||||
return
|
||||
}
|
||||
|
||||
// Validate ZVOL exists
|
||||
zvols, err := a.zfs.ListZVOLs("")
|
||||
if err != nil {
|
||||
log.Printf("list zvols error: %v", err)
|
||||
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": "failed to validate zvol"})
|
||||
return
|
||||
}
|
||||
|
||||
var zvolSize uint64
|
||||
zvolExists := false
|
||||
for _, zvol := range zvols {
|
||||
if zvol.Name == req.ZVOL {
|
||||
zvolExists = true
|
||||
zvolSize = zvol.Size
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !zvolExists {
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "zvol not found"})
|
||||
return
|
||||
}
|
||||
|
||||
lun, err := a.iscsiStore.AddLUN(id, req.ZVOL, zvolSize)
|
||||
if err != nil {
|
||||
if err == storage.ErrISCSITargetNotFound {
|
||||
writeJSON(w, http.StatusNotFound, map[string]string{"error": "target not found"})
|
||||
return
|
||||
}
|
||||
if err == storage.ErrLUNExists {
|
||||
writeJSON(w, http.StatusConflict, map[string]string{"error": "zvol already mapped to this target"})
|
||||
return
|
||||
}
|
||||
log.Printf("add LUN error: %v", err)
|
||||
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusCreated, lun)
|
||||
}
|
||||
|
||||
func (a *App) handleRemoveLUN(w http.ResponseWriter, r *http.Request) {
|
||||
id := pathParam(r, "/api/v1/iscsi/targets/")
|
||||
writeJSON(w, http.StatusNotImplemented, map[string]string{"error": "not implemented", "id": id})
|
||||
// Extract target ID from path like /api/v1/iscsi/targets/{id}/luns/remove
|
||||
path := strings.TrimPrefix(r.URL.Path, "/api/v1/iscsi/targets/")
|
||||
parts := strings.Split(path, "/")
|
||||
if len(parts) == 0 || parts[0] == "" {
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "target id required"})
|
||||
return
|
||||
}
|
||||
id := parts[0]
|
||||
|
||||
var req struct {
|
||||
LUNID int `json:"lun_id"`
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid request body"})
|
||||
return
|
||||
}
|
||||
|
||||
if err := a.iscsiStore.RemoveLUN(id, req.LUNID); err != nil {
|
||||
if err == storage.ErrISCSITargetNotFound {
|
||||
writeJSON(w, http.StatusNotFound, map[string]string{"error": "target not found"})
|
||||
return
|
||||
}
|
||||
if err == storage.ErrLUNNotFound {
|
||||
writeJSON(w, http.StatusNotFound, map[string]string{"error": "LUN not found"})
|
||||
return
|
||||
}
|
||||
log.Printf("remove LUN error: %v", err)
|
||||
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, map[string]string{"message": "LUN removed", "target_id": id, "lun_id": strconv.Itoa(req.LUNID)})
|
||||
}
|
||||
|
||||
// Job Handlers
|
||||
@@ -575,42 +988,201 @@ func (a *App) handleCancelJob(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(w, http.StatusOK, map[string]string{"message": "job cancelled", "id": id})
|
||||
}
|
||||
|
||||
// Auth Handlers (stubs)
|
||||
// Auth Handlers
|
||||
func (a *App) handleLogin(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(w, http.StatusNotImplemented, map[string]string{"error": "not implemented"})
|
||||
var req struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid request body"})
|
||||
return
|
||||
}
|
||||
|
||||
if req.Username == "" || req.Password == "" {
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "username and password are required"})
|
||||
return
|
||||
}
|
||||
|
||||
user, err := a.userStore.Authenticate(req.Username, req.Password)
|
||||
if err != nil {
|
||||
writeJSON(w, http.StatusUnauthorized, map[string]string{"error": "invalid credentials"})
|
||||
return
|
||||
}
|
||||
|
||||
token, err := a.authService.GenerateToken(user.ID, string(user.Role))
|
||||
if err != nil {
|
||||
log.Printf("generate token error: %v", err)
|
||||
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": "failed to generate token"})
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, map[string]interface{}{
|
||||
"token": token,
|
||||
"user": user,
|
||||
"expires_in": 86400, // 24 hours in seconds
|
||||
})
|
||||
}
|
||||
|
||||
func (a *App) handleLogout(w http.ResponseWriter, r *http.Request) {
|
||||
// JWT is stateless, so logout is just client-side token removal
|
||||
// In a stateful system, you'd invalidate the token here
|
||||
writeJSON(w, http.StatusOK, map[string]string{"message": "logged out"})
|
||||
}
|
||||
|
||||
// User Handlers
|
||||
func (a *App) handleListUsers(w http.ResponseWriter, r *http.Request) {
|
||||
users := []models.User{} // Stub
|
||||
// Only administrators can list users
|
||||
users := a.userStore.List()
|
||||
writeJSON(w, http.StatusOK, users)
|
||||
}
|
||||
|
||||
func (a *App) handleCreateUser(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(w, http.StatusNotImplemented, map[string]string{"error": "not implemented"})
|
||||
var req struct {
|
||||
Username string `json:"username"`
|
||||
Email string `json:"email"`
|
||||
Password string `json:"password"`
|
||||
Role models.Role `json:"role"`
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid request body"})
|
||||
return
|
||||
}
|
||||
|
||||
if req.Username == "" || req.Password == "" {
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "username and password are required"})
|
||||
return
|
||||
}
|
||||
|
||||
if req.Role == "" {
|
||||
req.Role = models.RoleViewer // Default role
|
||||
}
|
||||
|
||||
// Validate role
|
||||
if req.Role != models.RoleAdministrator && req.Role != models.RoleOperator && req.Role != models.RoleViewer {
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid role"})
|
||||
return
|
||||
}
|
||||
|
||||
user, err := a.userStore.Create(req.Username, req.Email, req.Password, req.Role)
|
||||
if err != nil {
|
||||
if err == auth.ErrUserExists {
|
||||
writeJSON(w, http.StatusConflict, map[string]string{"error": "username already exists"})
|
||||
return
|
||||
}
|
||||
log.Printf("create user error: %v", err)
|
||||
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusCreated, user)
|
||||
}
|
||||
|
||||
func (a *App) handleGetUser(w http.ResponseWriter, r *http.Request) {
|
||||
id := pathParam(r, "/api/v1/users/")
|
||||
writeJSON(w, http.StatusNotImplemented, map[string]string{"error": "not implemented", "id": id})
|
||||
if id == "" {
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "user id required"})
|
||||
return
|
||||
}
|
||||
|
||||
user, err := a.userStore.GetByID(id)
|
||||
if err != nil {
|
||||
writeJSON(w, http.StatusNotFound, map[string]string{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, user)
|
||||
}
|
||||
|
||||
func (a *App) handleUpdateUser(w http.ResponseWriter, r *http.Request) {
|
||||
id := pathParam(r, "/api/v1/users/")
|
||||
writeJSON(w, http.StatusNotImplemented, map[string]string{"error": "not implemented", "id": id})
|
||||
if id == "" {
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "user id required"})
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
Email string `json:"email"`
|
||||
Role models.Role `json:"role"`
|
||||
Active bool `json:"active"`
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid request body"})
|
||||
return
|
||||
}
|
||||
|
||||
// Validate role if provided
|
||||
if req.Role != "" && req.Role != models.RoleAdministrator && req.Role != models.RoleOperator && req.Role != models.RoleViewer {
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid role"})
|
||||
return
|
||||
}
|
||||
|
||||
// Use existing role if not provided
|
||||
if req.Role == "" {
|
||||
existingUser, err := a.userStore.GetByID(id)
|
||||
if err != nil {
|
||||
writeJSON(w, http.StatusNotFound, map[string]string{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
req.Role = existingUser.Role
|
||||
}
|
||||
|
||||
if err := a.userStore.Update(id, req.Email, req.Role, req.Active); err != nil {
|
||||
log.Printf("update user error: %v", err)
|
||||
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
user, _ := a.userStore.GetByID(id)
|
||||
writeJSON(w, http.StatusOK, user)
|
||||
}
|
||||
|
||||
func (a *App) handleDeleteUser(w http.ResponseWriter, r *http.Request) {
|
||||
id := pathParam(r, "/api/v1/users/")
|
||||
writeJSON(w, http.StatusNotImplemented, map[string]string{"error": "not implemented", "id": id})
|
||||
if id == "" {
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "user id required"})
|
||||
return
|
||||
}
|
||||
|
||||
// Prevent deleting yourself
|
||||
currentUser, ok := getUserFromContext(r)
|
||||
if ok && currentUser.ID == id {
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "cannot delete your own account"})
|
||||
return
|
||||
}
|
||||
|
||||
if err := a.userStore.Delete(id); err != nil {
|
||||
log.Printf("delete user error: %v", err)
|
||||
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, map[string]string{"message": "user deleted", "id": id})
|
||||
}
|
||||
|
||||
// Audit Log Handlers
|
||||
func (a *App) handleListAuditLogs(w http.ResponseWriter, r *http.Request) {
|
||||
logs := []models.AuditLog{} // Stub
|
||||
// Get query parameters
|
||||
actor := r.URL.Query().Get("actor")
|
||||
action := r.URL.Query().Get("action")
|
||||
resource := r.URL.Query().Get("resource")
|
||||
limitStr := r.URL.Query().Get("limit")
|
||||
|
||||
limit := 0
|
||||
if limitStr != "" {
|
||||
if l, err := strconv.Atoi(limitStr); err == nil && l > 0 {
|
||||
limit = l
|
||||
}
|
||||
}
|
||||
|
||||
// Default limit to 100 if not specified
|
||||
if limit == 0 {
|
||||
limit = 100
|
||||
}
|
||||
|
||||
logs := a.auditStore.List(actor, action, resource, limit)
|
||||
writeJSON(w, http.StatusOK, logs)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user