assessment question domain fix

This commit is contained in:
Yared Yemane 2026-01-21 13:05:18 -08:00
parent 68472b09b1
commit d691edae8b
5 changed files with 1289 additions and 1120 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,42 +1,42 @@
definitions: definitions:
authentication.LoginRequest: domain.AgeGroup:
properties: enum:
email: - UNDER_13
type: string - "13_17"
otp_code: - "18_24"
type: string - "25_34"
password: - "35_44"
type: string - "45_54"
phone_number: - 55_PLUS
type: string type: string
type: object x-enum-varnames:
domain.AssessmentOption: - AgeUnder13
properties: - Age13To17
id: - Age18To24
format: int64 - Age25To34
type: integer - Age35To44
isCorrect: - Age45To54
type: boolean - Age55Plus
optionText:
type: string
type: object
domain.AssessmentQuestion: domain.AssessmentQuestion:
properties: properties:
created_at:
type: string
description: description:
type: string type: string
difficultyLevel: difficulty_level:
type: string type: string
id: id:
format: int64
type: integer type: integer
options: is_active:
items: type: boolean
$ref: '#/definitions/domain.AssessmentOption' points:
type: array type: integer
questionType: question_type:
type: string type: string
title: title:
type: string type: string
updated_at:
type: string
type: object type: object
domain.Course: domain.Course:
properties: properties:
@ -66,6 +66,40 @@ definitions:
description: '"Learning English", "Other Courses"' description: '"Learning English", "Other Courses"'
type: string type: string
type: object type: object
domain.CreateAssessmentQuestionInput:
properties:
correctAnswer:
description: Short Answer only
type: string
description:
type: string
difficultyLevel:
type: string
isActive:
type: boolean
options:
description: Multiple Choice only
items:
$ref: '#/definitions/domain.CreateQuestionOptionInput'
type: array
points:
format: int32
type: integer
questionType:
$ref: '#/definitions/domain.QuestionType'
title:
type: string
type: object
domain.CreateQuestionOptionInput:
properties:
isCorrect:
type: boolean
order:
format: int32
type: integer
text:
type: string
type: object
domain.ErrorResponse: domain.ErrorResponse:
properties: properties:
error: error:
@ -129,6 +163,17 @@ definitions:
pagination: pagination:
$ref: '#/definitions/domain.Pagination' $ref: '#/definitions/domain.Pagination'
type: object type: object
domain.LoginRequest:
properties:
email:
type: string
otp_code:
type: string
password:
type: string
phone_number:
type: string
type: object
domain.Module: domain.Module:
properties: properties:
content: content:
@ -268,46 +313,44 @@ definitions:
description: '"public", "private", "unlisted"' description: '"public", "private", "unlisted"'
type: string type: string
type: object type: object
domain.QuestionOption:
properties:
option_text:
type: string
question_id:
type: integer
type: object
domain.QuestionType:
enum:
- MULTIPLE_CHOICE
- TRUE_FALSE
- SHORT_ANSWER
type: string
x-enum-varnames:
- MultipleChoice
- TrueFalse
- ShortAnswer
domain.QuestionWithDetails:
properties:
options:
items:
$ref: '#/definitions/domain.QuestionOption'
type: array
question:
$ref: '#/definitions/domain.AssessmentQuestion'
type: object
domain.RegisterUserReq: domain.RegisterUserReq:
properties: properties:
age:
type: integer
country:
type: string
education_level:
type: string
email: email:
type: string type: string
favoutite_topic:
type: string
first_name:
type: string
language_challange:
type: string
language_goal:
type: string
last_name:
type: string
learning_goal:
type: string
nick_name:
type: string
occupation:
type: string
otp_medium: otp_medium:
$ref: '#/definitions/domain.OtpMedium' $ref: '#/definitions/domain.OtpMedium'
password: password:
type: string type: string
phone_number: phone_number:
type: string type: string
preferred_language:
type: string
region:
type: string
role: role:
type: string type: string
user_name:
type: string
type: object type: object
domain.ResendOtpReq: domain.ResendOtpReq:
properties: properties:
@ -351,52 +394,52 @@ definitions:
type: object type: object
domain.UpdateUserReq: domain.UpdateUserReq:
properties: properties:
age: age_group:
$ref: '#/definitions/domain.ValidInt' $ref: '#/definitions/domain.AgeGroup'
birth_day:
description: YYYY-MM-DD
type: string
country: country:
$ref: '#/definitions/domain.ValidString' type: string
educationLevel: education_level:
$ref: '#/definitions/domain.ValidString' type: string
favoutiteTopic: favourite_topic:
$ref: '#/definitions/domain.ValidString' type: string
firstName: first_name:
$ref: '#/definitions/domain.ValidString' type: string
knowledgeLevel: gender:
allOf: type: string
- $ref: '#/definitions/domain.ValidString' initial_assessment_completed:
description: Profile fields type: boolean
languageChallange: knowledge_level:
$ref: '#/definitions/domain.ValidString' type: string
languageGoal: language_challange:
$ref: '#/definitions/domain.ValidString' type: string
lastName: language_goal:
$ref: '#/definitions/domain.ValidString' type: string
learningGoal: last_name:
$ref: '#/definitions/domain.ValidString' type: string
nickName: learning_goal:
$ref: '#/definitions/domain.ValidString' type: string
nick_name:
type: string
occupation: occupation:
$ref: '#/definitions/domain.ValidString' type: string
preferredLanguage: preferred_language:
$ref: '#/definitions/domain.ValidString' type: string
profileCompleted: profile_completed:
$ref: '#/definitions/domain.ValidBool' type: boolean
profilePictureURL: profile_picture_url:
$ref: '#/definitions/domain.ValidString' type: string
region: region:
$ref: '#/definitions/domain.ValidString' type: string
status:
$ref: '#/definitions/domain.ValidString'
userID:
format: int64
type: integer
userName:
$ref: '#/definitions/domain.ValidString'
type: object type: object
domain.UserProfileResponse: domain.UserProfileResponse:
properties: properties:
age: age_group:
type: integer type: string
birth_day:
type: string
country: country:
type: string type: string
created_at: created_at:
@ -404,6 +447,7 @@ definitions:
education_level: education_level:
type: string type: string
email: email:
description: UserName string `json:"user_name,omitempty"`
type: string type: string
email_verified: email_verified:
type: boolean type: boolean
@ -411,6 +455,8 @@ definitions:
type: string type: string
first_name: first_name:
type: string type: string
gender:
type: string
id: id:
type: integer type: integer
initial_assessment_completed: initial_assessment_completed:
@ -448,8 +494,6 @@ definitions:
$ref: '#/definitions/domain.UserStatus' $ref: '#/definitions/domain.UserStatus'
updated_at: updated_at:
type: string type: string
user_name:
type: string
type: object type: object
domain.UserStatus: domain.UserStatus:
enum: enum:
@ -463,27 +507,6 @@ definitions:
- UserStatusActive - UserStatusActive
- UserStatusSuspended - UserStatusSuspended
- UserStatusDeactivated - UserStatusDeactivated
domain.ValidBool:
properties:
valid:
type: boolean
value:
type: boolean
type: object
domain.ValidInt:
properties:
valid:
type: boolean
value:
type: integer
type: object
domain.ValidString:
properties:
valid:
type: boolean
value:
type: string
type: object
domain.VerifyOtpReq: domain.VerifyOtpReq:
properties: properties:
email: email:
@ -615,21 +638,6 @@ definitions:
type: string type: string
type: object type: object
handlers.ResetPasswordReq: handlers.ResetPasswordReq:
properties:
otp:
example: "123456"
type: string
password:
example: newpassword123
minLength: 8
type: string
user_name:
example: johndoe
type: string
required:
- otp
- password
- user_name
type: object type: object
handlers.SearchUserByNameOrPhoneReq: handlers.SearchUserByNameOrPhoneReq:
properties: properties:
@ -740,7 +748,7 @@ paths:
name: login name: login
required: true required: true
schema: schema:
$ref: '#/definitions/authentication.LoginRequest' $ref: '#/definitions/domain.LoginRequest'
produces: produces:
- application/json - application/json
responses: responses:
@ -793,45 +801,6 @@ paths:
summary: Resend OTP summary: Resend OTP
tags: tags:
- otp - otp
/api/v1/{tenant_slug}/user:
put:
consumes:
- application/json
description: Updates user profile information (partial updates supported)
parameters:
- description: User ID
in: path
name: user_id
required: true
type: integer
- description: Update user payload
in: body
name: body
required: true
schema:
$ref: '#/definitions/domain.UpdateUserReq'
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/domain.Response'
"400":
description: Bad Request
schema:
$ref: '#/definitions/domain.ErrorResponse'
"404":
description: Not Found
schema:
$ref: '#/definitions/domain.ErrorResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/domain.ErrorResponse'
summary: Update user profile
tags:
- user
/api/v1/{tenant_slug}/user-login: /api/v1/{tenant_slug}/user-login:
post: post:
consumes: consumes:
@ -843,7 +812,7 @@ paths:
name: login name: login
required: true required: true
schema: schema:
$ref: '#/definitions/authentication.LoginRequest' $ref: '#/definitions/domain.LoginRequest'
produces: produces:
- application/json - application/json
responses: responses:
@ -866,39 +835,6 @@ paths:
summary: Login user summary: Login user
tags: tags:
- auth - auth
/api/v1/{tenant_slug}/user/{user_name}/is-pending:
get:
consumes:
- application/json
description: Returns whether the specified user has a status of "pending"
parameters:
- description: User Name
in: path
name: user_name
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/domain.Response'
"400":
description: Bad Request
schema:
$ref: '#/definitions/domain.ErrorResponse'
"404":
description: Not Found
schema:
$ref: '#/definitions/domain.ErrorResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/domain.ErrorResponse'
summary: Check if user status is pending
tags:
- user
/api/v1/{tenant_slug}/user/admin-profile: /api/v1/{tenant_slug}/user/admin-profile:
get: get:
consumes: consumes:
@ -1139,54 +1075,6 @@ paths:
summary: Get user profile summary: Get user profile
tags: tags:
- user - user
/api/v1/{tenant_slug}/users:
get:
consumes:
- application/json
description: Get users with optional filters
parameters:
- description: Role filter
in: query
name: role
type: string
- description: Search query
in: query
name: query
type: string
- description: Page number
in: query
name: page
type: integer
- description: Page size
in: query
name: page_size
type: integer
- description: Created before (RFC3339)
in: query
name: created_before
type: string
- description: Created after (RFC3339)
in: query
name: created_after
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/response.APIResponse'
"400":
description: Bad Request
schema:
$ref: '#/definitions/response.APIResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.APIResponse'
summary: Get all users
tags:
- user
/api/v1/admin: /api/v1/admin:
get: get:
consumes: consumes:
@ -1324,53 +1212,42 @@ paths:
- admin - admin
/api/v1/assessment/questions: /api/v1/assessment/questions:
get: get:
consumes: description: Returns all active assessment questions with their options or answers
- application/json
description: Returns all active questions used for initial knowledge assessment
produces: produces:
- application/json - application/json
responses: responses:
"200": "200":
description: OK description: OK
schema: schema:
allOf: items:
- $ref: '#/definitions/domain.Response' $ref: '#/definitions/domain.QuestionWithDetails'
- properties: type: array
data:
items:
$ref: '#/definitions/domain.AssessmentQuestion'
type: array
type: object
"500": "500":
description: Internal Server Error description: Internal Server Error
schema: schema:
$ref: '#/definitions/domain.ErrorResponse' $ref: '#/definitions/domain.ErrorResponse'
summary: Get active initial assessment questions summary: List assessment questions
tags: tags:
- assessment - assessment-question
post: post:
consumes: consumes:
- application/json - application/json
description: Creates a new question for the initial knowledge assessment description: Creates a new assessment question with options or short answer
depending on question type
parameters: parameters:
- description: Assessment question payload - description: Create question payload
in: body in: body
name: question name: body
required: true required: true
schema: schema:
$ref: '#/definitions/domain.AssessmentQuestion' $ref: '#/definitions/domain.CreateAssessmentQuestionInput'
produces: produces:
- application/json - application/json
responses: responses:
"201": "201":
description: Created description: Created
schema: schema:
allOf: $ref: '#/definitions/domain.Response'
- $ref: '#/definitions/domain.Response'
- properties:
data:
$ref: '#/definitions/domain.AssessmentQuestion'
type: object
"400": "400":
description: Bad Request description: Bad Request
schema: schema:
@ -1381,7 +1258,50 @@ paths:
$ref: '#/definitions/domain.ErrorResponse' $ref: '#/definitions/domain.ErrorResponse'
summary: Create assessment question summary: Create assessment question
tags: tags:
- assessment - assessment-question
/api/v1/assessment/questions/{id}:
get:
description: Returns a single assessment question with its options or answer
parameters:
- description: Question ID
in: path
name: id
required: true
type: integer
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/domain.QuestionWithDetails'
"400":
description: Bad Request
schema:
$ref: '#/definitions/domain.ErrorResponse'
"404":
description: Not Found
schema:
$ref: '#/definitions/domain.ErrorResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/domain.ErrorResponse'
summary: Get assessment question by ID
tags:
- assessment-question
/api/v1/auth/google/callback:
get:
responses: {}
summary: Google login callback
tags:
- auth
/api/v1/auth/google/login:
get:
responses: {}
summary: Google login redirect
tags:
- auth
/api/v1/auth/logout: /api/v1/auth/logout:
post: post:
consumes: consumes:
@ -2102,7 +2022,7 @@ paths:
name: login name: login
required: true required: true
schema: schema:
$ref: '#/definitions/authentication.LoginRequest' $ref: '#/definitions/domain.LoginRequest'
produces: produces:
- application/json - application/json
responses: responses:
@ -2221,15 +2141,54 @@ paths:
summary: Check if phone number or email exist summary: Check if phone number or email exist
tags: tags:
- user - user
/api/v1/user/{user_name}/is-unique: /api/v1/user:
put:
consumes:
- application/json
description: Updates user profile information (partial updates supported)
parameters:
- description: User ID
in: path
name: user_id
required: true
type: integer
- description: Update user payload
in: body
name: body
required: true
schema:
$ref: '#/definitions/domain.UpdateUserReq'
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/domain.Response'
"400":
description: Bad Request
schema:
$ref: '#/definitions/domain.ErrorResponse'
"404":
description: Not Found
schema:
$ref: '#/definitions/domain.ErrorResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/domain.ErrorResponse'
summary: Update user profile
tags:
- user
/api/v1/user/{user_id}/is-pending:
get: get:
consumes: consumes:
- application/json - application/json
description: Returns whether the specified user_name is available (unique) description: Returns whether the specified user has a status of "pending"
parameters: parameters:
- description: User Name - description: User ID
in: path in: path
name: user_name name: user_id
required: true required: true
type: string type: string
produces: produces:
@ -2243,11 +2202,48 @@ paths:
description: Bad Request description: Bad Request
schema: schema:
$ref: '#/definitions/domain.ErrorResponse' $ref: '#/definitions/domain.ErrorResponse'
"404":
description: Not Found
schema:
$ref: '#/definitions/domain.ErrorResponse'
"500": "500":
description: Internal Server Error description: Internal Server Error
schema: schema:
$ref: '#/definitions/domain.ErrorResponse' $ref: '#/definitions/domain.ErrorResponse'
summary: Check if user_name is unique summary: Check if user status is pending
tags:
- user
/api/v1/user/{user_id}/is-profile-completed:
get:
consumes:
- application/json
description: Returns whether the specified user's profile is completed
parameters:
- description: User ID
in: path
name: user_id
required: true
type: integer
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/domain.Response'
"400":
description: Bad Request
schema:
$ref: '#/definitions/domain.ErrorResponse'
"404":
description: Not Found
schema:
$ref: '#/definitions/domain.ErrorResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/domain.ErrorResponse'
summary: Check if user profile is completed
tags: tags:
- user - user
/api/v1/user/delete/{id}: /api/v1/user/delete/{id}:
@ -2432,6 +2428,54 @@ paths:
summary: Verify OTP summary: Verify OTP
tags: tags:
- user - user
/api/v1/users:
get:
consumes:
- application/json
description: Get users with optional filters
parameters:
- description: Role filter
in: query
name: role
type: string
- description: Search query
in: query
name: query
type: string
- description: Page number
in: query
name: page
type: integer
- description: Page size
in: query
name: page_size
type: integer
- description: Created before (RFC3339)
in: query
name: created_before
type: string
- description: Created after (RFC3339)
in: query
name: created_after
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/response.APIResponse'
"400":
description: Bad Request
schema:
$ref: '#/definitions/response.APIResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.APIResponse'
summary: Get all users
tags:
- user
securityDefinitions: securityDefinitions:
Bearer: Bearer:
in: header in: header

View File

@ -1,6 +1,8 @@
package domain package domain
import dbgen "Yimaru-Backend/gen/db" import (
"time"
)
type QuestionType string type QuestionType string
@ -10,8 +12,20 @@ const (
ShortAnswer QuestionType = "SHORT_ANSWER" ShortAnswer QuestionType = "SHORT_ANSWER"
) )
type AssessmentQuestion struct {
ID int64 `json:"id"`
Title string `json:"title"`
Description string `json:"description"`
QuestionType string `json:"question_type"`
DifficultyLevel string `json:"difficulty_level"`
Points int32 `json:"points"`
IsActive bool `json:"is_active"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type QuestionWithDetails struct { type QuestionWithDetails struct {
Question dbgen.AssessmentQuestion Question AssessmentQuestion
Options []QuestionOption Options []QuestionOption
} }

View File

@ -5,6 +5,7 @@ import (
"Yimaru-Backend/internal/domain" "Yimaru-Backend/internal/domain"
"context" "context"
"errors" "errors"
"encoding/json"
"github.com/jackc/pgx/v5/pgtype" "github.com/jackc/pgx/v5/pgtype"
) )
@ -117,7 +118,15 @@ func (s *Service) ListQuestions(ctx context.Context) ([]domain.QuestionWithDetai
out := make([]domain.QuestionWithDetails, 0, len(questions)) out := make([]domain.QuestionWithDetails, 0, len(questions))
for _, q := range questions { for _, q := range questions {
item := domain.QuestionWithDetails{Question: q} var dq domain.AssessmentQuestion
b, err := json.Marshal(q)
if err != nil {
return nil, err
}
if err := json.Unmarshal(b, &dq); err != nil {
return nil, err
}
item := domain.QuestionWithDetails{Question: dq}
switch domain.QuestionType(q.QuestionType) { switch domain.QuestionType(q.QuestionType) {
case domain.MultipleChoice, domain.TrueFalse: case domain.MultipleChoice, domain.TrueFalse:
@ -156,7 +165,15 @@ func (s *Service) GetQuestionByID(ctx context.Context, id int64) (domain.Questio
return domain.QuestionWithDetails{}, err return domain.QuestionWithDetails{}, err
} }
item := domain.QuestionWithDetails{Question: q} var dq domain.AssessmentQuestion
b, err := json.Marshal(q)
if err != nil {
return domain.QuestionWithDetails{}, err
}
if err := json.Unmarshal(b, &dq); err != nil {
return domain.QuestionWithDetails{}, err
}
item := domain.QuestionWithDetails{Question: dq}
switch domain.QuestionType(q.QuestionType) { switch domain.QuestionType(q.QuestionType) {
case domain.MultipleChoice, domain.TrueFalse: case domain.MultipleChoice, domain.TrueFalse:
opts, err := repo.GetQuestionOptions(ctx, q.ID) opts, err := repo.GetQuestionOptions(ctx, q.ID)