4e6ed52e51
- API response helpers and error codes - Authentication handlers (login, logout, me, csrf) - Auth middleware (session validation, role checks, CSRF) - LDAP client with TLS/StartTLS support - LDAP filter construction from conditions - AD operations (group membership, move, create group/OU) - Credentials repository (CRUD, test results, usage check) - AD connections repository (CRUD, test results) - Schedules repository (CRUD, next run tracking) - Rules repository with nested condition groups and actions - go-ldap/ldap/v3 dependency added
155 lines
3.7 KiB
Go
155 lines
3.7 KiB
Go
// Package api provides HTTP handlers for the REST API
|
|
package api
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
)
|
|
|
|
// Response is a standard API response envelope
|
|
type Response struct {
|
|
Success bool `json:"success"`
|
|
Data interface{} `json:"data,omitempty"`
|
|
Error *ErrorInfo `json:"error,omitempty"`
|
|
Meta *Meta `json:"meta,omitempty"`
|
|
}
|
|
|
|
// ErrorInfo provides error details
|
|
type ErrorInfo struct {
|
|
Code string `json:"code"`
|
|
Message string `json:"message"`
|
|
Details string `json:"details,omitempty"`
|
|
}
|
|
|
|
// Meta provides pagination and other metadata
|
|
type Meta struct {
|
|
Page int `json:"page,omitempty"`
|
|
PageSize int `json:"pageSize,omitempty"`
|
|
TotalCount int `json:"totalCount,omitempty"`
|
|
TotalPages int `json:"totalPages,omitempty"`
|
|
}
|
|
|
|
// WriteJSON writes a JSON response
|
|
func WriteJSON(w http.ResponseWriter, status int, data interface{}) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(status)
|
|
json.NewEncoder(w).Encode(Response{
|
|
Success: status >= 200 && status < 300,
|
|
Data: data,
|
|
})
|
|
}
|
|
|
|
// WriteError writes an error response
|
|
func WriteError(w http.ResponseWriter, status int, code, message string) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(status)
|
|
json.NewEncoder(w).Encode(Response{
|
|
Success: false,
|
|
Error: &ErrorInfo{
|
|
Code: code,
|
|
Message: message,
|
|
},
|
|
})
|
|
}
|
|
|
|
// WriteErrorWithDetails writes an error response with additional details
|
|
func WriteErrorWithDetails(w http.ResponseWriter, status int, code, message, details string) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(status)
|
|
json.NewEncoder(w).Encode(Response{
|
|
Success: false,
|
|
Error: &ErrorInfo{
|
|
Code: code,
|
|
Message: message,
|
|
Details: details,
|
|
},
|
|
})
|
|
}
|
|
|
|
// WriteList writes a paginated list response
|
|
func WriteList(w http.ResponseWriter, data interface{}, page, pageSize, totalCount int) {
|
|
totalPages := (totalCount + pageSize - 1) / pageSize
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusOK)
|
|
json.NewEncoder(w).Encode(Response{
|
|
Success: true,
|
|
Data: data,
|
|
Meta: &Meta{
|
|
Page: page,
|
|
PageSize: pageSize,
|
|
TotalCount: totalCount,
|
|
TotalPages: totalPages,
|
|
},
|
|
})
|
|
}
|
|
|
|
// Error codes
|
|
const (
|
|
ErrCodeBadRequest = "BAD_REQUEST"
|
|
ErrCodeUnauthorized = "UNAUTHORIZED"
|
|
ErrCodeForbidden = "FORBIDDEN"
|
|
ErrCodeNotFound = "NOT_FOUND"
|
|
ErrCodeConflict = "CONFLICT"
|
|
ErrCodeValidation = "VALIDATION_ERROR"
|
|
ErrCodeInternalError = "INTERNAL_ERROR"
|
|
ErrCodeNotImplemented = "NOT_IMPLEMENTED"
|
|
ErrCodeServiceUnavailable = "SERVICE_UNAVAILABLE"
|
|
)
|
|
|
|
// DecodeJSON decodes a JSON request body
|
|
func DecodeJSON(r *http.Request, v interface{}) error {
|
|
return json.NewDecoder(r.Body).Decode(v)
|
|
}
|
|
|
|
// Pagination helpers
|
|
type PaginationParams struct {
|
|
Page int
|
|
PageSize int
|
|
SortBy string
|
|
SortDesc bool
|
|
}
|
|
|
|
// ParsePagination parses pagination parameters from query string
|
|
func ParsePagination(r *http.Request) PaginationParams {
|
|
p := PaginationParams{
|
|
Page: 1,
|
|
PageSize: 20,
|
|
SortBy: "created_utc",
|
|
SortDesc: true,
|
|
}
|
|
|
|
// Parse page
|
|
if page := r.URL.Query().Get("page"); page != "" {
|
|
if n, err := parseInt(page); err == nil && n > 0 {
|
|
p.Page = n
|
|
}
|
|
}
|
|
|
|
// Parse pageSize
|
|
if size := r.URL.Query().Get("pageSize"); size != "" {
|
|
if n, err := parseInt(size); err == nil && n > 0 && n <= 100 {
|
|
p.PageSize = n
|
|
}
|
|
}
|
|
|
|
// Parse sort
|
|
if sort := r.URL.Query().Get("sortBy"); sort != "" {
|
|
p.SortBy = sort
|
|
}
|
|
|
|
if desc := r.URL.Query().Get("sortDesc"); desc == "true" {
|
|
p.SortDesc = true
|
|
} else if desc == "false" {
|
|
p.SortDesc = false
|
|
}
|
|
|
|
return p
|
|
}
|
|
|
|
func parseInt(s string) (int, error) {
|
|
var n int
|
|
_, err := fmt.Sscanf(s, "%d", &n)
|
|
return n, err
|
|
}
|