better api paths

This commit is contained in:
samantha42
2026-03-21 09:25:02 +01:00
parent abc17d92dd
commit 3f203178b2
7 changed files with 166 additions and 84 deletions

View File

@@ -107,20 +107,20 @@ func (r *BudgetRepo) Create(ctx context.Context, req model.CreateBudgetRequest)
return b, nil
}
func (r *BudgetRepo) Update(ctx context.Context, id int, req model.UpdateBudgetRequest) (*model.Budget, error) {
func (r *BudgetRepo) Update(ctx context.Context, req model.UpdateBudgetRequest) (*model.Budget, error) {
_, err := r.db.ExecContext(ctx, `
UPDATE budgets
SET version=?, amount=?, notes=?,
updated_at=strftime('%Y-%m-%dT%H:%M:%SZ','now')
WHERE id=?`,
req.Version, req.Amount, req.Notes, id,
req.Version, req.Amount, req.Notes, req.ID,
)
if err != nil {
return nil, fmt.Errorf("update budget: %w", err)
}
row := r.db.QueryRowContext(ctx,
`SELECT`+budgetSelectCols+`FROM budgets WHERE id = ?`, id)
`SELECT`+budgetSelectCols+`FROM budgets WHERE id = ?`, req.ID)
b, err := scanBudget(row)
if err != nil {
return nil, fmt.Errorf("fetch updated budget: %w", err)
@@ -128,8 +128,8 @@ func (r *BudgetRepo) Update(ctx context.Context, id int, req model.UpdateBudgetR
return b, nil
}
func (r *BudgetRepo) Delete(ctx context.Context, id int) error {
_, err := r.db.ExecContext(ctx, `DELETE FROM budgets WHERE id = ?`, id)
func (r *BudgetRepo) Delete(ctx context.Context, req model.DeleteBudgetRequest) error {
_, err := r.db.ExecContext(ctx, `DELETE FROM budgets WHERE id = ?`, req.ID)
if err != nil {
return fmt.Errorf("delete budget: %w", err)
}

View File

@@ -3,7 +3,6 @@ package handler
import (
"encoding/json"
"net/http"
"strconv"
"strings"
"Engine/internal/model"
@@ -39,12 +38,8 @@ func (h *BudgetHandler) Create(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusCreated, budget)
}
// PUT /api/v1/budgets/update
func (h *BudgetHandler) Update(w http.ResponseWriter, r *http.Request) {
id, err := strconv.Atoi(r.PathValue("id"))
if err != nil {
writeError(w, http.StatusBadRequest, "invalid id")
return
}
var req model.UpdateBudgetRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeError(w, http.StatusBadRequest, "invalid request body")
@@ -57,7 +52,7 @@ func (h *BudgetHandler) Update(w http.ResponseWriter, r *http.Request) {
return
}
budget, err := h.svc.Update(r.Context(), id, req)
budget, err := h.svc.Update(r.Context(), req)
if err != nil {
writeError(w, http.StatusInternalServerError, err.Error())
return
@@ -65,13 +60,16 @@ func (h *BudgetHandler) Update(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, budget)
}
// DELETE /api/v1/budgets/delete
func (h *BudgetHandler) Delete(w http.ResponseWriter, r *http.Request) {
id, err := strconv.Atoi(r.PathValue("id"))
if err != nil {
var req struct {
ID int `json:"id"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeError(w, http.StatusBadRequest, "invalid id")
return
}
if err := h.svc.Delete(r.Context(), id); err != nil {
if err := h.svc.Delete(r.Context(), req); err != nil {
writeError(w, http.StatusInternalServerError, err.Error())
return
}

View File

@@ -20,7 +20,7 @@ func NewReferenceHandler(repo *database.ReferenceRepo) *ReferenceHandler {
// ── Departments ───────────────────────────────────────────────────────────────
// POST /api/v1/departments
// POST /api/v1/department/create
// PUT /api/v1/departments/{id} (same body, id from path)
func (h *ReferenceHandler) CreateDepartment(w http.ResponseWriter, r *http.Request) {
var req database.Department
@@ -47,7 +47,7 @@ func (h *ReferenceHandler) CreateDepartment(w http.ResponseWriter, r *http.Reque
writeJSON(w, http.StatusCreated, dept)
}
// GET /api/v1/departments
// GET /api/v1/department/list
func (h *ReferenceHandler) ListDepartments(w http.ResponseWriter, r *http.Request) {
depts, err := h.repo.ListDepartments(r.Context())
if err != nil {
@@ -60,8 +60,16 @@ func (h *ReferenceHandler) ListDepartments(w http.ResponseWriter, r *http.Reques
writeJSON(w, http.StatusOK, depts)
}
// DELETE /api/v1/departments/{id}
// DELETE /api/v1/department/delete
func (h *ReferenceHandler) DeleteDepartment(w http.ResponseWriter, r *http.Request) {
var req struct {
ID int `json:"id"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeError(w, http.StatusBadRequest, fmt.Sprintf("invalid body: %v", err))
return
}
id, err := pathID(r, "id")
if err != nil {
writeError(w, http.StatusBadRequest, "invalid id")

View File

@@ -27,6 +27,14 @@ type Department struct {
CostCenter string `json:"cost_center"`
}
type GetDepartmentBudget struct {
Code string `json:"code"`
}
type GetDepartmentActual struct {
Code string `json:"code"`
}
type GLAccount struct {
ID int `json:"id"`
Code string `json:"code"`
@@ -35,6 +43,14 @@ type GLAccount struct {
FavourHigh bool `json:"favour_high"`
}
type GetGLAccountBudget struct {
Code string `json:"code"`
}
type GetGLAccountActual struct {
Code string `json:"code"`
}
type Budget struct {
ID int `json:"id"`
FiscalYear int `json:"fiscal_year"`
@@ -94,44 +110,64 @@ func (cBudget *CreateBudgetRequest) Valid() []string {
}
type UpdateBudgetRequest struct {
ID int `json:"id"`
FiscalYear int `json:"fiscal_year"`
FiscalPeriod int `json:"fiscal_period"`
Version BudgetVersion `json:"version"`
DepartmentID int `json:"department_id"`
GLAccountID int `json:"gl_account_id"`
Amount float64 `json:"amount"`
Currency string `json:"currency"`
Notes string `json:"notes"`
ChangedBy string `json:"created_by"`
ID int // required, not a pointer
ChangedBy string // required, not a pointer
FiscalYear *int
FiscalPeriod *int
Version *string
DepartmentID *int
GLAccountID *int
Amount *float64
Currency *string
Notes *string
}
type DeleteBudgetRequest struct {
ID int `json:"id"`
}
func (uBudget *UpdateBudgetRequest) Valid() []string {
var errs []string
if uBudget.FiscalYear < 1 || uBudget.FiscalYear > 2200 {
errs = append(errs, "fiscal_year must be between 1 and 2200")
}
if uBudget.FiscalPeriod < 1 || uBudget.FiscalPeriod > 12 {
errs = append(errs, "fiscal_period must be between 1 and 12")
}
if uBudget.Version == "" {
errs = append(errs, "version is required")
}
if uBudget.DepartmentID == 0 {
errs = append(errs, "department_id is required")
}
if uBudget.GLAccountID == 0 {
errs = append(errs, "gl_account_id is required")
}
if uBudget.Amount <= 0 {
errs = append(errs, "amount must be greater than 0")
}
if uBudget.Currency == "" {
errs = append(errs, "currency is required")
// Always required: identity + audit
if uBudget.ID == 0 {
errs = append(errs, "id is required")
}
if uBudget.ChangedBy == "" {
errs = append(errs, "created_by is required")
errs = append(errs, "changed_by is required")
}
// Validate fields only if they were provided
if uBudget.FiscalYear != nil {
if *uBudget.FiscalYear < 1 || *uBudget.FiscalYear > 2200 {
errs = append(errs, "fiscal_year must be between 1 and 2200")
}
}
if uBudget.FiscalPeriod != nil {
if *uBudget.FiscalPeriod < 1 || *uBudget.FiscalPeriod > 12 {
errs = append(errs, "fiscal_period must be between 1 and 12")
}
}
if uBudget.Amount != nil && *uBudget.Amount <= 0 {
errs = append(errs, "amount must be greater than 0")
}
if uBudget.Version != nil && *uBudget.Version == "" {
errs = append(errs, "version cannot be empty")
}
if uBudget.Currency != nil && *uBudget.Currency == "" {
errs = append(errs, "currency cannot be empty")
}
// At least one field must be set — otherwise there's nothing to do
if uBudget.FiscalYear == nil &&
uBudget.FiscalPeriod == nil &&
uBudget.Version == nil &&
uBudget.DepartmentID == nil &&
uBudget.GLAccountID == nil &&
uBudget.Amount == nil &&
uBudget.Currency == nil &&
uBudget.Notes == nil {
errs = append(errs, "at least one field must be provided to update")
}
return errs

View File

@@ -19,10 +19,10 @@ func (s *BudgetService) Create(ctx context.Context, req model.CreateBudgetReques
return s.repo.Create(ctx, req)
}
func (s *BudgetService) Update(ctx context.Context, id int, req model.UpdateBudgetRequest) (*model.Budget, error) {
return s.repo.Update(ctx, id, req)
func (s *BudgetService) Update(ctx context.Context, req model.UpdateBudgetRequest) (*model.Budget, error) {
return s.repo.Update(ctx, req)
}
func (s *BudgetService) Delete(ctx context.Context, id int) error {
return s.repo.Delete(ctx, id)
func (s *BudgetService) Delete(ctx context.Context, req model.DeleteBudgetRequest) error {
return s.repo.Delete(ctx, req)
}