Spaces:
Configuration error
Configuration error
Commit ·
ff52ee7
1
Parent(s): b2f75d9
Deploy files from GitHub repository
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- config/database_connection_config.go +5 -0
- controller/quiz/answer_quiz_controller.go +24 -0
- controller/quiz/attempt_quiz_controller.go +24 -0
- controller/quiz/question_quiz_controller.go +25 -0
- models/database_orm_model.go +47 -0
- models/exception_model.go +3 -0
- models/request_model.go +9 -0
- models/response_model.go +11 -0
- repositories/academy_repository.go +11 -0
- repositories/question_repository.go +36 -0
- repositories/quiz_repository.go +101 -0
- router/quiz_route.go +16 -0
- router/router.go +1 -1
- services/academy_quiz_answer_service.go +56 -0
- services/academy_quiz_question_service.go +42 -0
- services/academy_quiz_service.go +124 -0
- space/controller/academy/academy_contents_controller.go +1 -1
- space/models/database_orm_model.go +5 -3
- space/models/response_model.go +1 -1
- space/repositories/academy_repository.go +4 -4
- space/services/academy_service.go +24 -9
- space/services/register_service.go +43 -3
- space/space/controller/academy/academy_contents_controller.go +19 -0
- space/space/controller/academy/academy_controller.go +1 -1
- space/space/controller/academy/academy_materials_controller.go +19 -0
- space/space/models/database_orm_model.go +8 -5
- space/space/repositories/academy_repository.go +11 -0
- space/space/router/academy_route.go +2 -1
- space/space/services/academy_service.go +36 -18
- space/space/services/jwt_service.go +1 -1
- space/space/services/service.go +3 -0
- space/space/space/services/email_verification_service.go +3 -14
- space/space/space/space/space/config/config.go +2 -0
- space/space/space/space/space/services/email_verification_service.go +100 -8
- space/space/space/space/space/space/services/external_authentication_service.go +10 -6
- space/space/space/space/space/space/space/services/email_verification_service.go +1 -4
- space/space/space/space/space/space/space/services/forgot_password_service.go +1 -4
- space/space/space/space/space/space/space/space/services/external_authentication_service.go +18 -1
- space/space/space/space/space/space/space/space/space/controller/academy/academy_controller.go +31 -0
- space/space/space/space/space/space/space/space/space/models/database_orm_model.go +7 -7
- space/space/space/space/space/space/space/space/space/repositories/academy_repository.go +28 -2
- space/space/space/space/space/space/space/space/space/router/academy_route.go +16 -0
- space/space/space/space/space/space/space/space/space/router/router.go +1 -1
- space/space/space/space/space/space/space/space/space/services/academy_service.go +75 -11
- space/space/space/space/space/space/space/space/space/services/email_verification_service.go +5 -2
- space/space/space/space/space/space/space/space/space/services/forgot_password_service.go +4 -1
- space/space/space/space/space/space/space/space/space/space/logs/error_log.txt +2 -1
- space/space/space/space/space/space/space/space/space/space/models/response_model.go +9 -6
- space/space/space/space/space/space/space/space/space/space/repositories/academy_repository.go +47 -0
- space/space/space/space/space/space/space/space/space/space/services/academy_service.go +40 -0
config/database_connection_config.go
CHANGED
|
@@ -64,6 +64,11 @@ func AutoMigrateAll(db *gorm.DB) {
|
|
| 64 |
&models.RegionProvince{},
|
| 65 |
&models.OptionCategory{},
|
| 66 |
&models.OptionValues{},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 67 |
)
|
| 68 |
|
| 69 |
if err != nil {
|
|
|
|
| 64 |
&models.RegionProvince{},
|
| 65 |
&models.OptionCategory{},
|
| 66 |
&models.OptionValues{},
|
| 67 |
+
&models.Quiz{},
|
| 68 |
+
&models.QuizAttempt{},
|
| 69 |
+
&models.Question{},
|
| 70 |
+
&models.Answer{},
|
| 71 |
+
&models.UserAnswer{},
|
| 72 |
)
|
| 73 |
|
| 74 |
if err != nil {
|
controller/quiz/answer_quiz_controller.go
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package controller
|
| 2 |
+
|
| 3 |
+
import (
|
| 4 |
+
"strconv"
|
| 5 |
+
|
| 6 |
+
"api.qobiltu.id/controller"
|
| 7 |
+
"api.qobiltu.id/models"
|
| 8 |
+
"api.qobiltu.id/services"
|
| 9 |
+
"github.com/gin-gonic/gin"
|
| 10 |
+
)
|
| 11 |
+
|
| 12 |
+
func Answer(c *gin.Context) {
|
| 13 |
+
quizAnswer := services.AnswerQuizService{}
|
| 14 |
+
quizAnswerController := controller.Controller[models.AnswerQuizRequest, models.Quiz, models.QuestionResponse]{
|
| 15 |
+
Service: &quizAnswer.Service,
|
| 16 |
+
}
|
| 17 |
+
quizAnswerController.RequestJSON(c, func() {
|
| 18 |
+
quizId, _ := strconv.Atoi(c.Param("quiz_id"))
|
| 19 |
+
academyId, _ := strconv.Atoi(c.Param("academy_id"))
|
| 20 |
+
quizAnswerController.Service.Constructor.ID = uint(quizId)
|
| 21 |
+
quizAnswerController.Service.Constructor.AcademyID = uint(academyId)
|
| 22 |
+
quizAnswer.Update(quizAnswerController.AccountData.UserID, quizAnswerController.Request.QuestionNo, quizAnswerController.Request.Answer)
|
| 23 |
+
})
|
| 24 |
+
}
|
controller/quiz/attempt_quiz_controller.go
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package controller
|
| 2 |
+
|
| 3 |
+
import (
|
| 4 |
+
"strconv"
|
| 5 |
+
|
| 6 |
+
"api.qobiltu.id/controller"
|
| 7 |
+
"api.qobiltu.id/models"
|
| 8 |
+
"api.qobiltu.id/services"
|
| 9 |
+
"github.com/gin-gonic/gin"
|
| 10 |
+
)
|
| 11 |
+
|
| 12 |
+
func Attempt(c *gin.Context) {
|
| 13 |
+
attemptQuiz := services.AttemptQuizService{}
|
| 14 |
+
attemptQuizController := controller.Controller[any, models.Quiz, models.QuizAttempt]{
|
| 15 |
+
Service: &attemptQuiz.Service,
|
| 16 |
+
}
|
| 17 |
+
attemptQuizController.RequestJSON(c, func() {
|
| 18 |
+
quizId, _ := strconv.Atoi(c.Param("quiz_id"))
|
| 19 |
+
academyId, _ := strconv.Atoi(c.Param("academy_id"))
|
| 20 |
+
attemptQuizController.Service.Constructor.ID = uint(quizId)
|
| 21 |
+
attemptQuizController.Service.Constructor.AcademyID = uint(academyId)
|
| 22 |
+
attemptQuiz.Create(attemptQuizController.AccountData.UserID)
|
| 23 |
+
})
|
| 24 |
+
}
|
controller/quiz/question_quiz_controller.go
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package controller
|
| 2 |
+
|
| 3 |
+
import (
|
| 4 |
+
"strconv"
|
| 5 |
+
|
| 6 |
+
"api.qobiltu.id/controller"
|
| 7 |
+
"api.qobiltu.id/models"
|
| 8 |
+
"api.qobiltu.id/services"
|
| 9 |
+
"github.com/gin-gonic/gin"
|
| 10 |
+
)
|
| 11 |
+
|
| 12 |
+
func Question(c *gin.Context) {
|
| 13 |
+
questionQuiz := services.QuestionQuizService{}
|
| 14 |
+
questionQuizController := controller.Controller[models.QuestionQuizRequest, models.Quiz, models.QuestionResponse]{
|
| 15 |
+
Service: &questionQuiz.Service,
|
| 16 |
+
}
|
| 17 |
+
questionQuizController.RequestJSON(c, func() {
|
| 18 |
+
quizId, _ := strconv.Atoi(c.Param("quiz_id"))
|
| 19 |
+
academyId, _ := strconv.Atoi(c.Param("academy_id"))
|
| 20 |
+
questionQuizController.Service.Constructor.ID = uint(quizId)
|
| 21 |
+
questionQuizController.Service.Constructor.AcademyID = uint(academyId)
|
| 22 |
+
questionQuiz.Retrieve(questionQuizController.AccountData.UserID, questionQuizController.Request.QuestionNo)
|
| 23 |
+
|
| 24 |
+
})
|
| 25 |
+
}
|
models/database_orm_model.go
CHANGED
|
@@ -137,6 +137,46 @@ type RegionCity struct {
|
|
| 137 |
FullCode string `json:"full_code"`
|
| 138 |
ProvinceID uint `json:"province_id"`
|
| 139 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 140 |
|
| 141 |
// Gorm table name settings
|
| 142 |
func (Account) TableName() string { return "account" }
|
|
@@ -152,3 +192,10 @@ func (AcademyMaterialProgress) TableName() string { return "academy_materials_pr
|
|
| 152 |
func (AcademyContentProgress) TableName() string { return "academy_contents_progress" }
|
| 153 |
func (RegionProvince) TableName() string { return "region_provinces" }
|
| 154 |
func (RegionCity) TableName() string { return "region_cities" }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 137 |
FullCode string `json:"full_code"`
|
| 138 |
ProvinceID uint `json:"province_id"`
|
| 139 |
}
|
| 140 |
+
type Answer struct {
|
| 141 |
+
ID uint `gorm:"primaryKey"`
|
| 142 |
+
QuestionID uint `json:"question_id"`
|
| 143 |
+
Content string `json:"content"`
|
| 144 |
+
IsCorrect bool `json:"-"`
|
| 145 |
+
}
|
| 146 |
+
type Question struct {
|
| 147 |
+
ID uint `gorm:"primaryKey"`
|
| 148 |
+
QuizID uint `json:"quiz_id"`
|
| 149 |
+
Content string `json:"content"`
|
| 150 |
+
Order int `json:"order"`
|
| 151 |
+
CorrectAnswer uint `json:"-"`
|
| 152 |
+
}
|
| 153 |
+
type Quiz struct {
|
| 154 |
+
ID uint `gorm:"primaryKey" json:"id"`
|
| 155 |
+
AcademyID uint `json:"academy_id"`
|
| 156 |
+
Title string `json:"title"`
|
| 157 |
+
Description string `json:"description"`
|
| 158 |
+
AttemptLimit int `json:"attempt_limit"`
|
| 159 |
+
TimeLimit int `json:"time_limit"`
|
| 160 |
+
MinScore int `json:"min_score"`
|
| 161 |
+
CreatedAt time.Time `json:"created_at"`
|
| 162 |
+
}
|
| 163 |
+
|
| 164 |
+
type QuizAttempt struct {
|
| 165 |
+
ID uint `gorm:"primaryKey"`
|
| 166 |
+
AccountID uint `json:"user_id"`
|
| 167 |
+
QuizID uint `json:"quiz_id"`
|
| 168 |
+
StartedAt time.Time `json:"started_at"`
|
| 169 |
+
DueAt time.Time `json:"due_at"`
|
| 170 |
+
FinishedAt *time.Time `json:"finished_at"`
|
| 171 |
+
Score float64 `json:"score"`
|
| 172 |
+
}
|
| 173 |
+
type UserAnswer struct {
|
| 174 |
+
ID uint `gorm:"primaryKey"`
|
| 175 |
+
QuizAttemptID uint `json:"quiz_attempt_id"`
|
| 176 |
+
QuestionID uint `json:"question_id"`
|
| 177 |
+
SelectedAnswer uint `json:"selected_answer"`
|
| 178 |
+
IsCorrect bool `json:"is_correct"`
|
| 179 |
+
}
|
| 180 |
|
| 181 |
// Gorm table name settings
|
| 182 |
func (Account) TableName() string { return "account" }
|
|
|
|
| 192 |
func (AcademyContentProgress) TableName() string { return "academy_contents_progress" }
|
| 193 |
func (RegionProvince) TableName() string { return "region_provinces" }
|
| 194 |
func (RegionCity) TableName() string { return "region_cities" }
|
| 195 |
+
func (Answer) TableName() string { return "answers" }
|
| 196 |
+
func (Question) TableName() string { return "questions" }
|
| 197 |
+
func (Quiz) TableName() string { return "quizzes" }
|
| 198 |
+
func (QuizAttempt) TableName() string { return "quiz_attempts" }
|
| 199 |
+
func (UserAnswer) TableName() string { return "user_answers" }
|
| 200 |
+
func (OptionCategory) TableName() string { return "option_categories" }
|
| 201 |
+
func (OptionValues) TableName() string { return "option_values" }
|
models/exception_model.go
CHANGED
|
@@ -8,5 +8,8 @@ type Exception struct {
|
|
| 8 |
DataDuplicate bool `json:"data_duplicate,omitempty"`
|
| 9 |
QueryError bool `json:"query_error,omitempty"`
|
| 10 |
InvalidPasswordLength bool `json:"invalid_password_length,omitempty"`
|
|
|
|
|
|
|
|
|
|
| 11 |
Message string `json:"message,omitempty"`
|
| 12 |
}
|
|
|
|
| 8 |
DataDuplicate bool `json:"data_duplicate,omitempty"`
|
| 9 |
QueryError bool `json:"query_error,omitempty"`
|
| 10 |
InvalidPasswordLength bool `json:"invalid_password_length,omitempty"`
|
| 11 |
+
IsPassTheLimit bool `json:"is_pass_the_limit,omitempty"`
|
| 12 |
+
IsTimeOut bool `json:"is_time_out,omitempty"`
|
| 13 |
+
AttemptNotFound bool `json:"attempt_not_found,omitempty"`
|
| 14 |
Message string `json:"message,omitempty"`
|
| 15 |
}
|
models/request_model.go
CHANGED
|
@@ -40,3 +40,12 @@ type ValidateForgotPasswordRequest struct {
|
|
| 40 |
Token uint `json:"token" binding:"required"`
|
| 41 |
NewPassword string `json:"new_password"`
|
| 42 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
Token uint `json:"token" binding:"required"`
|
| 41 |
NewPassword string `json:"new_password"`
|
| 42 |
}
|
| 43 |
+
|
| 44 |
+
type QuestionQuizRequest struct {
|
| 45 |
+
QuestionNo int `json:"question_no" binding:"required"`
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
type AnswerQuizRequest struct {
|
| 49 |
+
QuestionNo int `json:"question_no" binding:"required"`
|
| 50 |
+
Answer int `json:"answer" binding:"required"`
|
| 51 |
+
}
|
models/response_model.go
CHANGED
|
@@ -43,3 +43,14 @@ type AcademyResponse struct {
|
|
| 43 |
type AllAcademyResponse struct {
|
| 44 |
Academies []AcademyResponse `json:"academy_dasar"`
|
| 45 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
type AllAcademyResponse struct {
|
| 44 |
Academies []AcademyResponse `json:"academy_dasar"`
|
| 45 |
}
|
| 46 |
+
|
| 47 |
+
type AttemptExamResponse struct {
|
| 48 |
+
Exam Quiz `json:"exam"`
|
| 49 |
+
Questions []Question `json:"questions"`
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
type QuestionResponse struct {
|
| 53 |
+
Question Question `json:"question"`
|
| 54 |
+
Answer []Answer `json:"answer_options"`
|
| 55 |
+
UserAnswer int `json:"current_user_answer"`
|
| 56 |
+
}
|
repositories/academy_repository.go
CHANGED
|
@@ -82,3 +82,14 @@ func GetAcademyMaterialBySlug(slug string) Repository[models.AcademyMaterial, mo
|
|
| 82 |
)
|
| 83 |
return *repo
|
| 84 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 82 |
)
|
| 83 |
return *repo
|
| 84 |
}
|
| 85 |
+
|
| 86 |
+
func GetAcademyByID(id uint) Repository[models.Academy, models.Academy] {
|
| 87 |
+
repo := Construct[models.Academy, models.Academy](
|
| 88 |
+
models.Academy{ID: id},
|
| 89 |
+
)
|
| 90 |
+
repo.Transactions(
|
| 91 |
+
WhereGivenConstructor[models.Academy, models.Academy],
|
| 92 |
+
Find[models.Academy, models.Academy],
|
| 93 |
+
)
|
| 94 |
+
return *repo
|
| 95 |
+
}
|
repositories/question_repository.go
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package repositories
|
| 2 |
+
|
| 3 |
+
import "api.qobiltu.id/models"
|
| 4 |
+
|
| 5 |
+
func GetQuestionByQuizId(quizId uint) Repository[models.Question, []models.Question] {
|
| 6 |
+
repo := Construct[models.Question, []models.Question](
|
| 7 |
+
models.Question{QuizID: quizId},
|
| 8 |
+
)
|
| 9 |
+
repo.Transactions(
|
| 10 |
+
WhereGivenConstructor[models.Question, []models.Question],
|
| 11 |
+
Find[models.Question, []models.Question],
|
| 12 |
+
)
|
| 13 |
+
return *repo
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
func GetQuestionByOrder(quizId uint, order int) Repository[models.Question, models.Question] {
|
| 17 |
+
repo := Construct[models.Question, models.Question](
|
| 18 |
+
models.Question{QuizID: quizId, Order: order},
|
| 19 |
+
)
|
| 20 |
+
repo.Transactions(
|
| 21 |
+
WhereGivenConstructor[models.Question, models.Question],
|
| 22 |
+
Find[models.Question, models.Question],
|
| 23 |
+
)
|
| 24 |
+
return *repo
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
func GetAnswerByQuestionId(questionId uint) Repository[models.Answer, []models.Answer] {
|
| 28 |
+
repo := Construct[models.Answer, []models.Answer](
|
| 29 |
+
models.Answer{QuestionID: questionId},
|
| 30 |
+
)
|
| 31 |
+
repo.Transactions(
|
| 32 |
+
WhereGivenConstructor[models.Answer, []models.Answer],
|
| 33 |
+
Find[models.Answer, []models.Answer],
|
| 34 |
+
)
|
| 35 |
+
return *repo
|
| 36 |
+
}
|
repositories/quiz_repository.go
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package repositories
|
| 2 |
+
|
| 3 |
+
import (
|
| 4 |
+
"api.qobiltu.id/models"
|
| 5 |
+
)
|
| 6 |
+
|
| 7 |
+
func GetQuizbyAcademyId(academyId uint) Repository[models.Quiz, []models.Quiz] {
|
| 8 |
+
repo := Construct[models.Quiz, []models.Quiz](
|
| 9 |
+
models.Quiz{AcademyID: academyId},
|
| 10 |
+
)
|
| 11 |
+
repo.Transactions(
|
| 12 |
+
WhereGivenConstructor[models.Quiz, []models.Quiz],
|
| 13 |
+
Find[models.Quiz, []models.Quiz],
|
| 14 |
+
)
|
| 15 |
+
return *repo
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
func GetAllUserAttempt(userId uint, quizId uint) Repository[models.QuizAttempt, []models.QuizAttempt] {
|
| 19 |
+
repo := Construct[models.QuizAttempt, []models.QuizAttempt](
|
| 20 |
+
models.QuizAttempt{AccountID: userId, QuizID: quizId},
|
| 21 |
+
)
|
| 22 |
+
repo.Transactions(
|
| 23 |
+
WhereGivenConstructor[models.QuizAttempt, []models.QuizAttempt],
|
| 24 |
+
Find[models.QuizAttempt, []models.QuizAttempt],
|
| 25 |
+
)
|
| 26 |
+
return *repo
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
func GetQuizbyId(quizId uint) Repository[models.Quiz, models.Quiz] {
|
| 30 |
+
repo := Construct[models.Quiz, models.Quiz](
|
| 31 |
+
models.Quiz{ID: quizId},
|
| 32 |
+
)
|
| 33 |
+
repo.Transactions(
|
| 34 |
+
WhereGivenConstructor[models.Quiz, models.Quiz],
|
| 35 |
+
Find[models.Quiz, models.Quiz],
|
| 36 |
+
)
|
| 37 |
+
|
| 38 |
+
return *repo
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
func GetUserLastAttempt(userId uint, quizId uint) Repository[models.QuizAttempt, models.QuizAttempt] {
|
| 42 |
+
repo := Construct[models.QuizAttempt, models.QuizAttempt](
|
| 43 |
+
models.QuizAttempt{AccountID: userId, QuizID: quizId},
|
| 44 |
+
)
|
| 45 |
+
repo.Transaction.Where(&repo.Constructor).Last(&repo.Result)
|
| 46 |
+
repo.RowsError = repo.Transaction.Error
|
| 47 |
+
repo.NoRecord = false
|
| 48 |
+
// fmt.Println(repo.Transaction.RowsAffected) Kenapa 0 !!!!
|
| 49 |
+
return *repo
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
func GetAttemptById(attemptId uint) Repository[models.QuizAttempt, models.QuizAttempt] {
|
| 53 |
+
repo := Construct[models.QuizAttempt, models.QuizAttempt](
|
| 54 |
+
models.QuizAttempt{ID: attemptId},
|
| 55 |
+
)
|
| 56 |
+
repo.Transactions(
|
| 57 |
+
WhereGivenConstructor[models.QuizAttempt, models.QuizAttempt],
|
| 58 |
+
Find[models.QuizAttempt, models.QuizAttempt],
|
| 59 |
+
)
|
| 60 |
+
return *repo
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
func CreateAttempt(quizAttempt models.QuizAttempt) Repository[models.QuizAttempt, models.QuizAttempt] {
|
| 64 |
+
repo := Construct[models.QuizAttempt, models.QuizAttempt](
|
| 65 |
+
quizAttempt,
|
| 66 |
+
)
|
| 67 |
+
|
| 68 |
+
Create(repo)
|
| 69 |
+
return *repo
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
func GetUserAnswerByAttemptQuestionId(attemptId uint, questionId uint) Repository[models.UserAnswer, models.UserAnswer] {
|
| 73 |
+
repo := Construct[models.UserAnswer, models.UserAnswer](
|
| 74 |
+
models.UserAnswer{
|
| 75 |
+
QuizAttemptID: attemptId,
|
| 76 |
+
QuestionID: questionId,
|
| 77 |
+
},
|
| 78 |
+
)
|
| 79 |
+
repo.Transactions(
|
| 80 |
+
WhereGivenConstructor[models.UserAnswer, models.UserAnswer],
|
| 81 |
+
Find[models.UserAnswer, models.UserAnswer],
|
| 82 |
+
)
|
| 83 |
+
return *repo
|
| 84 |
+
}
|
| 85 |
+
|
| 86 |
+
func CreateUserAnswer(userAnswer models.UserAnswer) Repository[models.UserAnswer, models.UserAnswer] {
|
| 87 |
+
repo := Construct[models.UserAnswer, models.UserAnswer](
|
| 88 |
+
userAnswer,
|
| 89 |
+
)
|
| 90 |
+
|
| 91 |
+
Create(repo)
|
| 92 |
+
return *repo
|
| 93 |
+
}
|
| 94 |
+
|
| 95 |
+
func UpdateUserAnswer(userAnswer models.UserAnswer) Repository[models.UserAnswer, models.UserAnswer] {
|
| 96 |
+
repo := Construct[models.UserAnswer, models.UserAnswer](
|
| 97 |
+
userAnswer,
|
| 98 |
+
)
|
| 99 |
+
Update(repo)
|
| 100 |
+
return *repo
|
| 101 |
+
}
|
router/quiz_route.go
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package router
|
| 2 |
+
|
| 3 |
+
import (
|
| 4 |
+
QuizController "api.qobiltu.id/controller/quiz"
|
| 5 |
+
"api.qobiltu.id/middleware"
|
| 6 |
+
"github.com/gin-gonic/gin"
|
| 7 |
+
)
|
| 8 |
+
|
| 9 |
+
func QuizRoute(router *gin.Engine) {
|
| 10 |
+
routerGroup := router.Group("/api/v1/quiz")
|
| 11 |
+
{
|
| 12 |
+
routerGroup.POST("/:academy_id/:quiz_id/attempt", middleware.AuthUser, QuizController.Attempt)
|
| 13 |
+
routerGroup.GET("/:academy_id/:quiz_id/question", middleware.AuthUser, QuizController.Question)
|
| 14 |
+
routerGroup.PUT("/:academy_id/:quiz_id/choose-answer", middleware.AuthUser, QuizController.Answer)
|
| 15 |
+
}
|
| 16 |
+
}
|
router/router.go
CHANGED
|
@@ -11,12 +11,12 @@ import (
|
|
| 11 |
func StartService() {
|
| 12 |
router := gin.Default()
|
| 13 |
router.GET("/", controller.HomeController)
|
| 14 |
-
|
| 15 |
AuthRoute(router)
|
| 16 |
UserRoute(router)
|
| 17 |
EmailRoute(router)
|
| 18 |
OptionsRoute(router)
|
| 19 |
AcademyRoute(router)
|
|
|
|
| 20 |
err := router.Run(config.TCP_ADDRESS)
|
| 21 |
if err != nil {
|
| 22 |
log.Fatalf("Failed to run server: %v", err)
|
|
|
|
| 11 |
func StartService() {
|
| 12 |
router := gin.Default()
|
| 13 |
router.GET("/", controller.HomeController)
|
|
|
|
| 14 |
AuthRoute(router)
|
| 15 |
UserRoute(router)
|
| 16 |
EmailRoute(router)
|
| 17 |
OptionsRoute(router)
|
| 18 |
AcademyRoute(router)
|
| 19 |
+
QuizRoute(router)
|
| 20 |
err := router.Run(config.TCP_ADDRESS)
|
| 21 |
if err != nil {
|
| 22 |
log.Fatalf("Failed to run server: %v", err)
|
services/academy_quiz_answer_service.go
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package services
|
| 2 |
+
|
| 3 |
+
import (
|
| 4 |
+
"errors"
|
| 5 |
+
|
| 6 |
+
"api.qobiltu.id/models"
|
| 7 |
+
"api.qobiltu.id/repositories"
|
| 8 |
+
)
|
| 9 |
+
|
| 10 |
+
type AnswerQuizService struct {
|
| 11 |
+
Service[models.Quiz, models.QuestionResponse]
|
| 12 |
+
}
|
| 13 |
+
|
| 14 |
+
func (s *AnswerQuizService) Update(userID uint, questionNo int, answer int) {
|
| 15 |
+
QuizAttemptService := AttemptQuizService{}
|
| 16 |
+
QuizAttemptService.Constructor = s.Constructor
|
| 17 |
+
QuizAttemptService.Validate(userID, func(latestAttemptRepo repositories.Repository[models.QuizAttempt, models.QuizAttempt], quizRepo repositories.Repository[models.Quiz, models.Quiz]) {
|
| 18 |
+
|
| 19 |
+
questionRepo := repositories.GetQuestionByOrder(latestAttemptRepo.Result.QuizID, questionNo)
|
| 20 |
+
if questionRepo.NoRecord {
|
| 21 |
+
s.Exception.DataNotFound = true
|
| 22 |
+
s.Exception.Message = "There is no quiz with given academy!"
|
| 23 |
+
return
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
s.Error = questionRepo.RowsError
|
| 27 |
+
answerRepo := repositories.GetUserAnswerByAttemptQuestionId(latestAttemptRepo.Result.ID, questionRepo.Result.ID)
|
| 28 |
+
if answerRepo.NoRecord {
|
| 29 |
+
s.Exception.DataNotFound = true
|
| 30 |
+
s.Exception.Message = "There is no Answer with given AttemptId!"
|
| 31 |
+
return
|
| 32 |
+
}
|
| 33 |
+
s.Error = errors.Join(s.Error, answerRepo.RowsError)
|
| 34 |
+
|
| 35 |
+
answerRepo.Result.SelectedAnswer = uint(answer)
|
| 36 |
+
answerRepo.Result.IsCorrect = (questionRepo.Result.CorrectAnswer == uint(answer))
|
| 37 |
+
|
| 38 |
+
updatedAnswer := repositories.UpdateUserAnswer(answerRepo.Result)
|
| 39 |
+
|
| 40 |
+
s.Error = errors.Join(s.Error, updatedAnswer.RowsError)
|
| 41 |
+
|
| 42 |
+
questionRepo.Result.CorrectAnswer = uint(0)
|
| 43 |
+
answerOptionRepo := repositories.GetAnswerByQuestionId(questionRepo.Result.ID)
|
| 44 |
+
if answerOptionRepo.NoRecord {
|
| 45 |
+
s.Exception.DataNotFound = true
|
| 46 |
+
s.Exception.Message = "There is no Answer Option with given QuestionId!"
|
| 47 |
+
return
|
| 48 |
+
}
|
| 49 |
+
s.Result = models.QuestionResponse{
|
| 50 |
+
Question: questionRepo.Result,
|
| 51 |
+
Answer: answerOptionRepo.Result,
|
| 52 |
+
UserAnswer: int(answerRepo.Result.SelectedAnswer),
|
| 53 |
+
}
|
| 54 |
+
return
|
| 55 |
+
})
|
| 56 |
+
}
|
services/academy_quiz_question_service.go
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package services
|
| 2 |
+
|
| 3 |
+
import (
|
| 4 |
+
"api.qobiltu.id/models"
|
| 5 |
+
"api.qobiltu.id/repositories"
|
| 6 |
+
)
|
| 7 |
+
|
| 8 |
+
type QuestionQuizService struct {
|
| 9 |
+
Service[models.Quiz, models.QuestionResponse]
|
| 10 |
+
}
|
| 11 |
+
|
| 12 |
+
func (s *QuestionQuizService) Retrieve(userID uint, questionNo int) {
|
| 13 |
+
QuizAttemptService := AttemptQuizService{}
|
| 14 |
+
QuizAttemptService.Constructor = s.Constructor
|
| 15 |
+
QuizAttemptService.Validate(userID, func(latestAttemptRepo repositories.Repository[models.QuizAttempt, models.QuizAttempt], quizRepo repositories.Repository[models.Quiz, models.Quiz]) {
|
| 16 |
+
questionRepo := repositories.GetQuestionByOrder(s.Constructor.ID, questionNo)
|
| 17 |
+
s.Error = questionRepo.RowsError
|
| 18 |
+
if questionRepo.NoRecord {
|
| 19 |
+
s.Exception.DataNotFound = true
|
| 20 |
+
s.Exception.Message = "There is no quiz with given academy!"
|
| 21 |
+
return
|
| 22 |
+
}
|
| 23 |
+
answerRepo := repositories.GetUserAnswerByAttemptQuestionId(latestAttemptRepo.Result.ID, questionRepo.Result.ID)
|
| 24 |
+
if answerRepo.NoRecord {
|
| 25 |
+
s.Exception.DataNotFound = true
|
| 26 |
+
s.Exception.Message = "There is no Answer with given AttemptId!"
|
| 27 |
+
return
|
| 28 |
+
}
|
| 29 |
+
answerOptionRepo := repositories.GetAnswerByQuestionId(questionRepo.Result.ID)
|
| 30 |
+
if answerOptionRepo.NoRecord {
|
| 31 |
+
s.Exception.DataNotFound = true
|
| 32 |
+
s.Exception.Message = "There is no Answer Option with given QuestionId!"
|
| 33 |
+
return
|
| 34 |
+
}
|
| 35 |
+
s.Result = models.QuestionResponse{
|
| 36 |
+
Question: questionRepo.Result,
|
| 37 |
+
Answer: answerOptionRepo.Result,
|
| 38 |
+
UserAnswer: int(answerRepo.Result.SelectedAnswer),
|
| 39 |
+
}
|
| 40 |
+
return
|
| 41 |
+
})
|
| 42 |
+
}
|
services/academy_quiz_service.go
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package services
|
| 2 |
+
|
| 3 |
+
import (
|
| 4 |
+
"errors"
|
| 5 |
+
"fmt"
|
| 6 |
+
"time"
|
| 7 |
+
|
| 8 |
+
"api.qobiltu.id/models"
|
| 9 |
+
"api.qobiltu.id/repositories"
|
| 10 |
+
)
|
| 11 |
+
|
| 12 |
+
type AttemptQuizService struct {
|
| 13 |
+
Service[models.Quiz, models.QuizAttempt]
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
func AuthorizeQuizwithAcademy(s *AttemptQuizService, next func()) {
|
| 17 |
+
academyRepo := repositories.GetAcademyByID(s.Constructor.AcademyID)
|
| 18 |
+
s.Error = academyRepo.RowsError
|
| 19 |
+
if academyRepo.NoRecord {
|
| 20 |
+
s.Exception.DataNotFound = true
|
| 21 |
+
s.Exception.Message = "There is no academy with given slug!"
|
| 22 |
+
return
|
| 23 |
+
}
|
| 24 |
+
quizRepo := repositories.GetQuizbyAcademyId(academyRepo.Result.ID)
|
| 25 |
+
s.Error = quizRepo.RowsError
|
| 26 |
+
if quizRepo.NoRecord {
|
| 27 |
+
s.Exception.DataNotFound = true
|
| 28 |
+
s.Exception.Message = "There is no quiz with given academy!"
|
| 29 |
+
return
|
| 30 |
+
}
|
| 31 |
+
next()
|
| 32 |
+
}
|
| 33 |
+
func CheckUserAttemptLimit(s *AttemptQuizService, allAttemptsRepo repositories.Repository[models.QuizAttempt, []models.QuizAttempt], quizRepo repositories.Repository[models.Quiz, models.Quiz], userID uint, next func()) {
|
| 34 |
+
if (allAttemptsRepo.RowsCount >= quizRepo.Result.AttemptLimit) && (allAttemptsRepo.Result[allAttemptsRepo.RowsCount-1].FinishedAt != nil) {
|
| 35 |
+
s.Exception.IsPassTheLimit = true
|
| 36 |
+
s.Exception.Message = "You have reached the attempt limit!"
|
| 37 |
+
return
|
| 38 |
+
}
|
| 39 |
+
next()
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
func CheckUserLatestAttempt(s *AttemptQuizService, latestAttemptRepo repositories.Repository[models.QuizAttempt, models.QuizAttempt], quizRepo repositories.Repository[models.Quiz, models.Quiz], userID uint, next func()) {
|
| 43 |
+
currentTime := time.Now()
|
| 44 |
+
if currentTime.After(latestAttemptRepo.Result.DueAt) {
|
| 45 |
+
s.Exception.IsTimeOut = true
|
| 46 |
+
s.Exception.Message = "Your latest attempt is timeout!"
|
| 47 |
+
// Submit
|
| 48 |
+
return
|
| 49 |
+
}
|
| 50 |
+
next()
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
func CheckUserAttemptable(s *AttemptQuizService, latestAttemptRepo repositories.Repository[models.QuizAttempt, []models.QuizAttempt], quizRepo repositories.Repository[models.Quiz, models.Quiz], userID uint, next func()) {
|
| 54 |
+
s.Error = errors.Join(s.Error, latestAttemptRepo.RowsError, quizRepo.RowsError)
|
| 55 |
+
if latestAttemptRepo.Result[latestAttemptRepo.RowsCount-1].FinishedAt != nil {
|
| 56 |
+
s.Exception.IsPassTheLimit = true
|
| 57 |
+
s.Exception.Message = "You have reached the attempt limit!"
|
| 58 |
+
return
|
| 59 |
+
}
|
| 60 |
+
next()
|
| 61 |
+
}
|
| 62 |
+
func (s *AttemptQuizService) Validate(userID uint, next func(latestAttemptRepo repositories.Repository[models.QuizAttempt, models.QuizAttempt], quizRepo repositories.Repository[models.Quiz, models.Quiz])) {
|
| 63 |
+
AuthorizeQuizwithAcademy(s, func() {
|
| 64 |
+
quizRepo := repositories.GetQuizbyId(s.Constructor.ID)
|
| 65 |
+
if quizRepo.NoRecord {
|
| 66 |
+
s.Exception.DataNotFound = true
|
| 67 |
+
s.Exception.Message = "There is no quiz data with given Id!"
|
| 68 |
+
return
|
| 69 |
+
}
|
| 70 |
+
allAttemptsRepo := repositories.GetAllUserAttempt(userID, s.Constructor.ID)
|
| 71 |
+
if allAttemptsRepo.NoRecord {
|
| 72 |
+
Attempt(s, quizRepo, userID)
|
| 73 |
+
allAttemptsRepo = repositories.GetAllUserAttempt(userID, s.Constructor.ID)
|
| 74 |
+
}
|
| 75 |
+
s.Error = errors.Join(allAttemptsRepo.RowsError, quizRepo.RowsError)
|
| 76 |
+
CheckUserAttemptLimit(s, allAttemptsRepo, quizRepo, userID, func() {
|
| 77 |
+
fmt.Println("accountID", userID)
|
| 78 |
+
fmt.Println("quizID", s.Constructor.ID)
|
| 79 |
+
latestAttemptRepo := repositories.GetUserLastAttempt(userID, s.Constructor.ID)
|
| 80 |
+
if latestAttemptRepo.NoRecord {
|
| 81 |
+
s.Exception.DataNotFound = true
|
| 82 |
+
s.Exception.Message = "There is no quiz attempt with given user!"
|
| 83 |
+
return
|
| 84 |
+
}
|
| 85 |
+
s.Error = errors.Join(s.Error, latestAttemptRepo.RowsError)
|
| 86 |
+
CheckUserLatestAttempt(s, latestAttemptRepo, quizRepo, userID, func() {
|
| 87 |
+
next(latestAttemptRepo, quizRepo)
|
| 88 |
+
})
|
| 89 |
+
})
|
| 90 |
+
})
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
+
func Attempt(s *AttemptQuizService, quizRepo repositories.Repository[models.Quiz, models.Quiz], userID uint) {
|
| 94 |
+
startTime := time.Now()
|
| 95 |
+
dueTime := startTime.Add(time.Duration(quizRepo.Result.TimeLimit) * time.Minute)
|
| 96 |
+
createdAttemptRepo := repositories.CreateAttempt(models.QuizAttempt{
|
| 97 |
+
AccountID: userID,
|
| 98 |
+
QuizID: s.Constructor.ID,
|
| 99 |
+
StartedAt: startTime,
|
| 100 |
+
DueAt: dueTime,
|
| 101 |
+
})
|
| 102 |
+
s.Error = createdAttemptRepo.RowsError
|
| 103 |
+
questionsRepo := repositories.GetQuestionByQuizId(s.Constructor.ID)
|
| 104 |
+
for _, question := range questionsRepo.Result {
|
| 105 |
+
createdUserAnswer := repositories.CreateUserAnswer(models.UserAnswer{
|
| 106 |
+
QuizAttemptID: createdAttemptRepo.Result.ID,
|
| 107 |
+
QuestionID: question.ID,
|
| 108 |
+
})
|
| 109 |
+
if createdUserAnswer.RowsError != nil {
|
| 110 |
+
s.Error = createdUserAnswer.RowsError
|
| 111 |
+
return
|
| 112 |
+
}
|
| 113 |
+
}
|
| 114 |
+
s.Result = createdAttemptRepo.Result
|
| 115 |
+
}
|
| 116 |
+
func (s *AttemptQuizService) Create(userID uint) {
|
| 117 |
+
s.Validate(userID, func(latestAttemptRepo repositories.Repository[models.QuizAttempt, models.QuizAttempt], quizRepo repositories.Repository[models.Quiz, models.Quiz]) {
|
| 118 |
+
if latestAttemptRepo.Result.FinishedAt != nil {
|
| 119 |
+
Attempt(s, quizRepo, userID)
|
| 120 |
+
} else {
|
| 121 |
+
s.Result = latestAttemptRepo.Result
|
| 122 |
+
}
|
| 123 |
+
})
|
| 124 |
+
}
|
space/controller/academy/academy_contents_controller.go
CHANGED
|
@@ -9,7 +9,7 @@ import (
|
|
| 9 |
|
| 10 |
func ContentList(c *gin.Context) {
|
| 11 |
academyContent := services.AcademyContentService{}
|
| 12 |
-
academyController := controller.Controller[any, models.AcademyMaterial,
|
| 13 |
Service: &academyContent.Service,
|
| 14 |
}
|
| 15 |
academyController.Service.Constructor.Slug = c.Param("slug_material")
|
|
|
|
| 9 |
|
| 10 |
func ContentList(c *gin.Context) {
|
| 11 |
academyContent := services.AcademyContentService{}
|
| 12 |
+
academyController := controller.Controller[any, models.AcademyMaterial, models.AcademyContent]{
|
| 13 |
Service: &academyContent.Service,
|
| 14 |
}
|
| 15 |
academyController.Service.Constructor.Slug = c.Param("slug_material")
|
space/models/database_orm_model.go
CHANGED
|
@@ -71,9 +71,11 @@ type Academy struct {
|
|
| 71 |
ID uint `gorm:"primaryKey" json:"id"`
|
| 72 |
UUID uuid.UUID `gorm:"type:uuid" json:"uuid"`
|
| 73 |
Title string `json:"title"`
|
| 74 |
-
Slug string `json:"slug"`
|
| 75 |
TotalMaterial int `json:"total_material"`
|
| 76 |
CompletedMaterial int `json:"completed_material"`
|
|
|
|
|
|
|
| 77 |
Description string `json:"description"`
|
| 78 |
}
|
| 79 |
|
|
@@ -82,7 +84,7 @@ type AcademyMaterial struct {
|
|
| 82 |
UUID uuid.UUID `gorm:"type:uuid" json:"uuid"`
|
| 83 |
AcademyID uint `json:"academy_id"`
|
| 84 |
Title string `json:"title"`
|
| 85 |
-
Slug string `json:"slug"`
|
| 86 |
IsCompleted bool `json:"is_completed"`
|
| 87 |
Description string `json:"description"`
|
| 88 |
}
|
|
@@ -98,7 +100,7 @@ type AcademyContent struct {
|
|
| 98 |
type OptionCategory struct {
|
| 99 |
ID uint `gorm:"primaryKey" json:"id"`
|
| 100 |
OptionName string `json:"option_name"`
|
| 101 |
-
OptionSlug string `json:"option_slug"`
|
| 102 |
}
|
| 103 |
|
| 104 |
type OptionValues struct {
|
|
|
|
| 71 |
ID uint `gorm:"primaryKey" json:"id"`
|
| 72 |
UUID uuid.UUID `gorm:"type:uuid" json:"uuid"`
|
| 73 |
Title string `json:"title"`
|
| 74 |
+
Slug string `json:"slug" gorm:"uniqueIndex" `
|
| 75 |
TotalMaterial int `json:"total_material"`
|
| 76 |
CompletedMaterial int `json:"completed_material"`
|
| 77 |
+
IsCompletedRead bool `json:"is_read"`
|
| 78 |
+
IsPassedExam bool `json:"is_exam"`
|
| 79 |
Description string `json:"description"`
|
| 80 |
}
|
| 81 |
|
|
|
|
| 84 |
UUID uuid.UUID `gorm:"type:uuid" json:"uuid"`
|
| 85 |
AcademyID uint `json:"academy_id"`
|
| 86 |
Title string `json:"title"`
|
| 87 |
+
Slug string `json:"slug" gorm:"uniqueIndex"`
|
| 88 |
IsCompleted bool `json:"is_completed"`
|
| 89 |
Description string `json:"description"`
|
| 90 |
}
|
|
|
|
| 100 |
type OptionCategory struct {
|
| 101 |
ID uint `gorm:"primaryKey" json:"id"`
|
| 102 |
OptionName string `json:"option_name"`
|
| 103 |
+
OptionSlug string `json:"option_slug" gorm:"uniqueIndex"`
|
| 104 |
}
|
| 105 |
|
| 106 |
type OptionValues struct {
|
space/models/response_model.go
CHANGED
|
@@ -33,7 +33,7 @@ type UserProfileResponse struct {
|
|
| 33 |
|
| 34 |
type AcademyMaterialResponse struct {
|
| 35 |
Materials AcademyMaterial
|
| 36 |
-
Contents
|
| 37 |
}
|
| 38 |
type AcademyResponse struct {
|
| 39 |
Academy Academy `json:"academy"`
|
|
|
|
| 33 |
|
| 34 |
type AcademyMaterialResponse struct {
|
| 35 |
Materials AcademyMaterial
|
| 36 |
+
Contents AcademyContent
|
| 37 |
}
|
| 38 |
type AcademyResponse struct {
|
| 39 |
Academy Academy `json:"academy"`
|
space/repositories/academy_repository.go
CHANGED
|
@@ -35,13 +35,13 @@ func GetAllAcademyMaterialsByAcademyID(acaddemyId uint) Repository[models.Academ
|
|
| 35 |
return *repo
|
| 36 |
}
|
| 37 |
|
| 38 |
-
func GetAllAcademyContentsByMaterialID(materialId uint) Repository[models.AcademyContent,
|
| 39 |
-
repo := Construct[models.AcademyContent,
|
| 40 |
models.AcademyContent{AcademyMaterialID: materialId},
|
| 41 |
)
|
| 42 |
repo.Transactions(
|
| 43 |
-
WhereGivenConstructor[models.AcademyContent,
|
| 44 |
-
Find[models.AcademyContent,
|
| 45 |
)
|
| 46 |
return *repo
|
| 47 |
}
|
|
|
|
| 35 |
return *repo
|
| 36 |
}
|
| 37 |
|
| 38 |
+
func GetAllAcademyContentsByMaterialID(materialId uint) Repository[models.AcademyContent, models.AcademyContent] {
|
| 39 |
+
repo := Construct[models.AcademyContent, models.AcademyContent](
|
| 40 |
models.AcademyContent{AcademyMaterialID: materialId},
|
| 41 |
)
|
| 42 |
repo.Transactions(
|
| 43 |
+
WhereGivenConstructor[models.AcademyContent, models.AcademyContent],
|
| 44 |
+
Find[models.AcademyContent, models.AcademyContent],
|
| 45 |
)
|
| 46 |
return *repo
|
| 47 |
}
|
space/services/academy_service.go
CHANGED
|
@@ -1,6 +1,8 @@
|
|
| 1 |
package services
|
| 2 |
|
| 3 |
import (
|
|
|
|
|
|
|
| 4 |
"api.qobiltu.id/models"
|
| 5 |
"api.qobiltu.id/repositories"
|
| 6 |
"github.com/gosimple/slug"
|
|
@@ -20,12 +22,13 @@ type AcademyMaterialService struct {
|
|
| 20 |
}
|
| 21 |
|
| 22 |
type AcademyContentService struct {
|
| 23 |
-
Service[models.AcademyMaterial,
|
| 24 |
}
|
| 25 |
|
| 26 |
func castAcademyMaterials(academyId uint) []models.AcademyMaterialResponse {
|
| 27 |
var ArrMaterials []models.AcademyMaterialResponse
|
| 28 |
-
|
|
|
|
| 29 |
ArrMaterials = append(ArrMaterials, models.AcademyMaterialResponse{
|
| 30 |
Materials: academyMaterial,
|
| 31 |
Contents: repositories.GetAllAcademyContentsByMaterialID(academyMaterial.ID).Result,
|
|
@@ -78,7 +81,7 @@ func (s *CreateAcademyService) Create() {
|
|
| 78 |
s.Error = createdMaterial.RowsError
|
| 79 |
return
|
| 80 |
}
|
| 81 |
-
for _, content := range material.Contents {
|
| 82 |
content.UUID = uuid.NewV4()
|
| 83 |
content.AcademyMaterialID = createdMaterial.Result.ID
|
| 84 |
createdContent := repositories.CreateAcademyContent(content)
|
|
@@ -98,9 +101,15 @@ func (s *CreateAcademyService) Create() {
|
|
| 98 |
}
|
| 99 |
|
| 100 |
func (s *AcademyMaterialService) Retrieve() {
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 104 |
if academyMaterialRepo.NoRecord {
|
| 105 |
s.Exception.DataNotFound = true
|
| 106 |
s.Exception.Message = "There is no Academy Material with given ID"
|
|
@@ -110,9 +119,15 @@ func (s *AcademyMaterialService) Retrieve() {
|
|
| 110 |
}
|
| 111 |
|
| 112 |
func (s *AcademyContentService) Retrieve() {
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 116 |
if academyContentRepo.NoRecord {
|
| 117 |
s.Exception.DataNotFound = true
|
| 118 |
s.Exception.Message = "There is no Academy Contents with given Material ID"
|
|
|
|
| 1 |
package services
|
| 2 |
|
| 3 |
import (
|
| 4 |
+
"errors"
|
| 5 |
+
|
| 6 |
"api.qobiltu.id/models"
|
| 7 |
"api.qobiltu.id/repositories"
|
| 8 |
"github.com/gosimple/slug"
|
|
|
|
| 22 |
}
|
| 23 |
|
| 24 |
type AcademyContentService struct {
|
| 25 |
+
Service[models.AcademyMaterial, models.AcademyContent]
|
| 26 |
}
|
| 27 |
|
| 28 |
func castAcademyMaterials(academyId uint) []models.AcademyMaterialResponse {
|
| 29 |
var ArrMaterials []models.AcademyMaterialResponse
|
| 30 |
+
academyMaterialsRepo := repositories.GetAllAcademyMaterialsByAcademyID(academyId)
|
| 31 |
+
for _, academyMaterial := range academyMaterialsRepo.Result {
|
| 32 |
ArrMaterials = append(ArrMaterials, models.AcademyMaterialResponse{
|
| 33 |
Materials: academyMaterial,
|
| 34 |
Contents: repositories.GetAllAcademyContentsByMaterialID(academyMaterial.ID).Result,
|
|
|
|
| 81 |
s.Error = createdMaterial.RowsError
|
| 82 |
return
|
| 83 |
}
|
| 84 |
+
for _, content := range []models.AcademyContent{material.Contents} {
|
| 85 |
content.UUID = uuid.NewV4()
|
| 86 |
content.AcademyMaterialID = createdMaterial.Result.ID
|
| 87 |
createdContent := repositories.CreateAcademyContent(content)
|
|
|
|
| 101 |
}
|
| 102 |
|
| 103 |
func (s *AcademyMaterialService) Retrieve() {
|
| 104 |
+
academyRepo := repositories.GetAcademyDataBySlug(s.Constructor.Slug)
|
| 105 |
+
if academyRepo.NoRecord {
|
| 106 |
+
s.Exception.DataNotFound = true
|
| 107 |
+
s.Exception.Message = "There is no Academy found with given Slug!"
|
| 108 |
+
return
|
| 109 |
+
}
|
| 110 |
+
s.Error = academyRepo.RowsError
|
| 111 |
+
academyMaterialRepo := repositories.GetAllAcademyMaterialsByAcademyID(academyRepo.Result.ID)
|
| 112 |
+
s.Error = errors.Join(s.Error, academyMaterialRepo.RowsError)
|
| 113 |
if academyMaterialRepo.NoRecord {
|
| 114 |
s.Exception.DataNotFound = true
|
| 115 |
s.Exception.Message = "There is no Academy Material with given ID"
|
|
|
|
| 119 |
}
|
| 120 |
|
| 121 |
func (s *AcademyContentService) Retrieve() {
|
| 122 |
+
academyMaterialRepo := repositories.GetAcademyMaterialBySlug(s.Constructor.Slug)
|
| 123 |
+
s.Error = academyMaterialRepo.RowsError
|
| 124 |
+
if academyMaterialRepo.NoRecord {
|
| 125 |
+
s.Exception.DataNotFound = true
|
| 126 |
+
s.Exception.Message = "There is no Academy Material with given Slug"
|
| 127 |
+
return
|
| 128 |
+
}
|
| 129 |
+
academyContentRepo := repositories.GetAllAcademyContentsByMaterialID(academyMaterialRepo.Result.ID)
|
| 130 |
+
s.Error = errors.Join(s.Error, academyContentRepo.RowsError)
|
| 131 |
if academyContentRepo.NoRecord {
|
| 132 |
s.Exception.DataNotFound = true
|
| 133 |
s.Exception.Message = "There is no Academy Contents with given Material ID"
|
space/services/register_service.go
CHANGED
|
@@ -2,6 +2,7 @@ package services
|
|
| 2 |
|
| 3 |
import (
|
| 4 |
"errors"
|
|
|
|
| 5 |
|
| 6 |
"api.qobiltu.id/models"
|
| 7 |
"api.qobiltu.id/repositories"
|
|
@@ -13,10 +14,49 @@ type RegisterService struct {
|
|
| 13 |
Service[models.Account, models.Account]
|
| 14 |
}
|
| 15 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
func (s *RegisterService) Create() {
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
s.Exception
|
| 20 |
return
|
| 21 |
}
|
| 22 |
hashed_password, err_hash := HashPassword(s.Constructor.Password)
|
|
|
|
| 2 |
|
| 3 |
import (
|
| 4 |
"errors"
|
| 5 |
+
"unicode"
|
| 6 |
|
| 7 |
"api.qobiltu.id/models"
|
| 8 |
"api.qobiltu.id/repositories"
|
|
|
|
| 14 |
Service[models.Account, models.Account]
|
| 15 |
}
|
| 16 |
|
| 17 |
+
func ValidatePassword(password string) models.Exception {
|
| 18 |
+
var (
|
| 19 |
+
hasMinLen = false
|
| 20 |
+
hasUpper = false
|
| 21 |
+
hasLower = false
|
| 22 |
+
hasNumber = false
|
| 23 |
+
hasSpecial = false
|
| 24 |
+
)
|
| 25 |
+
|
| 26 |
+
if len(password) >= 8 {
|
| 27 |
+
hasMinLen = true
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
for _, char := range password {
|
| 31 |
+
switch {
|
| 32 |
+
case unicode.IsUpper(char):
|
| 33 |
+
hasUpper = true
|
| 34 |
+
break
|
| 35 |
+
case unicode.IsLower(char):
|
| 36 |
+
hasLower = true
|
| 37 |
+
break
|
| 38 |
+
case unicode.IsDigit(char):
|
| 39 |
+
hasNumber = true
|
| 40 |
+
break
|
| 41 |
+
case unicode.IsPunct(char) || unicode.IsSymbol(char):
|
| 42 |
+
hasSpecial = true
|
| 43 |
+
break
|
| 44 |
+
}
|
| 45 |
+
}
|
| 46 |
+
approve := hasMinLen && hasUpper && hasLower && hasNumber && hasSpecial
|
| 47 |
+
if !approve {
|
| 48 |
+
return models.Exception{
|
| 49 |
+
BadRequest: true,
|
| 50 |
+
Message: "Password must contain at least 8 characters, including uppercase, lowercase, number, and special character!",
|
| 51 |
+
}
|
| 52 |
+
}
|
| 53 |
+
return models.Exception{}
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
func (s *RegisterService) Create() {
|
| 57 |
+
validatePassword := ValidatePassword(s.Constructor.Password)
|
| 58 |
+
if validatePassword.BadRequest {
|
| 59 |
+
s.Exception = validatePassword
|
| 60 |
return
|
| 61 |
}
|
| 62 |
hashed_password, err_hash := HashPassword(s.Constructor.Password)
|
space/space/controller/academy/academy_contents_controller.go
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package academy
|
| 2 |
+
|
| 3 |
+
import (
|
| 4 |
+
"api.qobiltu.id/controller"
|
| 5 |
+
"api.qobiltu.id/models"
|
| 6 |
+
"api.qobiltu.id/services"
|
| 7 |
+
"github.com/gin-gonic/gin"
|
| 8 |
+
)
|
| 9 |
+
|
| 10 |
+
func ContentList(c *gin.Context) {
|
| 11 |
+
academyContent := services.AcademyContentService{}
|
| 12 |
+
academyController := controller.Controller[any, models.AcademyMaterial, []models.AcademyContent]{
|
| 13 |
+
Service: &academyContent.Service,
|
| 14 |
+
}
|
| 15 |
+
academyController.Service.Constructor.Slug = c.Param("slug_material")
|
| 16 |
+
academyContent.Retrieve()
|
| 17 |
+
academyController.Response(c)
|
| 18 |
+
|
| 19 |
+
}
|
space/space/controller/academy/academy_controller.go
CHANGED
|
@@ -9,7 +9,7 @@ import (
|
|
| 9 |
|
| 10 |
func List(c *gin.Context) {
|
| 11 |
academy := services.AcademyService{}
|
| 12 |
-
academyController := controller.Controller[any, models.Academy, models.
|
| 13 |
Service: &academy.Service,
|
| 14 |
}
|
| 15 |
academyController.Service.Constructor.Slug = c.Param("slug")
|
|
|
|
| 9 |
|
| 10 |
func List(c *gin.Context) {
|
| 11 |
academy := services.AcademyService{}
|
| 12 |
+
academyController := controller.Controller[any, models.Academy, []models.Academy]{
|
| 13 |
Service: &academy.Service,
|
| 14 |
}
|
| 15 |
academyController.Service.Constructor.Slug = c.Param("slug")
|
space/space/controller/academy/academy_materials_controller.go
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package academy
|
| 2 |
+
|
| 3 |
+
import (
|
| 4 |
+
"api.qobiltu.id/controller"
|
| 5 |
+
"api.qobiltu.id/models"
|
| 6 |
+
"api.qobiltu.id/services"
|
| 7 |
+
"github.com/gin-gonic/gin"
|
| 8 |
+
)
|
| 9 |
+
|
| 10 |
+
func MaterialsList(c *gin.Context) {
|
| 11 |
+
academyMaterial := services.AcademyMaterialService{}
|
| 12 |
+
academyController := controller.Controller[any, models.Academy, []models.AcademyMaterial]{
|
| 13 |
+
Service: &academyMaterial.Service,
|
| 14 |
+
}
|
| 15 |
+
academyController.Service.Constructor.Slug = c.Param("slug")
|
| 16 |
+
academyMaterial.Retrieve()
|
| 17 |
+
academyController.Response(c)
|
| 18 |
+
|
| 19 |
+
}
|
space/space/models/database_orm_model.go
CHANGED
|
@@ -68,11 +68,13 @@ type ForgotPassword struct {
|
|
| 68 |
}
|
| 69 |
|
| 70 |
type Academy struct {
|
| 71 |
-
ID
|
| 72 |
-
UUID
|
| 73 |
-
Title
|
| 74 |
-
Slug
|
| 75 |
-
|
|
|
|
|
|
|
| 76 |
}
|
| 77 |
|
| 78 |
type AcademyMaterial struct {
|
|
@@ -81,6 +83,7 @@ type AcademyMaterial struct {
|
|
| 81 |
AcademyID uint `json:"academy_id"`
|
| 82 |
Title string `json:"title"`
|
| 83 |
Slug string `json:"slug"`
|
|
|
|
| 84 |
Description string `json:"description"`
|
| 85 |
}
|
| 86 |
|
|
|
|
| 68 |
}
|
| 69 |
|
| 70 |
type Academy struct {
|
| 71 |
+
ID uint `gorm:"primaryKey" json:"id"`
|
| 72 |
+
UUID uuid.UUID `gorm:"type:uuid" json:"uuid"`
|
| 73 |
+
Title string `json:"title"`
|
| 74 |
+
Slug string `json:"slug"`
|
| 75 |
+
TotalMaterial int `json:"total_material"`
|
| 76 |
+
CompletedMaterial int `json:"completed_material"`
|
| 77 |
+
Description string `json:"description"`
|
| 78 |
}
|
| 79 |
|
| 80 |
type AcademyMaterial struct {
|
|
|
|
| 83 |
AcademyID uint `json:"academy_id"`
|
| 84 |
Title string `json:"title"`
|
| 85 |
Slug string `json:"slug"`
|
| 86 |
+
IsCompleted bool `json:"is_completed"`
|
| 87 |
Description string `json:"description"`
|
| 88 |
}
|
| 89 |
|
space/space/repositories/academy_repository.go
CHANGED
|
@@ -71,3 +71,14 @@ func CreateAcademyContent(academyContent models.AcademyContent) Repository[model
|
|
| 71 |
Create(repo)
|
| 72 |
return *repo
|
| 73 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 71 |
Create(repo)
|
| 72 |
return *repo
|
| 73 |
}
|
| 74 |
+
|
| 75 |
+
func GetAcademyMaterialBySlug(slug string) Repository[models.AcademyMaterial, models.AcademyMaterial] {
|
| 76 |
+
repo := Construct[models.AcademyMaterial, models.AcademyMaterial](
|
| 77 |
+
models.AcademyMaterial{Slug: slug},
|
| 78 |
+
)
|
| 79 |
+
repo.Transactions(
|
| 80 |
+
WhereGivenConstructor[models.AcademyMaterial, models.AcademyMaterial],
|
| 81 |
+
Find[models.AcademyMaterial, models.AcademyMaterial],
|
| 82 |
+
)
|
| 83 |
+
return *repo
|
| 84 |
+
}
|
space/space/router/academy_route.go
CHANGED
|
@@ -9,7 +9,8 @@ import (
|
|
| 9 |
func AcademyRoute(router *gin.Engine) {
|
| 10 |
routerGroup := router.Group("/api/v1/academy")
|
| 11 |
{
|
| 12 |
-
routerGroup.GET("/
|
|
|
|
| 13 |
routerGroup.GET("/list", middleware.AuthUser, AcademyController.List)
|
| 14 |
routerGroup.POST("/create", middleware.AuthUser, AcademyController.Create)
|
| 15 |
}
|
|
|
|
| 9 |
func AcademyRoute(router *gin.Engine) {
|
| 10 |
routerGroup := router.Group("/api/v1/academy")
|
| 11 |
{
|
| 12 |
+
routerGroup.GET("/:slug", middleware.AuthUser, AcademyController.MaterialsList)
|
| 13 |
+
routerGroup.GET("/:slug/:slug_material", middleware.AuthUser, AcademyController.ContentList)
|
| 14 |
routerGroup.GET("/list", middleware.AuthUser, AcademyController.List)
|
| 15 |
routerGroup.POST("/create", middleware.AuthUser, AcademyController.Create)
|
| 16 |
}
|
space/space/services/academy_service.go
CHANGED
|
@@ -8,13 +8,21 @@ import (
|
|
| 8 |
)
|
| 9 |
|
| 10 |
type AcademyService struct {
|
| 11 |
-
Service[models.Academy, models.
|
| 12 |
}
|
| 13 |
|
| 14 |
type CreateAcademyService struct {
|
| 15 |
Service[models.AllAcademyResponse, models.AllAcademyResponse]
|
| 16 |
}
|
| 17 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
func castAcademyMaterials(academyId uint) []models.AcademyMaterialResponse {
|
| 19 |
var ArrMaterials []models.AcademyMaterialResponse
|
| 20 |
for _, academyMaterial := range repositories.GetAllAcademyMaterialsByAcademyID(academyId).Result {
|
|
@@ -35,27 +43,13 @@ func (s *AcademyService) Retrieve() {
|
|
| 35 |
return
|
| 36 |
}
|
| 37 |
|
| 38 |
-
s.Result = models.
|
| 39 |
-
|
| 40 |
-
models.AcademyResponse{
|
| 41 |
-
Academy: AcademyRepo.Result,
|
| 42 |
-
Materials: castAcademyMaterials(s.Constructor.ID),
|
| 43 |
-
},
|
| 44 |
-
},
|
| 45 |
}
|
| 46 |
} else {
|
| 47 |
AcademyRepo := repositories.GetAllAcademy()
|
| 48 |
s.Error = AcademyRepo.RowsError
|
| 49 |
-
|
| 50 |
-
for _, academy := range AcademyRepo.Result {
|
| 51 |
-
ArrAcademy = append(ArrAcademy, models.AcademyResponse{
|
| 52 |
-
Academy: academy,
|
| 53 |
-
Materials: castAcademyMaterials(academy.ID),
|
| 54 |
-
})
|
| 55 |
-
}
|
| 56 |
-
s.Result = models.AllAcademyResponse{
|
| 57 |
-
Academies: ArrAcademy,
|
| 58 |
-
}
|
| 59 |
}
|
| 60 |
|
| 61 |
}
|
|
@@ -102,3 +96,27 @@ func (s *CreateAcademyService) Create() {
|
|
| 102 |
s.Result = models.AllAcademyResponse{Academies: ArrAcademy}
|
| 103 |
|
| 104 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
)
|
| 9 |
|
| 10 |
type AcademyService struct {
|
| 11 |
+
Service[models.Academy, []models.Academy]
|
| 12 |
}
|
| 13 |
|
| 14 |
type CreateAcademyService struct {
|
| 15 |
Service[models.AllAcademyResponse, models.AllAcademyResponse]
|
| 16 |
}
|
| 17 |
|
| 18 |
+
type AcademyMaterialService struct {
|
| 19 |
+
Service[models.Academy, []models.AcademyMaterial]
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
type AcademyContentService struct {
|
| 23 |
+
Service[models.AcademyMaterial, []models.AcademyContent]
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
func castAcademyMaterials(academyId uint) []models.AcademyMaterialResponse {
|
| 27 |
var ArrMaterials []models.AcademyMaterialResponse
|
| 28 |
for _, academyMaterial := range repositories.GetAllAcademyMaterialsByAcademyID(academyId).Result {
|
|
|
|
| 43 |
return
|
| 44 |
}
|
| 45 |
|
| 46 |
+
s.Result = []models.Academy{
|
| 47 |
+
AcademyRepo.Result,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
}
|
| 49 |
} else {
|
| 50 |
AcademyRepo := repositories.GetAllAcademy()
|
| 51 |
s.Error = AcademyRepo.RowsError
|
| 52 |
+
s.Result = AcademyRepo.Result
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
}
|
| 54 |
|
| 55 |
}
|
|
|
|
| 96 |
s.Result = models.AllAcademyResponse{Academies: ArrAcademy}
|
| 97 |
|
| 98 |
}
|
| 99 |
+
|
| 100 |
+
func (s *AcademyMaterialService) Retrieve() {
|
| 101 |
+
academyId := repositories.GetAcademyDataBySlug(s.Constructor.Slug).Result.ID
|
| 102 |
+
academyMaterialRepo := repositories.GetAllAcademyMaterialsByAcademyID(academyId)
|
| 103 |
+
s.Error = academyMaterialRepo.RowsError
|
| 104 |
+
if academyMaterialRepo.NoRecord {
|
| 105 |
+
s.Exception.DataNotFound = true
|
| 106 |
+
s.Exception.Message = "There is no Academy Material with given ID"
|
| 107 |
+
return
|
| 108 |
+
}
|
| 109 |
+
s.Result = academyMaterialRepo.Result
|
| 110 |
+
}
|
| 111 |
+
|
| 112 |
+
func (s *AcademyContentService) Retrieve() {
|
| 113 |
+
academyMaterialId := repositories.GetAcademyMaterialBySlug(s.Constructor.Slug).Result.ID
|
| 114 |
+
academyContentRepo := repositories.GetAllAcademyContentsByMaterialID(academyMaterialId)
|
| 115 |
+
s.Error = academyContentRepo.RowsError
|
| 116 |
+
if academyContentRepo.NoRecord {
|
| 117 |
+
s.Exception.DataNotFound = true
|
| 118 |
+
s.Exception.Message = "There is no Academy Contents with given Material ID"
|
| 119 |
+
return
|
| 120 |
+
}
|
| 121 |
+
s.Result = academyContentRepo.Result
|
| 122 |
+
}
|
space/space/services/jwt_service.go
CHANGED
|
@@ -20,7 +20,7 @@ func GenerateToken(user *models.Account) (string, error) {
|
|
| 20 |
RegisteredClaims: jwt.RegisteredClaims{
|
| 21 |
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24)), // Token berlaku 24 jam
|
| 22 |
IssuedAt: jwt.NewNumericDate(time.Now()),
|
| 23 |
-
Issuer: "
|
| 24 |
},
|
| 25 |
}
|
| 26 |
|
|
|
|
| 20 |
RegisteredClaims: jwt.RegisteredClaims{
|
| 21 |
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24)), // Token berlaku 24 jam
|
| 22 |
IssuedAt: jwt.NewNumericDate(time.Now()),
|
| 23 |
+
Issuer: "apqobiltu.id",
|
| 24 |
},
|
| 25 |
}
|
| 26 |
|
space/space/services/service.go
CHANGED
|
@@ -16,6 +16,9 @@ type (
|
|
| 16 |
Authenticate()
|
| 17 |
Authorize()
|
| 18 |
}
|
|
|
|
|
|
|
|
|
|
| 19 |
Service[TConstructor any, TResult any] struct {
|
| 20 |
Constructor TConstructor
|
| 21 |
Result TResult
|
|
|
|
| 16 |
Authenticate()
|
| 17 |
Authorize()
|
| 18 |
}
|
| 19 |
+
IService interface {
|
| 20 |
+
Implements()
|
| 21 |
+
}
|
| 22 |
Service[TConstructor any, TResult any] struct {
|
| 23 |
Constructor TConstructor
|
| 24 |
Result TResult
|
space/space/space/services/email_verification_service.go
CHANGED
|
@@ -75,18 +75,7 @@ func (s *EmailVerificationService) Create() {
|
|
| 75 |
return
|
| 76 |
}
|
| 77 |
|
| 78 |
-
// 2.
|
| 79 |
-
tlsconfig := &tls.Config{
|
| 80 |
-
ServerName: smtpHost,
|
| 81 |
-
}
|
| 82 |
-
|
| 83 |
-
if err = c.StartTLS(tlsconfig); err != nil {
|
| 84 |
-
s.Error = err
|
| 85 |
-
log.Printf("Error start TLS: %v", err)
|
| 86 |
-
return
|
| 87 |
-
}
|
| 88 |
-
|
| 89 |
-
// 3. Auth
|
| 90 |
auth := smtp.PlainAuth("", from, password, smtpHost)
|
| 91 |
if err = c.Auth(auth); err != nil {
|
| 92 |
s.Error = err
|
|
@@ -94,7 +83,7 @@ func (s *EmailVerificationService) Create() {
|
|
| 94 |
return
|
| 95 |
}
|
| 96 |
|
| 97 |
-
//
|
| 98 |
if err = c.Mail(from); err != nil {
|
| 99 |
s.Error = err
|
| 100 |
log.Printf("Error set mail from to: %v", err)
|
|
@@ -109,7 +98,7 @@ func (s *EmailVerificationService) Create() {
|
|
| 109 |
}
|
| 110 |
}
|
| 111 |
|
| 112 |
-
//
|
| 113 |
wc, err := c.Data()
|
| 114 |
if err != nil {
|
| 115 |
s.Error = err
|
|
|
|
| 75 |
return
|
| 76 |
}
|
| 77 |
|
| 78 |
+
// 2. Auth
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 79 |
auth := smtp.PlainAuth("", from, password, smtpHost)
|
| 80 |
if err = c.Auth(auth); err != nil {
|
| 81 |
s.Error = err
|
|
|
|
| 83 |
return
|
| 84 |
}
|
| 85 |
|
| 86 |
+
// 3. Set From and To
|
| 87 |
if err = c.Mail(from); err != nil {
|
| 88 |
s.Error = err
|
| 89 |
log.Printf("Error set mail from to: %v", err)
|
|
|
|
| 98 |
}
|
| 99 |
}
|
| 100 |
|
| 101 |
+
// 4. Send message
|
| 102 |
wc, err := c.Data()
|
| 103 |
if err != nil {
|
| 104 |
s.Error = err
|
space/space/space/space/space/config/config.go
CHANGED
|
@@ -7,6 +7,7 @@ import (
|
|
| 7 |
"github.com/joho/godotenv"
|
| 8 |
)
|
| 9 |
|
|
|
|
| 10 |
var TCP_ADDRESS string
|
| 11 |
var LOG_PATH string
|
| 12 |
|
|
@@ -21,6 +22,7 @@ var SMTP_PORT string
|
|
| 21 |
|
| 22 |
func init() {
|
| 23 |
godotenv.Load()
|
|
|
|
| 24 |
HOST_ADDRESS = os.Getenv("HOST_ADDRESS")
|
| 25 |
HOST_PORT = os.Getenv("HOST_PORT")
|
| 26 |
TCP_ADDRESS = HOST_ADDRESS + ":" + HOST_PORT
|
|
|
|
| 7 |
"github.com/joho/godotenv"
|
| 8 |
)
|
| 9 |
|
| 10 |
+
var ENV string
|
| 11 |
var TCP_ADDRESS string
|
| 12 |
var LOG_PATH string
|
| 13 |
|
|
|
|
| 22 |
|
| 23 |
func init() {
|
| 24 |
godotenv.Load()
|
| 25 |
+
ENV = os.Getenv("ENV")
|
| 26 |
HOST_ADDRESS = os.Getenv("HOST_ADDRESS")
|
| 27 |
HOST_PORT = os.Getenv("HOST_PORT")
|
| 28 |
TCP_ADDRESS = HOST_ADDRESS + ":" + HOST_PORT
|
space/space/space/space/space/services/email_verification_service.go
CHANGED
|
@@ -1,6 +1,7 @@
|
|
| 1 |
package services
|
| 2 |
|
| 3 |
import (
|
|
|
|
| 4 |
"fmt"
|
| 5 |
"log"
|
| 6 |
"math/rand/v2"
|
|
@@ -39,27 +40,118 @@ func (s *EmailVerificationService) Create() {
|
|
| 39 |
|
| 40 |
// ⬇ Kirim token ke email user menggunakan SMTP
|
| 41 |
go func(toEmail string, token uint) {
|
|
|
|
| 42 |
from := config.SMTP_SENDER_EMAIL
|
| 43 |
password := config.SMTP_SENDER_PASSWORD
|
| 44 |
smtpHost := config.SMTP_HOST
|
| 45 |
smtpPort := config.SMTP_PORT
|
|
|
|
| 46 |
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
subject := "Email Verification Token"
|
| 50 |
body := fmt.Sprintf("Your verification token is: %06d\nPlease use it before it expires.", token)
|
| 51 |
|
| 52 |
-
msg := []byte("
|
| 53 |
-
"
|
| 54 |
-
"\r\n" +
|
| 55 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
|
| 57 |
-
err :=
|
| 58 |
if err != nil {
|
| 59 |
s.Error = err
|
| 60 |
log.Printf("Error sending verification email: %v", err)
|
| 61 |
return
|
| 62 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 63 |
}(accountRepo.Result.Email, token)
|
| 64 |
// s.Result.Token = 0
|
| 65 |
}
|
|
|
|
| 1 |
package services
|
| 2 |
|
| 3 |
import (
|
| 4 |
+
"crypto/tls"
|
| 5 |
"fmt"
|
| 6 |
"log"
|
| 7 |
"math/rand/v2"
|
|
|
|
| 40 |
|
| 41 |
// ⬇ Kirim token ke email user menggunakan SMTP
|
| 42 |
go func(toEmail string, token uint) {
|
| 43 |
+
env := config.ENV
|
| 44 |
from := config.SMTP_SENDER_EMAIL
|
| 45 |
password := config.SMTP_SENDER_PASSWORD
|
| 46 |
smtpHost := config.SMTP_HOST
|
| 47 |
smtpPort := config.SMTP_PORT
|
| 48 |
+
to := []string{toEmail}
|
| 49 |
|
| 50 |
+
subject := "Verification token"
|
|
|
|
|
|
|
| 51 |
body := fmt.Sprintf("Your verification token is: %06d\nPlease use it before it expires.", token)
|
| 52 |
|
| 53 |
+
msg := []byte(fmt.Sprintf("From: %s\r\n", from) +
|
| 54 |
+
fmt.Sprintf("To: %s\r\n", toEmail) +
|
| 55 |
+
fmt.Sprintf("Subject: %s\r\n", subject) +
|
| 56 |
+
"\r\n" + body)
|
| 57 |
+
|
| 58 |
+
// 1. Connect to the server
|
| 59 |
+
conn, err := tls.Dial("tcp", fmt.Sprintf("[%s]:%s", smtpHost, smtpPort), &tls.Config{
|
| 60 |
+
InsecureSkipVerify: env != "production", // ⚠️ set false di production
|
| 61 |
+
ServerName: smtpHost,
|
| 62 |
+
})
|
| 63 |
|
| 64 |
+
// conn, err := net.Dial("tcp", fmt.Sprintf("[%s]:%s", smtpHost, smtpPort))
|
| 65 |
if err != nil {
|
| 66 |
s.Error = err
|
| 67 |
log.Printf("Error sending verification email: %v", err)
|
| 68 |
return
|
| 69 |
}
|
| 70 |
+
|
| 71 |
+
c, err := smtp.NewClient(conn, smtpHost)
|
| 72 |
+
if err != nil {
|
| 73 |
+
s.Error = err
|
| 74 |
+
log.Printf("Error create new client mail: %v", err)
|
| 75 |
+
return
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
// 2. Start TLS
|
| 79 |
+
tlsconfig := &tls.Config{
|
| 80 |
+
ServerName: smtpHost,
|
| 81 |
+
}
|
| 82 |
+
|
| 83 |
+
if err = c.StartTLS(tlsconfig); err != nil {
|
| 84 |
+
s.Error = err
|
| 85 |
+
log.Printf("Error start TLS: %v", err)
|
| 86 |
+
return
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
+
// 3. Auth
|
| 90 |
+
auth := smtp.PlainAuth("", from, password, smtpHost)
|
| 91 |
+
if err = c.Auth(auth); err != nil {
|
| 92 |
+
s.Error = err
|
| 93 |
+
log.Printf("Error auth mail: %v", err)
|
| 94 |
+
return
|
| 95 |
+
}
|
| 96 |
+
|
| 97 |
+
// 4. Set From and To
|
| 98 |
+
if err = c.Mail(from); err != nil {
|
| 99 |
+
s.Error = err
|
| 100 |
+
log.Printf("Error set mail from to: %v", err)
|
| 101 |
+
return
|
| 102 |
+
}
|
| 103 |
+
|
| 104 |
+
for _, addr := range to {
|
| 105 |
+
if err = c.Rcpt(addr); err != nil {
|
| 106 |
+
s.Error = err
|
| 107 |
+
log.Printf("Error receipt addr: %v", err)
|
| 108 |
+
return
|
| 109 |
+
}
|
| 110 |
+
}
|
| 111 |
+
|
| 112 |
+
// 5. Send message
|
| 113 |
+
wc, err := c.Data()
|
| 114 |
+
if err != nil {
|
| 115 |
+
s.Error = err
|
| 116 |
+
log.Printf("Error data Send Message: %v", err)
|
| 117 |
+
return
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
_, err = wc.Write(msg)
|
| 121 |
+
if err != nil {
|
| 122 |
+
s.Error = err
|
| 123 |
+
log.Printf("Error write Send Message: %v", err)
|
| 124 |
+
return
|
| 125 |
+
}
|
| 126 |
+
|
| 127 |
+
err = wc.Close()
|
| 128 |
+
if err != nil {
|
| 129 |
+
s.Error = err
|
| 130 |
+
log.Printf("Error close Send Message: %v", err)
|
| 131 |
+
return
|
| 132 |
+
}
|
| 133 |
+
|
| 134 |
+
c.Quit()
|
| 135 |
+
fmt.Println("Email sent successfully!")
|
| 136 |
+
|
| 137 |
+
// auth := smtp.PlainAuth("", from, password, smtpHost)
|
| 138 |
+
|
| 139 |
+
// subject := "Email Verification Token"
|
| 140 |
+
// body := fmt.Sprintf("Your verification token is: %06d\nPlease use it before it expires.", token)
|
| 141 |
+
|
| 142 |
+
// msg := []byte("To: " + toEmail + "\r\n" +
|
| 143 |
+
// "Subject: " + subject + "\r\n" +
|
| 144 |
+
// "\r\n" +
|
| 145 |
+
// body + "\r\n")
|
| 146 |
+
|
| 147 |
+
// err := smtp.SendMail(smtpHost+":"+smtpPort, auth, from, []string{toEmail}, msg)
|
| 148 |
+
// if err != nil {
|
| 149 |
+
// s.Error = err
|
| 150 |
+
// log.Printf("Error sending verification email: %v", err)
|
| 151 |
+
// return
|
| 152 |
+
// } else {
|
| 153 |
+
// log.Printf("Successfully sending verification email: %v", err)
|
| 154 |
+
// }
|
| 155 |
}(accountRepo.Result.Email, token)
|
| 156 |
// s.Result.Token = 0
|
| 157 |
}
|
space/space/space/space/space/space/services/external_authentication_service.go
CHANGED
|
@@ -24,6 +24,16 @@ func (s *GoogleAuthService) Authenticate(isAgree bool) {
|
|
| 24 |
return
|
| 25 |
}
|
| 26 |
email := payload.Claims["email"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
if GoogleAuth.NoRecord {
|
| 28 |
if !isAgree {
|
| 29 |
s.Exception.BadRequest = true
|
|
@@ -32,12 +42,6 @@ func (s *GoogleAuthService) Authenticate(isAgree bool) {
|
|
| 32 |
}
|
| 33 |
s.Constructor.UUID = uuid.NewV4()
|
| 34 |
s.Constructor.OauthProvider = "Google"
|
| 35 |
-
checkRegisteredEmail := repositories.GetAccountbyEmail(email.(string))
|
| 36 |
-
if !checkRegisteredEmail.NoRecord {
|
| 37 |
-
s.Exception.DataDuplicate = true
|
| 38 |
-
s.Exception.Message = "Account with email" + email.(string) + "already registered!"
|
| 39 |
-
return
|
| 40 |
-
}
|
| 41 |
|
| 42 |
createAccount := repositories.CreateAccount(models.Account{
|
| 43 |
UUID: uuid.NewV4(),
|
|
|
|
| 24 |
return
|
| 25 |
}
|
| 26 |
email := payload.Claims["email"]
|
| 27 |
+
checkRegisteredEmail := repositories.GetAccountbyEmail(email.(string))
|
| 28 |
+
if !checkRegisteredEmail.NoRecord {
|
| 29 |
+
token, _ := GenerateToken(&checkRegisteredEmail.Result)
|
| 30 |
+
checkRegisteredEmail.Result.Password = "SECRET"
|
| 31 |
+
s.Result = models.AuthenticatedUser{
|
| 32 |
+
Account: checkRegisteredEmail.Result,
|
| 33 |
+
Token: token,
|
| 34 |
+
}
|
| 35 |
+
return
|
| 36 |
+
}
|
| 37 |
if GoogleAuth.NoRecord {
|
| 38 |
if !isAgree {
|
| 39 |
s.Exception.BadRequest = true
|
|
|
|
| 42 |
}
|
| 43 |
s.Constructor.UUID = uuid.NewV4()
|
| 44 |
s.Constructor.OauthProvider = "Google"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
|
| 46 |
createAccount := repositories.CreateAccount(models.Account{
|
| 47 |
UUID: uuid.NewV4(),
|
space/space/space/space/space/space/space/services/email_verification_service.go
CHANGED
|
@@ -29,10 +29,7 @@ func (s *EmailVerificationService) Create() {
|
|
| 29 |
remainingTime := time.Duration(config.EMAIL_VERIFICATION_DURATION) * time.Hour
|
| 30 |
dueTime := CalculateDueTime(remainingTime)
|
| 31 |
|
| 32 |
-
token := uint(rand.IntN(
|
| 33 |
-
for token < 1000000 {
|
| 34 |
-
token = uint(rand.IntN(1000000))
|
| 35 |
-
}
|
| 36 |
s.Constructor.UUID = uuid.NewV4()
|
| 37 |
|
| 38 |
repo := repositories.CreateEmailVerification(s.Constructor.UUID, s.Constructor.AccountID, dueTime, token)
|
|
|
|
| 29 |
remainingTime := time.Duration(config.EMAIL_VERIFICATION_DURATION) * time.Hour
|
| 30 |
dueTime := CalculateDueTime(remainingTime)
|
| 31 |
|
| 32 |
+
token := uint(rand.IntN(999999-100000) + 100000)
|
|
|
|
|
|
|
|
|
|
| 33 |
s.Constructor.UUID = uuid.NewV4()
|
| 34 |
|
| 35 |
repo := repositories.CreateEmailVerification(s.Constructor.UUID, s.Constructor.AccountID, dueTime, token)
|
space/space/space/space/space/space/space/services/forgot_password_service.go
CHANGED
|
@@ -34,10 +34,7 @@ func (s *ForgotPasswordService) Create(email string) {
|
|
| 34 |
remainingTime := time.Duration(config.EMAIL_VERIFICATION_DURATION) * time.Hour
|
| 35 |
dueTime := CalculateDueTime(remainingTime)
|
| 36 |
|
| 37 |
-
token := uint(rand.IntN(
|
| 38 |
-
for token < 1000000 {
|
| 39 |
-
token = uint(rand.IntN(1000000))
|
| 40 |
-
}
|
| 41 |
s.Constructor.UUID = uuid.NewV4()
|
| 42 |
s.Constructor.ExpiredAt = dueTime
|
| 43 |
s.Constructor.AccountID = accountRepo.Result.Id
|
|
|
|
| 34 |
remainingTime := time.Duration(config.EMAIL_VERIFICATION_DURATION) * time.Hour
|
| 35 |
dueTime := CalculateDueTime(remainingTime)
|
| 36 |
|
| 37 |
+
token := uint(rand.IntN(999999-100000) + 100000)
|
|
|
|
|
|
|
|
|
|
| 38 |
s.Constructor.UUID = uuid.NewV4()
|
| 39 |
s.Constructor.ExpiredAt = dueTime
|
| 40 |
s.Constructor.AccountID = accountRepo.Result.Id
|
space/space/space/space/space/space/space/space/services/external_authentication_service.go
CHANGED
|
@@ -32,17 +32,34 @@ func (s *GoogleAuthService) Authenticate(isAgree bool) {
|
|
| 32 |
}
|
| 33 |
s.Constructor.UUID = uuid.NewV4()
|
| 34 |
s.Constructor.OauthProvider = "Google"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
createAccount := repositories.CreateAccount(models.Account{
|
| 36 |
UUID: uuid.NewV4(),
|
| 37 |
Email: email.(string),
|
| 38 |
IsEmailVerified: true,
|
| 39 |
})
|
|
|
|
| 40 |
s.Constructor.AccountID = createAccount.Result.Id
|
| 41 |
createGoogleAuth := repositories.CreateExternalAuth(s.Constructor)
|
| 42 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
s.Error = createGoogleAuth.RowsError
|
| 44 |
s.Error = errors.Join(s.Error, createAccount.RowsError)
|
| 45 |
}
|
|
|
|
| 46 |
accountData := repositories.GetAccountById(GoogleAuth.Result.AccountID)
|
| 47 |
token, err_tok := GenerateToken(&accountData.Result)
|
| 48 |
|
|
|
|
| 32 |
}
|
| 33 |
s.Constructor.UUID = uuid.NewV4()
|
| 34 |
s.Constructor.OauthProvider = "Google"
|
| 35 |
+
checkRegisteredEmail := repositories.GetAccountbyEmail(email.(string))
|
| 36 |
+
if !checkRegisteredEmail.NoRecord {
|
| 37 |
+
s.Exception.DataDuplicate = true
|
| 38 |
+
s.Exception.Message = "Account with email" + email.(string) + "already registered!"
|
| 39 |
+
return
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
createAccount := repositories.CreateAccount(models.Account{
|
| 43 |
UUID: uuid.NewV4(),
|
| 44 |
Email: email.(string),
|
| 45 |
IsEmailVerified: true,
|
| 46 |
})
|
| 47 |
+
|
| 48 |
s.Constructor.AccountID = createAccount.Result.Id
|
| 49 |
createGoogleAuth := repositories.CreateExternalAuth(s.Constructor)
|
| 50 |
+
|
| 51 |
+
GoogleAuth.Result.AccountID = createGoogleAuth.Result.AccountID
|
| 52 |
+
userProfile := UserProfileService{}
|
| 53 |
+
userProfile.Constructor.AccountID = GoogleAuth.Result.AccountID
|
| 54 |
+
userProfile.Create()
|
| 55 |
+
if userProfile.Error != nil {
|
| 56 |
+
s.Error = userProfile.Error
|
| 57 |
+
return
|
| 58 |
+
}
|
| 59 |
s.Error = createGoogleAuth.RowsError
|
| 60 |
s.Error = errors.Join(s.Error, createAccount.RowsError)
|
| 61 |
}
|
| 62 |
+
|
| 63 |
accountData := repositories.GetAccountById(GoogleAuth.Result.AccountID)
|
| 64 |
token, err_tok := GenerateToken(&accountData.Result)
|
| 65 |
|
space/space/space/space/space/space/space/space/space/controller/academy/academy_controller.go
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package academy
|
| 2 |
+
|
| 3 |
+
import (
|
| 4 |
+
"api.qobiltu.id/controller"
|
| 5 |
+
"api.qobiltu.id/models"
|
| 6 |
+
"api.qobiltu.id/services"
|
| 7 |
+
"github.com/gin-gonic/gin"
|
| 8 |
+
)
|
| 9 |
+
|
| 10 |
+
func List(c *gin.Context) {
|
| 11 |
+
academy := services.AcademyService{}
|
| 12 |
+
academyController := controller.Controller[any, models.Academy, models.AllAcademyResponse]{
|
| 13 |
+
Service: &academy.Service,
|
| 14 |
+
}
|
| 15 |
+
academyController.Service.Constructor.Slug = c.Param("slug")
|
| 16 |
+
academy.Retrieve()
|
| 17 |
+
academyController.Response(c)
|
| 18 |
+
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
func Create(c *gin.Context) {
|
| 22 |
+
createAcademy := services.CreateAcademyService{}
|
| 23 |
+
academyController := controller.Controller[models.AllAcademyResponse, models.AllAcademyResponse, models.AllAcademyResponse]{
|
| 24 |
+
Service: &createAcademy.Service,
|
| 25 |
+
}
|
| 26 |
+
academyController.RequestJSON(c, func() {
|
| 27 |
+
academyController.Service.Constructor.Academies = academyController.Request.Academies
|
| 28 |
+
createAcademy.Create()
|
| 29 |
+
})
|
| 30 |
+
|
| 31 |
+
}
|
space/space/space/space/space/space/space/space/space/models/database_orm_model.go
CHANGED
|
@@ -30,7 +30,7 @@ type AccountDetails struct {
|
|
| 30 |
LastEducation *string `json:"last_education"`
|
| 31 |
MaritalStatus *string `json:"marital_status"`
|
| 32 |
Avatar *string `json:"avatar"`
|
| 33 |
-
PhoneNumber *
|
| 34 |
}
|
| 35 |
|
| 36 |
type EmailVerification struct {
|
|
@@ -85,12 +85,12 @@ type AcademyMaterial struct {
|
|
| 85 |
}
|
| 86 |
|
| 87 |
type AcademyContent struct {
|
| 88 |
-
ID uint
|
| 89 |
-
UUID
|
| 90 |
-
Title string
|
| 91 |
-
Order uint
|
| 92 |
-
AcademyMaterialID uint
|
| 93 |
-
Description string
|
| 94 |
}
|
| 95 |
type OptionCategory struct {
|
| 96 |
ID uint `gorm:"primaryKey" json:"id"`
|
|
|
|
| 30 |
LastEducation *string `json:"last_education"`
|
| 31 |
MaritalStatus *string `json:"marital_status"`
|
| 32 |
Avatar *string `json:"avatar"`
|
| 33 |
+
PhoneNumber *string `json:"phone_number"`
|
| 34 |
}
|
| 35 |
|
| 36 |
type EmailVerification struct {
|
|
|
|
| 85 |
}
|
| 86 |
|
| 87 |
type AcademyContent struct {
|
| 88 |
+
ID uint `gorm:"primaryKey" json:"id"`
|
| 89 |
+
UUID uuid.UUID `json:"uuid"`
|
| 90 |
+
Title string `json:"title"`
|
| 91 |
+
Order uint `json:"order"`
|
| 92 |
+
AcademyMaterialID uint `json:"academy_material_id"`
|
| 93 |
+
Description string `json:"description"`
|
| 94 |
}
|
| 95 |
type OptionCategory struct {
|
| 96 |
ID uint `gorm:"primaryKey" json:"id"`
|
space/space/space/space/space/space/space/space/space/repositories/academy_repository.go
CHANGED
|
@@ -13,9 +13,9 @@ func GetAllAcademy() Repository[models.Academy, []models.Academy] {
|
|
| 13 |
return *repo
|
| 14 |
}
|
| 15 |
|
| 16 |
-
func
|
| 17 |
repo := Construct[models.Academy, models.Academy](
|
| 18 |
-
models.Academy{
|
| 19 |
)
|
| 20 |
repo.Transactions(
|
| 21 |
WhereGivenConstructor[models.Academy, models.Academy],
|
|
@@ -45,3 +45,29 @@ func GetAllAcademyContentsByMaterialID(materialId uint) Repository[models.Academ
|
|
| 45 |
)
|
| 46 |
return *repo
|
| 47 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
return *repo
|
| 14 |
}
|
| 15 |
|
| 16 |
+
func GetAcademyDataBySlug(slug string) Repository[models.Academy, models.Academy] {
|
| 17 |
repo := Construct[models.Academy, models.Academy](
|
| 18 |
+
models.Academy{Slug: slug},
|
| 19 |
)
|
| 20 |
repo.Transactions(
|
| 21 |
WhereGivenConstructor[models.Academy, models.Academy],
|
|
|
|
| 45 |
)
|
| 46 |
return *repo
|
| 47 |
}
|
| 48 |
+
|
| 49 |
+
func CreateAcademy(academies models.Academy) Repository[models.Academy, models.Academy] {
|
| 50 |
+
repo := Construct[models.Academy, models.Academy](
|
| 51 |
+
academies,
|
| 52 |
+
)
|
| 53 |
+
|
| 54 |
+
Create(repo)
|
| 55 |
+
return *repo
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
func CreateAcademyMaterial(academyMaterial models.AcademyMaterial) Repository[models.AcademyMaterial, models.AcademyMaterial] {
|
| 59 |
+
repo := Construct[models.AcademyMaterial, models.AcademyMaterial](
|
| 60 |
+
academyMaterial,
|
| 61 |
+
)
|
| 62 |
+
|
| 63 |
+
Create(repo)
|
| 64 |
+
return *repo
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
func CreateAcademyContent(academyContent models.AcademyContent) Repository[models.AcademyContent, models.AcademyContent] {
|
| 68 |
+
repo := Construct[models.AcademyContent, models.AcademyContent](
|
| 69 |
+
academyContent,
|
| 70 |
+
)
|
| 71 |
+
Create(repo)
|
| 72 |
+
return *repo
|
| 73 |
+
}
|
space/space/space/space/space/space/space/space/space/router/academy_route.go
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package router
|
| 2 |
+
|
| 3 |
+
import (
|
| 4 |
+
AcademyController "api.qobiltu.id/controller/academy"
|
| 5 |
+
"api.qobiltu.id/middleware"
|
| 6 |
+
"github.com/gin-gonic/gin"
|
| 7 |
+
)
|
| 8 |
+
|
| 9 |
+
func AcademyRoute(router *gin.Engine) {
|
| 10 |
+
routerGroup := router.Group("/api/v1/academy")
|
| 11 |
+
{
|
| 12 |
+
routerGroup.GET("/list/:slug", middleware.AuthUser, AcademyController.List)
|
| 13 |
+
routerGroup.GET("/list", middleware.AuthUser, AcademyController.List)
|
| 14 |
+
routerGroup.POST("/create", middleware.AuthUser, AcademyController.Create)
|
| 15 |
+
}
|
| 16 |
+
}
|
space/space/space/space/space/space/space/space/space/router/router.go
CHANGED
|
@@ -16,7 +16,7 @@ func StartService() {
|
|
| 16 |
UserRoute(router)
|
| 17 |
EmailRoute(router)
|
| 18 |
OptionsRoute(router)
|
| 19 |
-
|
| 20 |
err := router.Run(config.TCP_ADDRESS)
|
| 21 |
if err != nil {
|
| 22 |
log.Fatalf("Failed to run server: %v", err)
|
|
|
|
| 16 |
UserRoute(router)
|
| 17 |
EmailRoute(router)
|
| 18 |
OptionsRoute(router)
|
| 19 |
+
AcademyRoute(router)
|
| 20 |
err := router.Run(config.TCP_ADDRESS)
|
| 21 |
if err != nil {
|
| 22 |
log.Fatalf("Failed to run server: %v", err)
|
space/space/space/space/space/space/space/space/space/services/academy_service.go
CHANGED
|
@@ -3,38 +3,102 @@ package services
|
|
| 3 |
import (
|
| 4 |
"api.qobiltu.id/models"
|
| 5 |
"api.qobiltu.id/repositories"
|
|
|
|
|
|
|
| 6 |
)
|
| 7 |
|
| 8 |
type AcademyService struct {
|
| 9 |
Service[models.Academy, models.AllAcademyResponse]
|
| 10 |
}
|
| 11 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
func (s *AcademyService) Retrieve() {
|
| 13 |
-
if s.Constructor.
|
| 14 |
-
AcademyRepo := repositories.
|
| 15 |
s.Error = AcademyRepo.RowsError
|
| 16 |
if AcademyRepo.NoRecord {
|
| 17 |
s.Exception.Message = "Academy not found"
|
| 18 |
s.Exception.DataNotFound = true
|
| 19 |
return
|
| 20 |
}
|
| 21 |
-
|
| 22 |
-
for _, academyMaterial := range repositories.GetAllAcademyMaterialsByAcademyID(s.Constructor.ID).Result {
|
| 23 |
-
ArrMaterials = append(ArrMaterials, models.AcademyMaterialResponse{
|
| 24 |
-
Materials: academyMaterial,
|
| 25 |
-
Contents: repositories.GetAllAcademyContentsByMaterialID(academyMaterial.ID).Result,
|
| 26 |
-
})
|
| 27 |
-
}
|
| 28 |
s.Result = models.AllAcademyResponse{
|
| 29 |
Academies: []models.AcademyResponse{
|
| 30 |
models.AcademyResponse{
|
| 31 |
Academy: AcademyRepo.Result,
|
| 32 |
-
Materials:
|
| 33 |
},
|
| 34 |
},
|
| 35 |
}
|
| 36 |
} else {
|
| 37 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
}
|
|
|
|
| 39 |
|
| 40 |
}
|
|
|
|
| 3 |
import (
|
| 4 |
"api.qobiltu.id/models"
|
| 5 |
"api.qobiltu.id/repositories"
|
| 6 |
+
"github.com/gosimple/slug"
|
| 7 |
+
uuid "github.com/satori/go.uuid"
|
| 8 |
)
|
| 9 |
|
| 10 |
type AcademyService struct {
|
| 11 |
Service[models.Academy, models.AllAcademyResponse]
|
| 12 |
}
|
| 13 |
|
| 14 |
+
type CreateAcademyService struct {
|
| 15 |
+
Service[models.AllAcademyResponse, models.AllAcademyResponse]
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
func castAcademyMaterials(academyId uint) []models.AcademyMaterialResponse {
|
| 19 |
+
var ArrMaterials []models.AcademyMaterialResponse
|
| 20 |
+
for _, academyMaterial := range repositories.GetAllAcademyMaterialsByAcademyID(academyId).Result {
|
| 21 |
+
ArrMaterials = append(ArrMaterials, models.AcademyMaterialResponse{
|
| 22 |
+
Materials: academyMaterial,
|
| 23 |
+
Contents: repositories.GetAllAcademyContentsByMaterialID(academyMaterial.ID).Result,
|
| 24 |
+
})
|
| 25 |
+
}
|
| 26 |
+
return ArrMaterials
|
| 27 |
+
}
|
| 28 |
func (s *AcademyService) Retrieve() {
|
| 29 |
+
if s.Constructor.Slug != "" {
|
| 30 |
+
AcademyRepo := repositories.GetAcademyDataBySlug(s.Constructor.Slug)
|
| 31 |
s.Error = AcademyRepo.RowsError
|
| 32 |
if AcademyRepo.NoRecord {
|
| 33 |
s.Exception.Message = "Academy not found"
|
| 34 |
s.Exception.DataNotFound = true
|
| 35 |
return
|
| 36 |
}
|
| 37 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
s.Result = models.AllAcademyResponse{
|
| 39 |
Academies: []models.AcademyResponse{
|
| 40 |
models.AcademyResponse{
|
| 41 |
Academy: AcademyRepo.Result,
|
| 42 |
+
Materials: castAcademyMaterials(s.Constructor.ID),
|
| 43 |
},
|
| 44 |
},
|
| 45 |
}
|
| 46 |
} else {
|
| 47 |
+
AcademyRepo := repositories.GetAllAcademy()
|
| 48 |
+
s.Error = AcademyRepo.RowsError
|
| 49 |
+
var ArrAcademy []models.AcademyResponse
|
| 50 |
+
for _, academy := range AcademyRepo.Result {
|
| 51 |
+
ArrAcademy = append(ArrAcademy, models.AcademyResponse{
|
| 52 |
+
Academy: academy,
|
| 53 |
+
Materials: castAcademyMaterials(academy.ID),
|
| 54 |
+
})
|
| 55 |
+
}
|
| 56 |
+
s.Result = models.AllAcademyResponse{
|
| 57 |
+
Academies: ArrAcademy,
|
| 58 |
+
}
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
func (s *CreateAcademyService) Create() {
|
| 64 |
+
var ArrAcademy []models.AcademyResponse
|
| 65 |
+
for _, academy := range s.Constructor.Academies {
|
| 66 |
+
academy.Academy.UUID = uuid.NewV4()
|
| 67 |
+
if academy.Academy.Slug == "" {
|
| 68 |
+
academy.Academy.Slug = slug.Make(academy.Academy.Title)
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
createdAcademy := repositories.CreateAcademy(academy.Academy)
|
| 72 |
+
if createdAcademy.RowsError != nil {
|
| 73 |
+
s.Error = createdAcademy.RowsError
|
| 74 |
+
return
|
| 75 |
+
}
|
| 76 |
+
for _, material := range academy.Materials {
|
| 77 |
+
material.Materials.AcademyID = createdAcademy.Result.ID
|
| 78 |
+
material.Materials.UUID = uuid.NewV4()
|
| 79 |
+
if material.Materials.Slug == "" {
|
| 80 |
+
material.Materials.Slug = slug.Make(material.Materials.Title)
|
| 81 |
+
}
|
| 82 |
+
createdMaterial := repositories.CreateAcademyMaterial(material.Materials)
|
| 83 |
+
if createdMaterial.RowsError != nil {
|
| 84 |
+
s.Error = createdMaterial.RowsError
|
| 85 |
+
return
|
| 86 |
+
}
|
| 87 |
+
for _, content := range material.Contents {
|
| 88 |
+
content.UUID = uuid.NewV4()
|
| 89 |
+
content.AcademyMaterialID = createdMaterial.Result.ID
|
| 90 |
+
createdContent := repositories.CreateAcademyContent(content)
|
| 91 |
+
if createdContent.RowsError != nil {
|
| 92 |
+
s.Error = createdContent.RowsError
|
| 93 |
+
return
|
| 94 |
+
}
|
| 95 |
+
ArrAcademy = append(ArrAcademy, models.AcademyResponse{
|
| 96 |
+
Academy: createdAcademy.Result,
|
| 97 |
+
Materials: castAcademyMaterials(createdAcademy.Result.ID),
|
| 98 |
+
})
|
| 99 |
+
}
|
| 100 |
+
}
|
| 101 |
}
|
| 102 |
+
s.Result = models.AllAcademyResponse{Academies: ArrAcademy}
|
| 103 |
|
| 104 |
}
|
space/space/space/space/space/space/space/space/space/services/email_verification_service.go
CHANGED
|
@@ -29,7 +29,10 @@ func (s *EmailVerificationService) Create() {
|
|
| 29 |
remainingTime := time.Duration(config.EMAIL_VERIFICATION_DURATION) * time.Hour
|
| 30 |
dueTime := CalculateDueTime(remainingTime)
|
| 31 |
|
| 32 |
-
token := uint(rand.IntN(
|
|
|
|
|
|
|
|
|
|
| 33 |
s.Constructor.UUID = uuid.NewV4()
|
| 34 |
|
| 35 |
repo := repositories.CreateEmailVerification(s.Constructor.UUID, s.Constructor.AccountID, dueTime, token)
|
|
@@ -83,7 +86,7 @@ func (s *EmailVerificationService) Validate() {
|
|
| 83 |
}
|
| 84 |
account := repositories.GetAccountById(repo.Result.AccountID)
|
| 85 |
account.Result.IsEmailVerified = true
|
| 86 |
-
|
| 87 |
repositories.UpdateAccount(account.Result)
|
| 88 |
s.Result = repo.Result
|
| 89 |
}
|
|
|
|
| 29 |
remainingTime := time.Duration(config.EMAIL_VERIFICATION_DURATION) * time.Hour
|
| 30 |
dueTime := CalculateDueTime(remainingTime)
|
| 31 |
|
| 32 |
+
token := uint(rand.IntN(1000000))
|
| 33 |
+
for token < 1000000 {
|
| 34 |
+
token = uint(rand.IntN(1000000))
|
| 35 |
+
}
|
| 36 |
s.Constructor.UUID = uuid.NewV4()
|
| 37 |
|
| 38 |
repo := repositories.CreateEmailVerification(s.Constructor.UUID, s.Constructor.AccountID, dueTime, token)
|
|
|
|
| 86 |
}
|
| 87 |
account := repositories.GetAccountById(repo.Result.AccountID)
|
| 88 |
account.Result.IsEmailVerified = true
|
| 89 |
+
|
| 90 |
repositories.UpdateAccount(account.Result)
|
| 91 |
s.Result = repo.Result
|
| 92 |
}
|
space/space/space/space/space/space/space/space/space/services/forgot_password_service.go
CHANGED
|
@@ -34,7 +34,10 @@ func (s *ForgotPasswordService) Create(email string) {
|
|
| 34 |
remainingTime := time.Duration(config.EMAIL_VERIFICATION_DURATION) * time.Hour
|
| 35 |
dueTime := CalculateDueTime(remainingTime)
|
| 36 |
|
| 37 |
-
token := uint(rand.IntN(
|
|
|
|
|
|
|
|
|
|
| 38 |
s.Constructor.UUID = uuid.NewV4()
|
| 39 |
s.Constructor.ExpiredAt = dueTime
|
| 40 |
s.Constructor.AccountID = accountRepo.Result.Id
|
|
|
|
| 34 |
remainingTime := time.Duration(config.EMAIL_VERIFICATION_DURATION) * time.Hour
|
| 35 |
dueTime := CalculateDueTime(remainingTime)
|
| 36 |
|
| 37 |
+
token := uint(rand.IntN(1000000))
|
| 38 |
+
for token < 1000000 {
|
| 39 |
+
token = uint(rand.IntN(1000000))
|
| 40 |
+
}
|
| 41 |
s.Constructor.UUID = uuid.NewV4()
|
| 42 |
s.Constructor.ExpiredAt = dueTime
|
| 43 |
s.Constructor.AccountID = accountRepo.Result.Id
|
space/space/space/space/space/space/space/space/space/space/logs/error_log.txt
CHANGED
|
@@ -1 +1,2 @@
|
|
| 1 |
-
|
|
|
|
|
|
| 1 |
+
2025/04/20 10:05:28 Error Log : duplicated key not allowed
|
| 2 |
+
2025/04/20 10:11:41 Error Log : duplicated key not allowed
|
space/space/space/space/space/space/space/space/space/space/models/response_model.go
CHANGED
|
@@ -31,12 +31,15 @@ type UserProfileResponse struct {
|
|
| 31 |
Details AccountDetails `json:"details"`
|
| 32 |
}
|
| 33 |
|
| 34 |
-
type
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
|
|
|
|
|
|
|
|
|
| 38 |
}
|
| 39 |
|
| 40 |
-
type
|
| 41 |
-
|
| 42 |
}
|
|
|
|
| 31 |
Details AccountDetails `json:"details"`
|
| 32 |
}
|
| 33 |
|
| 34 |
+
type AcademyMaterialResponse struct {
|
| 35 |
+
Materials AcademyMaterial
|
| 36 |
+
Contents []AcademyContent
|
| 37 |
+
}
|
| 38 |
+
type AcademyResponse struct {
|
| 39 |
+
Academy Academy `json:"academy"`
|
| 40 |
+
Materials []AcademyMaterialResponse `json:"academy_materials"`
|
| 41 |
}
|
| 42 |
|
| 43 |
+
type AllAcademyResponse struct {
|
| 44 |
+
Academies []AcademyResponse `json:"academy_dasar"`
|
| 45 |
}
|
space/space/space/space/space/space/space/space/space/space/repositories/academy_repository.go
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package repositories
|
| 2 |
+
|
| 3 |
+
import "api.qobiltu.id/models"
|
| 4 |
+
|
| 5 |
+
func GetAllAcademy() Repository[models.Academy, []models.Academy] {
|
| 6 |
+
repo := Construct[models.Academy, []models.Academy](
|
| 7 |
+
models.Academy{},
|
| 8 |
+
)
|
| 9 |
+
repo.Transactions(
|
| 10 |
+
WhereGivenConstructor[models.Academy, []models.Academy],
|
| 11 |
+
Find[models.Academy, []models.Academy],
|
| 12 |
+
)
|
| 13 |
+
return *repo
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
func GetAcademyDataById(academyId uint) Repository[models.Academy, models.Academy] {
|
| 17 |
+
repo := Construct[models.Academy, models.Academy](
|
| 18 |
+
models.Academy{ID: academyId},
|
| 19 |
+
)
|
| 20 |
+
repo.Transactions(
|
| 21 |
+
WhereGivenConstructor[models.Academy, models.Academy],
|
| 22 |
+
Find[models.Academy, models.Academy],
|
| 23 |
+
)
|
| 24 |
+
return *repo
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
func GetAllAcademyMaterialsByAcademyID(acaddemyId uint) Repository[models.AcademyMaterial, []models.AcademyMaterial] {
|
| 28 |
+
repo := Construct[models.AcademyMaterial, []models.AcademyMaterial](
|
| 29 |
+
models.AcademyMaterial{AcademyID: acaddemyId},
|
| 30 |
+
)
|
| 31 |
+
repo.Transactions(
|
| 32 |
+
WhereGivenConstructor[models.AcademyMaterial, []models.AcademyMaterial],
|
| 33 |
+
Find[models.AcademyMaterial, []models.AcademyMaterial],
|
| 34 |
+
)
|
| 35 |
+
return *repo
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
func GetAllAcademyContentsByMaterialID(materialId uint) Repository[models.AcademyContent, []models.AcademyContent] {
|
| 39 |
+
repo := Construct[models.AcademyContent, []models.AcademyContent](
|
| 40 |
+
models.AcademyContent{AcademyMaterialID: materialId},
|
| 41 |
+
)
|
| 42 |
+
repo.Transactions(
|
| 43 |
+
WhereGivenConstructor[models.AcademyContent, []models.AcademyContent],
|
| 44 |
+
Find[models.AcademyContent, []models.AcademyContent],
|
| 45 |
+
)
|
| 46 |
+
return *repo
|
| 47 |
+
}
|
space/space/space/space/space/space/space/space/space/space/services/academy_service.go
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package services
|
| 2 |
+
|
| 3 |
+
import (
|
| 4 |
+
"api.qobiltu.id/models"
|
| 5 |
+
"api.qobiltu.id/repositories"
|
| 6 |
+
)
|
| 7 |
+
|
| 8 |
+
type AcademyService struct {
|
| 9 |
+
Service[models.Academy, models.AllAcademyResponse]
|
| 10 |
+
}
|
| 11 |
+
|
| 12 |
+
func (s *AcademyService) Retrieve() {
|
| 13 |
+
if s.Constructor.ID != 0 {
|
| 14 |
+
AcademyRepo := repositories.GetAcademyDataById(s.Constructor.ID)
|
| 15 |
+
s.Error = AcademyRepo.RowsError
|
| 16 |
+
if AcademyRepo.NoRecord {
|
| 17 |
+
s.Exception.Message = "Academy not found"
|
| 18 |
+
s.Exception.DataNotFound = true
|
| 19 |
+
return
|
| 20 |
+
}
|
| 21 |
+
var ArrMaterials []models.AcademyMaterialResponse
|
| 22 |
+
for _, academyMaterial := range repositories.GetAllAcademyMaterialsByAcademyID(s.Constructor.ID).Result {
|
| 23 |
+
ArrMaterials = append(ArrMaterials, models.AcademyMaterialResponse{
|
| 24 |
+
Materials: academyMaterial,
|
| 25 |
+
Contents: repositories.GetAllAcademyContentsByMaterialID(academyMaterial.ID).Result,
|
| 26 |
+
})
|
| 27 |
+
}
|
| 28 |
+
s.Result = models.AllAcademyResponse{
|
| 29 |
+
Academies: []models.AcademyResponse{
|
| 30 |
+
models.AcademyResponse{
|
| 31 |
+
Academy: AcademyRepo.Result,
|
| 32 |
+
Materials: ArrMaterials,
|
| 33 |
+
},
|
| 34 |
+
},
|
| 35 |
+
}
|
| 36 |
+
} else {
|
| 37 |
+
// AcademyRepo := repositories.GetAllAcademy()
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
}
|