Spaces:
Runtime error
Runtime error
Commit ·
4542d62
1
Parent(s): a934882
Deploy files from GitHub repository
Browse files- .gitignore +1 -1
- controllers/academy_controller.go +20 -1
- controllers/event_controller.go +21 -2
- models/entity/contant.go +21 -7
- models/entity/entity.go +4 -0
- models/error/error.go +1 -1
- repositories/academy_repository.go +32 -23
- repositories/event_repository.go +33 -9
- router/academy_router.go +7 -25
- router/admin_router.go +48 -0
- router/authentication_router.go +3 -6
- router/event_router.go +4 -6
- router/router.go +5 -4
- services/academy_service.go +2 -16
- services/event_service.go +4 -9
- services/exam_event_service.go +8 -3
- space/.gitignore +1 -1
- utils/response_util.go +2 -3
- utils/utils.go +9 -0
.gitignore
CHANGED
|
@@ -1,2 +1,2 @@
|
|
| 1 |
-
|
| 2 |
go-boilerplate.exe
|
|
|
|
| 1 |
+
.env
|
| 2 |
go-boilerplate.exe
|
controllers/academy_controller.go
CHANGED
|
@@ -8,6 +8,7 @@ import (
|
|
| 8 |
entity "abdanhafidz.com/go-boilerplate/models/entity"
|
| 9 |
http_error "abdanhafidz.com/go-boilerplate/models/error"
|
| 10 |
"abdanhafidz.com/go-boilerplate/services"
|
|
|
|
| 11 |
"github.com/gin-gonic/gin"
|
| 12 |
"github.com/google/uuid"
|
| 13 |
)
|
|
@@ -86,6 +87,13 @@ func (c *academyController) ListAcademy(ctx *gin.Context) {
|
|
| 86 |
search := ctx.DefaultQuery("search", "")
|
| 87 |
sortBy := ctx.DefaultQuery("sortBy", "")
|
| 88 |
order := ctx.DefaultQuery("order", "")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 89 |
isModified := false
|
| 90 |
|
| 91 |
if limit < 1 {
|
|
@@ -101,8 +109,15 @@ func (c *academyController) ListAcademy(ctx *gin.Context) {
|
|
| 101 |
isModified = true
|
| 102 |
}
|
| 103 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 104 |
offset := (page - 1) * limit
|
| 105 |
-
p := entity.Pagination{Limit: limit, Offset: offset, Search: search, SortBy: sortBy, Order: order}
|
| 106 |
list, total, err := c.academyService.ListAcademy(ctx.Request.Context(), accountId, p)
|
| 107 |
|
| 108 |
if err != nil {
|
|
@@ -274,6 +289,10 @@ func (c *academyController) UpdateContentProgress(ctx *gin.Context) {
|
|
| 274 |
|
| 275 |
func (c *academyController) JoinAcademyByCode(ctx *gin.Context) {
|
| 276 |
req := RequestJSON[dto.JoinAcademyByCodeRequest](ctx)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 277 |
accountId := ParseAccountId(ctx)
|
| 278 |
res, err := c.academyService.JoinByCode(ctx.Request.Context(), accountId, req.Code)
|
| 279 |
ResponseJSON(ctx, req, res, err)
|
|
|
|
| 8 |
entity "abdanhafidz.com/go-boilerplate/models/entity"
|
| 9 |
http_error "abdanhafidz.com/go-boilerplate/models/error"
|
| 10 |
"abdanhafidz.com/go-boilerplate/services"
|
| 11 |
+
"abdanhafidz.com/go-boilerplate/utils"
|
| 12 |
"github.com/gin-gonic/gin"
|
| 13 |
"github.com/google/uuid"
|
| 14 |
)
|
|
|
|
| 87 |
search := ctx.DefaultQuery("search", "")
|
| 88 |
sortBy := ctx.DefaultQuery("sortBy", "")
|
| 89 |
order := ctx.DefaultQuery("order", "")
|
| 90 |
+
|
| 91 |
+
var registerStatus *int
|
| 92 |
+
if val := ctx.Query("registerStatus"); val != "" {
|
| 93 |
+
if i, err := strconv.Atoi(val); err == nil {
|
| 94 |
+
registerStatus = &i
|
| 95 |
+
}
|
| 96 |
+
}
|
| 97 |
isModified := false
|
| 98 |
|
| 99 |
if limit < 1 {
|
|
|
|
| 109 |
isModified = true
|
| 110 |
}
|
| 111 |
|
| 112 |
+
var status *string
|
| 113 |
+
if val := ctx.Query("status"); val != "" {
|
| 114 |
+
if val == entity.StatusNotStarted || val == entity.StatusInProgress || val == entity.StatusFinished {
|
| 115 |
+
status = &val
|
| 116 |
+
}
|
| 117 |
+
}
|
| 118 |
+
|
| 119 |
offset := (page - 1) * limit
|
| 120 |
+
p := entity.Pagination{Limit: limit, Offset: offset, Search: search, SortBy: sortBy, Order: order, RegisterStatus: registerStatus, Status: status}
|
| 121 |
list, total, err := c.academyService.ListAcademy(ctx.Request.Context(), accountId, p)
|
| 122 |
|
| 123 |
if err != nil {
|
|
|
|
| 289 |
|
| 290 |
func (c *academyController) JoinAcademyByCode(ctx *gin.Context) {
|
| 291 |
req := RequestJSON[dto.JoinAcademyByCodeRequest](ctx)
|
| 292 |
+
if err := utils.ValidateCode(req.Code); err != nil {
|
| 293 |
+
ResponseJSON[any, any](ctx, req, nil, http_error.INVALID_CODE)
|
| 294 |
+
return
|
| 295 |
+
}
|
| 296 |
accountId := ParseAccountId(ctx)
|
| 297 |
res, err := c.academyService.JoinByCode(ctx.Request.Context(), accountId, req.Code)
|
| 298 |
ResponseJSON(ctx, req, res, err)
|
controllers/event_controller.go
CHANGED
|
@@ -7,6 +7,7 @@ import (
|
|
| 7 |
entity "abdanhafidz.com/go-boilerplate/models/entity"
|
| 8 |
http_error "abdanhafidz.com/go-boilerplate/models/error"
|
| 9 |
"abdanhafidz.com/go-boilerplate/services"
|
|
|
|
| 10 |
"github.com/gin-gonic/gin"
|
| 11 |
"github.com/google/uuid"
|
| 12 |
)
|
|
@@ -34,6 +35,20 @@ func (c *eventController) List(ctx *gin.Context) {
|
|
| 34 |
search := ctx.DefaultQuery("search", "")
|
| 35 |
sortBy := ctx.DefaultQuery("sortBy", "")
|
| 36 |
order := ctx.DefaultQuery("order", "")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
if limit < 1 {
|
| 38 |
limit = 10
|
| 39 |
} else if limit > 50 {
|
|
@@ -43,7 +58,7 @@ func (c *eventController) List(ctx *gin.Context) {
|
|
| 43 |
page = 1
|
| 44 |
}
|
| 45 |
offset := (page - 1) * limit
|
| 46 |
-
p := entity.Pagination{Limit: limit, Offset: offset, Search: search, SortBy: sortBy, Order: order}
|
| 47 |
accountId := ParseAccountId(ctx)
|
| 48 |
list, total, err := c.eventService.List(ctx.Request.Context(), accountId, p)
|
| 49 |
var totalPages int
|
|
@@ -75,9 +90,13 @@ func (c *eventController) DetailBySlug(ctx *gin.Context) {
|
|
| 75 |
|
| 76 |
func (c *eventController) Join(ctx *gin.Context) {
|
| 77 |
req := RequestJSON[dto.JoinEventRequest](ctx)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 78 |
accountId := ParseAccountId(ctx)
|
| 79 |
res, err := c.eventService.JoinByCode(ctx.Request.Context(), accountId, req.EventCode)
|
| 80 |
-
ResponseJSON(ctx, req, res, err)
|
| 81 |
}
|
| 82 |
|
| 83 |
func (c *eventController) CreateEvent(ctx *gin.Context) {
|
|
|
|
| 7 |
entity "abdanhafidz.com/go-boilerplate/models/entity"
|
| 8 |
http_error "abdanhafidz.com/go-boilerplate/models/error"
|
| 9 |
"abdanhafidz.com/go-boilerplate/services"
|
| 10 |
+
"abdanhafidz.com/go-boilerplate/utils"
|
| 11 |
"github.com/gin-gonic/gin"
|
| 12 |
"github.com/google/uuid"
|
| 13 |
)
|
|
|
|
| 35 |
search := ctx.DefaultQuery("search", "")
|
| 36 |
sortBy := ctx.DefaultQuery("sortBy", "")
|
| 37 |
order := ctx.DefaultQuery("order", "")
|
| 38 |
+
|
| 39 |
+
var registerStatus *int
|
| 40 |
+
if val := ctx.Query("registerStatus"); val != "" {
|
| 41 |
+
if i, err := strconv.Atoi(val); err == nil {
|
| 42 |
+
registerStatus = &i
|
| 43 |
+
}
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
var status *string
|
| 47 |
+
if val := ctx.Query("status"); val != "" {
|
| 48 |
+
if val == entity.EventStatusUpcoming || val == entity.EventStatusOngoing || val == entity.EventStatusEnded {
|
| 49 |
+
status = &val
|
| 50 |
+
}
|
| 51 |
+
}
|
| 52 |
if limit < 1 {
|
| 53 |
limit = 10
|
| 54 |
} else if limit > 50 {
|
|
|
|
| 58 |
page = 1
|
| 59 |
}
|
| 60 |
offset := (page - 1) * limit
|
| 61 |
+
p := entity.Pagination{Limit: limit, Offset: offset, Search: search, SortBy: sortBy, Order: order, RegisterStatus: registerStatus, Status: status}
|
| 62 |
accountId := ParseAccountId(ctx)
|
| 63 |
list, total, err := c.eventService.List(ctx.Request.Context(), accountId, p)
|
| 64 |
var totalPages int
|
|
|
|
| 90 |
|
| 91 |
func (c *eventController) Join(ctx *gin.Context) {
|
| 92 |
req := RequestJSON[dto.JoinEventRequest](ctx)
|
| 93 |
+
if err := utils.ValidateCode(req.EventCode); err != nil {
|
| 94 |
+
ResponseJSON[any, any](ctx, req, nil, err)
|
| 95 |
+
return
|
| 96 |
+
}
|
| 97 |
accountId := ParseAccountId(ctx)
|
| 98 |
res, err := c.eventService.JoinByCode(ctx.Request.Context(), accountId, req.EventCode)
|
| 99 |
+
ResponseJSON[any, any](ctx, req, res, err)
|
| 100 |
}
|
| 101 |
|
| 102 |
func (c *eventController) CreateEvent(ctx *gin.Context) {
|
models/entity/contant.go
CHANGED
|
@@ -1,17 +1,31 @@
|
|
| 1 |
package models
|
| 2 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
const (
|
| 4 |
StatusNotStarted = "NOT_STARTED"
|
| 5 |
StatusInProgress = "IN_PROGRESS"
|
| 6 |
-
StatusFinished
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
)
|
| 8 |
|
| 9 |
const MB = 1024 * 1024
|
| 10 |
|
|
|
|
|
|
|
| 11 |
type Pagination struct {
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
|
|
|
|
|
|
|
|
| 1 |
package models
|
| 2 |
|
| 3 |
+
import (
|
| 4 |
+
"regexp"
|
| 5 |
+
)
|
| 6 |
+
|
| 7 |
const (
|
| 8 |
StatusNotStarted = "NOT_STARTED"
|
| 9 |
StatusInProgress = "IN_PROGRESS"
|
| 10 |
+
StatusFinished = "FINISHED"
|
| 11 |
+
)
|
| 12 |
+
|
| 13 |
+
const (
|
| 14 |
+
EventStatusUpcoming = "UPCOMING"
|
| 15 |
+
EventStatusOngoing = "ONGOING"
|
| 16 |
+
EventStatusEnded = "ENDED"
|
| 17 |
)
|
| 18 |
|
| 19 |
const MB = 1024 * 1024
|
| 20 |
|
| 21 |
+
var CodeRegex = regexp.MustCompile(`^[a-zA-Z0-9]{6,12}$`)
|
| 22 |
+
|
| 23 |
type Pagination struct {
|
| 24 |
+
Limit int
|
| 25 |
+
Offset int
|
| 26 |
+
Search string
|
| 27 |
+
SortBy string
|
| 28 |
+
Order string
|
| 29 |
+
RegisterStatus *int
|
| 30 |
+
Status *string
|
| 31 |
+
}
|
models/entity/entity.go
CHANGED
|
@@ -86,6 +86,7 @@ type Events struct {
|
|
| 86 |
ImgBanner string `json:"img_banner,omitempty"`
|
| 87 |
EventCode string `json:"event_code,omitempty"`
|
| 88 |
IsPublic bool `json:"is_public,omitempty"`
|
|
|
|
| 89 |
DeletedAt gorm.DeletedAt `json:"deleted_at,omitempty" gorm:"index"`
|
| 90 |
RegisterStatus int `gorm:"-" json:"register_status"`
|
| 91 |
}
|
|
@@ -272,6 +273,7 @@ type Academy struct {
|
|
| 272 |
Materials []AcademyMaterial `gorm:"foreignKey:AcademyId;references:Id" json:"materials,omitempty"`
|
| 273 |
AcademyProgress AcademyProgress `gorm:"foreignKey:AcademyId;references:Id" json:"academy_progress,omitempty"`
|
| 274 |
RegisterStatus int `gorm:"-" json:"register_status"`
|
|
|
|
| 275 |
DeletedAt gorm.DeletedAt `json:"deleted_at,omitempty" gorm:"index"`
|
| 276 |
}
|
| 277 |
|
|
@@ -287,6 +289,7 @@ type AcademyMaterial struct {
|
|
| 287 |
ContentsCount int64 `json:"contents_count,omitempty"`
|
| 288 |
Contents []AcademyContent `gorm:"foreignKey:MaterialId;references:Id" json:"contents,omitempty"`
|
| 289 |
AcademyMaterialProgress AcademyMaterialProgress `gorm:"foreignKey:MaterialId;references:Id" json:"academy_material_progress,omitempty"`
|
|
|
|
| 290 |
DeletedAt gorm.DeletedAt `json:"deleted_at,omitempty" gorm:"index"`
|
| 291 |
}
|
| 292 |
|
|
@@ -299,6 +302,7 @@ type AcademyContent struct {
|
|
| 299 |
Order uint `json:"order,omitempty"`
|
| 300 |
Contents string `json:"contents,omitempty"`
|
| 301 |
AcademyContentProgress AcademyContentProgress `gorm:"foreignKey:ContentId;references:Id" json:"academy_content_progress,omitempty"`
|
|
|
|
| 302 |
DeletedAt gorm.DeletedAt `json:"deleted_at,omitempty" gorm:"index"`
|
| 303 |
}
|
| 304 |
|
|
|
|
| 86 |
ImgBanner string `json:"img_banner,omitempty"`
|
| 87 |
EventCode string `json:"event_code,omitempty"`
|
| 88 |
IsPublic bool `json:"is_public,omitempty"`
|
| 89 |
+
CreatedAt time.Time `json:"created_at,omitempty"`
|
| 90 |
DeletedAt gorm.DeletedAt `json:"deleted_at,omitempty" gorm:"index"`
|
| 91 |
RegisterStatus int `gorm:"-" json:"register_status"`
|
| 92 |
}
|
|
|
|
| 273 |
Materials []AcademyMaterial `gorm:"foreignKey:AcademyId;references:Id" json:"materials,omitempty"`
|
| 274 |
AcademyProgress AcademyProgress `gorm:"foreignKey:AcademyId;references:Id" json:"academy_progress,omitempty"`
|
| 275 |
RegisterStatus int `gorm:"-" json:"register_status"`
|
| 276 |
+
CreatedAt time.Time `json:"created_at,omitempty"`
|
| 277 |
DeletedAt gorm.DeletedAt `json:"deleted_at,omitempty" gorm:"index"`
|
| 278 |
}
|
| 279 |
|
|
|
|
| 289 |
ContentsCount int64 `json:"contents_count,omitempty"`
|
| 290 |
Contents []AcademyContent `gorm:"foreignKey:MaterialId;references:Id" json:"contents,omitempty"`
|
| 291 |
AcademyMaterialProgress AcademyMaterialProgress `gorm:"foreignKey:MaterialId;references:Id" json:"academy_material_progress,omitempty"`
|
| 292 |
+
CreatedAt time.Time `json:"created_at,omitempty"`
|
| 293 |
DeletedAt gorm.DeletedAt `json:"deleted_at,omitempty" gorm:"index"`
|
| 294 |
}
|
| 295 |
|
|
|
|
| 302 |
Order uint `json:"order,omitempty"`
|
| 303 |
Contents string `json:"contents,omitempty"`
|
| 304 |
AcademyContentProgress AcademyContentProgress `gorm:"foreignKey:ContentId;references:Id" json:"academy_content_progress,omitempty"`
|
| 305 |
+
CreatedAt time.Time `json:"created_at,omitempty"`
|
| 306 |
DeletedAt gorm.DeletedAt `json:"deleted_at,omitempty" gorm:"index"`
|
| 307 |
}
|
| 308 |
|
models/error/error.go
CHANGED
|
@@ -36,7 +36,7 @@ var (
|
|
| 36 |
IMAGE_REQUIRED = errors.New("Image is required")
|
| 37 |
DESCRIPTION_REQUIRED = errors.New("Description is required")
|
| 38 |
CODE_REQUIRED = errors.New("Code is required")
|
| 39 |
-
INVALID_CODE = errors.New("Code
|
| 40 |
EVENT_START_DATE_INVALID = errors.New("Event start date must be in the future")
|
| 41 |
EVENT_END_DATE_INVALID = errors.New("Event end date must be after start date")
|
| 42 |
INVALID_DATE_FORMAT = errors.New("Invalid date format, please use RFC3339")
|
|
|
|
| 36 |
IMAGE_REQUIRED = errors.New("Image is required")
|
| 37 |
DESCRIPTION_REQUIRED = errors.New("Description is required")
|
| 38 |
CODE_REQUIRED = errors.New("Code is required")
|
| 39 |
+
INVALID_CODE = errors.New("Code must be on range 6-12")
|
| 40 |
EVENT_START_DATE_INVALID = errors.New("Event start date must be in the future")
|
| 41 |
EVENT_END_DATE_INVALID = errors.New("Event end date must be after start date")
|
| 42 |
INVALID_DATE_FORMAT = errors.New("Invalid date format, please use RFC3339")
|
repositories/academy_repository.go
CHANGED
|
@@ -159,36 +159,44 @@ func (r *academyRepository) ListVisibleAcademy(ctx context.Context, accountId uu
|
|
| 159 |
|
| 160 |
sub := r.db.WithContext(ctx).Model(&entity.AcademyAssign{}).Select("academy_id").Where("account_id = ?", accountId)
|
| 161 |
|
|
|
|
| 162 |
q := r.db.WithContext(ctx).Model(&entity.Academy{}).
|
| 163 |
-
Where(r.db.Where("is_public = ?", true).Or("id IN (?)", sub))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 164 |
|
| 165 |
if p != nil {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 166 |
sortColumn := "title"
|
| 167 |
switch p.SortBy {
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
case "description":
|
| 173 |
-
sortColumn = "description"
|
| 174 |
-
case "createdAt", "created_at":
|
| 175 |
-
sortColumn = "created_at"
|
| 176 |
-
case "updatedAt", "updated_at":
|
| 177 |
-
sortColumn = "updated_at"
|
| 178 |
-
case "materials_count", "materialsCount":
|
| 179 |
-
sortColumn = "materials_count"
|
| 180 |
}
|
| 181 |
|
| 182 |
if s := strings.TrimSpace(p.Search); s != "" {
|
| 183 |
s = strings.Trim(s, "\"'")
|
| 184 |
s = strings.ToLower(s)
|
| 185 |
like := "%" + s + "%"
|
| 186 |
-
|
| 187 |
-
if p.SortBy != "" {
|
| 188 |
-
q = q.Where(fmt.Sprintf("LOWER(%s) LIKE ?", sortColumn), like)
|
| 189 |
-
} else {
|
| 190 |
-
q = q.Where("LOWER(title) LIKE ? OR LOWER(slug) LIKE ?", like, like)
|
| 191 |
-
}
|
| 192 |
}
|
| 193 |
|
| 194 |
sortDirection := "DESC"
|
|
@@ -196,8 +204,13 @@ func (r *academyRepository) ListVisibleAcademy(ctx context.Context, accountId uu
|
|
| 196 |
sortDirection = "ASC"
|
| 197 |
}
|
| 198 |
|
|
|
|
| 199 |
q = q.Order(fmt.Sprintf("%s %s", sortColumn, sortDirection))
|
| 200 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 201 |
if p.Limit > 0 {
|
| 202 |
q = q.Limit(p.Limit)
|
| 203 |
}
|
|
@@ -206,10 +219,6 @@ func (r *academyRepository) ListVisibleAcademy(ctx context.Context, accountId uu
|
|
| 206 |
}
|
| 207 |
}
|
| 208 |
|
| 209 |
-
if err := q.Count(&total).Error; err != nil {
|
| 210 |
-
return nil, 0, err
|
| 211 |
-
}
|
| 212 |
-
|
| 213 |
if err := q.Find(&list).Error; err != nil {
|
| 214 |
return nil, 0, err
|
| 215 |
}
|
|
|
|
| 159 |
|
| 160 |
sub := r.db.WithContext(ctx).Model(&entity.AcademyAssign{}).Select("academy_id").Where("account_id = ?", accountId)
|
| 161 |
|
| 162 |
+
// Base query
|
| 163 |
q := r.db.WithContext(ctx).Model(&entity.Academy{}).
|
| 164 |
+
Where(r.db.Where("academy.is_public = ?", true).Or("academy.id IN (?)", sub))
|
| 165 |
+
|
| 166 |
+
// Join for status filtering
|
| 167 |
+
if p != nil && p.Status != nil {
|
| 168 |
+
q = q.Joins("LEFT JOIN academy_progress ap ON ap.academy_id = academy.id AND ap.account_id = ?", accountId)
|
| 169 |
+
status := *p.Status
|
| 170 |
+
if status == entity.StatusNotStarted {
|
| 171 |
+
q = q.Where("ap.status IS NULL OR ap.status = ?", entity.StatusNotStarted)
|
| 172 |
+
} else {
|
| 173 |
+
q = q.Where("ap.status = ?", status)
|
| 174 |
+
}
|
| 175 |
+
}
|
| 176 |
|
| 177 |
if p != nil {
|
| 178 |
+
if p.RegisterStatus != nil {
|
| 179 |
+
switch *p.RegisterStatus {
|
| 180 |
+
case 1:
|
| 181 |
+
q = q.Where("academy.id IN (?)", sub)
|
| 182 |
+
case 0:
|
| 183 |
+
q = q.Where("academy.id NOT IN (?)", sub)
|
| 184 |
+
}
|
| 185 |
+
}
|
| 186 |
+
|
| 187 |
sortColumn := "title"
|
| 188 |
switch p.SortBy {
|
| 189 |
+
case "title":
|
| 190 |
+
sortColumn = "title"
|
| 191 |
+
case "createdAt", "created_at":
|
| 192 |
+
sortColumn = "created_at"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 193 |
}
|
| 194 |
|
| 195 |
if s := strings.TrimSpace(p.Search); s != "" {
|
| 196 |
s = strings.Trim(s, "\"'")
|
| 197 |
s = strings.ToLower(s)
|
| 198 |
like := "%" + s + "%"
|
| 199 |
+
q = q.Where("LOWER(academy.title) LIKE ? OR LOWER(academy.slug) LIKE ?", like, like)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 200 |
}
|
| 201 |
|
| 202 |
sortDirection := "DESC"
|
|
|
|
| 204 |
sortDirection = "ASC"
|
| 205 |
}
|
| 206 |
|
| 207 |
+
// Disambiguate sort column
|
| 208 |
q = q.Order(fmt.Sprintf("%s %s", sortColumn, sortDirection))
|
| 209 |
|
| 210 |
+
if err := q.Count(&total).Error; err != nil {
|
| 211 |
+
return nil, 0, err
|
| 212 |
+
}
|
| 213 |
+
|
| 214 |
if p.Limit > 0 {
|
| 215 |
q = q.Limit(p.Limit)
|
| 216 |
}
|
|
|
|
| 219 |
}
|
| 220 |
}
|
| 221 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 222 |
if err := q.Find(&list).Error; err != nil {
|
| 223 |
return nil, 0, err
|
| 224 |
}
|
repositories/event_repository.go
CHANGED
|
@@ -3,6 +3,7 @@ package repositories
|
|
| 3 |
import (
|
| 4 |
"context"
|
| 5 |
"strings"
|
|
|
|
| 6 |
|
| 7 |
entity "abdanhafidz.com/go-boilerplate/models/entity"
|
| 8 |
"github.com/google/uuid"
|
|
@@ -87,10 +88,8 @@ func (r *eventsRepository) GetAllPaginate(ctx context.Context, p entity.Paginati
|
|
| 87 |
ord = "asc"
|
| 88 |
}
|
| 89 |
switch col {
|
| 90 |
-
case "title", "
|
| 91 |
q = q.Order(col + " " + ord)
|
| 92 |
-
case "id_event", "id":
|
| 93 |
-
q = q.Order("id " + ord)
|
| 94 |
default:
|
| 95 |
q = q.Order("title " + ord)
|
| 96 |
}
|
|
@@ -129,8 +128,10 @@ func (r *eventsRepository) ListPublic(ctx context.Context, p *entity.Pagination)
|
|
| 129 |
ord = "asc"
|
| 130 |
}
|
| 131 |
switch col {
|
| 132 |
-
case "title", "slug", "start_event", "end_event", "event_code", "overview", "is_public":
|
| 133 |
q = q.Order(col + " " + ord)
|
|
|
|
|
|
|
| 134 |
case "id_event", "id":
|
| 135 |
q = q.Order("id " + ord)
|
| 136 |
default:
|
|
@@ -163,6 +164,27 @@ func (r *eventsRepository) ListVisible(ctx context.Context, accountId uuid.UUID,
|
|
| 163 |
}
|
| 164 |
|
| 165 |
if p != nil {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 166 |
if s := strings.TrimSpace(p.Search); s != "" {
|
| 167 |
s = strings.Trim(s, "\"'")
|
| 168 |
s = strings.ToLower(s)
|
|
@@ -182,23 +204,25 @@ func (r *eventsRepository) ListVisible(ctx context.Context, accountId uuid.UUID,
|
|
| 182 |
ord = "asc"
|
| 183 |
}
|
| 184 |
switch col {
|
| 185 |
-
case "title", "slug", "start_event", "end_event", "overview":
|
| 186 |
q = q.Order(col + " " + ord)
|
|
|
|
|
|
|
| 187 |
case "id_event", "id":
|
| 188 |
q = q.Order("id " + ord)
|
| 189 |
default:
|
| 190 |
q = q.Order("title " + ord)
|
| 191 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 192 |
if p.Limit > 0 {
|
| 193 |
q = q.Limit(p.Limit)
|
| 194 |
}
|
| 195 |
if p.Offset > 0 {
|
| 196 |
q = q.Offset(p.Offset)
|
| 197 |
}
|
| 198 |
-
} else {
|
| 199 |
-
if err := q.Count(&total).Error; err != nil {
|
| 200 |
-
return nil, 0, err
|
| 201 |
-
}
|
| 202 |
}
|
| 203 |
|
| 204 |
if err := q.Find(&list).Error; err != nil {
|
|
|
|
| 3 |
import (
|
| 4 |
"context"
|
| 5 |
"strings"
|
| 6 |
+
"time"
|
| 7 |
|
| 8 |
entity "abdanhafidz.com/go-boilerplate/models/entity"
|
| 9 |
"github.com/google/uuid"
|
|
|
|
| 88 |
ord = "asc"
|
| 89 |
}
|
| 90 |
switch col {
|
| 91 |
+
case "title", "start_event", "end_event", "overview", "is_public", "created_at":
|
| 92 |
q = q.Order(col + " " + ord)
|
|
|
|
|
|
|
| 93 |
default:
|
| 94 |
q = q.Order("title " + ord)
|
| 95 |
}
|
|
|
|
| 128 |
ord = "asc"
|
| 129 |
}
|
| 130 |
switch col {
|
| 131 |
+
case "title", "slug", "start_event", "end_event", "event_code", "overview", "is_public", "created_at":
|
| 132 |
q = q.Order(col + " " + ord)
|
| 133 |
+
case "createdat":
|
| 134 |
+
q = q.Order("created_at " + ord)
|
| 135 |
case "id_event", "id":
|
| 136 |
q = q.Order("id " + ord)
|
| 137 |
default:
|
|
|
|
| 164 |
}
|
| 165 |
|
| 166 |
if p != nil {
|
| 167 |
+
if p.RegisterStatus != nil {
|
| 168 |
+
switch *p.RegisterStatus {
|
| 169 |
+
case 1:
|
| 170 |
+
q = q.Where("academy.id IN (?)", sub)
|
| 171 |
+
case 0:
|
| 172 |
+
q = q.Where("academy.id NOT IN (?)", sub)
|
| 173 |
+
}
|
| 174 |
+
}
|
| 175 |
+
|
| 176 |
+
if p.Status != nil {
|
| 177 |
+
now := time.Now()
|
| 178 |
+
switch *p.Status {
|
| 179 |
+
case entity.EventStatusUpcoming:
|
| 180 |
+
q = q.Where("start_event > ?", now)
|
| 181 |
+
case entity.EventStatusOngoing:
|
| 182 |
+
q = q.Where("start_event <= ? AND end_event >= ?", now, now)
|
| 183 |
+
case entity.EventStatusEnded:
|
| 184 |
+
q = q.Where("end_event < ?", now)
|
| 185 |
+
}
|
| 186 |
+
}
|
| 187 |
+
|
| 188 |
if s := strings.TrimSpace(p.Search); s != "" {
|
| 189 |
s = strings.Trim(s, "\"'")
|
| 190 |
s = strings.ToLower(s)
|
|
|
|
| 204 |
ord = "asc"
|
| 205 |
}
|
| 206 |
switch col {
|
| 207 |
+
case "title", "slug", "start_event", "end_event", "overview", "created_at":
|
| 208 |
q = q.Order(col + " " + ord)
|
| 209 |
+
case "createdat":
|
| 210 |
+
q = q.Order("created_at " + ord)
|
| 211 |
case "id_event", "id":
|
| 212 |
q = q.Order("id " + ord)
|
| 213 |
default:
|
| 214 |
q = q.Order("title " + ord)
|
| 215 |
}
|
| 216 |
+
if err := q.Count(&total).Error; err != nil {
|
| 217 |
+
return nil, 0, err
|
| 218 |
+
}
|
| 219 |
+
|
| 220 |
if p.Limit > 0 {
|
| 221 |
q = q.Limit(p.Limit)
|
| 222 |
}
|
| 223 |
if p.Offset > 0 {
|
| 224 |
q = q.Offset(p.Offset)
|
| 225 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 226 |
}
|
| 227 |
|
| 228 |
if err := q.Find(&list).Error; err != nil {
|
router/academy_router.go
CHANGED
|
@@ -9,34 +9,16 @@ import (
|
|
| 9 |
func AcademyRouter(router *gin.Engine, middleware provider.MiddlewareProvider, controller provider.ControllerProvider) {
|
| 10 |
academyController := controller.ProvideAcademyController()
|
| 11 |
authenticationMiddleware := middleware.ProvideAuthenticationMiddleware()
|
| 12 |
-
|
| 13 |
routerGroup := router.Group("/api/v1/academy")
|
| 14 |
|
| 15 |
routerGroup.Use(gzip.Gzip(gzip.DefaultCompression))
|
| 16 |
{
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
adminGroup.POST("/materials", academyController.CreateMaterial)
|
| 25 |
-
adminGroup.DELETE("/materials/:id", academyController.DeleteMaterial)
|
| 26 |
-
|
| 27 |
-
adminGroup.POST("/contents", academyController.CreateContent)
|
| 28 |
-
adminGroup.DELETE("/contents/:id", academyController.DeleteContent)
|
| 29 |
-
|
| 30 |
-
adminGroup.POST("/assign", academyController.AssignAccountToAcademy)
|
| 31 |
-
adminGroup.DELETE("/assign/:id", academyController.UnassignAccountFromAcademy)
|
| 32 |
-
adminGroup.GET("/assign/:academy_id", academyController.ListAssignmentsByAcademy)
|
| 33 |
}
|
| 34 |
-
|
| 35 |
-
routerGroup.GET("/", authenticationMiddleware.VerifyAccount, academyController.ListAcademy)
|
| 36 |
-
routerGroup.POST("/join", authenticationMiddleware.VerifyAccount, academyController.JoinAcademyByCode)
|
| 37 |
-
routerGroup.GET("/:academy_slug", authenticationMiddleware.VerifyAccount, academyController.GetAcademy)
|
| 38 |
-
routerGroup.GET("/:academy_slug/:material_slug", authenticationMiddleware.VerifyAccount, academyController.GetMaterial)
|
| 39 |
-
routerGroup.GET("/:academy_slug/:material_slug/:order", authenticationMiddleware.VerifyAccount, academyController.GetContent)
|
| 40 |
-
|
| 41 |
-
routerGroup.POST("/:academy_slug/:material_slug/:order", authenticationMiddleware.VerifyAccount, academyController.UpdateContentProgress)
|
| 42 |
}
|
|
|
|
| 9 |
func AcademyRouter(router *gin.Engine, middleware provider.MiddlewareProvider, controller provider.ControllerProvider) {
|
| 10 |
academyController := controller.ProvideAcademyController()
|
| 11 |
authenticationMiddleware := middleware.ProvideAuthenticationMiddleware()
|
| 12 |
+
|
| 13 |
routerGroup := router.Group("/api/v1/academy")
|
| 14 |
|
| 15 |
routerGroup.Use(gzip.Gzip(gzip.DefaultCompression))
|
| 16 |
{
|
| 17 |
+
routerGroup.GET("/", authenticationMiddleware.VerifyAccount, academyController.ListAcademy)
|
| 18 |
+
routerGroup.POST("/join", authenticationMiddleware.VerifyAccount, academyController.JoinAcademyByCode)
|
| 19 |
+
routerGroup.GET("/:academy_slug", authenticationMiddleware.VerifyAccount, academyController.GetAcademy)
|
| 20 |
+
routerGroup.GET("/:academy_slug/:material_slug", authenticationMiddleware.VerifyAccount, academyController.GetMaterial)
|
| 21 |
+
routerGroup.GET("/:academy_slug/:material_slug/:order", authenticationMiddleware.VerifyAccount, academyController.GetContent)
|
| 22 |
+
routerGroup.POST("/:academy_slug/:material_slug/:order", authenticationMiddleware.VerifyAccount, academyController.UpdateContentProgress)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
}
|
router/admin_router.go
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package router
|
| 2 |
+
|
| 3 |
+
import (
|
| 4 |
+
"abdanhafidz.com/go-boilerplate/provider"
|
| 5 |
+
"github.com/gin-gonic/gin"
|
| 6 |
+
)
|
| 7 |
+
|
| 8 |
+
func AdminRouter(router *gin.Engine, middleware provider.MiddlewareProvider, controller provider.ControllerProvider) {
|
| 9 |
+
authenticationMiddleware := middleware.ProvideAuthenticationMiddleware()
|
| 10 |
+
authorizationMiddleware := middleware.ProvideAuthorizationMiddleware()
|
| 11 |
+
|
| 12 |
+
eventController := controller.ProvideEventController()
|
| 13 |
+
academyController := controller.ProvideAcademyController()
|
| 14 |
+
authenticationController := controller.ProvideAuthenticationController()
|
| 15 |
+
|
| 16 |
+
// Event Admin Routes
|
| 17 |
+
eventAdminGroup := router.Group("api/v1/admin/events", authenticationMiddleware.VerifyAccount, authorizationMiddleware.VerifyAdmin)
|
| 18 |
+
{
|
| 19 |
+
eventAdminGroup.POST("/", eventController.CreateEvent)
|
| 20 |
+
eventAdminGroup.PUT("/:id", eventController.UpdateEvent)
|
| 21 |
+
eventAdminGroup.DELETE("/:id", eventController.DeleteEvent)
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
// Academy Admin Routes
|
| 25 |
+
academyAdminGroup := router.Group("/api/v1/admin/academy", authenticationMiddleware.VerifyAccount, authorizationMiddleware.VerifyAdmin)
|
| 26 |
+
{
|
| 27 |
+
academyAdminGroup.POST("/", academyController.CreateAcademy)
|
| 28 |
+
academyAdminGroup.GET("/id/:id/detail", academyController.GetAcademyDetail)
|
| 29 |
+
academyAdminGroup.PUT("/id/:id", academyController.UpdateAcademy)
|
| 30 |
+
academyAdminGroup.DELETE("/id/:id", academyController.DeleteAcademy)
|
| 31 |
+
|
| 32 |
+
academyAdminGroup.POST("/materials", academyController.CreateMaterial)
|
| 33 |
+
academyAdminGroup.DELETE("/materials/:id", academyController.DeleteMaterial)
|
| 34 |
+
|
| 35 |
+
academyAdminGroup.POST("/contents", academyController.CreateContent)
|
| 36 |
+
academyAdminGroup.DELETE("/contents/:id", academyController.DeleteContent)
|
| 37 |
+
|
| 38 |
+
academyAdminGroup.POST("/assign", academyController.AssignAccountToAcademy)
|
| 39 |
+
academyAdminGroup.DELETE("/assign/:id", academyController.UnassignAccountFromAcademy)
|
| 40 |
+
academyAdminGroup.GET("/assign/:academy_id", academyController.ListAssignmentsByAcademy)
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
// Authentication Admin Routes
|
| 44 |
+
authAdminGroup := router.Group("/api/v1/admin/authentication", authenticationMiddleware.VerifyAccount, authorizationMiddleware.VerifySuperAdmin)
|
| 45 |
+
{
|
| 46 |
+
authAdminGroup.PUT("/:account_id/assign", authenticationController.UpdateUserRole)
|
| 47 |
+
}
|
| 48 |
+
}
|
router/authentication_router.go
CHANGED
|
@@ -2,6 +2,7 @@ package router
|
|
| 2 |
|
| 3 |
import (
|
| 4 |
"abdanhafidz.com/go-boilerplate/provider"
|
|
|
|
| 5 |
"github.com/gin-gonic/gin"
|
| 6 |
)
|
| 7 |
|
|
@@ -9,16 +10,12 @@ func AuthenticationRouter(router *gin.Engine, middleware provider.MiddlewareProv
|
|
| 9 |
routerGroup := router.Group("/api/v1/authentication")
|
| 10 |
authenticationController := controller.ProvideAuthenticationController()
|
| 11 |
authenticationmiddleware := middleware.ProvideAuthenticationMiddleware()
|
| 12 |
-
|
|
|
|
| 13 |
{
|
| 14 |
routerGroup.POST("/external-login", authenticationController.ExternalAuth)
|
| 15 |
routerGroup.POST("/login", authenticationController.SignIn)
|
| 16 |
routerGroup.POST("/register", authenticationController.SignUp)
|
| 17 |
routerGroup.PUT("/change-password", authenticationmiddleware.VerifyAccount, authenticationController.ChangePassword)
|
| 18 |
}
|
| 19 |
-
|
| 20 |
-
userGroup := router.Group("/api/v1/users")
|
| 21 |
-
{
|
| 22 |
-
userGroup.PUT("/:account_id/assign", authorizationMiddleware.VerifySuperAdmin, authenticationController.UpdateUserRole)
|
| 23 |
-
}
|
| 24 |
}
|
|
|
|
| 2 |
|
| 3 |
import (
|
| 4 |
"abdanhafidz.com/go-boilerplate/provider"
|
| 5 |
+
"github.com/gin-contrib/gzip"
|
| 6 |
"github.com/gin-gonic/gin"
|
| 7 |
)
|
| 8 |
|
|
|
|
| 10 |
routerGroup := router.Group("/api/v1/authentication")
|
| 11 |
authenticationController := controller.ProvideAuthenticationController()
|
| 12 |
authenticationmiddleware := middleware.ProvideAuthenticationMiddleware()
|
| 13 |
+
|
| 14 |
+
routerGroup.Use(gzip.Gzip(gzip.DefaultCompression))
|
| 15 |
{
|
| 16 |
routerGroup.POST("/external-login", authenticationController.ExternalAuth)
|
| 17 |
routerGroup.POST("/login", authenticationController.SignIn)
|
| 18 |
routerGroup.POST("/register", authenticationController.SignUp)
|
| 19 |
routerGroup.PUT("/change-password", authenticationmiddleware.VerifyAccount, authenticationController.ChangePassword)
|
| 20 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
}
|
router/event_router.go
CHANGED
|
@@ -2,22 +2,20 @@ package router
|
|
| 2 |
|
| 3 |
import (
|
| 4 |
"abdanhafidz.com/go-boilerplate/provider"
|
|
|
|
| 5 |
"github.com/gin-gonic/gin"
|
| 6 |
)
|
| 7 |
|
| 8 |
func EventRouter(router *gin.Engine, middleware provider.MiddlewareProvider, controller provider.ControllerProvider) {
|
| 9 |
eventController := controller.ProvideEventController()
|
| 10 |
authenticationMiddleware := middleware.ProvideAuthenticationMiddleware()
|
| 11 |
-
|
| 12 |
routerGroup := router.Group("api/v1/events")
|
|
|
|
|
|
|
| 13 |
{
|
| 14 |
routerGroup.GET("/", authenticationMiddleware.VerifyAccount, eventController.List)
|
| 15 |
routerGroup.GET("/:event_slug", authenticationMiddleware.VerifyAccount, eventController.DetailBySlug)
|
| 16 |
routerGroup.POST("/register-event", authenticationMiddleware.VerifyAccount, eventController.Join)
|
| 17 |
-
|
| 18 |
-
adminGroup := routerGroup.Group("/admin", authorizationMiddleware.VerifyAdmin)
|
| 19 |
-
adminGroup.POST("/", eventController.CreateEvent)
|
| 20 |
-
adminGroup.PUT("/:id", eventController.UpdateEvent)
|
| 21 |
-
adminGroup.DELETE("/:id", eventController.DeleteEvent)
|
| 22 |
}
|
| 23 |
}
|
|
|
|
| 2 |
|
| 3 |
import (
|
| 4 |
"abdanhafidz.com/go-boilerplate/provider"
|
| 5 |
+
"github.com/gin-contrib/gzip"
|
| 6 |
"github.com/gin-gonic/gin"
|
| 7 |
)
|
| 8 |
|
| 9 |
func EventRouter(router *gin.Engine, middleware provider.MiddlewareProvider, controller provider.ControllerProvider) {
|
| 10 |
eventController := controller.ProvideEventController()
|
| 11 |
authenticationMiddleware := middleware.ProvideAuthenticationMiddleware()
|
| 12 |
+
|
| 13 |
routerGroup := router.Group("api/v1/events")
|
| 14 |
+
|
| 15 |
+
routerGroup.Use(gzip.Gzip(gzip.DefaultCompression))
|
| 16 |
{
|
| 17 |
routerGroup.GET("/", authenticationMiddleware.VerifyAccount, eventController.List)
|
| 18 |
routerGroup.GET("/:event_slug", authenticationMiddleware.VerifyAccount, eventController.DetailBySlug)
|
| 19 |
routerGroup.POST("/register-event", authenticationMiddleware.VerifyAccount, eventController.Join)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
}
|
| 21 |
}
|
router/router.go
CHANGED
|
@@ -13,8 +13,9 @@ func RunRouter(appProvider provider.AppProvider) {
|
|
| 13 |
EventRouter(router, middleware, controller)
|
| 14 |
OptionsRouter(router, controller)
|
| 15 |
AcademyRouter(router, middleware, controller)
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
|
|
|
| 20 |
}
|
|
|
|
| 13 |
EventRouter(router, middleware, controller)
|
| 14 |
OptionsRouter(router, controller)
|
| 15 |
AcademyRouter(router, middleware, controller)
|
| 16 |
+
ExamEventRouter(router, middleware, controller)
|
| 17 |
+
AcademyExamRouter(router, middleware, controller)
|
| 18 |
+
UploadRouter(router, middleware, controller)
|
| 19 |
+
AdminRouter(router, middleware, controller)
|
| 20 |
+
router.Run(config.ProvideEnvConfig().GetTCPAddress())
|
| 21 |
}
|
services/academy_service.go
CHANGED
|
@@ -79,13 +79,8 @@ func (s *academyService) CreateAcademy(ctx context.Context, req dto.CreateAcadem
|
|
| 79 |
if strings.TrimSpace(req.ImageUrl) == "" {
|
| 80 |
return entity.Academy{}, http_error.IMAGE_REQUIRED
|
| 81 |
}
|
| 82 |
-
if
|
| 83 |
-
return entity.Academy{},
|
| 84 |
-
}
|
| 85 |
-
for i := 0; i < 6; i++ {
|
| 86 |
-
if !((req.Code[i] >= 'A' && req.Code[i] <= 'Z') || (req.Code[i] >= '0' && req.Code[i] <= '9')) {
|
| 87 |
-
return entity.Academy{}, http_error.INVALID_CODE
|
| 88 |
-
}
|
| 89 |
}
|
| 90 |
|
| 91 |
slugVal := req.Slug
|
|
@@ -824,15 +819,6 @@ func (s *academyService) ListAssignmentsByAcademy(ctx context.Context, academyId
|
|
| 824 |
return s.academyRepo.ListAssignmentsByAcademy(ctx, academyId)
|
| 825 |
}
|
| 826 |
func (s *academyService) JoinByCode(ctx context.Context, accountId uuid.UUID, code string) (entity.AcademyAssign, error) {
|
| 827 |
-
if len(code) != 6 {
|
| 828 |
-
return entity.AcademyAssign{}, http_error.BAD_REQUEST_ERROR
|
| 829 |
-
}
|
| 830 |
-
for i := 0; i < 6; i++ {
|
| 831 |
-
ch := code[i]
|
| 832 |
-
if !((ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9')) {
|
| 833 |
-
return entity.AcademyAssign{}, http_error.BAD_REQUEST_ERROR
|
| 834 |
-
}
|
| 835 |
-
}
|
| 836 |
ac, err := s.academyRepo.GetAcademyByCode(ctx, code)
|
| 837 |
if err != nil {
|
| 838 |
return entity.AcademyAssign{}, http_error.ACADEMY_NOT_FOUND
|
|
|
|
| 79 |
if strings.TrimSpace(req.ImageUrl) == "" {
|
| 80 |
return entity.Academy{}, http_error.IMAGE_REQUIRED
|
| 81 |
}
|
| 82 |
+
if err := utils.ValidateCode(req.Code); err != nil {
|
| 83 |
+
return entity.Academy{}, err
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 84 |
}
|
| 85 |
|
| 86 |
slugVal := req.Slug
|
|
|
|
| 819 |
return s.academyRepo.ListAssignmentsByAcademy(ctx, academyId)
|
| 820 |
}
|
| 821 |
func (s *academyService) JoinByCode(ctx context.Context, accountId uuid.UUID, code string) (entity.AcademyAssign, error) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 822 |
ac, err := s.academyRepo.GetAcademyByCode(ctx, code)
|
| 823 |
if err != nil {
|
| 824 |
return entity.AcademyAssign{}, http_error.ACADEMY_NOT_FOUND
|
services/event_service.go
CHANGED
|
@@ -9,6 +9,7 @@ import (
|
|
| 9 |
entity "abdanhafidz.com/go-boilerplate/models/entity"
|
| 10 |
http_error "abdanhafidz.com/go-boilerplate/models/error"
|
| 11 |
"abdanhafidz.com/go-boilerplate/repositories"
|
|
|
|
| 12 |
"github.com/google/uuid"
|
| 13 |
)
|
| 14 |
|
|
@@ -104,7 +105,7 @@ func (s *eventService) GetStatus(ctx context.Context, slug string, accountId uui
|
|
| 104 |
event, err := s.DetailBySlug(ctx, slug, accountId)
|
| 105 |
currentTime := time.Now()
|
| 106 |
eventStatus.IsHasNotStarted = currentTime.Before(event.Data.StartEvent)
|
| 107 |
-
eventStatus.IsFinished = currentTime.
|
| 108 |
eventStatus.IsOnGoing = !(eventStatus.IsHasNotStarted) && !(eventStatus.IsFinished)
|
| 109 |
return eventStatus, err
|
| 110 |
}
|
|
@@ -125,14 +126,8 @@ func (s *eventService) CreateEvent(ctx context.Context, req dto.CreateEventReque
|
|
| 125 |
return entity.Events{}, http_error.INVALID_DATE_FORMAT
|
| 126 |
}
|
| 127 |
|
| 128 |
-
if
|
| 129 |
-
return entity.Events{},
|
| 130 |
-
}
|
| 131 |
-
for i := 0; i < 6; i++ {
|
| 132 |
-
c := req.EventCode[i]
|
| 133 |
-
if !((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9')) {
|
| 134 |
-
return entity.Events{}, http_error.INVALID_CODE
|
| 135 |
-
}
|
| 136 |
}
|
| 137 |
|
| 138 |
slugVal := strings.ToLower(strings.ReplaceAll(req.Title, " ", "-"))
|
|
|
|
| 9 |
entity "abdanhafidz.com/go-boilerplate/models/entity"
|
| 10 |
http_error "abdanhafidz.com/go-boilerplate/models/error"
|
| 11 |
"abdanhafidz.com/go-boilerplate/repositories"
|
| 12 |
+
"abdanhafidz.com/go-boilerplate/utils"
|
| 13 |
"github.com/google/uuid"
|
| 14 |
)
|
| 15 |
|
|
|
|
| 105 |
event, err := s.DetailBySlug(ctx, slug, accountId)
|
| 106 |
currentTime := time.Now()
|
| 107 |
eventStatus.IsHasNotStarted = currentTime.Before(event.Data.StartEvent)
|
| 108 |
+
eventStatus.IsFinished = currentTime.After(event.Data.EndEvent)
|
| 109 |
eventStatus.IsOnGoing = !(eventStatus.IsHasNotStarted) && !(eventStatus.IsFinished)
|
| 110 |
return eventStatus, err
|
| 111 |
}
|
|
|
|
| 126 |
return entity.Events{}, http_error.INVALID_DATE_FORMAT
|
| 127 |
}
|
| 128 |
|
| 129 |
+
if err := utils.ValidateCode(req.EventCode); err != nil {
|
| 130 |
+
return entity.Events{}, err
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 131 |
}
|
| 132 |
|
| 133 |
slugVal := strings.ToLower(strings.ReplaceAll(req.Title, " ", "-"))
|
services/exam_event_service.go
CHANGED
|
@@ -125,13 +125,14 @@ func (s *examService) GetExamEventAttempt(ctx context.Context, eventSlug string,
|
|
| 125 |
|
| 126 |
examEventAttempt, err := s.examEventAttemptRepo.GetByExamEvent(ctx, ev.Data.Id, exam.Id, accountId)
|
| 127 |
fmt.Println("Error Exam Event Attempt", errors.Is(err, gorm.ErrRecordNotFound))
|
|
|
|
| 128 |
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
| 129 |
return dto.UserExamStatus{}, entity.ExamEventAttempt{}, err
|
| 130 |
}
|
| 131 |
|
| 132 |
var attemptStatus dto.UserExamStatus
|
| 133 |
attemptStatus.IsNotAttempt = errors.Is(err, gorm.ErrRecordNotFound)
|
| 134 |
-
attemptStatus.IsTimeOut = (utils.CalculateRemainingTime(examEventAttempt.CreatedAt, examEventAttempt.DueAt) == 0)
|
| 135 |
attemptStatus.IsSubmitted = examEventAttempt.Submitted
|
| 136 |
attemptStatus.IsOnAttempt = !attemptStatus.IsNotAttempt && !attemptStatus.IsTimeOut && !attemptStatus.IsSubmitted
|
| 137 |
return attemptStatus, examEventAttempt, nil
|
|
@@ -235,7 +236,9 @@ func (s *examService) AttemptExamEvent(ctx context.Context, eventSlug string, ex
|
|
| 235 |
return entity.ExamEventAttempt{}, err
|
| 236 |
}
|
| 237 |
attemptStatus, examEventAttempt, err := s.GetExamEventAttempt(ctx, eventSlug, examSlug, accountId)
|
| 238 |
-
|
|
|
|
|
|
|
| 239 |
if err != nil {
|
| 240 |
return entity.ExamEventAttempt{}, err
|
| 241 |
}
|
|
@@ -246,10 +249,11 @@ func (s *examService) AttemptExamEvent(ctx context.Context, eventSlug string, ex
|
|
| 246 |
if err != nil {
|
| 247 |
return entity.ExamEventAttempt{}, err
|
| 248 |
}
|
|
|
|
| 249 |
if attemptStatus.IsNotAttempt {
|
| 250 |
|
| 251 |
if eventStatus.IsFinished {
|
| 252 |
-
return entity.ExamEventAttempt{}, err
|
| 253 |
}
|
| 254 |
|
| 255 |
startTime, dueTime := s.SetupExamTimer(ctx, exam)
|
|
@@ -406,3 +410,4 @@ func (s *examService) AnswerExamEvent(ctx context.Context, eventSlug string, att
|
|
| 406 |
return CPQuestionVerdict, err
|
| 407 |
}
|
| 408 |
|
|
|
|
|
|
| 125 |
|
| 126 |
examEventAttempt, err := s.examEventAttemptRepo.GetByExamEvent(ctx, ev.Data.Id, exam.Id, accountId)
|
| 127 |
fmt.Println("Error Exam Event Attempt", errors.Is(err, gorm.ErrRecordNotFound))
|
| 128 |
+
|
| 129 |
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
| 130 |
return dto.UserExamStatus{}, entity.ExamEventAttempt{}, err
|
| 131 |
}
|
| 132 |
|
| 133 |
var attemptStatus dto.UserExamStatus
|
| 134 |
attemptStatus.IsNotAttempt = errors.Is(err, gorm.ErrRecordNotFound)
|
| 135 |
+
attemptStatus.IsTimeOut = !attemptStatus.IsNotAttempt && (utils.CalculateRemainingTime(examEventAttempt.CreatedAt, examEventAttempt.DueAt) == 0)
|
| 136 |
attemptStatus.IsSubmitted = examEventAttempt.Submitted
|
| 137 |
attemptStatus.IsOnAttempt = !attemptStatus.IsNotAttempt && !attemptStatus.IsTimeOut && !attemptStatus.IsSubmitted
|
| 138 |
return attemptStatus, examEventAttempt, nil
|
|
|
|
| 236 |
return entity.ExamEventAttempt{}, err
|
| 237 |
}
|
| 238 |
attemptStatus, examEventAttempt, err := s.GetExamEventAttempt(ctx, eventSlug, examSlug, accountId)
|
| 239 |
+
|
| 240 |
+
fmt.Println("Get AttemptStatus = ", attemptStatus, "Err =", err)
|
| 241 |
+
|
| 242 |
if err != nil {
|
| 243 |
return entity.ExamEventAttempt{}, err
|
| 244 |
}
|
|
|
|
| 249 |
if err != nil {
|
| 250 |
return entity.ExamEventAttempt{}, err
|
| 251 |
}
|
| 252 |
+
|
| 253 |
if attemptStatus.IsNotAttempt {
|
| 254 |
|
| 255 |
if eventStatus.IsFinished {
|
| 256 |
+
return entity.ExamEventAttempt{}, err
|
| 257 |
}
|
| 258 |
|
| 259 |
startTime, dueTime := s.SetupExamTimer(ctx, exam)
|
|
|
|
| 410 |
return CPQuestionVerdict, err
|
| 411 |
}
|
| 412 |
|
| 413 |
+
|
space/.gitignore
CHANGED
|
@@ -1,2 +1,2 @@
|
|
| 1 |
-
|
| 2 |
go-boilerplate.exe
|
|
|
|
| 1 |
+
.env
|
| 2 |
go-boilerplate.exe
|
utils/response_util.go
CHANGED
|
@@ -59,7 +59,7 @@ func ResponseFAILED[TMetaData any](c *gin.Context, metaData TMetaData, err error
|
|
| 59 |
MetaData: metaData,
|
| 60 |
})
|
| 61 |
return
|
| 62 |
-
} else if errors.Is(err, http_error.FORBIDDEN_ERROR) {
|
| 63 |
c.JSON(403, dto.ErrorResponse{
|
| 64 |
Status: "error",
|
| 65 |
Error: err,
|
|
@@ -70,8 +70,7 @@ func ResponseFAILED[TMetaData any](c *gin.Context, metaData TMetaData, err error
|
|
| 70 |
} else if errors.Is(err, http_error.EVENT_START_DATE_IN_PAST) ||
|
| 71 |
errors.Is(err, http_error.EVENT_START_DATE_INVALID) ||
|
| 72 |
errors.Is(err, http_error.EVENT_END_DATE_INVALID) ||
|
| 73 |
-
errors.Is(err, http_error.INVALID_DATE_FORMAT)
|
| 74 |
-
errors.Is(err, http_error.INVALID_CODE) {
|
| 75 |
c.JSON(400, dto.ErrorResponse{
|
| 76 |
Status: "error",
|
| 77 |
Error: err,
|
|
|
|
| 59 |
MetaData: metaData,
|
| 60 |
})
|
| 61 |
return
|
| 62 |
+
} else if errors.Is(err, http_error.FORBIDDEN_ERROR) || errors.Is(err, http_error.INVALID_CODE) {
|
| 63 |
c.JSON(403, dto.ErrorResponse{
|
| 64 |
Status: "error",
|
| 65 |
Error: err,
|
|
|
|
| 70 |
} else if errors.Is(err, http_error.EVENT_START_DATE_IN_PAST) ||
|
| 71 |
errors.Is(err, http_error.EVENT_START_DATE_INVALID) ||
|
| 72 |
errors.Is(err, http_error.EVENT_END_DATE_INVALID) ||
|
| 73 |
+
errors.Is(err, http_error.INVALID_DATE_FORMAT) {
|
|
|
|
| 74 |
c.JSON(400, dto.ErrorResponse{
|
| 75 |
Status: "error",
|
| 76 |
Error: err,
|
utils/utils.go
CHANGED
|
@@ -3,6 +3,7 @@ package utils
|
|
| 3 |
import (
|
| 4 |
"time"
|
| 5 |
|
|
|
|
| 6 |
http_error "abdanhafidz.com/go-boilerplate/models/error"
|
| 7 |
"github.com/google/uuid"
|
| 8 |
)
|
|
@@ -43,3 +44,11 @@ func TimePtrToString(t *time.Time) *string {
|
|
| 43 |
s := t.Format(time.RFC3339)
|
| 44 |
return &s
|
| 45 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
import (
|
| 4 |
"time"
|
| 5 |
|
| 6 |
+
models "abdanhafidz.com/go-boilerplate/models/entity"
|
| 7 |
http_error "abdanhafidz.com/go-boilerplate/models/error"
|
| 8 |
"github.com/google/uuid"
|
| 9 |
)
|
|
|
|
| 44 |
s := t.Format(time.RFC3339)
|
| 45 |
return &s
|
| 46 |
}
|
| 47 |
+
|
| 48 |
+
func ValidateCode(code string) error {
|
| 49 |
+
if !models.CodeRegex.MatchString(code) {
|
| 50 |
+
return http_error.INVALID_CODE
|
| 51 |
+
}
|
| 52 |
+
return nil
|
| 53 |
+
}
|
| 54 |
+
|