feat: added API gateway
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"datarush/internal/gw/domain"
|
||||
"datarush/internal/gw/grpc_client"
|
||||
"datarush/internal/gw/utils"
|
||||
)
|
||||
|
||||
type AchievementsHandler struct {
|
||||
achievementsClient *grpc_client.AchievementsClient
|
||||
}
|
||||
|
||||
func NewAchievementsHandler(achievementsClient *grpc_client.AchievementsClient) *AchievementsHandler {
|
||||
return &AchievementsHandler{achievementsClient: achievementsClient}
|
||||
}
|
||||
|
||||
func (h *AchievementsHandler) ListAchievements(w http.ResponseWriter, r *http.Request) {
|
||||
achievements, err := h.achievementsClient.ListAchievements(r.Context())
|
||||
if err != nil {
|
||||
utils.RespondError(w, domain.NewInternalServerError("failed to list achievements"))
|
||||
return
|
||||
}
|
||||
|
||||
response := make([]domain.AchievementResponse, len(achievements))
|
||||
for i, ach := range achievements {
|
||||
response[i] = *utils.AchievementProtoToHTTP(ach)
|
||||
}
|
||||
|
||||
utils.RespondJSON(w, http.StatusOK, &domain.ListAchievementsResponse{
|
||||
Achievements: response,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *AchievementsHandler) GetAchievement(w http.ResponseWriter, r *http.Request) {
|
||||
achievementID := getPathParam(r, "achievement_id")
|
||||
|
||||
achievement, err := h.achievementsClient.GetAchievement(r.Context(), achievementID)
|
||||
if err != nil {
|
||||
utils.RespondError(w, domain.NewNotFoundError("achievement not found"))
|
||||
return
|
||||
}
|
||||
|
||||
utils.RespondJSON(w, http.StatusOK, utils.AchievementProtoToHTTP(achievement))
|
||||
}
|
||||
|
||||
func (h *AchievementsHandler) GetUserAchievements(w http.ResponseWriter, r *http.Request) {
|
||||
userID := getPathParam(r, "user_id")
|
||||
|
||||
achievements, err := h.achievementsClient.GetUserAchievements(r.Context(), userID)
|
||||
if err != nil {
|
||||
utils.RespondError(w, domain.NewInternalServerError("failed to get user achievements"))
|
||||
return
|
||||
}
|
||||
|
||||
response := make([]domain.UserAchievementResponse, len(achievements))
|
||||
for i, ach := range achievements {
|
||||
response[i] = domain.UserAchievementResponse{
|
||||
AchievementID: ach.Achievement.Id,
|
||||
UserID: userID,
|
||||
EarnedAt: ach.ReceivedAt.AsTime(),
|
||||
}
|
||||
}
|
||||
|
||||
utils.RespondJSON(w, http.StatusOK, &domain.ListUserAchievementsResponse{
|
||||
Achievements: response,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"datarush/internal/gw/domain"
|
||||
"datarush/internal/gw/grpc_client"
|
||||
"datarush/internal/gw/utils"
|
||||
)
|
||||
|
||||
type AuthHandler struct {
|
||||
authClient *grpc_client.AuthClient
|
||||
userClient *grpc_client.UserClient
|
||||
}
|
||||
|
||||
func NewAuthHandler(authClient *grpc_client.AuthClient, userClient *grpc_client.UserClient) *AuthHandler {
|
||||
return &AuthHandler{
|
||||
authClient: authClient,
|
||||
userClient: userClient,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *AuthHandler) SignUp(w http.ResponseWriter, r *http.Request) {
|
||||
var req domain.SignUpRequest
|
||||
if err := utils.DecodeJSON(r, &req); err != nil {
|
||||
utils.RespondError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
token, err := h.authClient.SignUp(r.Context(), req.Email, req.Username, req.Password)
|
||||
if err != nil {
|
||||
utils.RespondError(w, domain.NewInternalServerError("sign up failed"))
|
||||
return
|
||||
}
|
||||
|
||||
utils.RespondJSON(w, http.StatusCreated, &domain.TokenResponse{Token: token})
|
||||
}
|
||||
|
||||
func (h *AuthHandler) SignIn(w http.ResponseWriter, r *http.Request) {
|
||||
var req domain.SignInRequest
|
||||
if err := utils.DecodeJSON(r, &req); err != nil {
|
||||
utils.RespondError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
token, err := h.authClient.SignIn(r.Context(), req.Email, req.Password)
|
||||
if err != nil {
|
||||
utils.RespondError(w, domain.NewUnauthorizedError("invalid credentials"))
|
||||
return
|
||||
}
|
||||
|
||||
utils.RespondJSON(w, http.StatusOK, &domain.TokenResponse{Token: token})
|
||||
}
|
||||
|
||||
func (h *AuthHandler) GetMe(w http.ResponseWriter, r *http.Request) {
|
||||
userID, err := getUserIDFromContext(r.Context())
|
||||
if err != nil {
|
||||
utils.RespondError(w, domain.NewUnauthorizedError("unauthorized"))
|
||||
return
|
||||
}
|
||||
|
||||
user, err := h.userClient.GetProfile(r.Context(), userID)
|
||||
if err != nil {
|
||||
utils.RespondError(w, domain.NewInternalServerError("failed to get user profile"))
|
||||
return
|
||||
}
|
||||
|
||||
utils.RespondJSON(w, http.StatusOK, utils.UserProtoToHTTP(user))
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"datarush/internal/gw/domain"
|
||||
"datarush/internal/gw/middleware"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
func getUserIDFromContext(ctx context.Context) (string, error) {
|
||||
return middleware.GetUserIDFromContext(ctx)
|
||||
}
|
||||
|
||||
func getPathParam(r *http.Request, key string) string {
|
||||
vars := mux.Vars(r)
|
||||
return vars[key]
|
||||
}
|
||||
|
||||
func getQueryParam(r *http.Request, key string) string {
|
||||
return r.URL.Query().Get(key)
|
||||
}
|
||||
|
||||
func getQueryParamInt(r *http.Request, key string, defaultValue int) int {
|
||||
val := r.URL.Query().Get(key)
|
||||
if val == "" {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
intVal, err := strconv.Atoi(val)
|
||||
if err != nil {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
return intVal
|
||||
}
|
||||
|
||||
func getQueryParamInt32(r *http.Request, key string, defaultValue int32) int32 {
|
||||
return int32(getQueryParamInt(r, key, int(defaultValue)))
|
||||
}
|
||||
|
||||
func getQueryParamBool(r *http.Request, key string) *bool {
|
||||
val := r.URL.Query().Get(key)
|
||||
if val == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
boolVal, err := strconv.ParseBool(val)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &boolVal
|
||||
}
|
||||
|
||||
type PingHandler struct{}
|
||||
|
||||
func NewPingHandler() *PingHandler {
|
||||
return &PingHandler{}
|
||||
}
|
||||
|
||||
func (h *PingHandler) Ping(w http.ResponseWriter, r *http.Request) {
|
||||
response := &domain.PingResponse{
|
||||
Message: "pong",
|
||||
Status: "ok",
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
json.NewEncoder(w).Encode(response)
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"datarush/internal/gw/domain"
|
||||
"datarush/internal/gw/grpc_client"
|
||||
"datarush/internal/gw/utils"
|
||||
|
||||
comppb "datarush/pkg/api/competition"
|
||||
)
|
||||
|
||||
type CompetitionHandler struct {
|
||||
competitionClient *grpc_client.CompetitionClient
|
||||
userClient *grpc_client.UserClient
|
||||
}
|
||||
|
||||
func NewCompetitionHandler(
|
||||
competitionClient *grpc_client.CompetitionClient,
|
||||
userClient *grpc_client.UserClient,
|
||||
) *CompetitionHandler {
|
||||
return &CompetitionHandler{
|
||||
competitionClient: competitionClient,
|
||||
userClient: userClient,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *CompetitionHandler) CreateCompetition(w http.ResponseWriter, r *http.Request) {
|
||||
var req domain.CompetitionRequest
|
||||
if err := utils.DecodeJSON(r, &req); err != nil {
|
||||
utils.RespondError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
competition := utils.CompetitionHTTPToProto(&req)
|
||||
resp, err := h.competitionClient.CreateCompetition(r.Context(), competition)
|
||||
if err != nil {
|
||||
utils.RespondError(w, domain.NewInternalServerError("failed to create competition"))
|
||||
return
|
||||
}
|
||||
|
||||
utils.RespondJSON(w, http.StatusCreated, utils.CompetitionProtoToHTTP(resp))
|
||||
}
|
||||
|
||||
func (h *CompetitionHandler) GetCompetition(w http.ResponseWriter, r *http.Request) {
|
||||
competitionID := getPathParam(r, "competition_id")
|
||||
|
||||
competition, err := h.competitionClient.GetCompetition(r.Context(), competitionID)
|
||||
if err != nil {
|
||||
utils.RespondError(w, domain.NewNotFoundError("competition not found"))
|
||||
return
|
||||
}
|
||||
|
||||
utils.RespondJSON(w, http.StatusOK, utils.CompetitionProtoToHTTP(competition))
|
||||
}
|
||||
|
||||
func (h *CompetitionHandler) UpdateCompetition(w http.ResponseWriter, r *http.Request) {
|
||||
competitionID := getPathParam(r, "competition_id")
|
||||
|
||||
var req domain.CompetitionRequest
|
||||
if err := utils.DecodeJSON(r, &req); err != nil {
|
||||
utils.RespondError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
req.ID = competitionID
|
||||
competition := utils.CompetitionHTTPToProto(&req)
|
||||
resp, err := h.competitionClient.EditCompetition(r.Context(), competition)
|
||||
if err != nil {
|
||||
utils.RespondError(w, domain.NewInternalServerError("failed to update competition"))
|
||||
return
|
||||
}
|
||||
|
||||
utils.RespondJSON(w, http.StatusOK, utils.CompetitionProtoToHTTP(resp))
|
||||
}
|
||||
|
||||
func (h *CompetitionHandler) DeleteCompetition(w http.ResponseWriter, r *http.Request) {
|
||||
competitionID := getPathParam(r, "competition_id")
|
||||
|
||||
if err := h.competitionClient.DeleteCompetition(r.Context(), competitionID); err != nil {
|
||||
utils.RespondError(w, domain.NewInternalServerError("failed to delete competition"))
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
func (h *CompetitionHandler) ListCompetitions(w http.ResponseWriter, r *http.Request) {
|
||||
pageSize := getQueryParamInt32(r, "page_size", 20)
|
||||
pageToken := getQueryParamInt32(r, "page_token", 0)
|
||||
state := getQueryParam(r, "state")
|
||||
searchQuery := getQueryParam(r, "search_query")
|
||||
isParticipating := getQueryParamBool(r, "is_participating")
|
||||
|
||||
req := &comppb.ListCompetitionsRequest{
|
||||
PageSize: pageSize,
|
||||
PageToken: pageToken,
|
||||
}
|
||||
|
||||
if state != "" {
|
||||
s := utils.StringToCompetitionState(state)
|
||||
req.State = &s
|
||||
}
|
||||
|
||||
if searchQuery != "" {
|
||||
req.SearchQuery = &searchQuery
|
||||
}
|
||||
|
||||
if isParticipating != nil {
|
||||
req.IsParticipating = isParticipating
|
||||
}
|
||||
|
||||
resp, err := h.competitionClient.ListCompetitions(r.Context(), req)
|
||||
if err != nil {
|
||||
utils.RespondError(w, domain.NewInternalServerError("failed to list competitions"))
|
||||
return
|
||||
}
|
||||
|
||||
competitions := make([]domain.CompetitionResponse, len(resp.Competitions))
|
||||
for i, comp := range resp.Competitions {
|
||||
competitions[i] = *utils.CompetitionProtoToHTTP(comp)
|
||||
}
|
||||
|
||||
utils.RespondJSON(w, http.StatusOK, &domain.ListCompetitionsResponse{
|
||||
TotalCount: resp.TotalCount,
|
||||
NextPageToken: resp.NextPageToken,
|
||||
Competitions: competitions,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *CompetitionHandler) ChangeCompetitionState(w http.ResponseWriter, r *http.Request) {
|
||||
competitionID := getPathParam(r, "competition_id")
|
||||
|
||||
var req domain.ChangeCompetitionStateRequest
|
||||
if err := utils.DecodeJSON(r, &req); err != nil {
|
||||
utils.RespondError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
state := utils.StringToCompetitionState(req.State)
|
||||
competition, err := h.competitionClient.ChangeCompetitionState(r.Context(), competitionID, state)
|
||||
if err != nil {
|
||||
utils.RespondError(w, domain.NewInternalServerError("failed to change competition state"))
|
||||
return
|
||||
}
|
||||
|
||||
utils.RespondJSON(w, http.StatusOK, utils.CompetitionProtoToHTTP(competition))
|
||||
}
|
||||
|
||||
func (h *CompetitionHandler) JoinCompetition(w http.ResponseWriter, r *http.Request) {
|
||||
competitionID := getPathParam(r, "competition_id")
|
||||
|
||||
if err := h.userClient.RegisterForCompetition(r.Context(), competitionID); err != nil {
|
||||
utils.RespondError(w, domain.NewInternalServerError("failed to join competition"))
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"datarush/internal/gw/domain"
|
||||
"datarush/internal/gw/grpc_client"
|
||||
"datarush/internal/gw/utils"
|
||||
|
||||
resultspb "datarush/pkg/api/results"
|
||||
)
|
||||
|
||||
type ResultsHandler struct {
|
||||
resultsClient *grpc_client.ResultsClient
|
||||
}
|
||||
|
||||
func NewResultsHandler(resultsClient *grpc_client.ResultsClient) *ResultsHandler {
|
||||
return &ResultsHandler{resultsClient: resultsClient}
|
||||
}
|
||||
|
||||
func (h *ResultsHandler) GetCompetitionResults(w http.ResponseWriter, r *http.Request) {
|
||||
competitionID := getPathParam(r, "competition_id")
|
||||
pageSize := getQueryParamInt32(r, "page_size", 20)
|
||||
pageToken := getQueryParamInt32(r, "page_token", 0)
|
||||
|
||||
req := &resultspb.GetCompetitionResultsRequest{
|
||||
CompetitionId: competitionID,
|
||||
PageSize: pageSize,
|
||||
PageToken: pageToken,
|
||||
}
|
||||
|
||||
resp, err := h.resultsClient.GetCompetitionResults(r.Context(), req)
|
||||
if err != nil {
|
||||
utils.RespondError(w, domain.NewInternalServerError("failed to get competition results"))
|
||||
return
|
||||
}
|
||||
|
||||
results := make([]domain.UserResultResponse, len(resp.Results))
|
||||
for i, result := range resp.Results {
|
||||
results[i] = *utils.UserResultProtoToHTTP(result)
|
||||
}
|
||||
|
||||
utils.RespondJSON(w, http.StatusOK, &domain.CompetitionResultsResponse{
|
||||
Results: results,
|
||||
TotalCount: resp.TotalCount,
|
||||
NextPageToken: resp.NextPageToken,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *ResultsHandler) GetMyResults(w http.ResponseWriter, r *http.Request) {
|
||||
userID, err := getUserIDFromContext(r.Context())
|
||||
if err != nil {
|
||||
utils.RespondError(w, domain.NewUnauthorizedError("unauthorized"))
|
||||
return
|
||||
}
|
||||
|
||||
competitionID := getPathParam(r, "competition_id")
|
||||
|
||||
result, err := h.resultsClient.GetUserCompetitionResults(r.Context(), competitionID, userID)
|
||||
if err != nil {
|
||||
utils.RespondError(w, domain.NewInternalServerError("failed to get user results"))
|
||||
return
|
||||
}
|
||||
|
||||
utils.RespondJSON(w, http.StatusOK, utils.UserResultProtoToHTTP(result))
|
||||
}
|
||||
|
||||
func (h *ResultsHandler) RecalculateResults(w http.ResponseWriter, r *http.Request) {
|
||||
competitionID := getPathParam(r, "competition_id")
|
||||
|
||||
if err := h.resultsClient.RecalculateResults(r.Context(), competitionID); err != nil {
|
||||
utils.RespondError(w, domain.NewInternalServerError("failed to recalculate results"))
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"datarush/internal/gw/domain"
|
||||
"datarush/internal/gw/grpc_client"
|
||||
"datarush/internal/gw/utils"
|
||||
|
||||
reviewpb "datarush/pkg/api/review"
|
||||
)
|
||||
|
||||
type ReviewHandler struct {
|
||||
reviewClient *grpc_client.ReviewClient
|
||||
}
|
||||
|
||||
func NewReviewHandler(reviewClient *grpc_client.ReviewClient) *ReviewHandler {
|
||||
return &ReviewHandler{reviewClient: reviewClient}
|
||||
}
|
||||
|
||||
func (h *ReviewHandler) ListSubmissionsForReview(w http.ResponseWriter, r *http.Request) {
|
||||
token := getPathParam(r, "token")
|
||||
pageSize := getQueryParamInt32(r, "page_size", 20)
|
||||
pageToken := getQueryParamInt32(r, "page_token", 0)
|
||||
statusStr := getQueryParam(r, "status")
|
||||
|
||||
req := &reviewpb.ListSubmissionsForReviewRequest{
|
||||
Token: token,
|
||||
PageSize: pageSize,
|
||||
PageToken: pageToken,
|
||||
}
|
||||
|
||||
if statusStr != "" {
|
||||
status := stringToReviewStatus(statusStr)
|
||||
req.Status = &status
|
||||
}
|
||||
|
||||
resp, err := h.reviewClient.ListSubmissionsForReview(r.Context(), req)
|
||||
if err != nil {
|
||||
utils.RespondError(w, domain.NewUnauthorizedError("invalid review token"))
|
||||
return
|
||||
}
|
||||
|
||||
submissions := make([]domain.SubmissionSummaryResponse, len(resp.Submissions))
|
||||
for i, sub := range resp.Submissions {
|
||||
submissions[i] = *utils.SubmissionSummaryProtoToHTTP(sub)
|
||||
}
|
||||
|
||||
utils.RespondJSON(w, http.StatusOK, &domain.ListSubmissionsForReviewResponse{
|
||||
TotalCount: resp.TotalCount,
|
||||
NextPageToken: resp.NextPageToken,
|
||||
Submissions: submissions,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *ReviewHandler) GetSubmissionForReview(w http.ResponseWriter, r *http.Request) {
|
||||
token := getPathParam(r, "token")
|
||||
submissionID := getPathParam(r, "submission_id")
|
||||
|
||||
submission, err := h.reviewClient.GetSubmissionForReview(r.Context(), token, submissionID)
|
||||
if err != nil {
|
||||
utils.RespondError(w, domain.NewNotFoundError("submission not found"))
|
||||
return
|
||||
}
|
||||
|
||||
utils.RespondJSON(w, http.StatusOK, utils.SubmissionForReviewProtoToHTTP(submission))
|
||||
}
|
||||
|
||||
func (h *ReviewHandler) EvaluateSubmission(w http.ResponseWriter, r *http.Request) {
|
||||
token := getPathParam(r, "token")
|
||||
submissionID := getPathParam(r, "submission_id")
|
||||
|
||||
var reqBody domain.EvaluateSubmissionRequest
|
||||
if err := utils.DecodeJSON(r, &reqBody); err != nil {
|
||||
utils.RespondError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
marks := make([]*reviewpb.CriteriaMark, len(reqBody.Marks))
|
||||
for i, m := range reqBody.Marks {
|
||||
marks[i] = &reviewpb.CriteriaMark{
|
||||
Slug: m.Slug,
|
||||
Mark: m.Mark,
|
||||
}
|
||||
}
|
||||
|
||||
req := &reviewpb.EvaluateSubmissionRequest{
|
||||
Token: token,
|
||||
SubmissionId: submissionID,
|
||||
EarnedPoints: reqBody.EarnedPoints,
|
||||
ReviewerComment: reqBody.ReviewerComment,
|
||||
Marks: marks,
|
||||
}
|
||||
|
||||
resp, err := h.reviewClient.EvaluateSubmission(r.Context(), req)
|
||||
if err != nil {
|
||||
utils.RespondError(w, domain.NewInternalServerError("failed to evaluate submission"))
|
||||
return
|
||||
}
|
||||
|
||||
utils.RespondJSON(w, http.StatusOK, &domain.EvaluateSubmissionResponse{
|
||||
SubmissionID: resp.SubmissionId,
|
||||
FinalScore: resp.FinalScore,
|
||||
NewStatus: utils.ReviewStatusToString(resp.NewStatus),
|
||||
})
|
||||
}
|
||||
|
||||
func (h *ReviewHandler) ReleaseSubmission(w http.ResponseWriter, r *http.Request) {
|
||||
token := getPathParam(r, "token")
|
||||
submissionID := getPathParam(r, "submission_id")
|
||||
|
||||
if err := h.reviewClient.ReleaseSubmission(r.Context(), token, submissionID); err != nil {
|
||||
utils.RespondError(w, domain.NewInternalServerError("failed to release submission"))
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
func stringToReviewStatus(s string) reviewpb.ReviewStatus {
|
||||
switch s {
|
||||
case "pending":
|
||||
return reviewpb.ReviewStatus_REVIEW_STATUS_PENDING
|
||||
case "in_review":
|
||||
return reviewpb.ReviewStatus_REVIEW_STATUS_IN_REVIEW
|
||||
case "completed":
|
||||
return reviewpb.ReviewStatus_REVIEW_STATUS_COMPLETED
|
||||
case "rejected":
|
||||
return reviewpb.ReviewStatus_REVIEW_STATUS_REJECTED
|
||||
default:
|
||||
return reviewpb.ReviewStatus_REVIEW_STATUS_UNSPECIFIED
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"datarush/internal/gw/domain"
|
||||
"datarush/internal/gw/grpc_client"
|
||||
"datarush/internal/gw/storage"
|
||||
"datarush/internal/gw/utils"
|
||||
)
|
||||
|
||||
type SubmissionHandler struct {
|
||||
submissionClient *grpc_client.SubmissionClient
|
||||
s3Storage *storage.S3Storage
|
||||
}
|
||||
|
||||
func NewSubmissionHandler(
|
||||
submissionClient *grpc_client.SubmissionClient,
|
||||
s3Storage *storage.S3Storage,
|
||||
) *SubmissionHandler {
|
||||
return &SubmissionHandler{
|
||||
submissionClient: submissionClient,
|
||||
s3Storage: s3Storage,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *SubmissionHandler) SubmitTask(w http.ResponseWriter, r *http.Request) {
|
||||
userID, err := getUserIDFromContext(r.Context())
|
||||
if err != nil {
|
||||
utils.RespondError(w, domain.NewUnauthorizedError("unauthorized"))
|
||||
return
|
||||
}
|
||||
|
||||
competitionID := getPathParam(r, "competition_id")
|
||||
taskID := getPathParam(r, "task_id")
|
||||
|
||||
if err := r.ParseMultipartForm(32 << 20); err != nil { // 32 MB max
|
||||
utils.RespondError(w, domain.NewBadRequestError("failed to parse form"))
|
||||
return
|
||||
}
|
||||
|
||||
file, header, err := r.FormFile("content")
|
||||
if err != nil {
|
||||
utils.RespondError(w, domain.NewBadRequestError("missing or invalid file"))
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
fileURL, err := h.s3Storage.UploadFile(r.Context(), file, header)
|
||||
if err != nil {
|
||||
utils.RespondError(w, domain.NewInternalServerError("failed to upload file"))
|
||||
return
|
||||
}
|
||||
|
||||
submission, err := h.submissionClient.SubmitTask(r.Context(), userID, competitionID, taskID, fileURL)
|
||||
if err != nil {
|
||||
utils.RespondError(w, domain.NewInternalServerError("failed to submit task"))
|
||||
return
|
||||
}
|
||||
|
||||
utils.RespondJSON(w, http.StatusCreated, &domain.SubmitTaskResponse{
|
||||
SubmissionID: submission.Id,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *SubmissionHandler) GetSubmissionHistory(w http.ResponseWriter, r *http.Request) {
|
||||
userID, err := getUserIDFromContext(r.Context())
|
||||
if err != nil {
|
||||
utils.RespondError(w, domain.NewUnauthorizedError("unauthorized"))
|
||||
return
|
||||
}
|
||||
|
||||
competitionID := getPathParam(r, "competition_id")
|
||||
taskID := getPathParam(r, "task_id")
|
||||
|
||||
submissions, err := h.submissionClient.GetSubmissionsHistory(r.Context(), userID, competitionID, taskID)
|
||||
if err != nil {
|
||||
utils.RespondError(w, domain.NewInternalServerError("failed to get submission history"))
|
||||
return
|
||||
}
|
||||
|
||||
response := make([]domain.SubmissionResponse, len(submissions))
|
||||
for i, sub := range submissions {
|
||||
response[i] = *utils.SubmissionProtoToHTTP(sub)
|
||||
}
|
||||
|
||||
utils.RespondJSON(w, http.StatusOK, &domain.SubmissionHistoryResponse{
|
||||
Submissions: response,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"datarush/internal/gw/domain"
|
||||
"datarush/internal/gw/grpc_client"
|
||||
"datarush/internal/gw/utils"
|
||||
)
|
||||
|
||||
type TaskHandler struct {
|
||||
taskClient *grpc_client.TaskClient
|
||||
}
|
||||
|
||||
func NewTaskHandler(taskClient *grpc_client.TaskClient) *TaskHandler {
|
||||
return &TaskHandler{taskClient: taskClient}
|
||||
}
|
||||
|
||||
func (h *TaskHandler) CreateTask(w http.ResponseWriter, r *http.Request) {
|
||||
competitionID := getPathParam(r, "competition_id")
|
||||
|
||||
var req domain.TaskRequest
|
||||
if err := utils.DecodeJSON(r, &req); err != nil {
|
||||
utils.RespondError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
req.CompetitionID = competitionID
|
||||
task := utils.TaskHTTPToProto(&req)
|
||||
resp, err := h.taskClient.CreateTask(r.Context(), task)
|
||||
if err != nil {
|
||||
utils.RespondError(w, domain.NewInternalServerError("failed to create task"))
|
||||
return
|
||||
}
|
||||
|
||||
utils.RespondJSON(w, http.StatusCreated, utils.TaskProtoToHTTP(resp))
|
||||
}
|
||||
|
||||
func (h *TaskHandler) GetTask(w http.ResponseWriter, r *http.Request) {
|
||||
taskID := getPathParam(r, "task_id")
|
||||
|
||||
task, err := h.taskClient.GetTask(r.Context(), taskID)
|
||||
if err != nil {
|
||||
utils.RespondError(w, domain.NewNotFoundError("task not found"))
|
||||
return
|
||||
}
|
||||
|
||||
utils.RespondJSON(w, http.StatusOK, utils.TaskProtoToHTTP(task))
|
||||
}
|
||||
|
||||
func (h *TaskHandler) UpdateTask(w http.ResponseWriter, r *http.Request) {
|
||||
competitionID := getPathParam(r, "competition_id")
|
||||
taskID := getPathParam(r, "task_id")
|
||||
|
||||
var req domain.TaskRequest
|
||||
if err := utils.DecodeJSON(r, &req); err != nil {
|
||||
utils.RespondError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
req.ID = taskID
|
||||
req.CompetitionID = competitionID
|
||||
task := utils.TaskHTTPToProto(&req)
|
||||
resp, err := h.taskClient.EditTask(r.Context(), task)
|
||||
if err != nil {
|
||||
utils.RespondError(w, domain.NewInternalServerError("failed to update task"))
|
||||
return
|
||||
}
|
||||
|
||||
utils.RespondJSON(w, http.StatusOK, utils.TaskProtoToHTTP(resp))
|
||||
}
|
||||
|
||||
func (h *TaskHandler) DeleteTask(w http.ResponseWriter, r *http.Request) {
|
||||
taskID := getPathParam(r, "task_id")
|
||||
|
||||
if err := h.taskClient.DeleteTask(r.Context(), taskID); err != nil {
|
||||
utils.RespondError(w, domain.NewInternalServerError("failed to delete task"))
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
func (h *TaskHandler) ListTasks(w http.ResponseWriter, r *http.Request) {
|
||||
competitionID := getPathParam(r, "competition_id")
|
||||
|
||||
tasks, err := h.taskClient.ListCompetitionTasks(r.Context(), competitionID)
|
||||
if err != nil {
|
||||
utils.RespondError(w, domain.NewInternalServerError("failed to list tasks"))
|
||||
return
|
||||
}
|
||||
|
||||
response := make([]domain.TaskResponse, len(tasks))
|
||||
for i, task := range tasks {
|
||||
response[i] = *utils.TaskProtoToHTTP(task)
|
||||
}
|
||||
|
||||
utils.RespondJSON(w, http.StatusOK, &domain.ListTasksResponse{Tasks: response})
|
||||
}
|
||||
Reference in New Issue
Block a user