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
112 changes: 112 additions & 0 deletions internal/api/http/routes/languages_management.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package routes

import (
"log"
"net/http"
"strconv"

"github.com/gorilla/mux"
"github.com/mini-maxit/backend/internal/api/http/httputils"
_ "github.com/mini-maxit/backend/package/domain/schemas"
"github.com/mini-maxit/backend/package/service"
"github.com/mini-maxit/backend/package/utils"
"go.uber.org/zap"
)

type LanguagesManagementRoute interface {
GetAllLanguages(w http.ResponseWriter, r *http.Request)
ToggleLanguageVisibility(w http.ResponseWriter, r *http.Request)
}

type languagesManagementRoute struct {
languageService service.LanguageService
logger *zap.SugaredLogger
}

// GetAllLanguages godoc
//
// @Tags languages-management
// @Summary Get all languages
// @Description Get all language configurations including visibility state
// @Produce json
// @Failure 500 {object} httputils.APIError
// @Success 200 {object} httputils.APIResponse[[]schemas.LanguageConfig]
// @Router /languages-management/languages [get]
func (lr *languagesManagementRoute) GetAllLanguages(w http.ResponseWriter, r *http.Request) {
db := httputils.GetDatabase(r)

languages, err := lr.languageService.GetAll(db)
if err != nil {
httputils.HandleServiceError(w, err, db, lr.logger)
return
}

httputils.ReturnSuccess(w, http.StatusOK, languages)
}

// ToggleLanguageVisibility godoc
//
// @Tags languages-management
// @Summary Toggle language visibility
// @Description Toggle the enabled/disabled state of a language
// @Produce json
// @Param id path int true "Language ID"
// @Failure 400 {object} httputils.APIError
// @Failure 404 {object} httputils.APIError
// @Failure 500 {object} httputils.APIError
// @Success 200 {object} httputils.APIResponse[httputils.MessageResponse]
// @Router /languages-management/languages/{id} [patch]
func (lr *languagesManagementRoute) ToggleLanguageVisibility(w http.ResponseWriter, r *http.Request) {
languageIDStr := httputils.GetPathValue(r, "id")
if languageIDStr == "" {
httputils.ReturnError(w, http.StatusBadRequest, "Language ID is required.")
return
}

languageID, err := strconv.ParseInt(languageIDStr, 10, 64)
if err != nil {
httputils.ReturnError(w, http.StatusBadRequest, "Invalid language ID.")
return
}

db := httputils.GetDatabase(r)

err = lr.languageService.ToggleLanguageVisibility(db, languageID)
if err != nil {
httputils.HandleServiceError(w, err, db, lr.logger)
return
}

httputils.ReturnSuccess(w, http.StatusOK, httputils.NewMessageResponse("Language visibility toggled successfully"))
}

func NewLanguagesManagementRoute(languageService service.LanguageService) LanguagesManagementRoute {
route := &languagesManagementRoute{
languageService: languageService,
logger: utils.NewNamedLogger("languages-management-route"),
}

if err := utils.ValidateStruct(*route); err != nil {
log.Panicf("LanguagesManagementRoute struct is not valid: %s", err.Error())
}
return route
}

func RegisterLanguagesManagementRoutes(mux *mux.Router, route LanguagesManagementRoute) {
mux.HandleFunc("/languages", func(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
route.GetAllLanguages(w, r)
default:
httputils.ReturnError(w, http.StatusMethodNotAllowed, "Method not allowed")
}
})
mux.HandleFunc("/languages/{id}", func(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodPatch:
route.ToggleLanguageVisibility(w, r)
default:
httputils.ReturnError(w, http.StatusMethodNotAllowed, "Method not allowed")
}
})
}
110 changes: 110 additions & 0 deletions internal/api/http/routes/languages_management_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package routes_test

import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"

"github.com/gorilla/mux"
"github.com/mini-maxit/backend/internal/api/http/httputils"
"github.com/mini-maxit/backend/internal/api/http/routes"
"github.com/mini-maxit/backend/internal/testutils"
"github.com/mini-maxit/backend/package/domain/schemas"
mock_service "github.com/mini-maxit/backend/package/service/mocks"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/mock/gomock"
)

func TestLanguagesManagement_GetAllLanguages(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()

ls := mock_service.NewMockLanguageService(ctrl)
route := routes.NewLanguagesManagementRoute(ls)
db := &testutils.MockDatabase{}
handler := httputils.MockDatabaseMiddleware(http.HandlerFunc(route.GetAllLanguages), db)
server := httptest.NewServer(handler)
defer server.Close()

expected := []schemas.LanguageConfig{
{ID: 1, Type: "python", Version: "3.10", FileExtension: ".py", IsDisabled: false},
{ID: 2, Type: "CPP", Version: "20", FileExtension: ".cpp", IsDisabled: true},
}
ls.EXPECT().GetAll(gomock.Any()).Return(expected, nil).Times(1)

resp, err := http.Get(server.URL)
require.NoError(t, err)
defer resp.Body.Close()

assert.Equal(t, http.StatusOK, resp.StatusCode)

var body httputils.APIResponse[[]schemas.LanguageConfig]
err = json.NewDecoder(resp.Body).Decode(&body)
require.NoError(t, err)
assert.Len(t, body.Data, 2)
assert.True(t, body.Data[1].IsDisabled)
}

func TestLanguagesManagement_GetAllLanguages_Error(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()

ls := mock_service.NewMockLanguageService(ctrl)
route := routes.NewLanguagesManagementRoute(ls)
db := &testutils.MockDatabase{}
handler := httputils.MockDatabaseMiddleware(http.HandlerFunc(route.GetAllLanguages), db)
server := httptest.NewServer(handler)
defer server.Close()

ls.EXPECT().GetAll(gomock.Any()).Return(nil, assert.AnError).Times(1)

resp, err := http.Get(server.URL)
require.NoError(t, err)
defer resp.Body.Close()

assert.Equal(t, http.StatusInternalServerError, resp.StatusCode)
}

func TestLanguagesManagement_ToggleLanguageVisibility(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()

ls := mock_service.NewMockLanguageService(ctrl)
route := routes.NewLanguagesManagementRoute(ls)
db := &testutils.MockDatabase{}
handler := httputils.MockDatabaseMiddleware(http.HandlerFunc(route.ToggleLanguageVisibility), db)

t.Run("Toggle valid language", func(t *testing.T) {
ls.EXPECT().ToggleLanguageVisibility(gomock.Any(), int64(3)).Return(nil).Times(1)

req := httptest.NewRequest(http.MethodPatch, "/languages/3", bytes.NewBufferString(`{}`))
req = mux.SetURLVars(req, map[string]string{"id": "3"})
w := httptest.NewRecorder()

handler.ServeHTTP(w, req)

assert.Equal(t, http.StatusOK, w.Code)
})

t.Run("Invalid language ID", func(t *testing.T) {
req := httptest.NewRequest(http.MethodPatch, "/languages/abc", bytes.NewBufferString(`{}`))
req = mux.SetURLVars(req, map[string]string{"id": "abc"})
w := httptest.NewRecorder()

handler.ServeHTTP(w, req)

assert.Equal(t, http.StatusBadRequest, w.Code)
})

t.Run("Missing language ID", func(t *testing.T) {
req := httptest.NewRequest(http.MethodPatch, "/languages/", bytes.NewBufferString(`{}`))
w := httptest.NewRecorder()

handler.ServeHTTP(w, req)

assert.Equal(t, http.StatusBadRequest, w.Code)
})
}
5 changes: 5 additions & 0 deletions internal/api/http/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,10 @@ func NewServer(init *initialization.Initialization, log *zap.SugaredLogger) *Ser
contestManagementMux := mux.NewRouter()
routes.RegisterContestsManagementRoute(contestManagementMux, init.ContestManagementRoute)

// Languages management routes
languagesManagementMux := mux.NewRouter()
routes.RegisterLanguagesManagementRoutes(languagesManagementMux, init.LanguagesManagementRoute)

// Worker routes
workerMux := mux.NewRouter()
routes.RegisterWorkerRoutes(workerMux, init.WorkerRoute)
Expand All @@ -106,6 +110,7 @@ func NewServer(init *initialization.Initialization, log *zap.SugaredLogger) *Ser
secureMux.PathPrefix("/users").Handler(userMux)
secureMux.PathPrefix("/groups-management/").Handler(http.StripPrefix("/groups-management", groupMux))
secureMux.PathPrefix("/contests-management/").Handler(http.StripPrefix("/contests-management", contestManagementMux))
secureMux.PathPrefix("/languages-management/").Handler(http.StripPrefix("/languages-management", languagesManagementMux))
secureMux.PathPrefix("/contests").Handler(contestMux)
secureMux.PathPrefix("/workers/").Handler(http.StripPrefix("/workers", workerMux))

Expand Down
43 changes: 23 additions & 20 deletions internal/initialization/initialization.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,17 @@ type Initialization struct {
JWTService service.JWTService
QueueService service.QueueService

AuthRoute routes.AuthRoute
ContestRoute routes.ContestRoute
ContestManagementRoute routes.ContestsManagementRoute
GroupRoute routes.GroupRoute
SubmissionRoute routes.SubmissionRoutes
TaskRoute routes.TaskRoute
TaskManagementRoute routes.TasksManagementRoute
AccessControlRoute routes.AccessControlRoute
UserRoute routes.UserRoute
WorkerRoute routes.WorkerRoute
AuthRoute routes.AuthRoute
ContestRoute routes.ContestRoute
ContestManagementRoute routes.ContestsManagementRoute
GroupRoute routes.GroupRoute
LanguagesManagementRoute routes.LanguagesManagementRoute
SubmissionRoute routes.SubmissionRoutes
TaskRoute routes.TaskRoute
TaskManagementRoute routes.TasksManagementRoute
AccessControlRoute routes.AccessControlRoute
UserRoute routes.UserRoute
WorkerRoute routes.WorkerRoute

QueueListener queue.Listener

Expand Down Expand Up @@ -151,6 +152,7 @@ func NewInitialization(cfg *config.Config) *Initialization {
accessControlRoute := routes.NewAccessControlRoute(accessControlService)
userRoute := routes.NewUserRoute(userService)
workerRoute := routes.NewWorkerRoute(workerService)
languagesManagementRoute := routes.NewLanguagesManagementRoute(langService)

// Queue listener - uses the same queue client as queue service
queueListener := queue.NewListener(
Expand All @@ -175,16 +177,17 @@ func NewInitialization(cfg *config.Config) *Initialization {
JWTService: jwtService,
QueueService: queueService,

AuthRoute: authRoute,
ContestRoute: contestRoute,
ContestManagementRoute: contestManagementRoute,
GroupRoute: groupRoute,
SubmissionRoute: submissionRoute,
TaskRoute: taskRoute,
TaskManagementRoute: tasksManagementRoute,
AccessControlRoute: accessControlRoute,
UserRoute: userRoute,
WorkerRoute: workerRoute,
AuthRoute: authRoute,
ContestRoute: contestRoute,
ContestManagementRoute: contestManagementRoute,
GroupRoute: groupRoute,
LanguagesManagementRoute: languagesManagementRoute,
SubmissionRoute: submissionRoute,
TaskRoute: taskRoute,
TaskManagementRoute: tasksManagementRoute,
AccessControlRoute: accessControlRoute,
UserRoute: userRoute,
WorkerRoute: workerRoute,

QueueListener: queueListener,
}
Expand Down
1 change: 1 addition & 0 deletions package/domain/schemas/language_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ type LanguageConfig struct {
Type string `json:"language"`
Version string `json:"version"`
FileExtension string `json:"fileExtension"`
IsDisabled bool `json:"isDisabled"`
}
Loading
Loading