Skip to content

Commit cab5029

Browse files
authored
feat: introduce extensions (#430)
2 parents 0baec5b + 54c82ae commit cab5029

File tree

96 files changed

+8043
-91
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

96 files changed

+8043
-91
lines changed

api/api/versions.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33
{
44
"version": "v1",
55
"status": "active",
6-
"release_date": "2025-10-11T20:19:39.955308752+05:30",
6+
"release_date": "2025-10-15T21:35:07.482629937+05:30",
77
"end_of_life": "0001-01-01T00:00:00Z",
88
"changes": [
99
"Initial API version"
1010
]
1111
}
1212
]
13-
}
13+
}

api/doc/openapi.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package controller
2+
3+
import (
4+
"net/http"
5+
6+
"github.com/go-fuego/fuego"
7+
"github.com/raghavyuva/nixopus-api/internal/features/logger"
8+
)
9+
10+
func (c *ExtensionsController) DeleteFork(ctx fuego.ContextNoBody) (*struct {
11+
Status string `json:"status"`
12+
}, error) {
13+
id := ctx.PathParam("id")
14+
if id == "" {
15+
return nil, fuego.HTTPError{Err: nil, Status: http.StatusBadRequest}
16+
}
17+
if err := c.service.DeleteFork(id); err != nil {
18+
c.logger.Log(logger.Error, err.Error(), "")
19+
return nil, fuego.HTTPError{Err: err, Status: http.StatusBadRequest}
20+
}
21+
return &struct {
22+
Status string `json:"status"`
23+
}{Status: "ok"}, nil
24+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package controller
2+
3+
import (
4+
"net/http"
5+
6+
"github.com/go-fuego/fuego"
7+
"github.com/raghavyuva/nixopus-api/internal/features/logger"
8+
"github.com/raghavyuva/nixopus-api/internal/types"
9+
)
10+
11+
type ForkExtensionRequest struct {
12+
YAMLContent *string `json:"yaml_content"`
13+
}
14+
15+
func (c *ExtensionsController) ForkExtension(ctx fuego.ContextWithBody[ForkExtensionRequest]) (types.Extension, error) {
16+
extensionID := ctx.PathParam("extension_id")
17+
if extensionID == "" {
18+
return types.Extension{}, fuego.HTTPError{Err: nil, Status: http.StatusBadRequest}
19+
}
20+
req, err := ctx.Body()
21+
if err != nil {
22+
return types.Extension{}, fuego.HTTPError{Err: err, Status: http.StatusBadRequest}
23+
}
24+
var yamlOverride string
25+
if req.YAMLContent != nil {
26+
yamlOverride = *req.YAMLContent
27+
}
28+
authorName := ""
29+
if userAny := ctx.Request().Context().Value(types.UserContextKey); userAny != nil {
30+
if u, ok := userAny.(*types.User); ok && u != nil {
31+
authorName = u.Username
32+
}
33+
}
34+
newExt, err := c.service.ForkExtension(extensionID, yamlOverride, authorName)
35+
if err != nil {
36+
c.logger.Log(logger.Error, err.Error(), "")
37+
return types.Extension{}, fuego.HTTPError{Err: err, Status: http.StatusInternalServerError}
38+
}
39+
return *newExt, nil
40+
}
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
package controller
2+
3+
import (
4+
"net/http"
5+
"strconv"
6+
7+
"github.com/go-fuego/fuego"
8+
"github.com/raghavyuva/nixopus-api/internal/features/logger"
9+
"github.com/raghavyuva/nixopus-api/internal/types"
10+
)
11+
12+
func (c *ExtensionsController) GetExtensions(ctx fuego.ContextNoBody) (*types.ExtensionListResponse, error) {
13+
params := types.ExtensionListParams{}
14+
15+
categoryParam := ctx.QueryParam("category")
16+
if categoryParam != "" {
17+
cat := types.ExtensionCategory(categoryParam)
18+
params.Category = &cat
19+
}
20+
21+
searchParam := ctx.QueryParam("search")
22+
if searchParam != "" {
23+
params.Search = searchParam
24+
}
25+
26+
if typeParam := ctx.QueryParam("type"); typeParam != "" {
27+
et := types.ExtensionType(typeParam)
28+
params.Type = &et
29+
}
30+
31+
sortByParam := ctx.QueryParam("sort_by")
32+
if sortByParam != "" {
33+
params.SortBy = types.ExtensionSortField(sortByParam)
34+
}
35+
36+
sortDirParam := ctx.QueryParam("sort_dir")
37+
if sortDirParam != "" {
38+
params.SortDir = types.SortDirection(sortDirParam)
39+
}
40+
41+
pageParam := ctx.QueryParam("page")
42+
if pageParam != "" {
43+
if page, err := strconv.Atoi(pageParam); err == nil && page > 0 {
44+
params.Page = page
45+
}
46+
}
47+
48+
pageSizeParam := ctx.QueryParam("page_size")
49+
if pageSizeParam != "" {
50+
if pageSize, err := strconv.Atoi(pageSizeParam); err == nil && pageSize > 0 {
51+
params.PageSize = pageSize
52+
}
53+
}
54+
55+
response, err := c.service.ListExtensions(params)
56+
if err != nil {
57+
c.logger.Log(logger.Error, err.Error(), "")
58+
return nil, fuego.HTTPError{
59+
Err: err,
60+
Status: http.StatusInternalServerError,
61+
}
62+
}
63+
64+
return response, nil
65+
}
66+
67+
func (c *ExtensionsController) GetCategories(ctx fuego.ContextNoBody) ([]types.ExtensionCategory, error) {
68+
cats, err := c.service.ListCategories()
69+
if err != nil {
70+
c.logger.Log(logger.Error, err.Error(), "")
71+
return nil, fuego.HTTPError{Err: err, Status: http.StatusInternalServerError}
72+
}
73+
return cats, nil
74+
}
75+
76+
func (c *ExtensionsController) GetExtension(ctx fuego.ContextNoBody) (types.Extension, error) {
77+
id := ctx.PathParam("id")
78+
if id == "" {
79+
return types.Extension{}, fuego.HTTPError{
80+
Err: nil,
81+
Status: http.StatusBadRequest,
82+
}
83+
}
84+
85+
extension, err := c.service.GetExtension(id)
86+
if err != nil {
87+
if err.Error() == "extension not found" {
88+
return types.Extension{}, fuego.HTTPError{
89+
Err: err,
90+
Status: http.StatusNotFound,
91+
}
92+
}
93+
c.logger.Log(logger.Error, err.Error(), "")
94+
return types.Extension{}, fuego.HTTPError{
95+
Err: err,
96+
Status: http.StatusInternalServerError,
97+
}
98+
}
99+
100+
return *extension, nil
101+
}
102+
103+
func (c *ExtensionsController) GetExtensionByExtensionID(ctx fuego.ContextNoBody) (types.Extension, error) {
104+
extensionID := ctx.PathParam("extension_id")
105+
if extensionID == "" {
106+
return types.Extension{}, fuego.HTTPError{
107+
Err: nil,
108+
Status: http.StatusBadRequest,
109+
}
110+
}
111+
112+
extension, err := c.service.GetExtensionByID(extensionID)
113+
if err != nil {
114+
if err.Error() == "extension not found" {
115+
return types.Extension{}, fuego.HTTPError{
116+
Err: err,
117+
Status: http.StatusNotFound,
118+
}
119+
}
120+
c.logger.Log(logger.Error, err.Error(), "")
121+
return types.Extension{}, fuego.HTTPError{
122+
Err: err,
123+
Status: http.StatusInternalServerError,
124+
}
125+
}
126+
127+
return *extension, nil
128+
}
129+
130+
func (c *ExtensionsController) GetExecution(ctx fuego.ContextNoBody) (*types.ExtensionExecution, error) {
131+
id := ctx.PathParam("execution_id")
132+
if id == "" {
133+
return nil, fuego.HTTPError{
134+
Err: nil,
135+
Status: http.StatusBadRequest,
136+
}
137+
}
138+
139+
exec, err := c.service.GetExecutionByID(id)
140+
if err != nil {
141+
c.logger.Log(logger.Error, err.Error(), "")
142+
return nil, fuego.HTTPError{
143+
Err: err,
144+
Status: http.StatusInternalServerError,
145+
}
146+
}
147+
return exec, nil
148+
}
149+
150+
func (c *ExtensionsController) ListExecutionsByExtensionID(ctx fuego.ContextNoBody) ([]types.ExtensionExecution, error) {
151+
extensionID := ctx.PathParam("extension_id")
152+
if extensionID == "" {
153+
return nil, fuego.HTTPError{
154+
Err: nil,
155+
Status: http.StatusBadRequest,
156+
}
157+
}
158+
execs, err := c.service.ListExecutionsByExtensionID(extensionID)
159+
if err != nil {
160+
c.logger.Log(logger.Error, err.Error(), "")
161+
return nil, fuego.HTTPError{
162+
Err: err,
163+
Status: http.StatusInternalServerError,
164+
}
165+
}
166+
return execs, nil
167+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package controller
2+
3+
import (
4+
"context"
5+
"net/http"
6+
7+
"github.com/raghavyuva/nixopus-api/internal/features/extension/service"
8+
"github.com/raghavyuva/nixopus-api/internal/features/extension/storage"
9+
"github.com/raghavyuva/nixopus-api/internal/features/extension/validation"
10+
"github.com/raghavyuva/nixopus-api/internal/features/logger"
11+
shared_storage "github.com/raghavyuva/nixopus-api/internal/storage"
12+
"github.com/raghavyuva/nixopus-api/internal/utils"
13+
)
14+
15+
type ExtensionsController struct {
16+
store *shared_storage.Store
17+
service *service.ExtensionService
18+
validator *validation.Validator
19+
ctx context.Context
20+
logger logger.Logger
21+
}
22+
23+
func NewExtensionsController(
24+
store *shared_storage.Store,
25+
ctx context.Context,
26+
l logger.Logger,
27+
) *ExtensionsController {
28+
storage := storage.ExtensionStorage{DB: store.DB, Ctx: ctx}
29+
return &ExtensionsController{
30+
store: store,
31+
service: service.NewExtensionService(store, ctx, l, &storage),
32+
validator: validation.NewValidator(&storage),
33+
ctx: ctx,
34+
logger: l,
35+
}
36+
}
37+
38+
// parseAndValidate parses and validates the request body.
39+
//
40+
// This method attempts to parse the request body into the provided 'req' interface
41+
// using the controller's validator. If parsing fails, an error response is sent
42+
// and the method returns false. It also validates the parsed request object and
43+
// returns false if validation fails. If both operations are successful, it returns true.
44+
//
45+
// Parameters:
46+
//
47+
// w - the HTTP response writer to send error responses.
48+
// r - the HTTP request containing the body to parse.
49+
// req - the interface to populate with the parsed request body.
50+
//
51+
// Returns:
52+
//
53+
// bool - true if parsing and validation succeed, false otherwise.
54+
func (c *ExtensionsController) parseAndValidate(w http.ResponseWriter, r *http.Request, req interface{}) bool {
55+
if err := c.validator.ParseRequestBody(r, r.Body, req); err != nil {
56+
c.logger.Log(logger.Error, "Failed to decode request", err.Error())
57+
utils.SendErrorResponse(w, "Failed to decode request", http.StatusBadRequest)
58+
return false
59+
}
60+
61+
if err := c.validator.ValidateRequest(req); err != nil {
62+
c.logger.Log(logger.Error, err.Error(), err.Error())
63+
utils.SendErrorResponse(w, err.Error(), http.StatusBadRequest)
64+
return false
65+
}
66+
67+
return true
68+
}
69+
70+
type RunExtensionRequest struct {
71+
Variables map[string]interface{} `json:"variables"`
72+
}

0 commit comments

Comments
 (0)