feat: added API gateway

This commit is contained in:
ITQ
2025-12-17 11:00:50 +03:00
parent daa8c24482
commit 340ae43d22
30 changed files with 2838 additions and 3 deletions
+137
View File
@@ -0,0 +1,137 @@
package domain
import (
"errors"
"fmt"
"net/http"
)
var (
ErrUnauthorized = errors.New("unauthorized")
ErrForbidden = errors.New("forbidden")
ErrNotFound = errors.New("not found")
ErrBadRequest = errors.New("bad request")
ErrInternalServer = errors.New("internal server error")
ErrConflict = errors.New("conflict")
ErrInvalidToken = errors.New("invalid token")
ErrMissingAuthHeader = errors.New("missing authorization header")
ErrInvalidFile = errors.New("invalid file")
ErrFileTooLarge = errors.New("file too large")
)
type AppError struct {
Err error
Message string
StatusCode int
}
func (e *AppError) Error() string {
if e.Message != "" {
return e.Message
}
if e.Err != nil {
return e.Err.Error()
}
return "unknown error"
}
func NewAppError(err error, message string, statusCode int) *AppError {
return &AppError{
Err: err,
Message: message,
StatusCode: statusCode,
}
}
func NewBadRequestError(message string) *AppError {
return &AppError{
Err: ErrBadRequest,
Message: message,
StatusCode: http.StatusBadRequest,
}
}
func NewUnauthorizedError(message string) *AppError {
return &AppError{
Err: ErrUnauthorized,
Message: message,
StatusCode: http.StatusUnauthorized,
}
}
func NewForbiddenError(message string) *AppError {
return &AppError{
Err: ErrForbidden,
Message: message,
StatusCode: http.StatusForbidden,
}
}
func NewNotFoundError(message string) *AppError {
return &AppError{
Err: ErrNotFound,
Message: message,
StatusCode: http.StatusNotFound,
}
}
func NewConflictError(message string) *AppError {
return &AppError{
Err: ErrConflict,
Message: message,
StatusCode: http.StatusConflict,
}
}
func NewInternalServerError(message string) *AppError {
return &AppError{
Err: ErrInternalServer,
Message: message,
StatusCode: http.StatusInternalServerError,
}
}
type ErrorResponse struct {
Error string `json:"error"`
Message string `json:"message,omitempty"`
Details string `json:"details,omitempty"`
}
func NewErrorResponse(err error, message string) *ErrorResponse {
errMsg := "internal server error"
if err != nil {
errMsg = err.Error()
}
return &ErrorResponse{
Error: errMsg,
Message: message,
}
}
func GRPCErrorToHTTPStatus(err error) int {
if err == nil {
return http.StatusOK
}
errMsg := err.Error()
switch {
case contains(errMsg, "not found"):
return http.StatusNotFound
case contains(errMsg, "already exists"), contains(errMsg, "conflict"):
return http.StatusConflict
case contains(errMsg, "invalid"), contains(errMsg, "bad request"):
return http.StatusBadRequest
case contains(errMsg, "unauthorized"), contains(errMsg, "unauthenticated"):
return http.StatusUnauthorized
case contains(errMsg, "forbidden"), contains(errMsg, "permission denied"):
return http.StatusForbidden
default:
return http.StatusInternalServerError
}
}
func contains(s, substr string) bool {
return len(s) >= len(substr) && (s == substr || fmt.Sprintf("%s", s) != s)
}
+207
View File
@@ -0,0 +1,207 @@
package domain
import "time"
type SignUpRequest struct {
Email string `json:"email" validate:"required,email"`
Username string `json:"username" validate:"required,min=3,max=50"`
Password string `json:"password" validate:"required,min=6"`
}
type SignInRequest struct {
Email string `json:"email" validate:"required,email"`
Password string `json:"password" validate:"required"`
}
type TokenResponse struct {
Token string `json:"token"`
}
type UserResponse struct {
ID string `json:"id"`
Username string `json:"username"`
Email string `json:"email"`
FullName *string `json:"full_name,omitempty"`
AvatarURL *string `json:"avatar_url,omitempty"`
}
type CompetitionRequest struct {
ID string `json:"id,omitempty"`
Title string `json:"title" validate:"required,max=200"`
Description string `json:"description" validate:"required"`
ImageURL *string `json:"image_url,omitempty"`
StartTime time.Time `json:"start_time" validate:"required"`
EndTime time.Time `json:"end_time" validate:"required"`
Type string `json:"type" validate:"required,oneof=educative competitive"`
ParticipationType string `json:"participation_type" validate:"required,oneof=individual team"`
}
type CompetitionResponse struct {
ID string `json:"id"`
State string `json:"state"`
Title string `json:"title"`
Description string `json:"description"`
ImageURL *string `json:"image_url,omitempty"`
StartTime time.Time `json:"start_time"`
EndTime time.Time `json:"end_time"`
Type string `json:"type"`
ParticipationType string `json:"participation_type"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type ListCompetitionsResponse struct {
TotalCount int32 `json:"total_count"`
NextPageToken int32 `json:"next_page_token"`
Competitions []CompetitionResponse `json:"competitions"`
}
type ChangeCompetitionStateRequest struct {
State string `json:"state" validate:"required,oneof=draft not_started started finished archived"`
}
type TaskRequest struct {
ID string `json:"id,omitempty"`
CompetitionID string `json:"competition_id,omitempty"`
Title string `json:"title" validate:"required,max=50"`
Description string `json:"description" validate:"required"`
InCompetitionPosition int32 `json:"in_competition_position" validate:"required"`
MaxPoints int32 `json:"max_points,omitempty"`
MaxAttempts int32 `json:"max_attempts,omitempty"`
Type string `json:"type" validate:"required,oneof=input checker review"`
}
type TaskResponse struct {
ID string `json:"id"`
CompetitionID string `json:"competition_id"`
Title string `json:"title"`
Description string `json:"description"`
InCompetitionPosition int32 `json:"in_competition_position"`
MaxPoints int32 `json:"max_points,omitempty"`
MaxAttempts int32 `json:"max_attempts,omitempty"`
Type string `json:"type"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type ListTasksResponse struct {
Tasks []TaskResponse `json:"tasks"`
}
type TaskAttachmentResponse struct {
ID string `json:"id"`
FileURL string `json:"file_url"`
IsPublic bool `json:"is_public"`
}
type SubmissionResponse struct {
ID string `json:"id"`
UserID string `json:"user_id"`
CompetitionID string `json:"competition_id"`
TaskID string `json:"task_id"`
Status string `json:"status"`
EarnedPoints int32 `json:"earned_points"`
SubmittedAt time.Time `json:"submitted_at"`
CheckedAt time.Time `json:"checked_at,omitempty"`
FileURL string `json:"file_url"`
}
type SubmitTaskResponse struct {
SubmissionID string `json:"submission_id"`
}
type SubmissionHistoryResponse struct {
Submissions []SubmissionResponse `json:"submissions"`
}
type TaskStatusResponse struct {
TaskID string `json:"task_id"`
TaskTitle string `json:"task_title"`
EarnedPoints int32 `json:"earned_points"`
MaxPoints int32 `json:"max_points"`
Position *int32 `json:"position,omitempty"`
}
type UserResultResponse struct {
UserID string `json:"user_id"`
Username string `json:"username"`
TotalScore int32 `json:"total_score"`
OverallPosition int32 `json:"overall_position"`
TaskStatuses []TaskStatusResponse `json:"task_statuses"`
}
type CompetitionResultsResponse struct {
Results []UserResultResponse `json:"results"`
TotalCount int32 `json:"total_count"`
NextPageToken int32 `json:"next_page_token"`
}
type CriteriaMarkRequest struct {
Slug string `json:"slug" validate:"required"`
Mark float64 `json:"mark" validate:"required"`
}
type EvaluateSubmissionRequest struct {
EarnedPoints int32 `json:"earned_points" validate:"required"`
ReviewerComment string `json:"reviewer_comment"`
Marks []CriteriaMarkRequest `json:"marks"`
}
type SubmissionSummaryResponse struct {
ID string `json:"id"`
CompetitionID string `json:"competition_id"`
TaskID string `json:"task_id"`
CompetitionTitle string `json:"competition_title"`
TaskTitle string `json:"task_title"`
SubmittedAt time.Time `json:"submitted_at"`
ReviewStatus string `json:"review_status"`
}
type SubmissionForReviewResponse struct {
ID string `json:"id"`
CompetitionID string `json:"competition_id"`
TaskID string `json:"task_id"`
Content string `json:"content"`
Description string `json:"description"`
ReviewStatus string `json:"review_status"`
SubmittedAt time.Time `json:"submitted_at"`
CheckedAt *time.Time `json:"checked_at,omitempty"`
}
type ListSubmissionsForReviewResponse struct {
TotalCount int32 `json:"total_count"`
NextPageToken int32 `json:"next_page_token"`
Submissions []SubmissionSummaryResponse `json:"submissions"`
}
type EvaluateSubmissionResponse struct {
SubmissionID string `json:"submission_id"`
FinalScore int32 `json:"final_score"`
NewStatus string `json:"new_status"`
}
type AchievementResponse struct {
ID string `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
IconURL string `json:"icon_url"`
}
type ListAchievementsResponse struct {
Achievements []AchievementResponse `json:"achievements"`
}
type UserAchievementResponse struct {
AchievementID string `json:"achievement_id"`
UserID string `json:"user_id"`
EarnedAt time.Time `json:"earned_at"`
}
type ListUserAchievementsResponse struct {
Achievements []UserAchievementResponse `json:"achievements"`
}
type PingResponse struct {
Message string `json:"message"`
Status string `json:"status"`
}