Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
152 changes: 152 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -920,6 +920,158 @@ func BadExample(ctx context.Context) {
- ❌ Routine operations in loops
- ❌ Successful validation steps

### API Response Format

All REST API endpoints use a unified response format. Use the response builder functions from `internal/api/response.go`.

**Success Response Format:**

```json
{
"success": true,
"data": { ... }
}
```

**Success with Pagination:**

```json
{
"success": true,
"data": [ ... ],
"meta": {
"total": 100,
"page": 1,
"page_size": 20,
"total_pages": 5
}
}
```

**Error Response Format:**

```json
{
"success": false,
"error": {
"code": "ERROR_CODE",
"message": "Human readable message",
"details": "Additional context or technical details"
}
}
```

**Response Builder Functions:**

```go
import "github.com/javi11/altmount/internal/api"

// ✅ Good: Use response builders
func (s *Server) handleGetItem(c *fiber.Ctx) error {
item, err := s.repo.GetItem(c.Context(), id)
if err != nil {
return api.RespondInternalError(c, "Failed to get item", err.Error())
}
if item == nil {
return api.RespondNotFound(c, "Item", "")
}
return api.RespondSuccess(c, item)
}

// ❌ Avoid: Inline JSON responses
func (s *Server) handleGetItem(c *fiber.Ctx) error {
item, err := s.repo.GetItem(c.Context(), id)
if err != nil {
return c.Status(500).JSON(fiber.Map{
"success": false,
"message": "Failed to get item",
})
}
return c.Status(200).JSON(fiber.Map{
"success": true,
"data": item,
})
}
```

**Available Response Builders:**

| Function | HTTP Status | Use Case |
|----------|-------------|----------|
| `RespondSuccess(c, data)` | 200 | Successful response with data |
| `RespondSuccessWithMeta(c, data, meta)` | 200 | Paginated list responses |
| `RespondCreated(c, data)` | 201 | Resource created successfully |
| `RespondNoContent(c)` | 204 | Successful deletion, no body |
| `RespondMessage(c, message)` | 200 | Success with message only |
| `RespondBadRequest(c, message, details)` | 400 | Invalid request syntax |
| `RespondValidationError(c, message, details)` | 400 | Validation failures |
| `RespondUnauthorized(c, message, details)` | 401 | Authentication required |
| `RespondForbidden(c, message, details)` | 403 | Insufficient permissions |
| `RespondNotFound(c, resource, details)` | 404 | Resource not found |
| `RespondConflict(c, message, details)` | 409 | Resource conflict |
| `RespondInternalError(c, message, details)` | 500 | Server errors |
| `RespondServiceUnavailable(c, message, details)` | 503 | Service unavailable |

**Error Codes:**

Standard error codes are defined in `internal/api/errors.go`:

- `BAD_REQUEST` - Invalid request format
- `VALIDATION_ERROR` - Validation failures
- `UNAUTHORIZED` - Authentication required
- `FORBIDDEN` - Insufficient permissions
- `NOT_FOUND` - Resource not found
- `CONFLICT` - Resource conflict
- `INTERNAL_SERVER_ERROR` - Server errors

**Example Handler Migration:**

```go
// Before (inline responses)
func (s *Server) handleDeleteItem(c *fiber.Ctx) error {
id := c.Params("id")
if id == "" {
return c.Status(400).JSON(fiber.Map{
"success": false,
"error": fiber.Map{
"code": "BAD_REQUEST",
"message": "Item ID is required",
"details": "",
},
})
}

if err := s.repo.Delete(c.Context(), id); err != nil {
return c.Status(500).JSON(fiber.Map{
"success": false,
"error": fiber.Map{
"code": "INTERNAL_SERVER_ERROR",
"message": "Failed to delete item",
"details": err.Error(),
},
})
}

return c.SendStatus(204)
}

// After (response builders)
func (s *Server) handleDeleteItem(c *fiber.Ctx) error {
id := c.Params("id")
if id == "" {
return RespondBadRequest(c, "Item ID is required", "")
}

if err := s.repo.Delete(c.Context(), id); err != nil {
return RespondInternalError(c, "Failed to delete item", err.Error())
}

return RespondNoContent(c)
}
```

**Note**: SABnzbd API handlers (`sabnzbd_handlers.go`) use a different response format for API compatibility with SABnzbd clients and should NOT use these builders.

---

This document should be updated as the project evolves and new patterns emerge.
6 changes: 2 additions & 4 deletions cmd/altmount/cmd/scan.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import (
"io"
"log/slog"
"net/http"
"time"

"github.com/javi11/altmount/internal/config"
"github.com/javi11/altmount/internal/httpclient"
"github.com/spf13/cobra"
)

Expand Down Expand Up @@ -48,9 +48,7 @@ func runScan(cmd *cobra.Command, args []string) error {
slog.Info("Triggering library scan", "url", url, "dry_run", dryRun)

// Create client with timeout
client := &http.Client{
Timeout: 30 * time.Second,
}
client := httpclient.NewDefault()

// Create request
req, err := http.NewRequest("POST", url, nil)
Expand Down
3 changes: 2 additions & 1 deletion cmd/altmount/cmd/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"github.com/javi11/altmount/internal/config"
"github.com/javi11/altmount/internal/database"
"github.com/javi11/altmount/internal/health"
"github.com/javi11/altmount/internal/httpclient"
"github.com/javi11/altmount/internal/importer"
"github.com/javi11/altmount/internal/metadata"
"github.com/javi11/altmount/internal/nzbfilesystem"
Expand Down Expand Up @@ -141,7 +142,7 @@ func setupNNTPPool(ctx context.Context, cfg *config.Config, poolManager pool.Man
// setupRCloneClient creates an RClone client if enabled
func setupRCloneClient(ctx context.Context, cfg *config.Config, configManager *config.Manager) rclonecli.RcloneRcClient {
if cfg.RClone.RCEnabled != nil && *cfg.RClone.RCEnabled {
httpClient := &http.Client{}
httpClient := httpclient.NewDefault()
rcloneClient := rclonecli.NewRcloneRcClient(configManager, httpClient)

if cfg.RClone.RCUrl != "" {
Expand Down
5 changes: 3 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ require (
github.com/gofiber/fiber/v2 v2.52.9
github.com/google/uuid v1.6.0
github.com/hanwen/go-fuse/v2 v2.9.0
github.com/javi11/nntppool/v2 v2.3.1
github.com/javi11/nntppool/v2 v2.3.0
github.com/javi11/nxg v0.1.0
github.com/javi11/nzbparser v0.5.4
github.com/javi11/rardecode/v2 v2.1.2-0.20251031153435-d6d75db6d6ca
Expand Down Expand Up @@ -148,6 +148,7 @@ require (
github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/jgautheron/goconst v1.8.2 // indirect
github.com/jingyugao/rowserrcheck v1.1.1 // indirect
github.com/jinzhu/copier v0.4.0 // indirect
github.com/jjti/go-spancheck v0.6.5 // indirect
github.com/jstemmer/go-junit-report/v2 v2.1.0 // indirect
github.com/julz/importas v0.2.0 // indirect
Expand Down Expand Up @@ -177,7 +178,7 @@ require (
github.com/mfridman/interpolate v0.0.2 // indirect
github.com/mgechev/revive v1.11.0 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mnightingale/rapidyenc v0.0.0-20251128204712-7aafef1eaf1c // indirect
github.com/mnightingale/rapidyenc v0.0.0-20250628164132-aaf36ba945ef // indirect
github.com/montanaflynn/stats v0.7.1 // indirect
github.com/moricho/tparallel v0.3.2 // indirect
github.com/muesli/termenv v0.16.0 // indirect
Expand Down
10 changes: 6 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -350,8 +350,8 @@ github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/javi11/nntp-server-mock v0.0.1 h1:05KbUlFSnzk82CG/sHWLp9s6Vg8exL3wvdAetX8Bwhc=
github.com/javi11/nntp-server-mock v0.0.1/go.mod h1:+QBpPor0q44ttab8kWo08+/t5C26IHhUpBTC1lZqujA=
github.com/javi11/nntppool/v2 v2.3.1 h1:eMm/GSbGPTyQuR+3yXLfTQIPq64XwKoObDVnKR4SsTQ=
github.com/javi11/nntppool/v2 v2.3.1/go.mod h1:MiVgxT+c+qj7vGT3IdOQ3ZeKBEalIfA4Ab+3stBKesA=
github.com/javi11/nntppool/v2 v2.3.0 h1:6Jb5/8HHq741Tn1DxiyZ21Wsi401s3WTGa5cp73Qjo8=
github.com/javi11/nntppool/v2 v2.3.0/go.mod h1:941UHsGeGtbZrHxmOZBS1hUPf9jITbojuRJlvUbMR/4=
github.com/javi11/nxg v0.1.0 h1:CTThldYlaVIPIhpkrMw0HcTD0NLrW1uYMoDILjjEOtM=
github.com/javi11/nxg v0.1.0/go.mod h1:+GvYpp+y1oq+qBOWxFMvfTjtin/0zCeomWfjiPkiu8A=
github.com/javi11/nzbparser v0.5.4 h1:0aYyORZipp7iX8eNpT/efnzCeVO+9C0sE2HWCGc/JaI=
Expand All @@ -364,6 +364,8 @@ github.com/jgautheron/goconst v1.8.2 h1:y0XF7X8CikZ93fSNT6WBTb/NElBu9IjaY7CCYQrC
github.com/jgautheron/goconst v1.8.2/go.mod h1:A0oxgBCHy55NQn6sYpO7UdnA9p+h7cPtoOZUmvNIako=
github.com/jingyugao/rowserrcheck v1.1.1 h1:zibz55j/MJtLsjP1OF4bSdgXxwL1b+Vn7Tjzq7gFzUs=
github.com/jingyugao/rowserrcheck v1.1.1/go.mod h1:4yvlZSDb3IyDTUZJUmpZfm2Hwok+Dtp+nu2qOq+er9c=
github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8=
github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
github.com/jjti/go-spancheck v0.6.5 h1:lmi7pKxa37oKYIMScialXUK6hP3iY5F1gu+mLBPgYB8=
github.com/jjti/go-spancheck v0.6.5/go.mod h1:aEogkeatBrbYsyW6y5TgDfihCulDYciL1B7rG2vSsrU=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
Expand Down Expand Up @@ -445,8 +447,8 @@ github.com/mgechev/revive v1.11.0 h1:b/gLLpBE427o+Xmd8G58gSA+KtBwxWinH/A565Awh0w
github.com/mgechev/revive v1.11.0/go.mod h1:tI0oLF/2uj+InHCBLrrqfTKfjtFTBCFFfG05auyzgdw=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mnightingale/rapidyenc v0.0.0-20251128204712-7aafef1eaf1c h1:UFEKx2AsNb8Tx80rlOwUCCz4lDxSsZ1tjq2+QDBNOUA=
github.com/mnightingale/rapidyenc v0.0.0-20251128204712-7aafef1eaf1c/go.mod h1:7KM6S+qWTq1757nqywpgj5SxykojksJepW+a28MEDdk=
github.com/mnightingale/rapidyenc v0.0.0-20250628164132-aaf36ba945ef h1:LFK5FupkzCnQqAYEhvy3JMQwCMC6gXDJQe2+jeJyGAM=
github.com/mnightingale/rapidyenc v0.0.0-20250628164132-aaf36ba945ef/go.mod h1:OwCiJ/ffT27hY2V+WIU4Q6JgCFzGxP89/6UR/WZtJ+E=
github.com/moby/sys/mountinfo v0.7.2 h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9KouLrg=
github.com/moby/sys/mountinfo v0.7.2/go.mod h1:1YOa8w8Ih7uW0wALDUgT1dTTSBrZ+HiBLGws92L2RU4=
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
Expand Down
Loading