feat: added API gateway
This commit is contained in:
@@ -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)
|
||||
}
|
||||
@@ -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"`
|
||||
}
|
||||
Reference in New Issue
Block a user