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

import (
"net/http"
"strconv"

"github.com/gorilla/mux"
"github.com/mini-maxit/backend/internal/api/http/httputils"
"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
// @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 visibility (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 {
panic(err)
}
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")
}
})
}
172 changes: 172 additions & 0 deletions internal/api/http/routes/languages_management_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
package routes_test

import (
"context"
"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"
"github.com/mini-maxit/backend/package/errors"
mock_service "github.com/mini-maxit/backend/package/service/mocks"
"github.com/stretchr/testify/assert"
"go.uber.org/mock/gomock"
)

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

ls := mock_service.NewMockLanguageService(ctrl)
route := routes.NewLanguagesManagementRoute(ls)
db := &testutils.MockDatabase{}

router := mux.NewRouter()
router.HandleFunc("/languages", func(w http.ResponseWriter, r *http.Request) {
route.GetAllLanguages(w, r)
}).Methods(http.MethodGet)

handler := httputils.MockDatabaseMiddleware(router, db)

server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
mockUser := schemas.User{
ID: 1,
Role: "admin",
Email: "test@example.com",
}
ctx := context.WithValue(r.Context(), httputils.UserKey, mockUser)
handler.ServeHTTP(w, r.WithContext(ctx))
}))
defer server.Close()

t.Run("Success", func(t *testing.T) {
expectedLanguages := []schemas.LanguageConfig{
{ID: 1, Type: "C", Version: "11", FileExtension: ".c", IsDisabled: false},
{ID: 2, Type: "C++", Version: "17", FileExtension: ".cpp", IsDisabled: false},
}

ls.EXPECT().GetAll(gomock.Any()).Return(expectedLanguages, nil)

req, err := http.NewRequest(http.MethodGet, server.URL+"/languages", nil)
if err != nil {
t.Fatalf("Failed to create request: %v", err)
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
t.Fatalf("Failed to make request: %v", err)
}
defer resp.Body.Close()

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

var apiResponse httputils.APIResponse[[]schemas.LanguageConfig]
err = json.NewDecoder(resp.Body).Decode(&apiResponse)
if err != nil {
t.Fatalf("Failed to decode response: %v", err)
}

assert.Equal(t, expectedLanguages, apiResponse.Data)
})

t.Run("Service error", func(t *testing.T) {
ls.EXPECT().GetAll(gomock.Any()).Return(nil, assert.AnError)

req, err := http.NewRequest(http.MethodGet, server.URL+"/languages", nil)
if err != nil {
t.Fatalf("Failed to create request: %v", err)
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
t.Fatalf("Failed to make request: %v", err)
}
defer resp.Body.Close()

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

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

ls := mock_service.NewMockLanguageService(ctrl)
route := routes.NewLanguagesManagementRoute(ls)
db := &testutils.MockDatabase{}

router := mux.NewRouter()
router.HandleFunc("/languages/{id}", func(w http.ResponseWriter, r *http.Request) {
route.ToggleLanguageVisibility(w, r)
}).Methods(http.MethodPatch)

handler := httputils.MockDatabaseMiddleware(router, db)

server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
mockUser := schemas.User{
ID: 1,
Role: "admin",
Email: "test@example.com",
}
ctx := context.WithValue(r.Context(), httputils.UserKey, mockUser)
handler.ServeHTTP(w, r.WithContext(ctx))
}))
defer server.Close()

t.Run("Invalid language ID", func(t *testing.T) {
req, err := http.NewRequest(http.MethodPatch, server.URL+"/languages/invalid", nil)
if err != nil {
t.Fatalf("Failed to create request: %v", err)
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
t.Fatalf("Failed to make request: %v", err)
}
defer resp.Body.Close()

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

t.Run("Language not found", func(t *testing.T) {
ls.EXPECT().ToggleLanguageVisibility(gomock.Any(), int64(999)).Return(errors.ErrNotFound)

req, err := http.NewRequest(http.MethodPatch, server.URL+"/languages/999", nil)
if err != nil {
t.Fatalf("Failed to create request: %v", err)
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
t.Fatalf("Failed to make request: %v", err)
}
defer resp.Body.Close()

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

t.Run("Success", func(t *testing.T) {
ls.EXPECT().ToggleLanguageVisibility(gomock.Any(), int64(1)).Return(nil)

req, err := http.NewRequest(http.MethodPatch, server.URL+"/languages/1", nil)
if err != nil {
t.Fatalf("Failed to create request: %v", err)
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
t.Fatalf("Failed to make request: %v", err)
}
defer resp.Body.Close()

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

var apiResponse httputils.APIResponse[httputils.MessageResponse]
err = json.NewDecoder(resp.Body).Decode(&apiResponse)
if err != nil {
t.Fatalf("Failed to decode response: %v", err)
}

assert.Equal(t, "Language visibility toggled successfully", apiResponse.Data.Message)
})
}
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 @@ -107,6 +111,7 @@ func NewServer(init *initialization.Initialization, log *zap.SugaredLogger) *Ser
secureMux.PathPrefix("/groups-management/").Handler(http.StripPrefix("/groups-management", groupMux))
secureMux.PathPrefix("/contests-management/").Handler(http.StripPrefix("/contests-management", contestManagementMux))
secureMux.PathPrefix("/contests").Handler(contestMux)
secureMux.PathPrefix("/languages-management/").Handler(http.StripPrefix("/languages-management", languagesManagementMux))
secureMux.PathPrefix("/workers/").Handler(http.StripPrefix("/workers", workerMux))

// API routes
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
SubmissionRoute routes.SubmissionRoutes
TaskRoute routes.TaskRoute
TaskManagementRoute routes.TasksManagementRoute
AccessControlRoute routes.AccessControlRoute
UserRoute routes.UserRoute
WorkerRoute routes.WorkerRoute
LanguagesManagementRoute routes.LanguagesManagementRoute

QueueListener queue.Listener

Expand Down Expand Up @@ -147,6 +148,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 @@ -171,16 +173,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,
SubmissionRoute: submissionRoute,
TaskRoute: taskRoute,
TaskManagementRoute: tasksManagementRoute,
AccessControlRoute: accessControlRoute,
UserRoute: userRoute,
WorkerRoute: workerRoute,
LanguagesManagementRoute: languagesManagementRoute,

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