lifedebugger commited on
Commit
483bf6a
·
1 Parent(s): ab82ffd

Deploy files from GitHub repository

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .env.example +13 -0
  2. README.md +8 -8
  3. api_spec.txt +117 -117
  4. config/database_config.go +32 -27
  5. config/env_config.go +16 -1
  6. config/jwt_config.go +24 -24
  7. config/supabase.go +15 -0
  8. controllers/academy_controller.go +228 -145
  9. controllers/upload_controller.go +222 -0
  10. do_inject_config.ps1 +434 -434
  11. do_inject_controllers.ps1 +309 -309
  12. do_inject_middleware.ps1 +311 -311
  13. do_inject_repository.ps1 +326 -326
  14. do_inject_services.ps1 +382 -382
  15. go.mod +1 -0
  16. go.sum +2 -0
  17. middleware/authentication_middleware.go +48 -48
  18. middleware/authorization_middleware.go +43 -43
  19. middleware/middleware.go +1 -1
  20. models/dto/academy_dto.go +30 -30
  21. models/dto/account_details_dto.go +19 -19
  22. models/dto/event_dto.go +20 -20
  23. models/dto/exam_dto.go +23 -23
  24. models/dto/option_dto.go +12 -12
  25. models/dto/upload_dto.go +43 -0
  26. models/entity/contant.go +7 -0
  27. models/entity/entity.go +15 -0
  28. models/error/error.go +39 -15
  29. provider/config_provider.go +59 -38
  30. provider/controller_provider.go +106 -90
  31. provider/middleware_provider.go +30 -30
  32. provider/provider.go +85 -70
  33. provider/repositories_provider.go +178 -170
  34. provider/services_provider.go +121 -103
  35. provider/storage_interface.go +13 -0
  36. provider/supabase_storage.go +42 -0
  37. repositories/academy_repository.go +232 -157
  38. repositories/exam_event_answer_repository.go +59 -59
  39. repositories/exam_event_assign_repository.go +47 -47
  40. repositories/exam_event_attempt_repository.go +55 -55
  41. repositories/exam_event_repository.go +85 -85
  42. repositories/file_repository.go +43 -0
  43. repositories/problem_set_exam_assign_repository.go +40 -40
  44. repositories/problem_set_repository.go +57 -57
  45. repositories/question_repository.go +55 -55
  46. repositories/result_repository.go +60 -60
  47. router/academy_router.go +44 -38
  48. router/account_detail_router.go +18 -18
  49. router/email_router.go +17 -17
  50. router/event_router.go +19 -19
.env.example ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ DB_HOST =
2
+ DB_USER =
3
+ DB_PASSWORD =
4
+ DB_PORT =
5
+ DB_NAME =
6
+ HOST_ADDRESS =
7
+ HOST_PORT =
8
+ SALT =
9
+ LOG_PATH = logs
10
+ JWT_SECRET_KEY = s4b3s076
11
+ SUPABASE_URL=
12
+ SUPABASE_SERVICE_KEY=
13
+ SUPABASE_BUCKET_NAME=
README.md CHANGED
@@ -1,8 +1,8 @@
1
- ---
2
- title: Quzuu Api Dev
3
- emoji: 🐠
4
- colorFrom: indigo
5
- colorTo: gray
6
- sdk: docker
7
- pinned: false
8
- ---
 
1
+ ---
2
+ title: Quzuu Api Dev
3
+ emoji: 🐠
4
+ colorFrom: indigo
5
+ colorTo: gray
6
+ sdk: docker
7
+ pinned: false
8
+ ---
api_spec.txt CHANGED
@@ -1,118 +1,118 @@
1
-
2
- attempt:{
3
- id_event:
4
- id_exam:
5
- remaining_time:
6
- answered_question:[]str
7
- question
8
- }
9
-
10
- question:{
11
- id:uuid,
12
- question_type:string,
13
- content:string,
14
- options:[]string,
15
- current_answer:[]string,
16
- }
17
-
18
- Answer Soal -> (1) Request ke Backend untuk attempt
19
- (2) Rubah UI options
20
- (3) Fetch Status -> show Status
21
- (4) Set State question.current_answer update
22
- (5) Update navigation button soalnya sudah keisi = "hijau", sekarang di nomor berapa = "ungu"
23
-
24
- # Isian Singkat, Handle onChange =>
25
- [kjkajsksaf]
26
-
27
- Before:
28
- def sum(a,b):
29
- return {code}
30
-
31
- print({code})
32
-
33
- After :
34
- def sum(a,b):
35
- return <input type = "text" name="answer[0]" />
36
-
37
- print(<input type = "text" name="answer[1]" />)
38
-
39
- # Puzzle:
40
-
41
-
42
- #Payload ngirim jawaban (by default) -> Endpoint Ngirim Answer
43
-
44
- answer: {
45
- id_question:uuid,
46
- answers:[]str
47
- }
48
-
49
- #mc
50
- answer: {
51
- id_question:uuid,
52
- answers:["A"]str
53
- }
54
-
55
-
56
- #Complexmc T/F
57
- answer: {
58
- id_question:uuid,
59
- answers:[1,0,0,1]
60
- }
61
- +++
62
-
63
-
64
-
65
-
66
- ++++++++++++
67
- #code_Puzzle
68
-
69
- answer: {
70
- id_question:uuid,
71
- answers:["a+b","sum(a,b)"]
72
- }
73
-
74
- # short_answer
75
- answer: {
76
- id_question:uuid,
77
- answers:["jawaban"]
78
- }
79
-
80
- # upload_file
81
- upload_file -> Form Data
82
- answer: {
83
- id_question:uuid,
84
- answers:["{nama_file}"]
85
- }
86
-
87
- #competitive Programming
88
- question_type:mock_coding[language]
89
- upload_file -> Form Data (Generate nama_file)
90
- answer:{
91
- id_questions:uuid,
92
- answers:["{nama_file}"]
93
- }
94
-
95
- #response answer
96
- response:{
97
- success_response,
98
- meta_data{
99
- verdict: [AC/WA/TLE/RTE]
100
- score:
101
- time_exec:
102
- memory:
103
- }
104
- }
105
-
106
- #Submit
107
- {
108
- id_attempt:
109
- }
110
-
111
- Attempt{
112
- id_user:
113
- id_event:
114
- id_exam:
115
- }
116
-
117
-
118
  # Result / Scoreboard
 
1
+
2
+ attempt:{
3
+ id_event:
4
+ id_exam:
5
+ remaining_time:
6
+ answered_question:[]str
7
+ question
8
+ }
9
+
10
+ question:{
11
+ id:uuid,
12
+ question_type:string,
13
+ content:string,
14
+ options:[]string,
15
+ current_answer:[]string,
16
+ }
17
+
18
+ Answer Soal -> (1) Request ke Backend untuk attempt
19
+ (2) Rubah UI options
20
+ (3) Fetch Status -> show Status
21
+ (4) Set State question.current_answer update
22
+ (5) Update navigation button soalnya sudah keisi = "hijau", sekarang di nomor berapa = "ungu"
23
+
24
+ # Isian Singkat, Handle onChange =>
25
+ [kjkajsksaf]
26
+
27
+ Before:
28
+ def sum(a,b):
29
+ return {code}
30
+
31
+ print({code})
32
+
33
+ After :
34
+ def sum(a,b):
35
+ return <input type = "text" name="answer[0]" />
36
+
37
+ print(<input type = "text" name="answer[1]" />)
38
+
39
+ # Puzzle:
40
+
41
+
42
+ #Payload ngirim jawaban (by default) -> Endpoint Ngirim Answer
43
+
44
+ answer: {
45
+ id_question:uuid,
46
+ answers:[]str
47
+ }
48
+
49
+ #mc
50
+ answer: {
51
+ id_question:uuid,
52
+ answers:["A"]str
53
+ }
54
+
55
+
56
+ #Complexmc T/F
57
+ answer: {
58
+ id_question:uuid,
59
+ answers:[1,0,0,1]
60
+ }
61
+ +++
62
+
63
+
64
+
65
+
66
+ ++++++++++++
67
+ #code_Puzzle
68
+
69
+ answer: {
70
+ id_question:uuid,
71
+ answers:["a+b","sum(a,b)"]
72
+ }
73
+
74
+ # short_answer
75
+ answer: {
76
+ id_question:uuid,
77
+ answers:["jawaban"]
78
+ }
79
+
80
+ # upload_file
81
+ upload_file -> Form Data
82
+ answer: {
83
+ id_question:uuid,
84
+ answers:["{nama_file}"]
85
+ }
86
+
87
+ #competitive Programming
88
+ question_type:mock_coding[language]
89
+ upload_file -> Form Data (Generate nama_file)
90
+ answer:{
91
+ id_questions:uuid,
92
+ answers:["{nama_file}"]
93
+ }
94
+
95
+ #response answer
96
+ response:{
97
+ success_response,
98
+ meta_data{
99
+ verdict: [AC/WA/TLE/RTE]
100
+ score:
101
+ time_exec:
102
+ memory:
103
+ }
104
+ }
105
+
106
+ #Submit
107
+ {
108
+ id_attempt:
109
+ }
110
+
111
+ Attempt{
112
+ id_user:
113
+ id_event:
114
+ id_exam:
115
+ }
116
+
117
+
118
  # Result / Scoreboard
config/database_config.go CHANGED
@@ -1,51 +1,56 @@
1
  package config
2
 
3
  import (
4
- "fmt"
5
- "log"
6
- "gorm.io/driver/postgres"
7
- "gorm.io/gorm"
8
  )
9
 
10
  type DatabaseConfig interface {
11
- AutoMigrateAll(entities ...interface{}) error
12
- GetInstance() *gorm.DB
13
  }
 
14
  type databaseConfig struct {
15
- db *gorm.DB
16
  }
17
 
18
  func NewDatabaseConfig(DB_HOST, DB_USER, DB_PASSWORD, DB_NAME, DB_PORT string) DatabaseConfig {
19
- dsn := fmt.Sprintf(
20
- "host=%s user=%s password=%s dbname=%s port=%s sslmode=disable TimeZone=Asia/Jakarta ",
21
- DB_HOST, DB_USER, DB_PASSWORD, DB_NAME, DB_PORT,
22
- )
23
 
24
- db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{
25
- TranslateError: true,
26
- })
27
 
28
- db = db.Session(&gorm.Session{
29
- PrepareStmt: false,
30
- })
31
 
32
- if err != nil {
33
- log.Fatal("Failed to connect to database:", err)
34
- }
35
 
36
- return &databaseConfig{db: db}
37
  }
38
 
39
  func (cfg *databaseConfig) AutoMigrateAll(entities ...interface{}) error {
40
- // Enable UUID extension first
41
  if err := cfg.db.Exec("CREATE EXTENSION IF NOT EXISTS \"uuid-ossp\"").Error; err != nil {
42
- return err
43
  }
44
-
 
45
  err := cfg.db.AutoMigrate(entities...)
46
- return err
 
 
 
 
47
  }
48
 
49
  func (cfg *databaseConfig) GetInstance() *gorm.DB {
50
- return cfg.db
51
- }
 
1
  package config
2
 
3
  import (
4
+ "fmt"
5
+ "log"
6
+ "gorm.io/driver/postgres"
7
+ "gorm.io/gorm"
8
  )
9
 
10
  type DatabaseConfig interface {
11
+ AutoMigrateAll(entities ...interface{}) error
12
+ GetInstance() *gorm.DB
13
  }
14
+
15
  type databaseConfig struct {
16
+ db *gorm.DB
17
  }
18
 
19
  func NewDatabaseConfig(DB_HOST, DB_USER, DB_PASSWORD, DB_NAME, DB_PORT string) DatabaseConfig {
20
+ dsn := fmt.Sprintf(
21
+ "host=%s user=%s password=%s dbname=%s port=%s sslmode=disable TimeZone=Asia/Jakarta",
22
+ DB_HOST, DB_USER, DB_PASSWORD, DB_NAME, DB_PORT,
23
+ )
24
 
25
+ db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{
26
+ TranslateError: true,
27
+ })
28
 
29
+ if err != nil {
30
+ log.Fatal("Failed to connect to database:", err)
31
+ }
32
 
33
+ db = db.Session(&gorm.Session{
34
+ PrepareStmt: false,
35
+ })
36
 
37
+ return &databaseConfig{db: db}
38
  }
39
 
40
  func (cfg *databaseConfig) AutoMigrateAll(entities ...interface{}) error {
 
41
  if err := cfg.db.Exec("CREATE EXTENSION IF NOT EXISTS \"uuid-ossp\"").Error; err != nil {
42
+ log.Printf("Warning: Could not create uuid-ossp extension: %v", err)
43
  }
44
+
45
+ // Run migrations
46
  err := cfg.db.AutoMigrate(entities...)
47
+ if err != nil {
48
+ return fmt.Errorf("migration failed: %w", err)
49
+ }
50
+
51
+ return nil
52
  }
53
 
54
  func (cfg *databaseConfig) GetInstance() *gorm.DB {
55
+ return cfg.db
56
+ }
config/env_config.go CHANGED
@@ -18,6 +18,9 @@ type EnvConfig interface {
18
  GetDatabasePassword() string
19
  GetDatabaseName() string
20
  GetSalt() string
 
 
 
21
  }
22
 
23
  type envConfig struct {
@@ -79,7 +82,19 @@ func (e *envConfig) GetDatabaseName() string {
79
  func (e *envConfig) GetSalt() string {
80
  salt := os.Getenv("SALT")
81
  if salt == "" {
82
- return "Def4u|7" // Default salt value
83
  }
84
  return salt
85
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
18
  GetDatabasePassword() string
19
  GetDatabaseName() string
20
  GetSalt() string
21
+ GetSupabaseURL() string
22
+ GetSupabaseKey() string
23
+ GetSupabaseBucket() string
24
  }
25
 
26
  type envConfig struct {
 
82
  func (e *envConfig) GetSalt() string {
83
  salt := os.Getenv("SALT")
84
  if salt == "" {
85
+ return "Def4u|7"
86
  }
87
  return salt
88
  }
89
+
90
+ func (e *envConfig) GetSupabaseURL() string {
91
+ return os.Getenv("SUPABASE_URL")
92
+ }
93
+
94
+ func (e *envConfig) GetSupabaseKey() string {
95
+ return os.Getenv("SUPABASE_SERVICE_KEY")
96
+ }
97
+
98
+ func (e *envConfig) GetSupabaseBucket() string {
99
+ return os.Getenv("SUPABASE_BUCKET_NAME")
100
+ }
config/jwt_config.go CHANGED
@@ -1,24 +1,24 @@
1
- package config
2
-
3
- type JWTConfig interface {
4
- SetSecretKey(key string)
5
- GetSecretKey() string
6
- }
7
-
8
- type jwtConfig struct {
9
- secretKey string
10
- }
11
-
12
- func NewJWTConfig(secretKey string) JWTConfig {
13
- return &jwtConfig{
14
- secretKey: secretKey,
15
- }
16
- }
17
-
18
- func (cfg *jwtConfig) SetSecretKey(key string) {
19
- cfg.secretKey = key
20
- }
21
-
22
- func (cfg *jwtConfig) GetSecretKey() string {
23
- return cfg.secretKey
24
- }
 
1
+ package config
2
+
3
+ type JWTConfig interface {
4
+ SetSecretKey(key string)
5
+ GetSecretKey() string
6
+ }
7
+
8
+ type jwtConfig struct {
9
+ secretKey string
10
+ }
11
+
12
+ func NewJWTConfig(secretKey string) JWTConfig {
13
+ return &jwtConfig{
14
+ secretKey: secretKey,
15
+ }
16
+ }
17
+
18
+ func (cfg *jwtConfig) SetSecretKey(key string) {
19
+ cfg.secretKey = key
20
+ }
21
+
22
+ func (cfg *jwtConfig) GetSecretKey() string {
23
+ return cfg.secretKey
24
+ }
config/supabase.go ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package config
2
+
3
+ type SupabaseConfig struct {
4
+ URL string
5
+ ServiceKey string
6
+ BucketName string
7
+ }
8
+
9
+ func NewSupabaseConfig(url, key, bucket string) SupabaseConfig {
10
+ return SupabaseConfig{
11
+ URL: url,
12
+ ServiceKey: key,
13
+ BucketName: bucket,
14
+ }
15
+ }
controllers/academy_controller.go CHANGED
@@ -1,145 +1,228 @@
1
- package controllers
2
-
3
- import (
4
- "fmt"
5
- "net/http"
6
- "strconv"
7
-
8
- "abdanhafidz.com/go-boilerplate/models/dto"
9
- "abdanhafidz.com/go-boilerplate/services"
10
- "github.com/gin-gonic/gin"
11
- "github.com/google/uuid"
12
- )
13
-
14
- type AcademyController interface {
15
- CreateAcademy(ctx *gin.Context)
16
- GetAcademy(ctx *gin.Context)
17
- GetAcademyDetail(ctx *gin.Context)
18
- ListAcademies(ctx *gin.Context)
19
- UpdateAcademy(ctx *gin.Context)
20
- DeleteAcademy(ctx *gin.Context)
21
-
22
- GetMaterial(ctx *gin.Context)
23
- CreateMaterial(ctx *gin.Context)
24
-
25
- CreateContent(ctx *gin.Context)
26
- GetContent(ctx *gin.Context)
27
-
28
- UpdateContentProgress(ctx *gin.Context)
29
- }
30
-
31
- type academyController struct {
32
- academyService services.AcademyService
33
- }
34
-
35
- func NewAcademyController(academyService services.AcademyService) AcademyController {
36
- return &academyController{academyService}
37
- }
38
-
39
- func (c *academyController) GetAcademy(ctx *gin.Context) {
40
- academySlug := ctx.Param("academy_slug")
41
- accountId,_ := uuid.Parse(ctx.Value("account_id").(string))
42
- res, err := c.academyService.GetAcademy(ctx.Request.Context(), accountId, academySlug)
43
- ResponseJSON(ctx, gin.H{"academy_slug": academySlug}, res, err)
44
- }
45
-
46
- func (c *academyController) GetAcademyDetail(ctx *gin.Context) {
47
- id, _ := uuid.Parse(ctx.Param("id"))
48
- res, err := c.academyService.GetAcademyDetail(ctx.Request.Context(), id)
49
- ResponseJSON(ctx, gin.H{"id": id}, res, err)
50
- }
51
-
52
- func (c *academyController) ListAcademies(ctx *gin.Context) {
53
- accountId,_ := uuid.Parse(ctx.Value("account_id").(string))
54
- fmt.Println("Account ID in ListAcademies:", accountId)
55
- res, err := c.academyService.ListAcademies(ctx.Request.Context(), accountId)
56
- ResponseJSON(ctx, gin.H{}, res, err)
57
- }
58
-
59
- func (c *academyController) CreateAcademy(ctx *gin.Context) {
60
- req := RequestJSON[dto.CreateAcademyRequest](ctx)
61
- res, err := c.academyService.CreateAcademy(ctx.Request.Context(), req)
62
- ResponseJSON(ctx, req, res, err)
63
- }
64
-
65
- func (c *academyController) UpdateAcademy(ctx *gin.Context) {
66
- id, _ := uuid.Parse(ctx.Param("id"))
67
- req := RequestJSON[dto.UpdateAcademyRequest](ctx)
68
- res, err := c.academyService.UpdateAcademy(ctx.Request.Context(), id, req)
69
- ResponseJSON(ctx, req, res, err)
70
- }
71
-
72
- func (c *academyController) DeleteAcademy(ctx *gin.Context) {
73
- id, _ := uuid.Parse(ctx.Param("id"))
74
- err := c.academyService.DeleteAcademy(ctx.Request.Context(), id)
75
-
76
- ResponseJSON(ctx, gin.H{"id": id}, gin.H{"deleted": true}, err)
77
- }
78
-
79
- // MATERIAL
80
- func (c *academyController) GetMaterial(ctx *gin.Context) {
81
- academySlug := ctx.Param("academy_slug")
82
- materialSlug := ctx.Param("material_slug")
83
- accountId,_ := uuid.Parse(ctx.Value("account_id").(string))
84
-
85
- res, err := c.academyService.GetMaterial(ctx.Request.Context(), accountId, academySlug, materialSlug)
86
- ResponseJSON(ctx, gin.H{"academy_slug": academySlug, "material_slug": materialSlug}, res, err)
87
- }
88
-
89
- func (c *academyController) CreateMaterial(ctx *gin.Context) {
90
- req := RequestJSON[dto.CreateMaterialRequest](ctx)
91
-
92
- res, err := c.academyService.CreateMaterial(ctx.Request.Context(), req)
93
- ResponseJSON(ctx, req, res, err)
94
- }
95
-
96
-
97
- // CONTENT
98
- func (c *academyController) GetContent(ctx *gin.Context) {
99
- accountId,_ := uuid.Parse(ctx.Value("account_id").(string))
100
- academySlug := ctx.Param("academy_slug")
101
- materialSlug := ctx.Param("material_slug")
102
- orderString := ctx.Param("order")
103
-
104
- orderID64, err := strconv.ParseUint(orderString, 10, 64)
105
- if err != nil {
106
- ctx.JSON(http.StatusBadRequest, gin.H{"error": "Invalid 'order' parameter. Must be a positive integer."})
107
- return
108
- }
109
- order := uint(orderID64)
110
-
111
- res, err := c.academyService.GetContent(ctx.Request.Context(),accountId, academySlug, materialSlug, order)
112
- ResponseJSON(ctx, gin.H{"academy_slug": academySlug, "material_slug": materialSlug, "content_slug": order}, res, err)
113
- }
114
-
115
- func (c *academyController) CreateContent(ctx *gin.Context) {
116
- req := RequestJSON[dto.CreateContentRequest](ctx)
117
- res, err := c.academyService.CreateContent(ctx.Request.Context(), req)
118
- ResponseJSON(ctx, req, res, err)
119
- }
120
-
121
-
122
- func (c *academyController) UpdateContentProgress(ctx *gin.Context) {
123
- accountId,_ := uuid.Parse(ctx.Value("account_id").(string))
124
- academySlug := ctx.Param("academy_slug")
125
- materialSlug := ctx.Param("material_slug")
126
- orderString := ctx.Param("order")
127
-
128
- orderID64, err := strconv.ParseUint(orderString, 10, 64)
129
- if err != nil {
130
- ctx.JSON(http.StatusBadRequest, gin.H{"error": "Invalid 'order' parameter. Must be a positive integer."})
131
- return
132
- }
133
- order := uint(orderID64)
134
-
135
- contentProgress, materialProgress, academyProgress, err := c.academyService.UpdateContentProgress(ctx, accountId, academySlug, materialSlug, order)
136
- res := gin.H{
137
- "content_progress": contentProgress,
138
- "material_progress": materialProgress,
139
- "academy_progress": academyProgress,
140
- }
141
-
142
- ResponseJSON(ctx, gin.H{"academy_slug": academySlug, "material_slug": materialSlug, "content_slug": order}, res, err)
143
- }
144
-
145
- //! TODO: MAKE FULL CRUD FOR ADMIN USER
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package controllers
2
+
3
+ import (
4
+ "fmt"
5
+ "net/http"
6
+ "strconv"
7
+
8
+ "abdanhafidz.com/go-boilerplate/models/dto"
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
+ )
14
+
15
+ type AcademyController interface {
16
+ // Academy
17
+ CreateAcademy(ctx *gin.Context)
18
+ GetAcademy(ctx *gin.Context)
19
+ GetAcademyDetail(ctx *gin.Context)
20
+ ListAcademies(ctx *gin.Context)
21
+ UpdateAcademy(ctx *gin.Context)
22
+ DeleteAcademy(ctx *gin.Context)
23
+
24
+ // Material
25
+ GetMaterial(ctx *gin.Context)
26
+ CreateMaterial(ctx *gin.Context)
27
+ DeleteMaterial(ctx *gin.Context)
28
+
29
+ // Content
30
+ CreateContent(ctx *gin.Context)
31
+ GetContent(ctx *gin.Context)
32
+ DeleteContent(ctx *gin.Context)
33
+
34
+ // Progress
35
+ UpdateContentProgress(ctx *gin.Context)
36
+ }
37
+
38
+ type academyController struct {
39
+ academyService services.AcademyService
40
+ }
41
+
42
+ func NewAcademyController(academyService services.AcademyService) AcademyController {
43
+ return &academyController{academyService}
44
+ }
45
+
46
+ // ================= ACADEMY =================
47
+
48
+ func (c *academyController) GetAcademy(ctx *gin.Context) {
49
+ academySlug := ctx.Param("academy_slug")
50
+ accountIdStr := ctx.GetString("account_id")
51
+ accountId, err := uuid.Parse(accountIdStr)
52
+ if err != nil {
53
+ // [FIX] Tambahkan [any, any] agar compiler tau tipenya
54
+ ResponseJSON[any, any](ctx, nil, nil, http_error.UNAUTHORIZED)
55
+ return
56
+ }
57
+
58
+ res, err := c.academyService.GetAcademy(ctx.Request.Context(), accountId, academySlug)
59
+ ResponseJSON(ctx, gin.H{"academy_slug": academySlug}, res, err)
60
+ }
61
+
62
+ func (c *academyController) GetAcademyDetail(ctx *gin.Context) {
63
+ id, err := uuid.Parse(ctx.Param("id"))
64
+ if err != nil {
65
+ // [FIX] Tambahkan [any, any]
66
+ ResponseJSON[any, any](ctx, nil, nil, http_error.BAD_REQUEST_ERROR)
67
+ return
68
+ }
69
+
70
+ res, err := c.academyService.GetAcademyDetail(ctx.Request.Context(), id)
71
+ ResponseJSON(ctx, gin.H{"id": id}, res, err)
72
+ }
73
+
74
+ func (c *academyController) ListAcademies(ctx *gin.Context) {
75
+ accountIdStr := ctx.GetString("account_id")
76
+ accountId, err := uuid.Parse(accountIdStr)
77
+ if err != nil {
78
+ // [FIX] Tambahkan [any, any]
79
+ ResponseJSON[any, any](ctx, nil, nil, http_error.UNAUTHORIZED)
80
+ return
81
+ }
82
+
83
+ fmt.Println("Account ID in ListAcademies:", accountId)
84
+ res, err := c.academyService.ListAcademies(ctx.Request.Context(), accountId)
85
+ ResponseJSON(ctx, gin.H{}, res, err)
86
+ }
87
+
88
+ func (c *academyController) CreateAcademy(ctx *gin.Context) {
89
+ req := RequestJSON[dto.CreateAcademyRequest](ctx)
90
+ res, err := c.academyService.CreateAcademy(ctx.Request.Context(), req)
91
+ ResponseJSON(ctx, req, res, err)
92
+ }
93
+
94
+ func (c *academyController) UpdateAcademy(ctx *gin.Context) {
95
+ id, err := uuid.Parse(ctx.Param("id"))
96
+ if err != nil {
97
+ // [FIX] Tambahkan [any, any]
98
+ ResponseJSON[any, any](ctx, nil, nil, http_error.BAD_REQUEST_ERROR)
99
+ return
100
+ }
101
+
102
+ req := RequestJSON[dto.UpdateAcademyRequest](ctx)
103
+ res, err := c.academyService.UpdateAcademy(ctx.Request.Context(), id, req)
104
+ ResponseJSON(ctx, req, res, err)
105
+ }
106
+
107
+ func (c *academyController) DeleteAcademy(ctx *gin.Context) {
108
+ id, err := uuid.Parse(ctx.Param("id"))
109
+ if err != nil {
110
+ // [FIX] Tambahkan [any, any]
111
+ ResponseJSON[any, any](ctx, nil, nil, http_error.BAD_REQUEST_ERROR)
112
+ return
113
+ }
114
+
115
+ err = c.academyService.DeleteAcademy(ctx.Request.Context(), id)
116
+ ResponseJSON(ctx, gin.H{"id": id}, gin.H{"deleted": true}, err)
117
+ }
118
+
119
+ // ================= MATERIAL =================
120
+
121
+ func (c *academyController) GetMaterial(ctx *gin.Context) {
122
+ academySlug := ctx.Param("academy_slug")
123
+ materialSlug := ctx.Param("material_slug")
124
+
125
+ accountIdStr := ctx.GetString("account_id")
126
+ accountId, err := uuid.Parse(accountIdStr)
127
+ if err != nil {
128
+ // [FIX] Tambahkan [any, any]
129
+ ResponseJSON[any, any](ctx, nil, nil, http_error.UNAUTHORIZED)
130
+ return
131
+ }
132
+
133
+ res, err := c.academyService.GetMaterial(ctx.Request.Context(), accountId, academySlug, materialSlug)
134
+ ResponseJSON(ctx, gin.H{"academy_slug": academySlug, "material_slug": materialSlug}, res, err)
135
+ }
136
+
137
+ func (c *academyController) CreateMaterial(ctx *gin.Context) {
138
+ req := RequestJSON[dto.CreateMaterialRequest](ctx)
139
+ res, err := c.academyService.CreateMaterial(ctx.Request.Context(), req)
140
+ ResponseJSON(ctx, req, res, err)
141
+ }
142
+
143
+ func (c *academyController) DeleteMaterial(ctx *gin.Context) {
144
+ id, err := uuid.Parse(ctx.Param("id"))
145
+ if err != nil {
146
+ // [FIX] Tambahkan [any, any]
147
+ ResponseJSON[any, any](ctx, nil, nil, http_error.BAD_REQUEST_ERROR)
148
+ return
149
+ }
150
+
151
+ err = c.academyService.DeleteMaterial(ctx.Request.Context(), id)
152
+ ResponseJSON(ctx, gin.H{"id": id}, gin.H{"deleted": true}, err)
153
+ }
154
+
155
+ // ================= CONTENT =================
156
+
157
+ func (c *academyController) GetContent(ctx *gin.Context) {
158
+ accountIdStr := ctx.GetString("account_id")
159
+ accountId, err := uuid.Parse(accountIdStr)
160
+ if err != nil {
161
+ // [FIX] Tambahkan [any, any]
162
+ ResponseJSON[any, any](ctx, nil, nil, http_error.UNAUTHORIZED)
163
+ return
164
+ }
165
+
166
+ academySlug := ctx.Param("academy_slug")
167
+ materialSlug := ctx.Param("material_slug")
168
+
169
+ orderID64, err := strconv.ParseUint(ctx.Param("order"), 10, 64)
170
+ if err != nil {
171
+ ctx.JSON(http.StatusBadRequest, gin.H{"error": "Invalid 'order' parameter. Must be a positive integer."})
172
+ return
173
+ }
174
+ order := uint(orderID64)
175
+
176
+ res, err := c.academyService.GetContent(ctx.Request.Context(), accountId, academySlug, materialSlug, order)
177
+ ResponseJSON(ctx, gin.H{"academy_slug": academySlug, "material_slug": materialSlug, "content_order": order}, res, err)
178
+ }
179
+
180
+ func (c *academyController) CreateContent(ctx *gin.Context) {
181
+ req := RequestJSON[dto.CreateContentRequest](ctx)
182
+ res, err := c.academyService.CreateContent(ctx.Request.Context(), req)
183
+ ResponseJSON(ctx, req, res, err)
184
+ }
185
+
186
+ func (c *academyController) DeleteContent(ctx *gin.Context) {
187
+ id, err := uuid.Parse(ctx.Param("id"))
188
+ if err != nil {
189
+ // [FIX] Tambahkan [any, any]
190
+ ResponseJSON[any, any](ctx, nil, nil, http_error.BAD_REQUEST_ERROR)
191
+ return
192
+ }
193
+
194
+ err = c.academyService.DeleteContent(ctx.Request.Context(), id)
195
+ ResponseJSON(ctx, gin.H{"id": id}, gin.H{"deleted": true}, err)
196
+ }
197
+
198
+ // ================= PROGRESS =================
199
+
200
+ func (c *academyController) UpdateContentProgress(ctx *gin.Context) {
201
+ accountIdStr := ctx.GetString("account_id")
202
+ accountId, err := uuid.Parse(accountIdStr)
203
+ if err != nil {
204
+ // [FIX] Tambahkan [any, any]
205
+ ResponseJSON[any, any](ctx, nil, nil, http_error.UNAUTHORIZED)
206
+ return
207
+ }
208
+
209
+ academySlug := ctx.Param("academy_slug")
210
+ materialSlug := ctx.Param("material_slug")
211
+
212
+ orderID64, err := strconv.ParseUint(ctx.Param("order"), 10, 64)
213
+ if err != nil {
214
+ ctx.JSON(http.StatusBadRequest, gin.H{"error": "Invalid 'order' parameter. Must be a positive integer."})
215
+ return
216
+ }
217
+ order := uint(orderID64)
218
+
219
+ contentProgress, materialProgress, academyProgress, err := c.academyService.UpdateContentProgress(ctx.Request.Context(), accountId, academySlug, materialSlug, order)
220
+
221
+ res := gin.H{
222
+ "content_progress": contentProgress,
223
+ "material_progress": materialProgress,
224
+ "academy_progress": academyProgress,
225
+ }
226
+
227
+ ResponseJSON(ctx, gin.H{"academy_slug": academySlug, "material_slug": materialSlug, "content_order": order}, res, err)
228
+ }
controllers/upload_controller.go ADDED
@@ -0,0 +1,222 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package controllers
2
+
3
+ import (
4
+ "errors"
5
+ "net/http"
6
+ "path/filepath"
7
+ "strings"
8
+
9
+ "github.com/gin-gonic/gin"
10
+ "github.com/google/uuid"
11
+
12
+ "abdanhafidz.com/go-boilerplate/models/dto"
13
+ http_error "abdanhafidz.com/go-boilerplate/models/error"
14
+ "abdanhafidz.com/go-boilerplate/services"
15
+ )
16
+
17
+ type UploadController struct {
18
+ uploadService *services.UploadService
19
+ }
20
+
21
+ func NewUploadController(s *services.UploadService) *UploadController {
22
+ return &UploadController{uploadService: s}
23
+ }
24
+
25
+ func (c *UploadController) Upload(ctx *gin.Context) {
26
+ // 1. Parse Multipart Form (Limit 32MB)
27
+ if err := ctx.Request.ParseMultipartForm(32 << 20); err != nil {
28
+ if strings.Contains(err.Error(), "http: request body too large") {
29
+ ctx.JSON(http.StatusBadRequest, gin.H{
30
+ "status": "error",
31
+ "message": "File size exceeds the allowed limit of 32MB",
32
+ })
33
+ return
34
+ }
35
+ ctx.JSON(http.StatusBadRequest, gin.H{
36
+ "status": "error",
37
+ "code": "INVALID_FORM",
38
+ "message": "Failed to parse form data",
39
+ })
40
+ return
41
+ }
42
+
43
+ form, err := ctx.MultipartForm()
44
+ if err != nil {
45
+ ctx.JSON(http.StatusBadRequest, gin.H{
46
+ "status": "error",
47
+ "code": "INVALID_DATA",
48
+ "message": "Invalid form data",
49
+ })
50
+ return
51
+ }
52
+
53
+ files := form.File["files"]
54
+ if len(files) == 0 {
55
+ ctx.JSON(http.StatusBadRequest, gin.H{
56
+ "status": "error",
57
+ "message": "No files uploaded",
58
+ })
59
+ return
60
+ }
61
+
62
+ // 2. Determine Upload Context
63
+ uploadContext := ctx.PostForm("context")
64
+ if uploadContext == "" {
65
+ // Auto-infer context based on first file extension if not provided
66
+ ext := strings.ToLower(filepath.Ext(files[0].Filename))
67
+ uploadContext = c.inferContextFromExt(ext)
68
+ }
69
+
70
+ // 3. Get Account ID from Context (Middleware)
71
+ accountIDStr := ctx.GetString("account_id")
72
+ if accountIDStr == "" {
73
+ ctx.JSON(http.StatusUnauthorized, gin.H{
74
+ "status": "error",
75
+ "message": "Unauthorized: Missing account ID",
76
+ })
77
+ return
78
+ }
79
+
80
+ accountID, err := uuid.Parse(accountIDStr)
81
+ if err != nil {
82
+ ctx.JSON(http.StatusUnauthorized, gin.H{
83
+ "status": "error",
84
+ "message": "Unauthorized: Invalid UUID format",
85
+ })
86
+ return
87
+ }
88
+
89
+ // 4. Call Service
90
+ uploadedFiles, err := c.uploadService.UploadFiles(ctx, files, uploadContext, accountID)
91
+ if err != nil {
92
+ // Map Service Errors to HTTP Status
93
+ if errors.Is(err, http_error.FILE_TOO_LARGE) ||
94
+ errors.Is(err, http_error.INVALID_FILE_TYPE) ||
95
+ errors.Is(err, http_error.BAD_REQUEST_ERROR) {
96
+ ctx.JSON(http.StatusBadRequest, gin.H{
97
+ "status": "error",
98
+ "message": err.Error(),
99
+ })
100
+ return
101
+ }
102
+
103
+ if errors.Is(err, http_error.PARTIAL_UPLOAD_FAILURE) {
104
+ ctx.JSON(http.StatusUnprocessableEntity, gin.H{
105
+ "status": "error",
106
+ "message": err.Error(),
107
+ // Opsional: Anda bisa mengembalikan file yang berhasil di sini jika perlu
108
+ })
109
+ return
110
+ }
111
+
112
+ ctx.JSON(http.StatusInternalServerError, gin.H{
113
+ "status": "error",
114
+ "message": err.Error(),
115
+ })
116
+ return
117
+ }
118
+
119
+ // 5. Prepare Response DTO
120
+ var fileResponses []dto.FileResponse
121
+ for _, f := range uploadedFiles {
122
+ fileResponses = append(fileResponses, dto.FileResponse{
123
+ Id: f.Id,
124
+ OriginalName: f.OriginalName,
125
+ URL: f.Path,
126
+ MimeType: f.MimeType,
127
+ Size: f.Size,
128
+ CreatedAt: f.CreatedAt,
129
+ })
130
+ }
131
+
132
+ ctx.JSON(http.StatusCreated, dto.FileUploadResponse{
133
+ Status: "success",
134
+ Message: "Files uploaded successfully",
135
+ Data: fileResponses,
136
+ })
137
+ }
138
+
139
+ func (c *UploadController) GetFileByID(ctx *gin.Context) {
140
+ // 1. Validate Param ID
141
+ fileIDStr := ctx.Param("id")
142
+ fileID, err := uuid.Parse(fileIDStr)
143
+ if err != nil {
144
+ ctx.JSON(http.StatusBadRequest, gin.H{
145
+ "status": "error",
146
+ "message": "Invalid file ID format",
147
+ })
148
+ return
149
+ }
150
+
151
+ // 2. Validate Account ID
152
+ accountIDStr := ctx.GetString("account_id")
153
+ if accountIDStr == "" {
154
+ ctx.JSON(http.StatusUnauthorized, gin.H{
155
+ "status": "error",
156
+ "message": "Unauthorized: Missing account ID",
157
+ })
158
+ return
159
+ }
160
+
161
+ accountID, err := uuid.Parse(accountIDStr)
162
+ if err != nil {
163
+ ctx.JSON(http.StatusUnauthorized, gin.H{
164
+ "status": "error",
165
+ "message": "Unauthorized: Invalid UUID format",
166
+ })
167
+ return
168
+ }
169
+
170
+ // 3. Call Service
171
+ fileData, err := c.uploadService.GetFileByID(ctx, fileID, accountID)
172
+ if err != nil {
173
+ if errors.Is(err, http_error.NOT_FOUND_ERROR) {
174
+ ctx.JSON(http.StatusNotFound, gin.H{
175
+ "status": "error",
176
+ "message": "File not found or access denied",
177
+ })
178
+ return
179
+ }
180
+
181
+ ctx.JSON(http.StatusInternalServerError, gin.H{
182
+ "status": "error",
183
+ "message": err.Error(),
184
+ })
185
+ return
186
+ }
187
+
188
+ // 4. Response
189
+ response := dto.FileResponse{
190
+ Id: fileData.Id,
191
+ OriginalName: fileData.OriginalName,
192
+ URL: fileData.Path,
193
+ MimeType: fileData.MimeType,
194
+ Size: fileData.Size,
195
+ CreatedAt: fileData.CreatedAt,
196
+ }
197
+
198
+ ctx.JSON(http.StatusOK, dto.FileResponseSingle{
199
+ Status: "success",
200
+ Message: "File retrieved successfully",
201
+ Data: response,
202
+ })
203
+ }
204
+
205
+ // Helper untuk logika inferensi context (clean code)
206
+ func (c *UploadController) inferContextFromExt(ext string) string {
207
+ isSourceCode := map[string]bool{
208
+ ".cpp": true, ".c": true, ".py": true, ".java": true,
209
+ ".go": true, ".js": true, ".txt": true,
210
+ }
211
+ isDocument := map[string]bool{
212
+ ".pdf": true,
213
+ }
214
+
215
+ if isSourceCode[ext] {
216
+ return "submission"
217
+ }
218
+ if isDocument[ext] {
219
+ return "material"
220
+ }
221
+ return "general"
222
+ }
do_inject_config.ps1 CHANGED
@@ -1,435 +1,435 @@
1
- #Requires -Version 5.1
2
- <#
3
- .SYNOPSIS
4
- Automatic Dependency Injection Generator for Go Configuration
5
-
6
- .DESCRIPTION
7
- Scans ./config/ directory, discovers all config constructors, infers their dependencies,
8
- and generates provider/config_provider.go with full DI wiring. Special handling for
9
- chained dependencies where configs depend on other configs (e.g., DatabaseConfig depends on EnvConfig).
10
-
11
- .EXAMPLE
12
- .\config_injector.ps1
13
-
14
- .NOTES
15
- - Works with PowerShell 5.1+ and PowerShell 7+
16
- - No external dependencies required
17
- - Supports multi-line constructor signatures
18
- - Handles config-to-config dependencies with topological sorting
19
- - Special handling for method calls like envConfig.GetDatabaseHost()
20
- #>
21
-
22
- [CmdletBinding()]
23
- param()
24
-
25
- # Configuration
26
- $ConfigDir = "./config"
27
- $OutputFile = "provider/config_provider.go"
28
- $ModulePath = "abdanhafidz.com/go-boilerplate/config"
29
-
30
- # ANSI colors for better output
31
- $script:UseColors = $Host.UI.SupportsVirtualTerminal
32
- function Write-ColorOutput {
33
- param([string]$Message, [string]$Color = "White")
34
- if ($script:UseColors) {
35
- $colors = @{
36
- "Green" = "`e[32m"; "Yellow" = "`e[33m"; "Red" = "`e[31m"
37
- "Cyan" = "`e[36m"; "Blue" = "`e[34m"; "Reset" = "`e[0m"
38
- }
39
- Write-Host "$($colors[$Color])$Message$($colors['Reset'])"
40
- } else {
41
- Write-Host $Message
42
- }
43
- }
44
-
45
- # Data structures
46
- class ConfigInfo {
47
- [string]$ConstructorName # NewDatabaseConfig
48
- [string]$Domain # DatabaseConfig
49
- [string]$VarName # databaseConfig
50
- [System.Collections.Generic.List[Parameter]]$Parameters
51
- [System.Collections.Generic.List[string]]$ConfigDependencies
52
-
53
- ConfigInfo() {
54
- $this.Parameters = [System.Collections.Generic.List[Parameter]]::new()
55
- $this.ConfigDependencies = [System.Collections.Generic.List[string]]::new()
56
- }
57
- }
58
-
59
- class Parameter {
60
- [string]$Name
61
- [string]$RawType
62
- [string]$NormalizedType
63
- }
64
-
65
- function Get-LowerCamelCase {
66
- param([string]$Text)
67
- if ($Text.Length -eq 0) { return $Text }
68
- return $Text.Substring(0, 1).ToLower() + $Text.Substring(1)
69
- }
70
-
71
- function Normalize-TypeName {
72
- param([string]$TypeStr)
73
-
74
- # Remove leading pointer
75
- $cleaned = $TypeStr -replace '^\*+', ''
76
-
77
- # Remove package prefix (everything before last dot)
78
- if ($cleaned -match '\.([^.]+)$') {
79
- $cleaned = $matches[1]
80
- }
81
-
82
- return $cleaned.Trim()
83
- }
84
-
85
- function Parse-GoFiles {
86
- param([string]$Directory)
87
-
88
- Write-ColorOutput "Scanning for config constructors in $Directory..." "Cyan"
89
-
90
- if (-not (Test-Path $Directory)) {
91
- Write-ColorOutput "ERROR: Directory '$Directory' not found!" "Red"
92
- exit 1
93
- }
94
-
95
- $goFiles = Get-ChildItem -Path $Directory -Filter "*.go" -Recurse -File
96
- $configs = [System.Collections.Generic.List[ConfigInfo]]::new()
97
-
98
- foreach ($file in $goFiles) {
99
- $content = Get-Content $file.FullName -Raw
100
-
101
- # Match function signatures (support multi-line)
102
- # Pattern: func NewXxxConfig(...) XxxConfig
103
- $pattern = '(?ms)func\s+(New[a-zA-Z0-9]+Config)\s*\(([^)]*)\)\s+([a-zA-Z0-9*_.]+Config)'
104
- $matches = [regex]::Matches($content, $pattern)
105
-
106
- foreach ($match in $matches) {
107
- $constructorName = $match.Groups[1].Value
108
- $paramsStr = $match.Groups[2].Value
109
- $returnType = $match.Groups[3].Value
110
-
111
- # Extract domain name (XxxConfig)
112
- $domain = Normalize-TypeName $returnType
113
- $varName = Get-LowerCamelCase $domain
114
-
115
- $config = [ConfigInfo]::new()
116
- $config.ConstructorName = $constructorName
117
- $config.Domain = $domain
118
- $config.VarName = $varName
119
-
120
- # Parse parameters
121
- if ($paramsStr.Trim() -ne "") {
122
- # Split by comma, but be careful with nested types
123
- $paramList = $paramsStr -split ',\s*(?![^<>]*>)'
124
-
125
- foreach ($param in $paramList) {
126
- $param = $param.Trim()
127
- if ($param -eq "") { continue }
128
-
129
- # Split into name and type
130
- $parts = $param -split '\s+', 2
131
-
132
- $p = [Parameter]::new()
133
- if ($parts.Count -eq 2) {
134
- $p.Name = $parts[0]
135
- $p.RawType = $parts[1]
136
- } elseif ($parts.Count -eq 1) {
137
- # Anonymous parameter - synthesize name
138
- $p.Name = "param$($config.Parameters.Count)"
139
- $p.RawType = $parts[0]
140
- } else {
141
- continue
142
- }
143
-
144
- $p.NormalizedType = Normalize-TypeName $p.RawType
145
- $config.Parameters.Add($p)
146
-
147
- # Track config dependencies (configs that depend on other configs)
148
- if ($p.NormalizedType -match 'Config$') {
149
- $config.ConfigDependencies.Add($p.NormalizedType)
150
- }
151
- }
152
- }
153
-
154
- $configs.Add($config)
155
- Write-ColorOutput " Found: $constructorName" "Green"
156
- }
157
- }
158
-
159
- if ($configs.Count -eq 0) {
160
- Write-ColorOutput "No config constructors found matching pattern 'NewXxxConfig'!" "Red"
161
- exit 1
162
- }
163
-
164
- Write-ColorOutput "`nTotal configs discovered: $($configs.Count)" "Blue"
165
- return $configs
166
- }
167
-
168
- function Get-TopologicalOrder {
169
- param([System.Collections.Generic.List[ConfigInfo]]$Configs)
170
-
171
- Write-ColorOutput "`nBuilding dependency graph..." "Cyan"
172
-
173
- # Build adjacency list
174
- $graph = @{}
175
- $inDegree = @{}
176
- $domainToConfig = @{}
177
-
178
- foreach ($cfg in $Configs) {
179
- $graph[$cfg.Domain] = [System.Collections.Generic.List[string]]::new()
180
- $inDegree[$cfg.Domain] = 0
181
- $domainToConfig[$cfg.Domain] = $cfg
182
- }
183
-
184
- # Build edges (dependencies)
185
- foreach ($cfg in $Configs) {
186
- foreach ($dep in $cfg.ConfigDependencies) {
187
- if ($graph.ContainsKey($dep)) {
188
- $graph[$dep].Add($cfg.Domain)
189
- $inDegree[$cfg.Domain]++
190
- }
191
- }
192
- }
193
-
194
- # Kahn's algorithm for topological sort
195
- $queue = [System.Collections.Generic.Queue[string]]::new()
196
- foreach ($domain in $inDegree.Keys) {
197
- if ($inDegree[$domain] -eq 0) {
198
- $queue.Enqueue($domain)
199
- }
200
- }
201
-
202
- $sorted = [System.Collections.Generic.List[string]]::new()
203
-
204
- while ($queue.Count -gt 0) {
205
- $current = $queue.Dequeue()
206
- $sorted.Add($current)
207
-
208
- foreach ($neighbor in $graph[$current]) {
209
- $inDegree[$neighbor]--
210
- if ($inDegree[$neighbor] -eq 0) {
211
- $queue.Enqueue($neighbor)
212
- }
213
- }
214
- }
215
-
216
- # Check for cycles
217
- if ($sorted.Count -ne $Configs.Count) {
218
- $remaining = $inDegree.Keys | Where-Object { $inDegree[$_] -gt 0 }
219
- Write-ColorOutput "`nERROR: Circular dependency detected in configs!" "Red"
220
- Write-ColorOutput "Configs involved in cycle: $($remaining -join ', ')" "Yellow"
221
- exit 1
222
- }
223
-
224
- Write-ColorOutput " Dependency graph validated (no cycles)" "Green"
225
- Write-ColorOutput " Topological order: $($sorted -join ' -> ')" "Blue"
226
-
227
- # Return configs in topological order
228
- return $sorted | ForEach-Object { $domainToConfig[$_] }
229
- }
230
-
231
- function Resolve-ConfigArgument {
232
- param(
233
- [Parameter]$Param,
234
- [string]$DependentConfigVar
235
- )
236
-
237
- $type = $Param.NormalizedType
238
- $paramName = $Param.Name
239
-
240
- # SPECIAL CASE MAPPINGS FOR CONFIG
241
- # ============================================
242
-
243
- # 1. Config pattern: XxxConfig -> already instantiated config variable
244
- if ($type -match '^(.+)Config$') {
245
- $configVarName = Get-LowerCamelCase $type
246
- return $configVarName
247
- }
248
-
249
- # 2. String parameters - try to infer from parameter name and match with config getter methods
250
- if ($type -eq "string") {
251
- # Common patterns for EnvConfig getters
252
- $getterMappings = @{
253
- "host" = "GetDatabaseHost()"
254
- "databaseHost" = "GetDatabaseHost()"
255
- "user" = "GetDatabaseUser()"
256
- "databaseUser" = "GetDatabaseUser()"
257
- "password" = "GetDatabasePassword()"
258
- "databasePassword" = "GetDatabasePassword()"
259
- "name" = "GetDatabaseName()"
260
- "databaseName" = "GetDatabaseName()"
261
- "dbName" = "GetDatabaseName()"
262
- "port" = "GetDatabasePort()"
263
- "databasePort" = "GetDatabasePort()"
264
- "salt" = "GetSalt()"
265
- "secret" = "GetSecretKey()"
266
- "secretKey" = "GetSecretKey()"
267
- "jwtSecret" = "GetSecretKey()"
268
- "apiKey" = "GetAPIKey()"
269
- "timezone" = "GetTimezone()"
270
- }
271
-
272
- # Try to find matching getter
273
- foreach ($key in $getterMappings.Keys) {
274
- if ($paramName -like "*$key*") {
275
- return "envConfig.$($getterMappings[$key])"
276
- }
277
- }
278
-
279
- # If dependent on a config, try to construct getter name from param name
280
- if ($DependentConfigVar) {
281
- # Convert paramName to PascalCase for getter
282
- $getterName = (Get-Culture).TextInfo.ToTitleCase($paramName)
283
- $getterName = $getterName -replace '\s', ''
284
- return "${DependentConfigVar}.Get${getterName}()"
285
- }
286
- }
287
-
288
- # 3. Int/port parameters
289
- if ($type -eq "int" -or $type -eq "int32" -or $type -eq "int64") {
290
- if ($paramName -match "port") {
291
- return "envConfig.GetDatabasePort()"
292
- }
293
- }
294
-
295
- # ADD MORE SPECIAL CASES HERE:
296
- # --------------------------------------------
297
- # Example: Redis config
298
- # if ($paramName -match "redis") {
299
- # return "envConfig.GetRedisURL()"
300
- # }
301
- #
302
- # Example: Mail config
303
- # if ($paramName -match "smtp") {
304
- # return "envConfig.GetSMTPHost()"
305
- # }
306
- # --------------------------------------------
307
-
308
- # 4. Hardcoded constants (timezone example)
309
- if ($type -eq "string" -and $paramName -match "timezone|location") {
310
- return "`"Asia/Jakarta`""
311
- }
312
-
313
- # 5. Fallback: unresolved type
314
- return "/* TODO: provide $($Param.RawType) for $paramName */"
315
- }
316
-
317
- function Generate-ProviderCode {
318
- param([System.Collections.Generic.List[ConfigInfo]]$ConfigsInOrder)
319
-
320
- Write-ColorOutput "`nGenerating config provider code..." "Cyan"
321
-
322
- $sb = [System.Text.StringBuilder]::new()
323
- [void]$sb.AppendLine("package provider")
324
- [void]$sb.AppendLine()
325
- [void]$sb.AppendLine("import `"$ModulePath`"")
326
- [void]$sb.AppendLine()
327
-
328
- # Interface
329
- [void]$sb.AppendLine("type ConfigProvider interface {")
330
- foreach ($cfg in $ConfigsInOrder) {
331
- $line = "`tProvide$($cfg.Domain)() config.$($cfg.Domain)"
332
- [void]$sb.AppendLine($line)
333
- }
334
- [void]$sb.AppendLine("}")
335
- [void]$sb.AppendLine()
336
-
337
- # Struct
338
- [void]$sb.AppendLine("type configProvider struct {")
339
- foreach ($cfg in $ConfigsInOrder) {
340
- $line = "`t$($cfg.VarName) config.$($cfg.Domain)"
341
- [void]$sb.AppendLine($line)
342
- }
343
- [void]$sb.AppendLine("}")
344
- [void]$sb.AppendLine()
345
-
346
- # Constructor
347
- [void]$sb.AppendLine("func NewConfigProvider() ConfigProvider {")
348
-
349
- # Initialize configs in topological order
350
- foreach ($cfg in $ConfigsInOrder) {
351
- # Check if this config depends on another config
352
- $dependentConfigVar = $null
353
- if ($cfg.ConfigDependencies.Count -gt 0) {
354
- $dependentConfigVar = Get-LowerCamelCase $cfg.ConfigDependencies[0]
355
- }
356
-
357
- $args = @()
358
- foreach ($param in $cfg.Parameters) {
359
- $args += Resolve-ConfigArgument -Param $param -DependentConfigVar $dependentConfigVar
360
- }
361
- $argsStr = $args -join ", "
362
- $line = "`t$($cfg.VarName) := config.$($cfg.ConstructorName)($argsStr)"
363
- [void]$sb.AppendLine($line)
364
- }
365
-
366
- [void]$sb.AppendLine("`treturn &configProvider{")
367
- foreach ($cfg in $ConfigsInOrder) {
368
- $line = "`t`t$($cfg.VarName): $($cfg.VarName),"
369
- [void]$sb.AppendLine($line)
370
- }
371
- [void]$sb.AppendLine("`t}")
372
- [void]$sb.AppendLine("}")
373
- [void]$sb.AppendLine()
374
-
375
- # Getter methods
376
- foreach ($cfg in $ConfigsInOrder) {
377
- [void]$sb.AppendLine("func (c *configProvider) Provide$($cfg.Domain)() config.$($cfg.Domain) {")
378
- [void]$sb.AppendLine("`treturn c.$($cfg.VarName)")
379
- [void]$sb.AppendLine("}")
380
- [void]$sb.AppendLine()
381
- }
382
-
383
- return $sb.ToString()
384
- }
385
-
386
- function Write-ProviderFile {
387
- param([string]$Code, [string]$OutputPath)
388
-
389
- Write-ColorOutput "Writing to $OutputPath..." "Cyan"
390
-
391
- # Ensure directory exists
392
- $dir = Split-Path $OutputPath -Parent
393
- if ($dir -and -not (Test-Path $dir)) {
394
- New-Item -ItemType Directory -Path $dir -Force | Out-Null
395
- }
396
-
397
- # Write file as UTF-8 without BOM
398
- $utf8NoBom = [System.Text.UTF8Encoding]::new($false)
399
- [System.IO.File]::WriteAllText($OutputPath, $Code, $utf8NoBom)
400
-
401
- Write-ColorOutput " Successfully generated $OutputPath" "Green"
402
- }
403
-
404
- # ============================================
405
- # MAIN EXECUTION
406
- # ============================================
407
-
408
- try {
409
- Write-ColorOutput "`n=========================================" "Blue"
410
- Write-ColorOutput " Go Config Provider Generator v1.0" "Blue"
411
- Write-ColorOutput "=========================================`n" "Blue"
412
-
413
- # Step 1: Parse all config constructors
414
- $configs = Parse-GoFiles -Directory $ConfigDir
415
-
416
- # Step 2: Perform topological sort (configs can depend on other configs)
417
- $sortedConfigs = Get-TopologicalOrder -Configs $configs
418
-
419
- # Step 3: Generate provider code
420
- $code = Generate-ProviderCode -ConfigsInOrder $sortedConfigs
421
-
422
- # Step 4: Write to file
423
- Write-ProviderFile -Code $code -OutputPath $OutputFile
424
-
425
- Write-ColorOutput "`nSUCCESS! Config provider generated successfully.`n" "Green"
426
- Write-ColorOutput "Next steps:" "Cyan"
427
- Write-ColorOutput " 1. Review $OutputFile" "White"
428
- Write-ColorOutput " 2. Fill any /* TODO: provide ... */ placeholders" "White"
429
- Write-ColorOutput " 3. Run: go build ./provider" "White"
430
-
431
- } catch {
432
- Write-ColorOutput "`nERROR: $($_.Exception.Message)" "Red"
433
- Write-ColorOutput "Stack trace: $($_.ScriptStackTrace)" "Yellow"
434
- exit 1
435
  }
 
1
+ #Requires -Version 5.1
2
+ <#
3
+ .SYNOPSIS
4
+ Automatic Dependency Injection Generator for Go Configuration
5
+
6
+ .DESCRIPTION
7
+ Scans ./config/ directory, discovers all config constructors, infers their dependencies,
8
+ and generates provider/config_provider.go with full DI wiring. Special handling for
9
+ chained dependencies where configs depend on other configs (e.g., DatabaseConfig depends on EnvConfig).
10
+
11
+ .EXAMPLE
12
+ .\config_injector.ps1
13
+
14
+ .NOTES
15
+ - Works with PowerShell 5.1+ and PowerShell 7+
16
+ - No external dependencies required
17
+ - Supports multi-line constructor signatures
18
+ - Handles config-to-config dependencies with topological sorting
19
+ - Special handling for method calls like envConfig.GetDatabaseHost()
20
+ #>
21
+
22
+ [CmdletBinding()]
23
+ param()
24
+
25
+ # Configuration
26
+ $ConfigDir = "./config"
27
+ $OutputFile = "provider/config_provider.go"
28
+ $ModulePath = "abdanhafidz.com/go-boilerplate/config"
29
+
30
+ # ANSI colors for better output
31
+ $script:UseColors = $Host.UI.SupportsVirtualTerminal
32
+ function Write-ColorOutput {
33
+ param([string]$Message, [string]$Color = "White")
34
+ if ($script:UseColors) {
35
+ $colors = @{
36
+ "Green" = "`e[32m"; "Yellow" = "`e[33m"; "Red" = "`e[31m"
37
+ "Cyan" = "`e[36m"; "Blue" = "`e[34m"; "Reset" = "`e[0m"
38
+ }
39
+ Write-Host "$($colors[$Color])$Message$($colors['Reset'])"
40
+ } else {
41
+ Write-Host $Message
42
+ }
43
+ }
44
+
45
+ # Data structures
46
+ class ConfigInfo {
47
+ [string]$ConstructorName # NewDatabaseConfig
48
+ [string]$Domain # DatabaseConfig
49
+ [string]$VarName # databaseConfig
50
+ [System.Collections.Generic.List[Parameter]]$Parameters
51
+ [System.Collections.Generic.List[string]]$ConfigDependencies
52
+
53
+ ConfigInfo() {
54
+ $this.Parameters = [System.Collections.Generic.List[Parameter]]::new()
55
+ $this.ConfigDependencies = [System.Collections.Generic.List[string]]::new()
56
+ }
57
+ }
58
+
59
+ class Parameter {
60
+ [string]$Name
61
+ [string]$RawType
62
+ [string]$NormalizedType
63
+ }
64
+
65
+ function Get-LowerCamelCase {
66
+ param([string]$Text)
67
+ if ($Text.Length -eq 0) { return $Text }
68
+ return $Text.Substring(0, 1).ToLower() + $Text.Substring(1)
69
+ }
70
+
71
+ function Normalize-TypeName {
72
+ param([string]$TypeStr)
73
+
74
+ # Remove leading pointer
75
+ $cleaned = $TypeStr -replace '^\*+', ''
76
+
77
+ # Remove package prefix (everything before last dot)
78
+ if ($cleaned -match '\.([^.]+)$') {
79
+ $cleaned = $matches[1]
80
+ }
81
+
82
+ return $cleaned.Trim()
83
+ }
84
+
85
+ function Parse-GoFiles {
86
+ param([string]$Directory)
87
+
88
+ Write-ColorOutput "Scanning for config constructors in $Directory..." "Cyan"
89
+
90
+ if (-not (Test-Path $Directory)) {
91
+ Write-ColorOutput "ERROR: Directory '$Directory' not found!" "Red"
92
+ exit 1
93
+ }
94
+
95
+ $goFiles = Get-ChildItem -Path $Directory -Filter "*.go" -Recurse -File
96
+ $configs = [System.Collections.Generic.List[ConfigInfo]]::new()
97
+
98
+ foreach ($file in $goFiles) {
99
+ $content = Get-Content $file.FullName -Raw
100
+
101
+ # Match function signatures (support multi-line)
102
+ # Pattern: func NewXxxConfig(...) XxxConfig
103
+ $pattern = '(?ms)func\s+(New[a-zA-Z0-9]+Config)\s*\(([^)]*)\)\s+([a-zA-Z0-9*_.]+Config)'
104
+ $matches = [regex]::Matches($content, $pattern)
105
+
106
+ foreach ($match in $matches) {
107
+ $constructorName = $match.Groups[1].Value
108
+ $paramsStr = $match.Groups[2].Value
109
+ $returnType = $match.Groups[3].Value
110
+
111
+ # Extract domain name (XxxConfig)
112
+ $domain = Normalize-TypeName $returnType
113
+ $varName = Get-LowerCamelCase $domain
114
+
115
+ $config = [ConfigInfo]::new()
116
+ $config.ConstructorName = $constructorName
117
+ $config.Domain = $domain
118
+ $config.VarName = $varName
119
+
120
+ # Parse parameters
121
+ if ($paramsStr.Trim() -ne "") {
122
+ # Split by comma, but be careful with nested types
123
+ $paramList = $paramsStr -split ',\s*(?![^<>]*>)'
124
+
125
+ foreach ($param in $paramList) {
126
+ $param = $param.Trim()
127
+ if ($param -eq "") { continue }
128
+
129
+ # Split into name and type
130
+ $parts = $param -split '\s+', 2
131
+
132
+ $p = [Parameter]::new()
133
+ if ($parts.Count -eq 2) {
134
+ $p.Name = $parts[0]
135
+ $p.RawType = $parts[1]
136
+ } elseif ($parts.Count -eq 1) {
137
+ # Anonymous parameter - synthesize name
138
+ $p.Name = "param$($config.Parameters.Count)"
139
+ $p.RawType = $parts[0]
140
+ } else {
141
+ continue
142
+ }
143
+
144
+ $p.NormalizedType = Normalize-TypeName $p.RawType
145
+ $config.Parameters.Add($p)
146
+
147
+ # Track config dependencies (configs that depend on other configs)
148
+ if ($p.NormalizedType -match 'Config$') {
149
+ $config.ConfigDependencies.Add($p.NormalizedType)
150
+ }
151
+ }
152
+ }
153
+
154
+ $configs.Add($config)
155
+ Write-ColorOutput " Found: $constructorName" "Green"
156
+ }
157
+ }
158
+
159
+ if ($configs.Count -eq 0) {
160
+ Write-ColorOutput "No config constructors found matching pattern 'NewXxxConfig'!" "Red"
161
+ exit 1
162
+ }
163
+
164
+ Write-ColorOutput "`nTotal configs discovered: $($configs.Count)" "Blue"
165
+ return $configs
166
+ }
167
+
168
+ function Get-TopologicalOrder {
169
+ param([System.Collections.Generic.List[ConfigInfo]]$Configs)
170
+
171
+ Write-ColorOutput "`nBuilding dependency graph..." "Cyan"
172
+
173
+ # Build adjacency list
174
+ $graph = @{}
175
+ $inDegree = @{}
176
+ $domainToConfig = @{}
177
+
178
+ foreach ($cfg in $Configs) {
179
+ $graph[$cfg.Domain] = [System.Collections.Generic.List[string]]::new()
180
+ $inDegree[$cfg.Domain] = 0
181
+ $domainToConfig[$cfg.Domain] = $cfg
182
+ }
183
+
184
+ # Build edges (dependencies)
185
+ foreach ($cfg in $Configs) {
186
+ foreach ($dep in $cfg.ConfigDependencies) {
187
+ if ($graph.ContainsKey($dep)) {
188
+ $graph[$dep].Add($cfg.Domain)
189
+ $inDegree[$cfg.Domain]++
190
+ }
191
+ }
192
+ }
193
+
194
+ # Kahn's algorithm for topological sort
195
+ $queue = [System.Collections.Generic.Queue[string]]::new()
196
+ foreach ($domain in $inDegree.Keys) {
197
+ if ($inDegree[$domain] -eq 0) {
198
+ $queue.Enqueue($domain)
199
+ }
200
+ }
201
+
202
+ $sorted = [System.Collections.Generic.List[string]]::new()
203
+
204
+ while ($queue.Count -gt 0) {
205
+ $current = $queue.Dequeue()
206
+ $sorted.Add($current)
207
+
208
+ foreach ($neighbor in $graph[$current]) {
209
+ $inDegree[$neighbor]--
210
+ if ($inDegree[$neighbor] -eq 0) {
211
+ $queue.Enqueue($neighbor)
212
+ }
213
+ }
214
+ }
215
+
216
+ # Check for cycles
217
+ if ($sorted.Count -ne $Configs.Count) {
218
+ $remaining = $inDegree.Keys | Where-Object { $inDegree[$_] -gt 0 }
219
+ Write-ColorOutput "`nERROR: Circular dependency detected in configs!" "Red"
220
+ Write-ColorOutput "Configs involved in cycle: $($remaining -join ', ')" "Yellow"
221
+ exit 1
222
+ }
223
+
224
+ Write-ColorOutput " Dependency graph validated (no cycles)" "Green"
225
+ Write-ColorOutput " Topological order: $($sorted -join ' -> ')" "Blue"
226
+
227
+ # Return configs in topological order
228
+ return $sorted | ForEach-Object { $domainToConfig[$_] }
229
+ }
230
+
231
+ function Resolve-ConfigArgument {
232
+ param(
233
+ [Parameter]$Param,
234
+ [string]$DependentConfigVar
235
+ )
236
+
237
+ $type = $Param.NormalizedType
238
+ $paramName = $Param.Name
239
+
240
+ # SPECIAL CASE MAPPINGS FOR CONFIG
241
+ # ============================================
242
+
243
+ # 1. Config pattern: XxxConfig -> already instantiated config variable
244
+ if ($type -match '^(.+)Config$') {
245
+ $configVarName = Get-LowerCamelCase $type
246
+ return $configVarName
247
+ }
248
+
249
+ # 2. String parameters - try to infer from parameter name and match with config getter methods
250
+ if ($type -eq "string") {
251
+ # Common patterns for EnvConfig getters
252
+ $getterMappings = @{
253
+ "host" = "GetDatabaseHost()"
254
+ "databaseHost" = "GetDatabaseHost()"
255
+ "user" = "GetDatabaseUser()"
256
+ "databaseUser" = "GetDatabaseUser()"
257
+ "password" = "GetDatabasePassword()"
258
+ "databasePassword" = "GetDatabasePassword()"
259
+ "name" = "GetDatabaseName()"
260
+ "databaseName" = "GetDatabaseName()"
261
+ "dbName" = "GetDatabaseName()"
262
+ "port" = "GetDatabasePort()"
263
+ "databasePort" = "GetDatabasePort()"
264
+ "salt" = "GetSalt()"
265
+ "secret" = "GetSecretKey()"
266
+ "secretKey" = "GetSecretKey()"
267
+ "jwtSecret" = "GetSecretKey()"
268
+ "apiKey" = "GetAPIKey()"
269
+ "timezone" = "GetTimezone()"
270
+ }
271
+
272
+ # Try to find matching getter
273
+ foreach ($key in $getterMappings.Keys) {
274
+ if ($paramName -like "*$key*") {
275
+ return "envConfig.$($getterMappings[$key])"
276
+ }
277
+ }
278
+
279
+ # If dependent on a config, try to construct getter name from param name
280
+ if ($DependentConfigVar) {
281
+ # Convert paramName to PascalCase for getter
282
+ $getterName = (Get-Culture).TextInfo.ToTitleCase($paramName)
283
+ $getterName = $getterName -replace '\s', ''
284
+ return "${DependentConfigVar}.Get${getterName}()"
285
+ }
286
+ }
287
+
288
+ # 3. Int/port parameters
289
+ if ($type -eq "int" -or $type -eq "int32" -or $type -eq "int64") {
290
+ if ($paramName -match "port") {
291
+ return "envConfig.GetDatabasePort()"
292
+ }
293
+ }
294
+
295
+ # ADD MORE SPECIAL CASES HERE:
296
+ # --------------------------------------------
297
+ # Example: Redis config
298
+ # if ($paramName -match "redis") {
299
+ # return "envConfig.GetRedisURL()"
300
+ # }
301
+ #
302
+ # Example: Mail config
303
+ # if ($paramName -match "smtp") {
304
+ # return "envConfig.GetSMTPHost()"
305
+ # }
306
+ # --------------------------------------------
307
+
308
+ # 4. Hardcoded constants (timezone example)
309
+ if ($type -eq "string" -and $paramName -match "timezone|location") {
310
+ return "`"Asia/Jakarta`""
311
+ }
312
+
313
+ # 5. Fallback: unresolved type
314
+ return "/* TODO: provide $($Param.RawType) for $paramName */"
315
+ }
316
+
317
+ function Generate-ProviderCode {
318
+ param([System.Collections.Generic.List[ConfigInfo]]$ConfigsInOrder)
319
+
320
+ Write-ColorOutput "`nGenerating config provider code..." "Cyan"
321
+
322
+ $sb = [System.Text.StringBuilder]::new()
323
+ [void]$sb.AppendLine("package provider")
324
+ [void]$sb.AppendLine()
325
+ [void]$sb.AppendLine("import `"$ModulePath`"")
326
+ [void]$sb.AppendLine()
327
+
328
+ # Interface
329
+ [void]$sb.AppendLine("type ConfigProvider interface {")
330
+ foreach ($cfg in $ConfigsInOrder) {
331
+ $line = "`tProvide$($cfg.Domain)() config.$($cfg.Domain)"
332
+ [void]$sb.AppendLine($line)
333
+ }
334
+ [void]$sb.AppendLine("}")
335
+ [void]$sb.AppendLine()
336
+
337
+ # Struct
338
+ [void]$sb.AppendLine("type configProvider struct {")
339
+ foreach ($cfg in $ConfigsInOrder) {
340
+ $line = "`t$($cfg.VarName) config.$($cfg.Domain)"
341
+ [void]$sb.AppendLine($line)
342
+ }
343
+ [void]$sb.AppendLine("}")
344
+ [void]$sb.AppendLine()
345
+
346
+ # Constructor
347
+ [void]$sb.AppendLine("func NewConfigProvider() ConfigProvider {")
348
+
349
+ # Initialize configs in topological order
350
+ foreach ($cfg in $ConfigsInOrder) {
351
+ # Check if this config depends on another config
352
+ $dependentConfigVar = $null
353
+ if ($cfg.ConfigDependencies.Count -gt 0) {
354
+ $dependentConfigVar = Get-LowerCamelCase $cfg.ConfigDependencies[0]
355
+ }
356
+
357
+ $args = @()
358
+ foreach ($param in $cfg.Parameters) {
359
+ $args += Resolve-ConfigArgument -Param $param -DependentConfigVar $dependentConfigVar
360
+ }
361
+ $argsStr = $args -join ", "
362
+ $line = "`t$($cfg.VarName) := config.$($cfg.ConstructorName)($argsStr)"
363
+ [void]$sb.AppendLine($line)
364
+ }
365
+
366
+ [void]$sb.AppendLine("`treturn &configProvider{")
367
+ foreach ($cfg in $ConfigsInOrder) {
368
+ $line = "`t`t$($cfg.VarName): $($cfg.VarName),"
369
+ [void]$sb.AppendLine($line)
370
+ }
371
+ [void]$sb.AppendLine("`t}")
372
+ [void]$sb.AppendLine("}")
373
+ [void]$sb.AppendLine()
374
+
375
+ # Getter methods
376
+ foreach ($cfg in $ConfigsInOrder) {
377
+ [void]$sb.AppendLine("func (c *configProvider) Provide$($cfg.Domain)() config.$($cfg.Domain) {")
378
+ [void]$sb.AppendLine("`treturn c.$($cfg.VarName)")
379
+ [void]$sb.AppendLine("}")
380
+ [void]$sb.AppendLine()
381
+ }
382
+
383
+ return $sb.ToString()
384
+ }
385
+
386
+ function Write-ProviderFile {
387
+ param([string]$Code, [string]$OutputPath)
388
+
389
+ Write-ColorOutput "Writing to $OutputPath..." "Cyan"
390
+
391
+ # Ensure directory exists
392
+ $dir = Split-Path $OutputPath -Parent
393
+ if ($dir -and -not (Test-Path $dir)) {
394
+ New-Item -ItemType Directory -Path $dir -Force | Out-Null
395
+ }
396
+
397
+ # Write file as UTF-8 without BOM
398
+ $utf8NoBom = [System.Text.UTF8Encoding]::new($false)
399
+ [System.IO.File]::WriteAllText($OutputPath, $Code, $utf8NoBom)
400
+
401
+ Write-ColorOutput " Successfully generated $OutputPath" "Green"
402
+ }
403
+
404
+ # ============================================
405
+ # MAIN EXECUTION
406
+ # ============================================
407
+
408
+ try {
409
+ Write-ColorOutput "`n=========================================" "Blue"
410
+ Write-ColorOutput " Go Config Provider Generator v1.0" "Blue"
411
+ Write-ColorOutput "=========================================`n" "Blue"
412
+
413
+ # Step 1: Parse all config constructors
414
+ $configs = Parse-GoFiles -Directory $ConfigDir
415
+
416
+ # Step 2: Perform topological sort (configs can depend on other configs)
417
+ $sortedConfigs = Get-TopologicalOrder -Configs $configs
418
+
419
+ # Step 3: Generate provider code
420
+ $code = Generate-ProviderCode -ConfigsInOrder $sortedConfigs
421
+
422
+ # Step 4: Write to file
423
+ Write-ProviderFile -Code $code -OutputPath $OutputFile
424
+
425
+ Write-ColorOutput "`nSUCCESS! Config provider generated successfully.`n" "Green"
426
+ Write-ColorOutput "Next steps:" "Cyan"
427
+ Write-ColorOutput " 1. Review $OutputFile" "White"
428
+ Write-ColorOutput " 2. Fill any /* TODO: provide ... */ placeholders" "White"
429
+ Write-ColorOutput " 3. Run: go build ./provider" "White"
430
+
431
+ } catch {
432
+ Write-ColorOutput "`nERROR: $($_.Exception.Message)" "Red"
433
+ Write-ColorOutput "Stack trace: $($_.ScriptStackTrace)" "Yellow"
434
+ exit 1
435
  }
do_inject_controllers.ps1 CHANGED
@@ -1,310 +1,310 @@
1
- #Requires -Version 5.1
2
- <#
3
- .SYNOPSIS
4
- Automatic Dependency Injection Generator for Go Controllers
5
-
6
- .DESCRIPTION
7
- Scans ./controllers/ directory, discovers all controller constructors, infers their dependencies,
8
- and generates provider/controller_provider.go with full DI wiring.
9
-
10
- .EXAMPLE
11
- .\controller_injector.ps1
12
-
13
- .NOTES
14
- - Works with PowerShell 5.1+ and PowerShell 7+
15
- - No external dependencies required
16
- - Supports multi-line constructor signatures
17
- - Controllers depend on services from ServicesProvider
18
- #>
19
-
20
- [CmdletBinding()]
21
- param()
22
-
23
- # Configuration
24
- $ControllersDir = "./controllers"
25
- $OutputFile = "provider/controller_provider.go"
26
- $ModulePath = "abdanhafidz.com/go-boilerplate/controllers"
27
-
28
- # ANSI colors for better output
29
- $script:UseColors = $Host.UI.SupportsVirtualTerminal
30
- function Write-ColorOutput {
31
- param([string]$Message, [string]$Color = "White")
32
- if ($script:UseColors) {
33
- $colors = @{
34
- "Green" = "`e[32m"; "Yellow" = "`e[33m"; "Red" = "`e[31m"
35
- "Cyan" = "`e[36m"; "Blue" = "`e[34m"; "Reset" = "`e[0m"
36
- }
37
- Write-Host "$($colors[$Color])$Message$($colors['Reset'])"
38
- } else {
39
- Write-Host $Message
40
- }
41
- }
42
-
43
- # Data structures
44
- class ControllerInfo {
45
- [string]$ConstructorName # NewAccountController
46
- [string]$Domain # AccountController
47
- [string]$VarName # accountController
48
- [System.Collections.Generic.List[Parameter]]$Parameters
49
-
50
- ControllerInfo() {
51
- $this.Parameters = [System.Collections.Generic.List[Parameter]]::new()
52
- }
53
- }
54
-
55
- class Parameter {
56
- [string]$Name
57
- [string]$RawType
58
- [string]$NormalizedType
59
- }
60
-
61
- function Get-LowerCamelCase {
62
- param([string]$Text)
63
- if ($Text.Length -eq 0) { return $Text }
64
- return $Text.Substring(0, 1).ToLower() + $Text.Substring(1)
65
- }
66
-
67
- function Normalize-TypeName {
68
- param([string]$TypeStr)
69
-
70
- # Remove leading pointer
71
- $cleaned = $TypeStr -replace '^\*+', ''
72
-
73
- # Remove package prefix (everything before last dot)
74
- if ($cleaned -match '\.([^.]+)$') {
75
- $cleaned = $matches[1]
76
- }
77
-
78
- return $cleaned.Trim()
79
- }
80
-
81
- function Parse-GoFiles {
82
- param([string]$Directory)
83
-
84
- Write-ColorOutput "Scanning for controller constructors in $Directory..." "Cyan"
85
-
86
- if (-not (Test-Path $Directory)) {
87
- Write-ColorOutput "ERROR: Directory '$Directory' not found!" "Red"
88
- exit 1
89
- }
90
-
91
- $goFiles = Get-ChildItem -Path $Directory -Filter "*.go" -Recurse -File
92
- $controllers = [System.Collections.Generic.List[ControllerInfo]]::new()
93
-
94
- foreach ($file in $goFiles) {
95
- $content = Get-Content $file.FullName -Raw
96
-
97
- # Match function signatures (support multi-line)
98
- # Pattern: func NewXxxController(...) XxxController
99
- $pattern = '(?ms)func\s+(New[a-zA-Z0-9]+Controller)\s*\(([^)]*)\)\s+([a-zA-Z0-9*_.]+Controller)'
100
- $matches = [regex]::Matches($content, $pattern)
101
-
102
- foreach ($match in $matches) {
103
- $constructorName = $match.Groups[1].Value
104
- $paramsStr = $match.Groups[2].Value
105
- $returnType = $match.Groups[3].Value
106
-
107
- # Extract domain name (XxxController)
108
- $domain = Normalize-TypeName $returnType
109
- $varName = Get-LowerCamelCase $domain
110
-
111
- $controller = [ControllerInfo]::new()
112
- $controller.ConstructorName = $constructorName
113
- $controller.Domain = $domain
114
- $controller.VarName = $varName
115
-
116
- # Parse parameters
117
- if ($paramsStr.Trim() -ne "") {
118
- # Split by comma, but be careful with nested types
119
- $paramList = $paramsStr -split ',\s*(?![^<>]*>)'
120
-
121
- foreach ($param in $paramList) {
122
- $param = $param.Trim()
123
- if ($param -eq "") { continue }
124
-
125
- # Split into name and type
126
- $parts = $param -split '\s+', 2
127
-
128
- $p = [Parameter]::new()
129
- if ($parts.Count -eq 2) {
130
- $p.Name = $parts[0]
131
- $p.RawType = $parts[1]
132
- } elseif ($parts.Count -eq 1) {
133
- # Anonymous parameter - synthesize name
134
- $p.Name = "param$($controller.Parameters.Count)"
135
- $p.RawType = $parts[0]
136
- } else {
137
- continue
138
- }
139
-
140
- $p.NormalizedType = Normalize-TypeName $p.RawType
141
- $controller.Parameters.Add($p)
142
- }
143
- }
144
-
145
- $controllers.Add($controller)
146
- Write-ColorOutput " Found: $constructorName" "Green"
147
- }
148
- }
149
-
150
- if ($controllers.Count -eq 0) {
151
- Write-ColorOutput "No controller constructors found matching pattern 'NewXxxController'!" "Red"
152
- exit 1
153
- }
154
-
155
- Write-ColorOutput "`nTotal controllers discovered: $($controllers.Count)" "Blue"
156
- return $controllers
157
- }
158
-
159
- function Resolve-ControllerArgument {
160
- param([Parameter]$Param)
161
-
162
- $type = $Param.NormalizedType
163
-
164
- # DEPENDENCY RESOLUTION RULES
165
- # ============================================
166
-
167
- # 1. Service pattern: XxxxService -> servicesProvider.ProvideXxxxService()
168
- if ($type -match '^(.+)Service$') {
169
- $serviceName = $type
170
- return "servicesProvider.Provide${serviceName}()"
171
- }
172
-
173
- # ADD MORE SPECIAL CASES HERE:
174
- # --------------------------------------------
175
- # Example: Config dependency
176
- # if ($type -eq "Config") {
177
- # return "configProvider.ProvideConfig()"
178
- # }
179
- #
180
- # Example: Logger
181
- # if ($type -eq "Logger") {
182
- # return "loggerProvider.ProvideLogger()"
183
- # }
184
- #
185
- # Example: Validator
186
- # if ($type -eq "Validator") {
187
- # return "validatorProvider.ProvideValidator()"
188
- # }
189
- # --------------------------------------------
190
-
191
- # 2. Fallback: unresolved type
192
- return "/* TODO: provide $($Param.RawType) */"
193
- }
194
-
195
- function Generate-ProviderCode {
196
- param([System.Collections.Generic.List[ControllerInfo]]$Controllers)
197
-
198
- Write-ColorOutput "`nGenerating controller provider code..." "Cyan"
199
-
200
- # Sort controllers alphabetically for consistent output
201
- $sortedControllers = $Controllers | Sort-Object -Property Domain
202
-
203
- $sb = [System.Text.StringBuilder]::new()
204
- [void]$sb.AppendLine("package provider")
205
- [void]$sb.AppendLine()
206
- [void]$sb.AppendLine("import `"$ModulePath`"")
207
- [void]$sb.AppendLine()
208
-
209
- # Interface
210
- [void]$sb.AppendLine("type ControllerProvider interface {")
211
- foreach ($ctrl in $sortedControllers) {
212
- $line = "`tProvide$($ctrl.Domain)() controllers.$($ctrl.Domain)"
213
- [void]$sb.AppendLine($line)
214
- }
215
- [void]$sb.AppendLine("}")
216
- [void]$sb.AppendLine()
217
-
218
- # Struct
219
- [void]$sb.AppendLine("type controllerProvider struct {")
220
- foreach ($ctrl in $sortedControllers) {
221
- $line = "`t$($ctrl.VarName) controllers.$($ctrl.Domain)"
222
- [void]$sb.AppendLine($line)
223
- }
224
- [void]$sb.AppendLine("}")
225
- [void]$sb.AppendLine()
226
-
227
- # Constructor
228
- [void]$sb.AppendLine("func NewControllerProvider(servicesProvider ServicesProvider) ControllerProvider {")
229
- [void]$sb.AppendLine()
230
-
231
- # Initialize controllers
232
- foreach ($ctrl in $sortedControllers) {
233
- $args = @()
234
- foreach ($param in $ctrl.Parameters) {
235
- $args += Resolve-ControllerArgument $param
236
- }
237
- $argsStr = $args -join ", "
238
- $line = "`t$($ctrl.VarName) := controllers.$($ctrl.ConstructorName)($argsStr)"
239
- [void]$sb.AppendLine($line)
240
- }
241
-
242
- [void]$sb.AppendLine("`treturn &controllerProvider{")
243
- foreach ($ctrl in $sortedControllers) {
244
- $line = "`t`t$($ctrl.VarName): $($ctrl.VarName),"
245
- [void]$sb.AppendLine($line)
246
- }
247
- [void]$sb.AppendLine("`t}")
248
- [void]$sb.AppendLine("}")
249
- [void]$sb.AppendLine()
250
-
251
- # Getter methods
252
- [void]$sb.AppendLine("// --- Getter Methods ---")
253
- [void]$sb.AppendLine()
254
- foreach ($ctrl in $sortedControllers) {
255
- [void]$sb.AppendLine("func (c *controllerProvider) Provide$($ctrl.Domain)() controllers.$($ctrl.Domain) {")
256
- [void]$sb.AppendLine("`treturn c.$($ctrl.VarName)")
257
- [void]$sb.AppendLine("}")
258
- [void]$sb.AppendLine()
259
- }
260
-
261
- return $sb.ToString()
262
- }
263
-
264
- function Write-ProviderFile {
265
- param([string]$Code, [string]$OutputPath)
266
-
267
- Write-ColorOutput "Writing to $OutputPath..." "Cyan"
268
-
269
- # Ensure directory exists
270
- $dir = Split-Path $OutputPath -Parent
271
- if ($dir -and -not (Test-Path $dir)) {
272
- New-Item -ItemType Directory -Path $dir -Force | Out-Null
273
- }
274
-
275
- # Write file as UTF-8 without BOM
276
- $utf8NoBom = [System.Text.UTF8Encoding]::new($false)
277
- [System.IO.File]::WriteAllText($OutputPath, $Code, $utf8NoBom)
278
-
279
- Write-ColorOutput " Successfully generated $OutputPath" "Green"
280
- }
281
-
282
- # ============================================
283
- # MAIN EXECUTION
284
- # ============================================
285
-
286
- try {
287
- Write-ColorOutput "`n=========================================" "Blue"
288
- Write-ColorOutput " Go Controller Provider Generator v1.0" "Blue"
289
- Write-ColorOutput "=========================================`n" "Blue"
290
-
291
- # Step 1: Parse all controller constructors
292
- $controllers = Parse-GoFiles -Directory $ControllersDir
293
-
294
- # Step 2: Generate provider code
295
- $code = Generate-ProviderCode -Controllers $controllers
296
-
297
- # Step 3: Write to file
298
- Write-ProviderFile -Code $code -OutputPath $OutputFile
299
-
300
- Write-ColorOutput "`nSUCCESS! Controller provider generated successfully.`n" "Green"
301
- Write-ColorOutput "Next steps:" "Cyan"
302
- Write-ColorOutput " 1. Review $OutputFile" "White"
303
- Write-ColorOutput " 2. Fill any /* TODO: provide ... */ placeholders" "White"
304
- Write-ColorOutput " 3. Run: go build ./provider" "White"
305
-
306
- } catch {
307
- Write-ColorOutput "`nERROR: $($_.Exception.Message)" "Red"
308
- Write-ColorOutput "Stack trace: $($_.ScriptStackTrace)" "Yellow"
309
- exit 1
310
  }
 
1
+ #Requires -Version 5.1
2
+ <#
3
+ .SYNOPSIS
4
+ Automatic Dependency Injection Generator for Go Controllers
5
+
6
+ .DESCRIPTION
7
+ Scans ./controllers/ directory, discovers all controller constructors, infers their dependencies,
8
+ and generates provider/controller_provider.go with full DI wiring.
9
+
10
+ .EXAMPLE
11
+ .\controller_injector.ps1
12
+
13
+ .NOTES
14
+ - Works with PowerShell 5.1+ and PowerShell 7+
15
+ - No external dependencies required
16
+ - Supports multi-line constructor signatures
17
+ - Controllers depend on services from ServicesProvider
18
+ #>
19
+
20
+ [CmdletBinding()]
21
+ param()
22
+
23
+ # Configuration
24
+ $ControllersDir = "./controllers"
25
+ $OutputFile = "provider/controller_provider.go"
26
+ $ModulePath = "abdanhafidz.com/go-boilerplate/controllers"
27
+
28
+ # ANSI colors for better output
29
+ $script:UseColors = $Host.UI.SupportsVirtualTerminal
30
+ function Write-ColorOutput {
31
+ param([string]$Message, [string]$Color = "White")
32
+ if ($script:UseColors) {
33
+ $colors = @{
34
+ "Green" = "`e[32m"; "Yellow" = "`e[33m"; "Red" = "`e[31m"
35
+ "Cyan" = "`e[36m"; "Blue" = "`e[34m"; "Reset" = "`e[0m"
36
+ }
37
+ Write-Host "$($colors[$Color])$Message$($colors['Reset'])"
38
+ } else {
39
+ Write-Host $Message
40
+ }
41
+ }
42
+
43
+ # Data structures
44
+ class ControllerInfo {
45
+ [string]$ConstructorName # NewAccountController
46
+ [string]$Domain # AccountController
47
+ [string]$VarName # accountController
48
+ [System.Collections.Generic.List[Parameter]]$Parameters
49
+
50
+ ControllerInfo() {
51
+ $this.Parameters = [System.Collections.Generic.List[Parameter]]::new()
52
+ }
53
+ }
54
+
55
+ class Parameter {
56
+ [string]$Name
57
+ [string]$RawType
58
+ [string]$NormalizedType
59
+ }
60
+
61
+ function Get-LowerCamelCase {
62
+ param([string]$Text)
63
+ if ($Text.Length -eq 0) { return $Text }
64
+ return $Text.Substring(0, 1).ToLower() + $Text.Substring(1)
65
+ }
66
+
67
+ function Normalize-TypeName {
68
+ param([string]$TypeStr)
69
+
70
+ # Remove leading pointer
71
+ $cleaned = $TypeStr -replace '^\*+', ''
72
+
73
+ # Remove package prefix (everything before last dot)
74
+ if ($cleaned -match '\.([^.]+)$') {
75
+ $cleaned = $matches[1]
76
+ }
77
+
78
+ return $cleaned.Trim()
79
+ }
80
+
81
+ function Parse-GoFiles {
82
+ param([string]$Directory)
83
+
84
+ Write-ColorOutput "Scanning for controller constructors in $Directory..." "Cyan"
85
+
86
+ if (-not (Test-Path $Directory)) {
87
+ Write-ColorOutput "ERROR: Directory '$Directory' not found!" "Red"
88
+ exit 1
89
+ }
90
+
91
+ $goFiles = Get-ChildItem -Path $Directory -Filter "*.go" -Recurse -File
92
+ $controllers = [System.Collections.Generic.List[ControllerInfo]]::new()
93
+
94
+ foreach ($file in $goFiles) {
95
+ $content = Get-Content $file.FullName -Raw
96
+
97
+ # Match function signatures (support multi-line)
98
+ # Pattern: func NewXxxController(...) XxxController
99
+ $pattern = '(?ms)func\s+(New[a-zA-Z0-9]+Controller)\s*\(([^)]*)\)\s+([a-zA-Z0-9*_.]+Controller)'
100
+ $matches = [regex]::Matches($content, $pattern)
101
+
102
+ foreach ($match in $matches) {
103
+ $constructorName = $match.Groups[1].Value
104
+ $paramsStr = $match.Groups[2].Value
105
+ $returnType = $match.Groups[3].Value
106
+
107
+ # Extract domain name (XxxController)
108
+ $domain = Normalize-TypeName $returnType
109
+ $varName = Get-LowerCamelCase $domain
110
+
111
+ $controller = [ControllerInfo]::new()
112
+ $controller.ConstructorName = $constructorName
113
+ $controller.Domain = $domain
114
+ $controller.VarName = $varName
115
+
116
+ # Parse parameters
117
+ if ($paramsStr.Trim() -ne "") {
118
+ # Split by comma, but be careful with nested types
119
+ $paramList = $paramsStr -split ',\s*(?![^<>]*>)'
120
+
121
+ foreach ($param in $paramList) {
122
+ $param = $param.Trim()
123
+ if ($param -eq "") { continue }
124
+
125
+ # Split into name and type
126
+ $parts = $param -split '\s+', 2
127
+
128
+ $p = [Parameter]::new()
129
+ if ($parts.Count -eq 2) {
130
+ $p.Name = $parts[0]
131
+ $p.RawType = $parts[1]
132
+ } elseif ($parts.Count -eq 1) {
133
+ # Anonymous parameter - synthesize name
134
+ $p.Name = "param$($controller.Parameters.Count)"
135
+ $p.RawType = $parts[0]
136
+ } else {
137
+ continue
138
+ }
139
+
140
+ $p.NormalizedType = Normalize-TypeName $p.RawType
141
+ $controller.Parameters.Add($p)
142
+ }
143
+ }
144
+
145
+ $controllers.Add($controller)
146
+ Write-ColorOutput " Found: $constructorName" "Green"
147
+ }
148
+ }
149
+
150
+ if ($controllers.Count -eq 0) {
151
+ Write-ColorOutput "No controller constructors found matching pattern 'NewXxxController'!" "Red"
152
+ exit 1
153
+ }
154
+
155
+ Write-ColorOutput "`nTotal controllers discovered: $($controllers.Count)" "Blue"
156
+ return $controllers
157
+ }
158
+
159
+ function Resolve-ControllerArgument {
160
+ param([Parameter]$Param)
161
+
162
+ $type = $Param.NormalizedType
163
+
164
+ # DEPENDENCY RESOLUTION RULES
165
+ # ============================================
166
+
167
+ # 1. Service pattern: XxxxService -> servicesProvider.ProvideXxxxService()
168
+ if ($type -match '^(.+)Service$') {
169
+ $serviceName = $type
170
+ return "servicesProvider.Provide${serviceName}()"
171
+ }
172
+
173
+ # ADD MORE SPECIAL CASES HERE:
174
+ # --------------------------------------------
175
+ # Example: Config dependency
176
+ # if ($type -eq "Config") {
177
+ # return "configProvider.ProvideConfig()"
178
+ # }
179
+ #
180
+ # Example: Logger
181
+ # if ($type -eq "Logger") {
182
+ # return "loggerProvider.ProvideLogger()"
183
+ # }
184
+ #
185
+ # Example: Validator
186
+ # if ($type -eq "Validator") {
187
+ # return "validatorProvider.ProvideValidator()"
188
+ # }
189
+ # --------------------------------------------
190
+
191
+ # 2. Fallback: unresolved type
192
+ return "/* TODO: provide $($Param.RawType) */"
193
+ }
194
+
195
+ function Generate-ProviderCode {
196
+ param([System.Collections.Generic.List[ControllerInfo]]$Controllers)
197
+
198
+ Write-ColorOutput "`nGenerating controller provider code..." "Cyan"
199
+
200
+ # Sort controllers alphabetically for consistent output
201
+ $sortedControllers = $Controllers | Sort-Object -Property Domain
202
+
203
+ $sb = [System.Text.StringBuilder]::new()
204
+ [void]$sb.AppendLine("package provider")
205
+ [void]$sb.AppendLine()
206
+ [void]$sb.AppendLine("import `"$ModulePath`"")
207
+ [void]$sb.AppendLine()
208
+
209
+ # Interface
210
+ [void]$sb.AppendLine("type ControllerProvider interface {")
211
+ foreach ($ctrl in $sortedControllers) {
212
+ $line = "`tProvide$($ctrl.Domain)() controllers.$($ctrl.Domain)"
213
+ [void]$sb.AppendLine($line)
214
+ }
215
+ [void]$sb.AppendLine("}")
216
+ [void]$sb.AppendLine()
217
+
218
+ # Struct
219
+ [void]$sb.AppendLine("type controllerProvider struct {")
220
+ foreach ($ctrl in $sortedControllers) {
221
+ $line = "`t$($ctrl.VarName) controllers.$($ctrl.Domain)"
222
+ [void]$sb.AppendLine($line)
223
+ }
224
+ [void]$sb.AppendLine("}")
225
+ [void]$sb.AppendLine()
226
+
227
+ # Constructor
228
+ [void]$sb.AppendLine("func NewControllerProvider(servicesProvider ServicesProvider) ControllerProvider {")
229
+ [void]$sb.AppendLine()
230
+
231
+ # Initialize controllers
232
+ foreach ($ctrl in $sortedControllers) {
233
+ $args = @()
234
+ foreach ($param in $ctrl.Parameters) {
235
+ $args += Resolve-ControllerArgument $param
236
+ }
237
+ $argsStr = $args -join ", "
238
+ $line = "`t$($ctrl.VarName) := controllers.$($ctrl.ConstructorName)($argsStr)"
239
+ [void]$sb.AppendLine($line)
240
+ }
241
+
242
+ [void]$sb.AppendLine("`treturn &controllerProvider{")
243
+ foreach ($ctrl in $sortedControllers) {
244
+ $line = "`t`t$($ctrl.VarName): $($ctrl.VarName),"
245
+ [void]$sb.AppendLine($line)
246
+ }
247
+ [void]$sb.AppendLine("`t}")
248
+ [void]$sb.AppendLine("}")
249
+ [void]$sb.AppendLine()
250
+
251
+ # Getter methods
252
+ [void]$sb.AppendLine("// --- Getter Methods ---")
253
+ [void]$sb.AppendLine()
254
+ foreach ($ctrl in $sortedControllers) {
255
+ [void]$sb.AppendLine("func (c *controllerProvider) Provide$($ctrl.Domain)() controllers.$($ctrl.Domain) {")
256
+ [void]$sb.AppendLine("`treturn c.$($ctrl.VarName)")
257
+ [void]$sb.AppendLine("}")
258
+ [void]$sb.AppendLine()
259
+ }
260
+
261
+ return $sb.ToString()
262
+ }
263
+
264
+ function Write-ProviderFile {
265
+ param([string]$Code, [string]$OutputPath)
266
+
267
+ Write-ColorOutput "Writing to $OutputPath..." "Cyan"
268
+
269
+ # Ensure directory exists
270
+ $dir = Split-Path $OutputPath -Parent
271
+ if ($dir -and -not (Test-Path $dir)) {
272
+ New-Item -ItemType Directory -Path $dir -Force | Out-Null
273
+ }
274
+
275
+ # Write file as UTF-8 without BOM
276
+ $utf8NoBom = [System.Text.UTF8Encoding]::new($false)
277
+ [System.IO.File]::WriteAllText($OutputPath, $Code, $utf8NoBom)
278
+
279
+ Write-ColorOutput " Successfully generated $OutputPath" "Green"
280
+ }
281
+
282
+ # ============================================
283
+ # MAIN EXECUTION
284
+ # ============================================
285
+
286
+ try {
287
+ Write-ColorOutput "`n=========================================" "Blue"
288
+ Write-ColorOutput " Go Controller Provider Generator v1.0" "Blue"
289
+ Write-ColorOutput "=========================================`n" "Blue"
290
+
291
+ # Step 1: Parse all controller constructors
292
+ $controllers = Parse-GoFiles -Directory $ControllersDir
293
+
294
+ # Step 2: Generate provider code
295
+ $code = Generate-ProviderCode -Controllers $controllers
296
+
297
+ # Step 3: Write to file
298
+ Write-ProviderFile -Code $code -OutputPath $OutputFile
299
+
300
+ Write-ColorOutput "`nSUCCESS! Controller provider generated successfully.`n" "Green"
301
+ Write-ColorOutput "Next steps:" "Cyan"
302
+ Write-ColorOutput " 1. Review $OutputFile" "White"
303
+ Write-ColorOutput " 2. Fill any /* TODO: provide ... */ placeholders" "White"
304
+ Write-ColorOutput " 3. Run: go build ./provider" "White"
305
+
306
+ } catch {
307
+ Write-ColorOutput "`nERROR: $($_.Exception.Message)" "Red"
308
+ Write-ColorOutput "Stack trace: $($_.ScriptStackTrace)" "Yellow"
309
+ exit 1
310
  }
do_inject_middleware.ps1 CHANGED
@@ -1,312 +1,312 @@
1
- #Requires -Version 5.1
2
- <#
3
- .SYNOPSIS
4
- Automatic Dependency Injection Generator for Go Middleware
5
-
6
- .DESCRIPTION
7
- Scans ./middleware/ directory, discovers all middleware constructors, infers their dependencies,
8
- and generates provider/middleware_provider.go with full DI wiring.
9
-
10
- .EXAMPLE
11
- .\middleware_injector.ps1
12
-
13
- .NOTES
14
- - Works with PowerShell 5.1+ and PowerShell 7+
15
- - No external dependencies required
16
- - Supports multi-line constructor signatures
17
- - Middleware depend on services from ServicesProvider
18
- #>
19
-
20
- [CmdletBinding()]
21
- param()
22
-
23
- # Configuration
24
- $MiddlewareDir = "./middleware"
25
- $OutputFile = "provider/middleware_provider.go"
26
- $ModulePath = "abdanhafidz.com/go-boilerplate/middleware"
27
-
28
- # ANSI colors for better output
29
- $script:UseColors = $Host.UI.SupportsVirtualTerminal
30
- function Write-ColorOutput {
31
- param([string]$Message, [string]$Color = "White")
32
- if ($script:UseColors) {
33
- $colors = @{
34
- "Green" = "`e[32m"; "Yellow" = "`e[33m"; "Red" = "`e[31m"
35
- "Cyan" = "`e[36m"; "Blue" = "`e[34m"; "Reset" = "`e[0m"
36
- }
37
- Write-Host "$($colors[$Color])$Message$($colors['Reset'])"
38
- } else {
39
- Write-Host $Message
40
- }
41
- }
42
-
43
- # Data structures
44
- class MiddlewareInfo {
45
- [string]$ConstructorName # NewAuthenticationMiddleware
46
- [string]$Domain # AuthenticationMiddleware
47
- [string]$VarName # authenticationMiddleware
48
- [System.Collections.Generic.List[Parameter]]$Parameters
49
-
50
- MiddlewareInfo() {
51
- $this.Parameters = [System.Collections.Generic.List[Parameter]]::new()
52
- }
53
- }
54
-
55
- class Parameter {
56
- [string]$Name
57
- [string]$RawType
58
- [string]$NormalizedType
59
- }
60
-
61
- function Get-LowerCamelCase {
62
- param([string]$Text)
63
- if ($Text.Length -eq 0) { return $Text }
64
- return $Text.Substring(0, 1).ToLower() + $Text.Substring(1)
65
- }
66
-
67
- function Normalize-TypeName {
68
- param([string]$TypeStr)
69
-
70
- # Remove leading pointer
71
- $cleaned = $TypeStr -replace '^\*+', ''
72
-
73
- # Remove package prefix (everything before last dot)
74
- if ($cleaned -match '\.([^.]+)$') {
75
- $cleaned = $matches[1]
76
- }
77
-
78
- return $cleaned.Trim()
79
- }
80
-
81
- function Parse-GoFiles {
82
- param([string]$Directory)
83
-
84
- Write-ColorOutput "Scanning for middleware constructors in $Directory..." "Cyan"
85
-
86
- if (-not (Test-Path $Directory)) {
87
- Write-ColorOutput "ERROR: Directory '$Directory' not found!" "Red"
88
- exit 1
89
- }
90
-
91
- $goFiles = Get-ChildItem -Path $Directory -Filter "*.go" -Recurse -File
92
- $middlewares = [System.Collections.Generic.List[MiddlewareInfo]]::new()
93
-
94
- foreach ($file in $goFiles) {
95
- $content = Get-Content $file.FullName -Raw
96
-
97
- # Match function signatures (support multi-line)
98
- # Pattern: func NewXxxMiddleware(...) XxxMiddleware
99
- $pattern = '(?ms)func\s+(New[a-zA-Z0-9]+Middleware)\s*\(([^)]*)\)\s+([a-zA-Z0-9*_.]+Middleware)'
100
- $matches = [regex]::Matches($content, $pattern)
101
-
102
- foreach ($match in $matches) {
103
- $constructorName = $match.Groups[1].Value
104
- $paramsStr = $match.Groups[2].Value
105
- $returnType = $match.Groups[3].Value
106
-
107
- # Extract domain name (XxxMiddleware)
108
- $domain = Normalize-TypeName $returnType
109
- $varName = Get-LowerCamelCase $domain
110
-
111
- $middleware = [MiddlewareInfo]::new()
112
- $middleware.ConstructorName = $constructorName
113
- $middleware.Domain = $domain
114
- $middleware.VarName = $varName
115
-
116
- # Parse parameters
117
- if ($paramsStr.Trim() -ne "") {
118
- # Split by comma, but be careful with nested types
119
- $paramList = $paramsStr -split ',\s*(?![^<>]*>)'
120
-
121
- foreach ($param in $paramList) {
122
- $param = $param.Trim()
123
- if ($param -eq "") { continue }
124
-
125
- # Split into name and type
126
- $parts = $param -split '\s+', 2
127
-
128
- $p = [Parameter]::new()
129
- if ($parts.Count -eq 2) {
130
- $p.Name = $parts[0]
131
- $p.RawType = $parts[1]
132
- } elseif ($parts.Count -eq 1) {
133
- # Anonymous parameter - synthesize name
134
- $p.Name = "param$($middleware.Parameters.Count)"
135
- $p.RawType = $parts[0]
136
- } else {
137
- continue
138
- }
139
-
140
- $p.NormalizedType = Normalize-TypeName $p.RawType
141
- $middleware.Parameters.Add($p)
142
- }
143
- }
144
-
145
- $middlewares.Add($middleware)
146
- Write-ColorOutput " Found: $constructorName" "Green"
147
- }
148
- }
149
-
150
- if ($middlewares.Count -eq 0) {
151
- Write-ColorOutput "No middleware constructors found matching pattern 'NewXxxMiddleware'!" "Red"
152
- exit 1
153
- }
154
-
155
- Write-ColorOutput "`nTotal middleware discovered: $($middlewares.Count)" "Blue"
156
- return $middlewares
157
- }
158
-
159
- function Resolve-MiddlewareArgument {
160
- param([Parameter]$Param)
161
-
162
- $type = $Param.NormalizedType
163
-
164
- # DEPENDENCY RESOLUTION RULES
165
- # ============================================
166
-
167
- # 1. Service pattern: XxxxService -> servicesProvider.ProvideXxxxService()
168
- if ($type -match '^(.+)Service$') {
169
- $serviceName = $type
170
- return "servicesProvider.Provide${serviceName}()"
171
- }
172
-
173
- # ADD MORE SPECIAL CASES HERE:
174
- # --------------------------------------------
175
- # Example: Config dependency
176
- # if ($type -eq "Config") {
177
- # return "configProvider.ProvideConfig()"
178
- # }
179
- #
180
- # Example: Logger
181
- # if ($type -eq "Logger") {
182
- # return "loggerProvider.ProvideLogger()"
183
- # }
184
- #
185
- # Example: JWT Config
186
- # if ($type -eq "JWTConfig") {
187
- # return "configProvider.ProvideJWTConfig()"
188
- # }
189
- #
190
- # Example: Database
191
- # if ($type -eq "DB" -or $type -eq "Database") {
192
- # return "dbProvider.ProvideDatabase()"
193
- # }
194
- # --------------------------------------------
195
-
196
- # 2. Fallback: unresolved type
197
- return "/* TODO: provide $($Param.RawType) */"
198
- }
199
-
200
- function Generate-ProviderCode {
201
- param([System.Collections.Generic.List[MiddlewareInfo]]$Middlewares)
202
-
203
- Write-ColorOutput "`nGenerating middleware provider code..." "Cyan"
204
-
205
- # Sort middleware alphabetically for consistent output
206
- $sortedMiddlewares = $Middlewares | Sort-Object -Property Domain
207
-
208
- $sb = [System.Text.StringBuilder]::new()
209
- [void]$sb.AppendLine("package provider")
210
- [void]$sb.AppendLine()
211
- [void]$sb.AppendLine("import `"$ModulePath`"")
212
- [void]$sb.AppendLine()
213
-
214
- # Interface
215
- [void]$sb.AppendLine("type MiddlewareProvider interface {")
216
- foreach ($mw in $sortedMiddlewares) {
217
- $line = "`tProvide$($mw.Domain)() middleware.$($mw.Domain)"
218
- [void]$sb.AppendLine($line)
219
- }
220
- [void]$sb.AppendLine("}")
221
- [void]$sb.AppendLine()
222
-
223
- # Struct
224
- [void]$sb.AppendLine("type middlewareProvider struct {")
225
- foreach ($mw in $sortedMiddlewares) {
226
- $line = "`t$($mw.VarName) middleware.$($mw.Domain)"
227
- [void]$sb.AppendLine($line)
228
- }
229
- [void]$sb.AppendLine("}")
230
- [void]$sb.AppendLine()
231
-
232
- # Constructor
233
- [void]$sb.AppendLine("func NewMiddlewareProvider(servicesProvider ServicesProvider) MiddlewareProvider {")
234
-
235
- # Initialize middleware
236
- foreach ($mw in $sortedMiddlewares) {
237
- $args = @()
238
- foreach ($param in $mw.Parameters) {
239
- $args += Resolve-MiddlewareArgument $param
240
- }
241
- $argsStr = $args -join ", "
242
- $line = "`t$($mw.VarName) := middleware.$($mw.ConstructorName)($argsStr)"
243
- [void]$sb.AppendLine($line)
244
- }
245
-
246
- [void]$sb.AppendLine("`treturn &middlewareProvider{")
247
- foreach ($mw in $sortedMiddlewares) {
248
- $line = "`t`t$($mw.VarName): $($mw.VarName),"
249
- [void]$sb.AppendLine($line)
250
- }
251
- [void]$sb.AppendLine("`t}")
252
- [void]$sb.AppendLine("}")
253
- [void]$sb.AppendLine()
254
-
255
- # Getter methods
256
- foreach ($mw in $sortedMiddlewares) {
257
- [void]$sb.AppendLine("func (p *middlewareProvider) Provide$($mw.Domain)() middleware.$($mw.Domain) {")
258
- [void]$sb.AppendLine("`treturn p.$($mw.VarName)")
259
- [void]$sb.AppendLine("}")
260
- [void]$sb.AppendLine()
261
- }
262
-
263
- return $sb.ToString()
264
- }
265
-
266
- function Write-ProviderFile {
267
- param([string]$Code, [string]$OutputPath)
268
-
269
- Write-ColorOutput "Writing to $OutputPath..." "Cyan"
270
-
271
- # Ensure directory exists
272
- $dir = Split-Path $OutputPath -Parent
273
- if ($dir -and -not (Test-Path $dir)) {
274
- New-Item -ItemType Directory -Path $dir -Force | Out-Null
275
- }
276
-
277
- # Write file as UTF-8 without BOM
278
- $utf8NoBom = [System.Text.UTF8Encoding]::new($false)
279
- [System.IO.File]::WriteAllText($OutputPath, $Code, $utf8NoBom)
280
-
281
- Write-ColorOutput " Successfully generated $OutputPath" "Green"
282
- }
283
-
284
- # ============================================
285
- # MAIN EXECUTION
286
- # ============================================
287
-
288
- try {
289
- Write-ColorOutput "`n=========================================" "Blue"
290
- Write-ColorOutput " Go Middleware Provider Generator v1.0" "Blue"
291
- Write-ColorOutput "=========================================`n" "Blue"
292
-
293
- # Step 1: Parse all middleware constructors
294
- $middlewares = Parse-GoFiles -Directory $MiddlewareDir
295
-
296
- # Step 2: Generate provider code
297
- $code = Generate-ProviderCode -Middlewares $middlewares
298
-
299
- # Step 3: Write to file
300
- Write-ProviderFile -Code $code -OutputPath $OutputFile
301
-
302
- Write-ColorOutput "`nSUCCESS! Middleware provider generated successfully.`n" "Green"
303
- Write-ColorOutput "Next steps:" "Cyan"
304
- Write-ColorOutput " 1. Review $OutputFile" "White"
305
- Write-ColorOutput " 2. Fill any /* TODO: provide ... */ placeholders" "White"
306
- Write-ColorOutput " 3. Run: go build ./provider" "White"
307
-
308
- } catch {
309
- Write-ColorOutput "`nERROR: $($_.Exception.Message)" "Red"
310
- Write-ColorOutput "Stack trace: $($_.ScriptStackTrace)" "Yellow"
311
- exit 1
312
  }
 
1
+ #Requires -Version 5.1
2
+ <#
3
+ .SYNOPSIS
4
+ Automatic Dependency Injection Generator for Go Middleware
5
+
6
+ .DESCRIPTION
7
+ Scans ./middleware/ directory, discovers all middleware constructors, infers their dependencies,
8
+ and generates provider/middleware_provider.go with full DI wiring.
9
+
10
+ .EXAMPLE
11
+ .\middleware_injector.ps1
12
+
13
+ .NOTES
14
+ - Works with PowerShell 5.1+ and PowerShell 7+
15
+ - No external dependencies required
16
+ - Supports multi-line constructor signatures
17
+ - Middleware depend on services from ServicesProvider
18
+ #>
19
+
20
+ [CmdletBinding()]
21
+ param()
22
+
23
+ # Configuration
24
+ $MiddlewareDir = "./middleware"
25
+ $OutputFile = "provider/middleware_provider.go"
26
+ $ModulePath = "abdanhafidz.com/go-boilerplate/middleware"
27
+
28
+ # ANSI colors for better output
29
+ $script:UseColors = $Host.UI.SupportsVirtualTerminal
30
+ function Write-ColorOutput {
31
+ param([string]$Message, [string]$Color = "White")
32
+ if ($script:UseColors) {
33
+ $colors = @{
34
+ "Green" = "`e[32m"; "Yellow" = "`e[33m"; "Red" = "`e[31m"
35
+ "Cyan" = "`e[36m"; "Blue" = "`e[34m"; "Reset" = "`e[0m"
36
+ }
37
+ Write-Host "$($colors[$Color])$Message$($colors['Reset'])"
38
+ } else {
39
+ Write-Host $Message
40
+ }
41
+ }
42
+
43
+ # Data structures
44
+ class MiddlewareInfo {
45
+ [string]$ConstructorName # NewAuthenticationMiddleware
46
+ [string]$Domain # AuthenticationMiddleware
47
+ [string]$VarName # authenticationMiddleware
48
+ [System.Collections.Generic.List[Parameter]]$Parameters
49
+
50
+ MiddlewareInfo() {
51
+ $this.Parameters = [System.Collections.Generic.List[Parameter]]::new()
52
+ }
53
+ }
54
+
55
+ class Parameter {
56
+ [string]$Name
57
+ [string]$RawType
58
+ [string]$NormalizedType
59
+ }
60
+
61
+ function Get-LowerCamelCase {
62
+ param([string]$Text)
63
+ if ($Text.Length -eq 0) { return $Text }
64
+ return $Text.Substring(0, 1).ToLower() + $Text.Substring(1)
65
+ }
66
+
67
+ function Normalize-TypeName {
68
+ param([string]$TypeStr)
69
+
70
+ # Remove leading pointer
71
+ $cleaned = $TypeStr -replace '^\*+', ''
72
+
73
+ # Remove package prefix (everything before last dot)
74
+ if ($cleaned -match '\.([^.]+)$') {
75
+ $cleaned = $matches[1]
76
+ }
77
+
78
+ return $cleaned.Trim()
79
+ }
80
+
81
+ function Parse-GoFiles {
82
+ param([string]$Directory)
83
+
84
+ Write-ColorOutput "Scanning for middleware constructors in $Directory..." "Cyan"
85
+
86
+ if (-not (Test-Path $Directory)) {
87
+ Write-ColorOutput "ERROR: Directory '$Directory' not found!" "Red"
88
+ exit 1
89
+ }
90
+
91
+ $goFiles = Get-ChildItem -Path $Directory -Filter "*.go" -Recurse -File
92
+ $middlewares = [System.Collections.Generic.List[MiddlewareInfo]]::new()
93
+
94
+ foreach ($file in $goFiles) {
95
+ $content = Get-Content $file.FullName -Raw
96
+
97
+ # Match function signatures (support multi-line)
98
+ # Pattern: func NewXxxMiddleware(...) XxxMiddleware
99
+ $pattern = '(?ms)func\s+(New[a-zA-Z0-9]+Middleware)\s*\(([^)]*)\)\s+([a-zA-Z0-9*_.]+Middleware)'
100
+ $matches = [regex]::Matches($content, $pattern)
101
+
102
+ foreach ($match in $matches) {
103
+ $constructorName = $match.Groups[1].Value
104
+ $paramsStr = $match.Groups[2].Value
105
+ $returnType = $match.Groups[3].Value
106
+
107
+ # Extract domain name (XxxMiddleware)
108
+ $domain = Normalize-TypeName $returnType
109
+ $varName = Get-LowerCamelCase $domain
110
+
111
+ $middleware = [MiddlewareInfo]::new()
112
+ $middleware.ConstructorName = $constructorName
113
+ $middleware.Domain = $domain
114
+ $middleware.VarName = $varName
115
+
116
+ # Parse parameters
117
+ if ($paramsStr.Trim() -ne "") {
118
+ # Split by comma, but be careful with nested types
119
+ $paramList = $paramsStr -split ',\s*(?![^<>]*>)'
120
+
121
+ foreach ($param in $paramList) {
122
+ $param = $param.Trim()
123
+ if ($param -eq "") { continue }
124
+
125
+ # Split into name and type
126
+ $parts = $param -split '\s+', 2
127
+
128
+ $p = [Parameter]::new()
129
+ if ($parts.Count -eq 2) {
130
+ $p.Name = $parts[0]
131
+ $p.RawType = $parts[1]
132
+ } elseif ($parts.Count -eq 1) {
133
+ # Anonymous parameter - synthesize name
134
+ $p.Name = "param$($middleware.Parameters.Count)"
135
+ $p.RawType = $parts[0]
136
+ } else {
137
+ continue
138
+ }
139
+
140
+ $p.NormalizedType = Normalize-TypeName $p.RawType
141
+ $middleware.Parameters.Add($p)
142
+ }
143
+ }
144
+
145
+ $middlewares.Add($middleware)
146
+ Write-ColorOutput " Found: $constructorName" "Green"
147
+ }
148
+ }
149
+
150
+ if ($middlewares.Count -eq 0) {
151
+ Write-ColorOutput "No middleware constructors found matching pattern 'NewXxxMiddleware'!" "Red"
152
+ exit 1
153
+ }
154
+
155
+ Write-ColorOutput "`nTotal middleware discovered: $($middlewares.Count)" "Blue"
156
+ return $middlewares
157
+ }
158
+
159
+ function Resolve-MiddlewareArgument {
160
+ param([Parameter]$Param)
161
+
162
+ $type = $Param.NormalizedType
163
+
164
+ # DEPENDENCY RESOLUTION RULES
165
+ # ============================================
166
+
167
+ # 1. Service pattern: XxxxService -> servicesProvider.ProvideXxxxService()
168
+ if ($type -match '^(.+)Service$') {
169
+ $serviceName = $type
170
+ return "servicesProvider.Provide${serviceName}()"
171
+ }
172
+
173
+ # ADD MORE SPECIAL CASES HERE:
174
+ # --------------------------------------------
175
+ # Example: Config dependency
176
+ # if ($type -eq "Config") {
177
+ # return "configProvider.ProvideConfig()"
178
+ # }
179
+ #
180
+ # Example: Logger
181
+ # if ($type -eq "Logger") {
182
+ # return "loggerProvider.ProvideLogger()"
183
+ # }
184
+ #
185
+ # Example: JWT Config
186
+ # if ($type -eq "JWTConfig") {
187
+ # return "configProvider.ProvideJWTConfig()"
188
+ # }
189
+ #
190
+ # Example: Database
191
+ # if ($type -eq "DB" -or $type -eq "Database") {
192
+ # return "dbProvider.ProvideDatabase()"
193
+ # }
194
+ # --------------------------------------------
195
+
196
+ # 2. Fallback: unresolved type
197
+ return "/* TODO: provide $($Param.RawType) */"
198
+ }
199
+
200
+ function Generate-ProviderCode {
201
+ param([System.Collections.Generic.List[MiddlewareInfo]]$Middlewares)
202
+
203
+ Write-ColorOutput "`nGenerating middleware provider code..." "Cyan"
204
+
205
+ # Sort middleware alphabetically for consistent output
206
+ $sortedMiddlewares = $Middlewares | Sort-Object -Property Domain
207
+
208
+ $sb = [System.Text.StringBuilder]::new()
209
+ [void]$sb.AppendLine("package provider")
210
+ [void]$sb.AppendLine()
211
+ [void]$sb.AppendLine("import `"$ModulePath`"")
212
+ [void]$sb.AppendLine()
213
+
214
+ # Interface
215
+ [void]$sb.AppendLine("type MiddlewareProvider interface {")
216
+ foreach ($mw in $sortedMiddlewares) {
217
+ $line = "`tProvide$($mw.Domain)() middleware.$($mw.Domain)"
218
+ [void]$sb.AppendLine($line)
219
+ }
220
+ [void]$sb.AppendLine("}")
221
+ [void]$sb.AppendLine()
222
+
223
+ # Struct
224
+ [void]$sb.AppendLine("type middlewareProvider struct {")
225
+ foreach ($mw in $sortedMiddlewares) {
226
+ $line = "`t$($mw.VarName) middleware.$($mw.Domain)"
227
+ [void]$sb.AppendLine($line)
228
+ }
229
+ [void]$sb.AppendLine("}")
230
+ [void]$sb.AppendLine()
231
+
232
+ # Constructor
233
+ [void]$sb.AppendLine("func NewMiddlewareProvider(servicesProvider ServicesProvider) MiddlewareProvider {")
234
+
235
+ # Initialize middleware
236
+ foreach ($mw in $sortedMiddlewares) {
237
+ $args = @()
238
+ foreach ($param in $mw.Parameters) {
239
+ $args += Resolve-MiddlewareArgument $param
240
+ }
241
+ $argsStr = $args -join ", "
242
+ $line = "`t$($mw.VarName) := middleware.$($mw.ConstructorName)($argsStr)"
243
+ [void]$sb.AppendLine($line)
244
+ }
245
+
246
+ [void]$sb.AppendLine("`treturn &middlewareProvider{")
247
+ foreach ($mw in $sortedMiddlewares) {
248
+ $line = "`t`t$($mw.VarName): $($mw.VarName),"
249
+ [void]$sb.AppendLine($line)
250
+ }
251
+ [void]$sb.AppendLine("`t}")
252
+ [void]$sb.AppendLine("}")
253
+ [void]$sb.AppendLine()
254
+
255
+ # Getter methods
256
+ foreach ($mw in $sortedMiddlewares) {
257
+ [void]$sb.AppendLine("func (p *middlewareProvider) Provide$($mw.Domain)() middleware.$($mw.Domain) {")
258
+ [void]$sb.AppendLine("`treturn p.$($mw.VarName)")
259
+ [void]$sb.AppendLine("}")
260
+ [void]$sb.AppendLine()
261
+ }
262
+
263
+ return $sb.ToString()
264
+ }
265
+
266
+ function Write-ProviderFile {
267
+ param([string]$Code, [string]$OutputPath)
268
+
269
+ Write-ColorOutput "Writing to $OutputPath..." "Cyan"
270
+
271
+ # Ensure directory exists
272
+ $dir = Split-Path $OutputPath -Parent
273
+ if ($dir -and -not (Test-Path $dir)) {
274
+ New-Item -ItemType Directory -Path $dir -Force | Out-Null
275
+ }
276
+
277
+ # Write file as UTF-8 without BOM
278
+ $utf8NoBom = [System.Text.UTF8Encoding]::new($false)
279
+ [System.IO.File]::WriteAllText($OutputPath, $Code, $utf8NoBom)
280
+
281
+ Write-ColorOutput " Successfully generated $OutputPath" "Green"
282
+ }
283
+
284
+ # ============================================
285
+ # MAIN EXECUTION
286
+ # ============================================
287
+
288
+ try {
289
+ Write-ColorOutput "`n=========================================" "Blue"
290
+ Write-ColorOutput " Go Middleware Provider Generator v1.0" "Blue"
291
+ Write-ColorOutput "=========================================`n" "Blue"
292
+
293
+ # Step 1: Parse all middleware constructors
294
+ $middlewares = Parse-GoFiles -Directory $MiddlewareDir
295
+
296
+ # Step 2: Generate provider code
297
+ $code = Generate-ProviderCode -Middlewares $middlewares
298
+
299
+ # Step 3: Write to file
300
+ Write-ProviderFile -Code $code -OutputPath $OutputFile
301
+
302
+ Write-ColorOutput "`nSUCCESS! Middleware provider generated successfully.`n" "Green"
303
+ Write-ColorOutput "Next steps:" "Cyan"
304
+ Write-ColorOutput " 1. Review $OutputFile" "White"
305
+ Write-ColorOutput " 2. Fill any /* TODO: provide ... */ placeholders" "White"
306
+ Write-ColorOutput " 3. Run: go build ./provider" "White"
307
+
308
+ } catch {
309
+ Write-ColorOutput "`nERROR: $($_.Exception.Message)" "Red"
310
+ Write-ColorOutput "Stack trace: $($_.ScriptStackTrace)" "Yellow"
311
+ exit 1
312
  }
do_inject_repository.ps1 CHANGED
@@ -1,327 +1,327 @@
1
- #Requires -Version 5.1
2
- <#
3
- .SYNOPSIS
4
- Automatic Dependency Injection Generator for Go Repositories
5
-
6
- .DESCRIPTION
7
- Scans ./repositories/ directory, discovers all repository constructors,
8
- and generates provider/repositories_provider.go with full DI wiring.
9
- Repositories typically depend on database connection from ConfigProvider.
10
-
11
- .EXAMPLE
12
- .\repository_injector.ps1
13
-
14
- .NOTES
15
- - Works with PowerShell 5.1+ and PowerShell 7+
16
- - No external dependencies required
17
- - Supports multi-line constructor signatures
18
- - Repositories depend on database instance from ConfigProvider
19
- #>
20
-
21
- [CmdletBinding()]
22
- param()
23
-
24
- # Configuration
25
- $RepositoriesDir = "./repositories"
26
- $OutputFile = "provider/repositories_provider.go"
27
- $ModulePath = "abdanhafidz.com/go-boilerplate/repositories"
28
-
29
- # ANSI colors for better output
30
- $script:UseColors = $Host.UI.SupportsVirtualTerminal
31
- function Write-ColorOutput {
32
- param([string]$Message, [string]$Color = "White")
33
- if ($script:UseColors) {
34
- $colors = @{
35
- "Green" = "`e[32m"; "Yellow" = "`e[33m"; "Red" = "`e[31m"
36
- "Cyan" = "`e[36m"; "Blue" = "`e[34m"; "Reset" = "`e[0m"
37
- }
38
- Write-Host "$($colors[$Color])$Message$($colors['Reset'])"
39
- } else {
40
- Write-Host $Message
41
- }
42
- }
43
-
44
- # Data structures
45
- class RepositoryInfo {
46
- [string]$ConstructorName # NewAccountRepository
47
- [string]$Domain # AccountRepository
48
- [string]$VarName # accountRepository
49
- [System.Collections.Generic.List[Parameter]]$Parameters
50
-
51
- RepositoryInfo() {
52
- $this.Parameters = [System.Collections.Generic.List[Parameter]]::new()
53
- }
54
- }
55
-
56
- class Parameter {
57
- [string]$Name
58
- [string]$RawType
59
- [string]$NormalizedType
60
- }
61
-
62
- function Get-LowerCamelCase {
63
- param([string]$Text)
64
- if ($Text.Length -eq 0) { return $Text }
65
- return $Text.Substring(0, 1).ToLower() + $Text.Substring(1)
66
- }
67
-
68
- function Normalize-TypeName {
69
- param([string]$TypeStr)
70
-
71
- # Remove leading pointer
72
- $cleaned = $TypeStr -replace '^\*+', ''
73
-
74
- # Remove package prefix (everything before last dot)
75
- if ($cleaned -match '\.([^.]+)$') {
76
- $cleaned = $matches[1]
77
- }
78
-
79
- return $cleaned.Trim()
80
- }
81
-
82
- function Parse-GoFiles {
83
- param([string]$Directory)
84
-
85
- Write-ColorOutput "Scanning for repository constructors in $Directory..." "Cyan"
86
-
87
- if (-not (Test-Path $Directory)) {
88
- Write-ColorOutput "ERROR: Directory '$Directory' not found!" "Red"
89
- exit 1
90
- }
91
-
92
- $goFiles = Get-ChildItem -Path $Directory -Filter "*.go" -Recurse -File
93
- $repositories = [System.Collections.Generic.List[RepositoryInfo]]::new()
94
-
95
- foreach ($file in $goFiles) {
96
- $content = Get-Content $file.FullName -Raw
97
-
98
- # Match function signatures (support multi-line)
99
- # Pattern: func NewXxxRepository(...) XxxRepository
100
- $pattern = '(?ms)func\s+(New[a-zA-Z0-9]+Repository)\s*\(([^)]*)\)\s+([a-zA-Z0-9*_.]+Repository)'
101
- $matches = [regex]::Matches($content, $pattern)
102
-
103
- foreach ($match in $matches) {
104
- $constructorName = $match.Groups[1].Value
105
- $paramsStr = $match.Groups[2].Value
106
- $returnType = $match.Groups[3].Value
107
-
108
- # Extract domain name (XxxRepository)
109
- $domain = Normalize-TypeName $returnType
110
- $varName = Get-LowerCamelCase $domain
111
-
112
- $repo = [RepositoryInfo]::new()
113
- $repo.ConstructorName = $constructorName
114
- $repo.Domain = $domain
115
- $repo.VarName = $varName
116
-
117
- # Parse parameters
118
- if ($paramsStr.Trim() -ne "") {
119
- # Split by comma, but be careful with nested types
120
- $paramList = $paramsStr -split ',\s*(?![^<>]*>)'
121
-
122
- foreach ($param in $paramList) {
123
- $param = $param.Trim()
124
- if ($param -eq "") { continue }
125
-
126
- # Split into name and type
127
- $parts = $param -split '\s+', 2
128
-
129
- $p = [Parameter]::new()
130
- if ($parts.Count -eq 2) {
131
- $p.Name = $parts[0]
132
- $p.RawType = $parts[1]
133
- } elseif ($parts.Count -eq 1) {
134
- # Anonymous parameter - synthesize name
135
- $p.Name = "param$($repo.Parameters.Count)"
136
- $p.RawType = $parts[0]
137
- } else {
138
- continue
139
- }
140
-
141
- $p.NormalizedType = Normalize-TypeName $p.RawType
142
- $repo.Parameters.Add($p)
143
- }
144
- }
145
-
146
- $repositories.Add($repo)
147
- Write-ColorOutput " Found: $constructorName" "Green"
148
- }
149
- }
150
-
151
- if ($repositories.Count -eq 0) {
152
- Write-ColorOutput "No repository constructors found matching pattern 'NewXxxRepository'!" "Red"
153
- exit 1
154
- }
155
-
156
- Write-ColorOutput "`nTotal repositories discovered: $($repositories.Count)" "Blue"
157
- return $repositories
158
- }
159
-
160
- function Resolve-RepositoryArgument {
161
- param([Parameter]$Param)
162
-
163
- $type = $Param.NormalizedType
164
- $paramName = $Param.Name
165
-
166
- # DEPENDENCY RESOLUTION RULES FOR REPOSITORIES
167
- # ============================================
168
-
169
- # 1. Database connection patterns
170
- if ($type -match '^(DB|Database|Gorm|SqlDB|Connection)$' -or $paramName -match '^(db|database|conn|connection)$') {
171
- return "db"
172
- }
173
-
174
- # 2. *gorm.DB (most common in Go GORM projects)
175
- if ($Param.RawType -match 'gorm\.DB' -or $type -eq "DB") {
176
- return "db"
177
- }
178
-
179
- # 3. *sql.DB (standard library)
180
- if ($Param.RawType -match 'sql\.DB') {
181
- return "db"
182
- }
183
-
184
- # ADD MORE SPECIAL CASES HERE:
185
- # --------------------------------------------
186
- # Example: Redis connection
187
- # if ($type -eq "RedisClient" -or $paramName -match "redis") {
188
- # return "redisClient"
189
- # }
190
- #
191
- # Example: MongoDB connection
192
- # if ($type -eq "MongoClient" -or $paramName -match "mongo") {
193
- # return "mongoClient"
194
- # }
195
- #
196
- # Example: Cache
197
- # if ($type -eq "Cache" -or $paramName -match "cache") {
198
- # return "cache"
199
- # }
200
- #
201
- # Example: Logger
202
- # if ($type -eq "Logger" -or $paramName -match "logger") {
203
- # return "logger"
204
- # }
205
- # --------------------------------------------
206
-
207
- # 4. Fallback: unresolved type
208
- return "/* TODO: provide $($Param.RawType) */"
209
- }
210
-
211
- function Generate-ProviderCode {
212
- param([System.Collections.Generic.List[RepositoryInfo]]$Repositories)
213
-
214
- Write-ColorOutput "`nGenerating repositories provider code..." "Cyan"
215
-
216
- # Sort repositories alphabetically for consistent output
217
- $sortedRepos = $Repositories | Sort-Object -Property Domain
218
-
219
- $sb = [System.Text.StringBuilder]::new()
220
- [void]$sb.AppendLine("package provider")
221
- [void]$sb.AppendLine()
222
- [void]$sb.AppendLine("import `"$ModulePath`"")
223
- [void]$sb.AppendLine()
224
-
225
- # Interface
226
- [void]$sb.AppendLine("type RepositoriesProvider interface {")
227
- foreach ($repo in $sortedRepos) {
228
- $line = "`tProvide$($repo.Domain)() repositories.$($repo.Domain)"
229
- [void]$sb.AppendLine($line)
230
- }
231
- [void]$sb.AppendLine("}")
232
- [void]$sb.AppendLine()
233
-
234
- # Struct
235
- [void]$sb.AppendLine("type repositoriesProvider struct {")
236
- foreach ($repo in $sortedRepos) {
237
- $line = "`t$($repo.VarName) repositories.$($repo.Domain)"
238
- [void]$sb.AppendLine($line)
239
- }
240
- [void]$sb.AppendLine("}")
241
- [void]$sb.AppendLine()
242
-
243
- # Constructor
244
- [void]$sb.AppendLine("func NewRepositoriesProvider(cfg ConfigProvider) RepositoriesProvider {")
245
- [void]$sb.AppendLine("`tdbConfig := cfg.ProvideDatabaseConfig()")
246
- [void]$sb.AppendLine("`tdb := dbConfig.GetInstance()")
247
- [void]$sb.AppendLine()
248
-
249
- # Initialize repositories
250
- foreach ($repo in $sortedRepos) {
251
- $args = @()
252
- foreach ($param in $repo.Parameters) {
253
- $args += Resolve-RepositoryArgument $param
254
- }
255
- $argsStr = $args -join ", "
256
- $line = "`t$($repo.VarName) := repositories.$($repo.ConstructorName)($argsStr)"
257
- [void]$sb.AppendLine($line)
258
- }
259
-
260
- [void]$sb.AppendLine()
261
- [void]$sb.AppendLine("`treturn &repositoriesProvider{")
262
- foreach ($repo in $sortedRepos) {
263
- $line = "`t`t$($repo.VarName): $($repo.VarName),"
264
- [void]$sb.AppendLine($line)
265
- }
266
- [void]$sb.AppendLine("`t}")
267
- [void]$sb.AppendLine("}")
268
- [void]$sb.AppendLine()
269
-
270
- # Getter methods
271
- foreach ($repo in $sortedRepos) {
272
- [void]$sb.AppendLine("func (r *repositoriesProvider) Provide$($repo.Domain)() repositories.$($repo.Domain) {")
273
- [void]$sb.AppendLine("`treturn r.$($repo.VarName)")
274
- [void]$sb.AppendLine("}")
275
- [void]$sb.AppendLine()
276
- }
277
-
278
- return $sb.ToString()
279
- }
280
-
281
- function Write-ProviderFile {
282
- param([string]$Code, [string]$OutputPath)
283
-
284
- Write-ColorOutput "Writing to $OutputPath..." "Cyan"
285
-
286
- # Ensure directory exists
287
- $dir = Split-Path $OutputPath -Parent
288
- if ($dir -and -not (Test-Path $dir)) {
289
- New-Item -ItemType Directory -Path $dir -Force | Out-Null
290
- }
291
-
292
- # Write file as UTF-8 without BOM
293
- $utf8NoBom = [System.Text.UTF8Encoding]::new($false)
294
- [System.IO.File]::WriteAllText($OutputPath, $Code, $utf8NoBom)
295
-
296
- Write-ColorOutput " Successfully generated $OutputPath" "Green"
297
- }
298
-
299
- # ============================================
300
- # MAIN EXECUTION
301
- # ============================================
302
-
303
- try {
304
- Write-ColorOutput "`n=========================================" "Blue"
305
- Write-ColorOutput " Go Repository Provider Generator v1.0" "Blue"
306
- Write-ColorOutput "=========================================`n" "Blue"
307
-
308
- # Step 1: Parse all repository constructors
309
- $repositories = Parse-GoFiles -Directory $RepositoriesDir
310
-
311
- # Step 2: Generate provider code
312
- $code = Generate-ProviderCode -Repositories $repositories
313
-
314
- # Step 3: Write to file
315
- Write-ProviderFile -Code $code -OutputPath $OutputFile
316
-
317
- Write-ColorOutput "`nSUCCESS! Repositories provider generated successfully.`n" "Green"
318
- Write-ColorOutput "Next steps:" "Cyan"
319
- Write-ColorOutput " 1. Review $OutputFile" "White"
320
- Write-ColorOutput " 2. Fill any /* TODO: provide ... */ placeholders" "White"
321
- Write-ColorOutput " 3. Run: go build ./provider" "White"
322
-
323
- } catch {
324
- Write-ColorOutput "`nERROR: $($_.Exception.Message)" "Red"
325
- Write-ColorOutput "Stack trace: $($_.ScriptStackTrace)" "Yellow"
326
- exit 1
327
  }
 
1
+ #Requires -Version 5.1
2
+ <#
3
+ .SYNOPSIS
4
+ Automatic Dependency Injection Generator for Go Repositories
5
+
6
+ .DESCRIPTION
7
+ Scans ./repositories/ directory, discovers all repository constructors,
8
+ and generates provider/repositories_provider.go with full DI wiring.
9
+ Repositories typically depend on database connection from ConfigProvider.
10
+
11
+ .EXAMPLE
12
+ .\repository_injector.ps1
13
+
14
+ .NOTES
15
+ - Works with PowerShell 5.1+ and PowerShell 7+
16
+ - No external dependencies required
17
+ - Supports multi-line constructor signatures
18
+ - Repositories depend on database instance from ConfigProvider
19
+ #>
20
+
21
+ [CmdletBinding()]
22
+ param()
23
+
24
+ # Configuration
25
+ $RepositoriesDir = "./repositories"
26
+ $OutputFile = "provider/repositories_provider.go"
27
+ $ModulePath = "abdanhafidz.com/go-boilerplate/repositories"
28
+
29
+ # ANSI colors for better output
30
+ $script:UseColors = $Host.UI.SupportsVirtualTerminal
31
+ function Write-ColorOutput {
32
+ param([string]$Message, [string]$Color = "White")
33
+ if ($script:UseColors) {
34
+ $colors = @{
35
+ "Green" = "`e[32m"; "Yellow" = "`e[33m"; "Red" = "`e[31m"
36
+ "Cyan" = "`e[36m"; "Blue" = "`e[34m"; "Reset" = "`e[0m"
37
+ }
38
+ Write-Host "$($colors[$Color])$Message$($colors['Reset'])"
39
+ } else {
40
+ Write-Host $Message
41
+ }
42
+ }
43
+
44
+ # Data structures
45
+ class RepositoryInfo {
46
+ [string]$ConstructorName # NewAccountRepository
47
+ [string]$Domain # AccountRepository
48
+ [string]$VarName # accountRepository
49
+ [System.Collections.Generic.List[Parameter]]$Parameters
50
+
51
+ RepositoryInfo() {
52
+ $this.Parameters = [System.Collections.Generic.List[Parameter]]::new()
53
+ }
54
+ }
55
+
56
+ class Parameter {
57
+ [string]$Name
58
+ [string]$RawType
59
+ [string]$NormalizedType
60
+ }
61
+
62
+ function Get-LowerCamelCase {
63
+ param([string]$Text)
64
+ if ($Text.Length -eq 0) { return $Text }
65
+ return $Text.Substring(0, 1).ToLower() + $Text.Substring(1)
66
+ }
67
+
68
+ function Normalize-TypeName {
69
+ param([string]$TypeStr)
70
+
71
+ # Remove leading pointer
72
+ $cleaned = $TypeStr -replace '^\*+', ''
73
+
74
+ # Remove package prefix (everything before last dot)
75
+ if ($cleaned -match '\.([^.]+)$') {
76
+ $cleaned = $matches[1]
77
+ }
78
+
79
+ return $cleaned.Trim()
80
+ }
81
+
82
+ function Parse-GoFiles {
83
+ param([string]$Directory)
84
+
85
+ Write-ColorOutput "Scanning for repository constructors in $Directory..." "Cyan"
86
+
87
+ if (-not (Test-Path $Directory)) {
88
+ Write-ColorOutput "ERROR: Directory '$Directory' not found!" "Red"
89
+ exit 1
90
+ }
91
+
92
+ $goFiles = Get-ChildItem -Path $Directory -Filter "*.go" -Recurse -File
93
+ $repositories = [System.Collections.Generic.List[RepositoryInfo]]::new()
94
+
95
+ foreach ($file in $goFiles) {
96
+ $content = Get-Content $file.FullName -Raw
97
+
98
+ # Match function signatures (support multi-line)
99
+ # Pattern: func NewXxxRepository(...) XxxRepository
100
+ $pattern = '(?ms)func\s+(New[a-zA-Z0-9]+Repository)\s*\(([^)]*)\)\s+([a-zA-Z0-9*_.]+Repository)'
101
+ $matches = [regex]::Matches($content, $pattern)
102
+
103
+ foreach ($match in $matches) {
104
+ $constructorName = $match.Groups[1].Value
105
+ $paramsStr = $match.Groups[2].Value
106
+ $returnType = $match.Groups[3].Value
107
+
108
+ # Extract domain name (XxxRepository)
109
+ $domain = Normalize-TypeName $returnType
110
+ $varName = Get-LowerCamelCase $domain
111
+
112
+ $repo = [RepositoryInfo]::new()
113
+ $repo.ConstructorName = $constructorName
114
+ $repo.Domain = $domain
115
+ $repo.VarName = $varName
116
+
117
+ # Parse parameters
118
+ if ($paramsStr.Trim() -ne "") {
119
+ # Split by comma, but be careful with nested types
120
+ $paramList = $paramsStr -split ',\s*(?![^<>]*>)'
121
+
122
+ foreach ($param in $paramList) {
123
+ $param = $param.Trim()
124
+ if ($param -eq "") { continue }
125
+
126
+ # Split into name and type
127
+ $parts = $param -split '\s+', 2
128
+
129
+ $p = [Parameter]::new()
130
+ if ($parts.Count -eq 2) {
131
+ $p.Name = $parts[0]
132
+ $p.RawType = $parts[1]
133
+ } elseif ($parts.Count -eq 1) {
134
+ # Anonymous parameter - synthesize name
135
+ $p.Name = "param$($repo.Parameters.Count)"
136
+ $p.RawType = $parts[0]
137
+ } else {
138
+ continue
139
+ }
140
+
141
+ $p.NormalizedType = Normalize-TypeName $p.RawType
142
+ $repo.Parameters.Add($p)
143
+ }
144
+ }
145
+
146
+ $repositories.Add($repo)
147
+ Write-ColorOutput " Found: $constructorName" "Green"
148
+ }
149
+ }
150
+
151
+ if ($repositories.Count -eq 0) {
152
+ Write-ColorOutput "No repository constructors found matching pattern 'NewXxxRepository'!" "Red"
153
+ exit 1
154
+ }
155
+
156
+ Write-ColorOutput "`nTotal repositories discovered: $($repositories.Count)" "Blue"
157
+ return $repositories
158
+ }
159
+
160
+ function Resolve-RepositoryArgument {
161
+ param([Parameter]$Param)
162
+
163
+ $type = $Param.NormalizedType
164
+ $paramName = $Param.Name
165
+
166
+ # DEPENDENCY RESOLUTION RULES FOR REPOSITORIES
167
+ # ============================================
168
+
169
+ # 1. Database connection patterns
170
+ if ($type -match '^(DB|Database|Gorm|SqlDB|Connection)$' -or $paramName -match '^(db|database|conn|connection)$') {
171
+ return "db"
172
+ }
173
+
174
+ # 2. *gorm.DB (most common in Go GORM projects)
175
+ if ($Param.RawType -match 'gorm\.DB' -or $type -eq "DB") {
176
+ return "db"
177
+ }
178
+
179
+ # 3. *sql.DB (standard library)
180
+ if ($Param.RawType -match 'sql\.DB') {
181
+ return "db"
182
+ }
183
+
184
+ # ADD MORE SPECIAL CASES HERE:
185
+ # --------------------------------------------
186
+ # Example: Redis connection
187
+ # if ($type -eq "RedisClient" -or $paramName -match "redis") {
188
+ # return "redisClient"
189
+ # }
190
+ #
191
+ # Example: MongoDB connection
192
+ # if ($type -eq "MongoClient" -or $paramName -match "mongo") {
193
+ # return "mongoClient"
194
+ # }
195
+ #
196
+ # Example: Cache
197
+ # if ($type -eq "Cache" -or $paramName -match "cache") {
198
+ # return "cache"
199
+ # }
200
+ #
201
+ # Example: Logger
202
+ # if ($type -eq "Logger" -or $paramName -match "logger") {
203
+ # return "logger"
204
+ # }
205
+ # --------------------------------------------
206
+
207
+ # 4. Fallback: unresolved type
208
+ return "/* TODO: provide $($Param.RawType) */"
209
+ }
210
+
211
+ function Generate-ProviderCode {
212
+ param([System.Collections.Generic.List[RepositoryInfo]]$Repositories)
213
+
214
+ Write-ColorOutput "`nGenerating repositories provider code..." "Cyan"
215
+
216
+ # Sort repositories alphabetically for consistent output
217
+ $sortedRepos = $Repositories | Sort-Object -Property Domain
218
+
219
+ $sb = [System.Text.StringBuilder]::new()
220
+ [void]$sb.AppendLine("package provider")
221
+ [void]$sb.AppendLine()
222
+ [void]$sb.AppendLine("import `"$ModulePath`"")
223
+ [void]$sb.AppendLine()
224
+
225
+ # Interface
226
+ [void]$sb.AppendLine("type RepositoriesProvider interface {")
227
+ foreach ($repo in $sortedRepos) {
228
+ $line = "`tProvide$($repo.Domain)() repositories.$($repo.Domain)"
229
+ [void]$sb.AppendLine($line)
230
+ }
231
+ [void]$sb.AppendLine("}")
232
+ [void]$sb.AppendLine()
233
+
234
+ # Struct
235
+ [void]$sb.AppendLine("type repositoriesProvider struct {")
236
+ foreach ($repo in $sortedRepos) {
237
+ $line = "`t$($repo.VarName) repositories.$($repo.Domain)"
238
+ [void]$sb.AppendLine($line)
239
+ }
240
+ [void]$sb.AppendLine("}")
241
+ [void]$sb.AppendLine()
242
+
243
+ # Constructor
244
+ [void]$sb.AppendLine("func NewRepositoriesProvider(cfg ConfigProvider) RepositoriesProvider {")
245
+ [void]$sb.AppendLine("`tdbConfig := cfg.ProvideDatabaseConfig()")
246
+ [void]$sb.AppendLine("`tdb := dbConfig.GetInstance()")
247
+ [void]$sb.AppendLine()
248
+
249
+ # Initialize repositories
250
+ foreach ($repo in $sortedRepos) {
251
+ $args = @()
252
+ foreach ($param in $repo.Parameters) {
253
+ $args += Resolve-RepositoryArgument $param
254
+ }
255
+ $argsStr = $args -join ", "
256
+ $line = "`t$($repo.VarName) := repositories.$($repo.ConstructorName)($argsStr)"
257
+ [void]$sb.AppendLine($line)
258
+ }
259
+
260
+ [void]$sb.AppendLine()
261
+ [void]$sb.AppendLine("`treturn &repositoriesProvider{")
262
+ foreach ($repo in $sortedRepos) {
263
+ $line = "`t`t$($repo.VarName): $($repo.VarName),"
264
+ [void]$sb.AppendLine($line)
265
+ }
266
+ [void]$sb.AppendLine("`t}")
267
+ [void]$sb.AppendLine("}")
268
+ [void]$sb.AppendLine()
269
+
270
+ # Getter methods
271
+ foreach ($repo in $sortedRepos) {
272
+ [void]$sb.AppendLine("func (r *repositoriesProvider) Provide$($repo.Domain)() repositories.$($repo.Domain) {")
273
+ [void]$sb.AppendLine("`treturn r.$($repo.VarName)")
274
+ [void]$sb.AppendLine("}")
275
+ [void]$sb.AppendLine()
276
+ }
277
+
278
+ return $sb.ToString()
279
+ }
280
+
281
+ function Write-ProviderFile {
282
+ param([string]$Code, [string]$OutputPath)
283
+
284
+ Write-ColorOutput "Writing to $OutputPath..." "Cyan"
285
+
286
+ # Ensure directory exists
287
+ $dir = Split-Path $OutputPath -Parent
288
+ if ($dir -and -not (Test-Path $dir)) {
289
+ New-Item -ItemType Directory -Path $dir -Force | Out-Null
290
+ }
291
+
292
+ # Write file as UTF-8 without BOM
293
+ $utf8NoBom = [System.Text.UTF8Encoding]::new($false)
294
+ [System.IO.File]::WriteAllText($OutputPath, $Code, $utf8NoBom)
295
+
296
+ Write-ColorOutput " Successfully generated $OutputPath" "Green"
297
+ }
298
+
299
+ # ============================================
300
+ # MAIN EXECUTION
301
+ # ============================================
302
+
303
+ try {
304
+ Write-ColorOutput "`n=========================================" "Blue"
305
+ Write-ColorOutput " Go Repository Provider Generator v1.0" "Blue"
306
+ Write-ColorOutput "=========================================`n" "Blue"
307
+
308
+ # Step 1: Parse all repository constructors
309
+ $repositories = Parse-GoFiles -Directory $RepositoriesDir
310
+
311
+ # Step 2: Generate provider code
312
+ $code = Generate-ProviderCode -Repositories $repositories
313
+
314
+ # Step 3: Write to file
315
+ Write-ProviderFile -Code $code -OutputPath $OutputFile
316
+
317
+ Write-ColorOutput "`nSUCCESS! Repositories provider generated successfully.`n" "Green"
318
+ Write-ColorOutput "Next steps:" "Cyan"
319
+ Write-ColorOutput " 1. Review $OutputFile" "White"
320
+ Write-ColorOutput " 2. Fill any /* TODO: provide ... */ placeholders" "White"
321
+ Write-ColorOutput " 3. Run: go build ./provider" "White"
322
+
323
+ } catch {
324
+ Write-ColorOutput "`nERROR: $($_.Exception.Message)" "Red"
325
+ Write-ColorOutput "Stack trace: $($_.ScriptStackTrace)" "Yellow"
326
+ exit 1
327
  }
do_inject_services.ps1 CHANGED
@@ -1,383 +1,383 @@
1
- #Requires -Version 5.1
2
- <#
3
- .SYNOPSIS
4
- Automatic Dependency Injection Generator for Go Services
5
-
6
- .DESCRIPTION
7
- Scans ./services/ directory, discovers service constructors, resolves dependencies,
8
- performs topological sorting, and generates provider/services_provider.go with full DI wiring.
9
-
10
- .EXAMPLE
11
- .\service_injector.ps1
12
-
13
- .NOTES
14
- - Works with PowerShell 5.1+ and PowerShell 7+
15
- - No external dependencies required
16
- - Supports multi-line constructor signatures
17
- - Handles dependency cycles with clear error messages
18
- #>
19
-
20
- [CmdletBinding()]
21
- param()
22
-
23
- # Configuration
24
- $ServicesDir = "./services"
25
- $OutputFile = "provider/services_provider.go"
26
- $ModulePath = "abdanhafidz.com/go-boilerplate/services"
27
-
28
- # ANSI colors for better output (fallback to plain text if not supported)
29
- $script:UseColors = $Host.UI.SupportsVirtualTerminal
30
- function Write-ColorOutput {
31
- param([string]$Message, [string]$Color = "White")
32
- if ($script:UseColors) {
33
- $colors = @{
34
- "Green" = "`e[32m"; "Yellow" = "`e[33m"; "Red" = "`e[31m"
35
- "Cyan" = "`e[36m"; "Blue" = "`e[34m"; "Reset" = "`e[0m"
36
- }
37
- Write-Host "$($colors[$Color])$Message$($colors['Reset'])"
38
- } else {
39
- Write-Host $Message
40
- }
41
- }
42
-
43
- # Data structures
44
- class ServiceInfo {
45
- [string]$ConstructorName # NewAccountService
46
- [string]$Domain # AccountService
47
- [string]$VarName # accountService
48
- [System.Collections.Generic.List[Parameter]]$Parameters
49
- [System.Collections.Generic.List[string]]$ServiceDependencies
50
-
51
- ServiceInfo() {
52
- $this.Parameters = [System.Collections.Generic.List[Parameter]]::new()
53
- $this.ServiceDependencies = [System.Collections.Generic.List[string]]::new()
54
- }
55
- }
56
-
57
- class Parameter {
58
- [string]$Name
59
- [string]$RawType
60
- [string]$NormalizedType
61
- }
62
-
63
- function Get-LowerCamelCase {
64
- param([string]$Text)
65
- if ($Text.Length -eq 0) { return $Text }
66
- return $Text.Substring(0, 1).ToLower() + $Text.Substring(1)
67
- }
68
-
69
- function Normalize-TypeName {
70
- param([string]$TypeStr)
71
-
72
- # Remove leading pointer
73
- $cleaned = $TypeStr -replace '^\*+', ''
74
-
75
- # Remove package prefix (everything before last dot)
76
- if ($cleaned -match '\.([^.]+)$') {
77
- $cleaned = $matches[1]
78
- }
79
-
80
- return $cleaned.Trim()
81
- }
82
-
83
- function Parse-GoFiles {
84
- param([string]$Directory)
85
-
86
- Write-ColorOutput "Scanning for service constructors in $Directory..." "Cyan"
87
-
88
- if (-not (Test-Path $Directory)) {
89
- Write-ColorOutput "ERROR: Directory '$Directory' not found!" "Red"
90
- exit 1
91
- }
92
-
93
- $goFiles = Get-ChildItem -Path $Directory -Filter "*.go" -Recurse -File
94
- $services = [System.Collections.Generic.List[ServiceInfo]]::new()
95
-
96
- foreach ($file in $goFiles) {
97
- $content = Get-Content $file.FullName -Raw
98
-
99
- # Match function signatures (support multi-line)
100
- # Pattern: func NewXxxService(...) XxxService
101
- $pattern = '(?ms)func\s+(New[a-zA-Z0-9]+Service)\s*\(([^)]*)\)\s+([a-zA-Z0-9*_.]+Service)'
102
- $matches = [regex]::Matches($content, $pattern)
103
-
104
- foreach ($match in $matches) {
105
- $constructorName = $match.Groups[1].Value
106
- $paramsStr = $match.Groups[2].Value
107
- $returnType = $match.Groups[3].Value
108
-
109
- # Extract domain name (XxxService)
110
- $domain = Normalize-TypeName $returnType
111
- $varName = Get-LowerCamelCase $domain
112
-
113
- $service = [ServiceInfo]::new()
114
- $service.ConstructorName = $constructorName
115
- $service.Domain = $domain
116
- $service.VarName = $varName
117
-
118
- # Parse parameters
119
- if ($paramsStr.Trim() -ne "") {
120
- # Split by comma, but be careful with nested types
121
- $paramList = $paramsStr -split ',\s*(?![^<>]*>)'
122
-
123
- foreach ($param in $paramList) {
124
- $param = $param.Trim()
125
- if ($param -eq "") { continue }
126
-
127
- # Split into name and type
128
- $parts = $param -split '\s+', 2
129
-
130
- $p = [Parameter]::new()
131
- if ($parts.Count -eq 2) {
132
- $p.Name = $parts[0]
133
- $p.RawType = $parts[1]
134
- } elseif ($parts.Count -eq 1) {
135
- # Anonymous parameter - synthesize name
136
- $p.Name = "param$($service.Parameters.Count)"
137
- $p.RawType = $parts[0]
138
- } else {
139
- continue
140
- }
141
-
142
- $p.NormalizedType = Normalize-TypeName $p.RawType
143
- $service.Parameters.Add($p)
144
-
145
- # Track service dependencies
146
- if ($p.NormalizedType -match 'Service$') {
147
- $service.ServiceDependencies.Add($p.NormalizedType)
148
- }
149
- }
150
- }
151
-
152
- $services.Add($service)
153
- Write-ColorOutput " Found: $constructorName" "Green"
154
- }
155
- }
156
-
157
- if ($services.Count -eq 0) {
158
- Write-ColorOutput "No service constructors found matching pattern 'NewXxxService'!" "Red"
159
- exit 1
160
- }
161
-
162
- Write-ColorOutput "`nTotal services discovered: $($services.Count)" "Blue"
163
- return $services
164
- }
165
-
166
- function Get-TopologicalOrder {
167
- param([System.Collections.Generic.List[ServiceInfo]]$Services)
168
-
169
- Write-ColorOutput "`nBuilding dependency graph..." "Cyan"
170
-
171
- # Build adjacency list
172
- $graph = @{}
173
- $inDegree = @{}
174
- $domainToService = @{}
175
-
176
- foreach ($svc in $Services) {
177
- $graph[$svc.Domain] = [System.Collections.Generic.List[string]]::new()
178
- $inDegree[$svc.Domain] = 0
179
- $domainToService[$svc.Domain] = $svc
180
- }
181
-
182
- # Build edges
183
- foreach ($svc in $Services) {
184
- foreach ($dep in $svc.ServiceDependencies) {
185
- if ($graph.ContainsKey($dep)) {
186
- $graph[$dep].Add($svc.Domain)
187
- $inDegree[$svc.Domain]++
188
- }
189
- }
190
- }
191
-
192
- # Kahn's algorithm for topological sort
193
- $queue = [System.Collections.Generic.Queue[string]]::new()
194
- foreach ($domain in $inDegree.Keys) {
195
- if ($inDegree[$domain] -eq 0) {
196
- $queue.Enqueue($domain)
197
- }
198
- }
199
-
200
- $sorted = [System.Collections.Generic.List[string]]::new()
201
-
202
- while ($queue.Count -gt 0) {
203
- $current = $queue.Dequeue()
204
- $sorted.Add($current)
205
-
206
- foreach ($neighbor in $graph[$current]) {
207
- $inDegree[$neighbor]--
208
- if ($inDegree[$neighbor] -eq 0) {
209
- $queue.Enqueue($neighbor)
210
- }
211
- }
212
- }
213
-
214
- # Check for cycles
215
- if ($sorted.Count -ne $Services.Count) {
216
- $remaining = $inDegree.Keys | Where-Object { $inDegree[$_] -gt 0 }
217
- Write-ColorOutput "`nERROR: Circular dependency detected!" "Red"
218
- Write-ColorOutput "Services involved in cycle: $($remaining -join ', ')" "Yellow"
219
- exit 1
220
- }
221
-
222
- Write-ColorOutput " Dependency graph validated (no cycles)" "Green"
223
- Write-ColorOutput " Topological order: $($sorted -join ' -> ')" "Blue"
224
-
225
- # Return services in topological order
226
- return $sorted | ForEach-Object { $domainToService[$_] }
227
- }
228
-
229
- function Resolve-ConstructorArgument {
230
- param([Parameter]$Param)
231
-
232
- $type = $Param.NormalizedType
233
-
234
- # SPECIAL CASE MAPPINGS - Add more here as needed
235
- # ============================================
236
-
237
- # 1. JWT secret string
238
- if ($Param.RawType -eq "string" -and $Param.Name -match "secret|key") {
239
- return "configProvider.ProvideJWTConfig().GetSecretKey()"
240
- }
241
-
242
- # 2. Repository pattern: XxxxRepository -> repoProvider.ProvideXxxxRepository()
243
- if ($type -match '^(.+)Repository$') {
244
- $repoName = $matches[1]
245
- return "repoProvider.Provide${repoName}Repository()"
246
- }
247
-
248
- # 3. Service pattern: XxxxService -> use variable (will be constructed before this)
249
- if ($type -match 'Service$') {
250
- return (Get-LowerCamelCase $type)
251
- }
252
-
253
- # ADD MORE SPECIAL CASES HERE:
254
- # --------------------------------------------
255
- # Example: Mail config
256
- # if ($type -eq "MailConfig") {
257
- # return "configProvider.ProvideMailConfig()"
258
- # }
259
- #
260
- # Example: Redis client
261
- # if ($type -eq "RedisClient") {
262
- # return "configProvider.ProvideRedisClient()"
263
- # }
264
- # --------------------------------------------
265
-
266
- # 4. Fallback: unresolved type
267
- return "/* TODO: provide $($Param.RawType) */"
268
- }
269
-
270
- function Generate-ProviderCode {
271
- param([System.Collections.Generic.List[ServiceInfo]]$ServicesInOrder)
272
-
273
- Write-ColorOutput "`nGenerating provider code..." "Cyan"
274
-
275
- $sb = [System.Text.StringBuilder]::new()
276
- [void]$sb.AppendLine("package provider")
277
- [void]$sb.AppendLine()
278
- [void]$sb.AppendLine("import `"$ModulePath`"")
279
- [void]$sb.AppendLine()
280
-
281
- # Interface
282
- [void]$sb.AppendLine("type ServicesProvider interface {")
283
- foreach ($svc in $ServicesInOrder) {
284
- $line = "`tProvide$($svc.Domain)() services.$($svc.Domain)"
285
- [void]$sb.AppendLine($line)
286
- }
287
- [void]$sb.AppendLine("}")
288
- [void]$sb.AppendLine()
289
-
290
- # Struct
291
- [void]$sb.AppendLine("type servicesProvider struct {")
292
- foreach ($svc in $ServicesInOrder) {
293
- $line = "`t$($svc.VarName) services.$($svc.Domain)"
294
- [void]$sb.AppendLine($line)
295
- }
296
- [void]$sb.AppendLine("}")
297
- [void]$sb.AppendLine()
298
-
299
- # Constructor
300
- [void]$sb.AppendLine("func NewServicesProvider(repoProvider RepositoriesProvider, configProvider ConfigProvider) ServicesProvider {")
301
-
302
- # Initialize services in topological order
303
- foreach ($svc in $ServicesInOrder) {
304
- $args = @()
305
- foreach ($param in $svc.Parameters) {
306
- $args += Resolve-ConstructorArgument $param
307
- }
308
- $argsStr = $args -join ", "
309
- $line = "`t$($svc.VarName) := services.$($svc.ConstructorName)($argsStr)"
310
- [void]$sb.AppendLine($line)
311
- }
312
-
313
- [void]$sb.AppendLine()
314
- [void]$sb.AppendLine("`treturn &servicesProvider{")
315
- foreach ($svc in $ServicesInOrder) {
316
- $line = "`t`t$($svc.VarName): $($svc.VarName),"
317
- [void]$sb.AppendLine($line)
318
- }
319
- [void]$sb.AppendLine("`t}")
320
- [void]$sb.AppendLine("}")
321
- [void]$sb.AppendLine()
322
-
323
- # Getter methods
324
- foreach ($svc in $ServicesInOrder) {
325
- [void]$sb.AppendLine("func (s *servicesProvider) Provide$($svc.Domain)() services.$($svc.Domain) {")
326
- [void]$sb.AppendLine("`treturn s.$($svc.VarName)")
327
- [void]$sb.AppendLine("}")
328
- [void]$sb.AppendLine()
329
- }
330
-
331
- return $sb.ToString()
332
- }
333
-
334
- function Write-ProviderFile {
335
- param([string]$Code, [string]$OutputPath)
336
-
337
- Write-ColorOutput "Writing to $OutputPath..." "Cyan"
338
-
339
- # Ensure directory exists
340
- $dir = Split-Path $OutputPath -Parent
341
- if ($dir -and -not (Test-Path $dir)) {
342
- New-Item -ItemType Directory -Path $dir -Force | Out-Null
343
- }
344
-
345
- # Write file as UTF-8 without BOM
346
- $utf8NoBom = [System.Text.UTF8Encoding]::new($false)
347
- [System.IO.File]::WriteAllText($OutputPath, $Code, $utf8NoBom)
348
-
349
- Write-ColorOutput " Successfully generated $OutputPath" "Green"
350
- }
351
-
352
- # ============================================
353
- # MAIN EXECUTION
354
- # ============================================
355
-
356
- try {
357
- Write-ColorOutput "`n=========================================" "Blue"
358
- Write-ColorOutput " Go Service Dependency Injector v1.0" "Blue"
359
- Write-ColorOutput "=========================================`n" "Blue"
360
-
361
- # Step 1: Parse all service constructors
362
- $services = Parse-GoFiles -Directory $ServicesDir
363
-
364
- # Step 2: Perform topological sort
365
- $sortedServices = Get-TopologicalOrder -Services $services
366
-
367
- # Step 3: Generate provider code
368
- $code = Generate-ProviderCode -ServicesInOrder $sortedServices
369
-
370
- # Step 4: Write to file
371
- Write-ProviderFile -Code $code -OutputPath $OutputFile
372
-
373
- Write-ColorOutput "`nSUCCESS! Provider generated successfully.`n" "Green"
374
- Write-ColorOutput "Next steps:" "Cyan"
375
- Write-ColorOutput " 1. Review $OutputFile" "White"
376
- Write-ColorOutput " 2. Fill any /* TODO: provide ... */ placeholders" "White"
377
- Write-ColorOutput " 3. Run: go build ./provider" "White"
378
-
379
- } catch {
380
- Write-ColorOutput "`nERROR: $($_.Exception.Message)" "Red"
381
- Write-ColorOutput "Stack trace: $($_.ScriptStackTrace)" "Yellow"
382
- exit 1
383
  }
 
1
+ #Requires -Version 5.1
2
+ <#
3
+ .SYNOPSIS
4
+ Automatic Dependency Injection Generator for Go Services
5
+
6
+ .DESCRIPTION
7
+ Scans ./services/ directory, discovers service constructors, resolves dependencies,
8
+ performs topological sorting, and generates provider/services_provider.go with full DI wiring.
9
+
10
+ .EXAMPLE
11
+ .\service_injector.ps1
12
+
13
+ .NOTES
14
+ - Works with PowerShell 5.1+ and PowerShell 7+
15
+ - No external dependencies required
16
+ - Supports multi-line constructor signatures
17
+ - Handles dependency cycles with clear error messages
18
+ #>
19
+
20
+ [CmdletBinding()]
21
+ param()
22
+
23
+ # Configuration
24
+ $ServicesDir = "./services"
25
+ $OutputFile = "provider/services_provider.go"
26
+ $ModulePath = "abdanhafidz.com/go-boilerplate/services"
27
+
28
+ # ANSI colors for better output (fallback to plain text if not supported)
29
+ $script:UseColors = $Host.UI.SupportsVirtualTerminal
30
+ function Write-ColorOutput {
31
+ param([string]$Message, [string]$Color = "White")
32
+ if ($script:UseColors) {
33
+ $colors = @{
34
+ "Green" = "`e[32m"; "Yellow" = "`e[33m"; "Red" = "`e[31m"
35
+ "Cyan" = "`e[36m"; "Blue" = "`e[34m"; "Reset" = "`e[0m"
36
+ }
37
+ Write-Host "$($colors[$Color])$Message$($colors['Reset'])"
38
+ } else {
39
+ Write-Host $Message
40
+ }
41
+ }
42
+
43
+ # Data structures
44
+ class ServiceInfo {
45
+ [string]$ConstructorName # NewAccountService
46
+ [string]$Domain # AccountService
47
+ [string]$VarName # accountService
48
+ [System.Collections.Generic.List[Parameter]]$Parameters
49
+ [System.Collections.Generic.List[string]]$ServiceDependencies
50
+
51
+ ServiceInfo() {
52
+ $this.Parameters = [System.Collections.Generic.List[Parameter]]::new()
53
+ $this.ServiceDependencies = [System.Collections.Generic.List[string]]::new()
54
+ }
55
+ }
56
+
57
+ class Parameter {
58
+ [string]$Name
59
+ [string]$RawType
60
+ [string]$NormalizedType
61
+ }
62
+
63
+ function Get-LowerCamelCase {
64
+ param([string]$Text)
65
+ if ($Text.Length -eq 0) { return $Text }
66
+ return $Text.Substring(0, 1).ToLower() + $Text.Substring(1)
67
+ }
68
+
69
+ function Normalize-TypeName {
70
+ param([string]$TypeStr)
71
+
72
+ # Remove leading pointer
73
+ $cleaned = $TypeStr -replace '^\*+', ''
74
+
75
+ # Remove package prefix (everything before last dot)
76
+ if ($cleaned -match '\.([^.]+)$') {
77
+ $cleaned = $matches[1]
78
+ }
79
+
80
+ return $cleaned.Trim()
81
+ }
82
+
83
+ function Parse-GoFiles {
84
+ param([string]$Directory)
85
+
86
+ Write-ColorOutput "Scanning for service constructors in $Directory..." "Cyan"
87
+
88
+ if (-not (Test-Path $Directory)) {
89
+ Write-ColorOutput "ERROR: Directory '$Directory' not found!" "Red"
90
+ exit 1
91
+ }
92
+
93
+ $goFiles = Get-ChildItem -Path $Directory -Filter "*.go" -Recurse -File
94
+ $services = [System.Collections.Generic.List[ServiceInfo]]::new()
95
+
96
+ foreach ($file in $goFiles) {
97
+ $content = Get-Content $file.FullName -Raw
98
+
99
+ # Match function signatures (support multi-line)
100
+ # Pattern: func NewXxxService(...) XxxService
101
+ $pattern = '(?ms)func\s+(New[a-zA-Z0-9]+Service)\s*\(([^)]*)\)\s+([a-zA-Z0-9*_.]+Service)'
102
+ $matches = [regex]::Matches($content, $pattern)
103
+
104
+ foreach ($match in $matches) {
105
+ $constructorName = $match.Groups[1].Value
106
+ $paramsStr = $match.Groups[2].Value
107
+ $returnType = $match.Groups[3].Value
108
+
109
+ # Extract domain name (XxxService)
110
+ $domain = Normalize-TypeName $returnType
111
+ $varName = Get-LowerCamelCase $domain
112
+
113
+ $service = [ServiceInfo]::new()
114
+ $service.ConstructorName = $constructorName
115
+ $service.Domain = $domain
116
+ $service.VarName = $varName
117
+
118
+ # Parse parameters
119
+ if ($paramsStr.Trim() -ne "") {
120
+ # Split by comma, but be careful with nested types
121
+ $paramList = $paramsStr -split ',\s*(?![^<>]*>)'
122
+
123
+ foreach ($param in $paramList) {
124
+ $param = $param.Trim()
125
+ if ($param -eq "") { continue }
126
+
127
+ # Split into name and type
128
+ $parts = $param -split '\s+', 2
129
+
130
+ $p = [Parameter]::new()
131
+ if ($parts.Count -eq 2) {
132
+ $p.Name = $parts[0]
133
+ $p.RawType = $parts[1]
134
+ } elseif ($parts.Count -eq 1) {
135
+ # Anonymous parameter - synthesize name
136
+ $p.Name = "param$($service.Parameters.Count)"
137
+ $p.RawType = $parts[0]
138
+ } else {
139
+ continue
140
+ }
141
+
142
+ $p.NormalizedType = Normalize-TypeName $p.RawType
143
+ $service.Parameters.Add($p)
144
+
145
+ # Track service dependencies
146
+ if ($p.NormalizedType -match 'Service$') {
147
+ $service.ServiceDependencies.Add($p.NormalizedType)
148
+ }
149
+ }
150
+ }
151
+
152
+ $services.Add($service)
153
+ Write-ColorOutput " Found: $constructorName" "Green"
154
+ }
155
+ }
156
+
157
+ if ($services.Count -eq 0) {
158
+ Write-ColorOutput "No service constructors found matching pattern 'NewXxxService'!" "Red"
159
+ exit 1
160
+ }
161
+
162
+ Write-ColorOutput "`nTotal services discovered: $($services.Count)" "Blue"
163
+ return $services
164
+ }
165
+
166
+ function Get-TopologicalOrder {
167
+ param([System.Collections.Generic.List[ServiceInfo]]$Services)
168
+
169
+ Write-ColorOutput "`nBuilding dependency graph..." "Cyan"
170
+
171
+ # Build adjacency list
172
+ $graph = @{}
173
+ $inDegree = @{}
174
+ $domainToService = @{}
175
+
176
+ foreach ($svc in $Services) {
177
+ $graph[$svc.Domain] = [System.Collections.Generic.List[string]]::new()
178
+ $inDegree[$svc.Domain] = 0
179
+ $domainToService[$svc.Domain] = $svc
180
+ }
181
+
182
+ # Build edges
183
+ foreach ($svc in $Services) {
184
+ foreach ($dep in $svc.ServiceDependencies) {
185
+ if ($graph.ContainsKey($dep)) {
186
+ $graph[$dep].Add($svc.Domain)
187
+ $inDegree[$svc.Domain]++
188
+ }
189
+ }
190
+ }
191
+
192
+ # Kahn's algorithm for topological sort
193
+ $queue = [System.Collections.Generic.Queue[string]]::new()
194
+ foreach ($domain in $inDegree.Keys) {
195
+ if ($inDegree[$domain] -eq 0) {
196
+ $queue.Enqueue($domain)
197
+ }
198
+ }
199
+
200
+ $sorted = [System.Collections.Generic.List[string]]::new()
201
+
202
+ while ($queue.Count -gt 0) {
203
+ $current = $queue.Dequeue()
204
+ $sorted.Add($current)
205
+
206
+ foreach ($neighbor in $graph[$current]) {
207
+ $inDegree[$neighbor]--
208
+ if ($inDegree[$neighbor] -eq 0) {
209
+ $queue.Enqueue($neighbor)
210
+ }
211
+ }
212
+ }
213
+
214
+ # Check for cycles
215
+ if ($sorted.Count -ne $Services.Count) {
216
+ $remaining = $inDegree.Keys | Where-Object { $inDegree[$_] -gt 0 }
217
+ Write-ColorOutput "`nERROR: Circular dependency detected!" "Red"
218
+ Write-ColorOutput "Services involved in cycle: $($remaining -join ', ')" "Yellow"
219
+ exit 1
220
+ }
221
+
222
+ Write-ColorOutput " Dependency graph validated (no cycles)" "Green"
223
+ Write-ColorOutput " Topological order: $($sorted -join ' -> ')" "Blue"
224
+
225
+ # Return services in topological order
226
+ return $sorted | ForEach-Object { $domainToService[$_] }
227
+ }
228
+
229
+ function Resolve-ConstructorArgument {
230
+ param([Parameter]$Param)
231
+
232
+ $type = $Param.NormalizedType
233
+
234
+ # SPECIAL CASE MAPPINGS - Add more here as needed
235
+ # ============================================
236
+
237
+ # 1. JWT secret string
238
+ if ($Param.RawType -eq "string" -and $Param.Name -match "secret|key") {
239
+ return "configProvider.ProvideJWTConfig().GetSecretKey()"
240
+ }
241
+
242
+ # 2. Repository pattern: XxxxRepository -> repoProvider.ProvideXxxxRepository()
243
+ if ($type -match '^(.+)Repository$') {
244
+ $repoName = $matches[1]
245
+ return "repoProvider.Provide${repoName}Repository()"
246
+ }
247
+
248
+ # 3. Service pattern: XxxxService -> use variable (will be constructed before this)
249
+ if ($type -match 'Service$') {
250
+ return (Get-LowerCamelCase $type)
251
+ }
252
+
253
+ # ADD MORE SPECIAL CASES HERE:
254
+ # --------------------------------------------
255
+ # Example: Mail config
256
+ # if ($type -eq "MailConfig") {
257
+ # return "configProvider.ProvideMailConfig()"
258
+ # }
259
+ #
260
+ # Example: Redis client
261
+ # if ($type -eq "RedisClient") {
262
+ # return "configProvider.ProvideRedisClient()"
263
+ # }
264
+ # --------------------------------------------
265
+
266
+ # 4. Fallback: unresolved type
267
+ return "/* TODO: provide $($Param.RawType) */"
268
+ }
269
+
270
+ function Generate-ProviderCode {
271
+ param([System.Collections.Generic.List[ServiceInfo]]$ServicesInOrder)
272
+
273
+ Write-ColorOutput "`nGenerating provider code..." "Cyan"
274
+
275
+ $sb = [System.Text.StringBuilder]::new()
276
+ [void]$sb.AppendLine("package provider")
277
+ [void]$sb.AppendLine()
278
+ [void]$sb.AppendLine("import `"$ModulePath`"")
279
+ [void]$sb.AppendLine()
280
+
281
+ # Interface
282
+ [void]$sb.AppendLine("type ServicesProvider interface {")
283
+ foreach ($svc in $ServicesInOrder) {
284
+ $line = "`tProvide$($svc.Domain)() services.$($svc.Domain)"
285
+ [void]$sb.AppendLine($line)
286
+ }
287
+ [void]$sb.AppendLine("}")
288
+ [void]$sb.AppendLine()
289
+
290
+ # Struct
291
+ [void]$sb.AppendLine("type servicesProvider struct {")
292
+ foreach ($svc in $ServicesInOrder) {
293
+ $line = "`t$($svc.VarName) services.$($svc.Domain)"
294
+ [void]$sb.AppendLine($line)
295
+ }
296
+ [void]$sb.AppendLine("}")
297
+ [void]$sb.AppendLine()
298
+
299
+ # Constructor
300
+ [void]$sb.AppendLine("func NewServicesProvider(repoProvider RepositoriesProvider, configProvider ConfigProvider) ServicesProvider {")
301
+
302
+ # Initialize services in topological order
303
+ foreach ($svc in $ServicesInOrder) {
304
+ $args = @()
305
+ foreach ($param in $svc.Parameters) {
306
+ $args += Resolve-ConstructorArgument $param
307
+ }
308
+ $argsStr = $args -join ", "
309
+ $line = "`t$($svc.VarName) := services.$($svc.ConstructorName)($argsStr)"
310
+ [void]$sb.AppendLine($line)
311
+ }
312
+
313
+ [void]$sb.AppendLine()
314
+ [void]$sb.AppendLine("`treturn &servicesProvider{")
315
+ foreach ($svc in $ServicesInOrder) {
316
+ $line = "`t`t$($svc.VarName): $($svc.VarName),"
317
+ [void]$sb.AppendLine($line)
318
+ }
319
+ [void]$sb.AppendLine("`t}")
320
+ [void]$sb.AppendLine("}")
321
+ [void]$sb.AppendLine()
322
+
323
+ # Getter methods
324
+ foreach ($svc in $ServicesInOrder) {
325
+ [void]$sb.AppendLine("func (s *servicesProvider) Provide$($svc.Domain)() services.$($svc.Domain) {")
326
+ [void]$sb.AppendLine("`treturn s.$($svc.VarName)")
327
+ [void]$sb.AppendLine("}")
328
+ [void]$sb.AppendLine()
329
+ }
330
+
331
+ return $sb.ToString()
332
+ }
333
+
334
+ function Write-ProviderFile {
335
+ param([string]$Code, [string]$OutputPath)
336
+
337
+ Write-ColorOutput "Writing to $OutputPath..." "Cyan"
338
+
339
+ # Ensure directory exists
340
+ $dir = Split-Path $OutputPath -Parent
341
+ if ($dir -and -not (Test-Path $dir)) {
342
+ New-Item -ItemType Directory -Path $dir -Force | Out-Null
343
+ }
344
+
345
+ # Write file as UTF-8 without BOM
346
+ $utf8NoBom = [System.Text.UTF8Encoding]::new($false)
347
+ [System.IO.File]::WriteAllText($OutputPath, $Code, $utf8NoBom)
348
+
349
+ Write-ColorOutput " Successfully generated $OutputPath" "Green"
350
+ }
351
+
352
+ # ============================================
353
+ # MAIN EXECUTION
354
+ # ============================================
355
+
356
+ try {
357
+ Write-ColorOutput "`n=========================================" "Blue"
358
+ Write-ColorOutput " Go Service Dependency Injector v1.0" "Blue"
359
+ Write-ColorOutput "=========================================`n" "Blue"
360
+
361
+ # Step 1: Parse all service constructors
362
+ $services = Parse-GoFiles -Directory $ServicesDir
363
+
364
+ # Step 2: Perform topological sort
365
+ $sortedServices = Get-TopologicalOrder -Services $services
366
+
367
+ # Step 3: Generate provider code
368
+ $code = Generate-ProviderCode -ServicesInOrder $sortedServices
369
+
370
+ # Step 4: Write to file
371
+ Write-ProviderFile -Code $code -OutputPath $OutputFile
372
+
373
+ Write-ColorOutput "`nSUCCESS! Provider generated successfully.`n" "Green"
374
+ Write-ColorOutput "Next steps:" "Cyan"
375
+ Write-ColorOutput " 1. Review $OutputFile" "White"
376
+ Write-ColorOutput " 2. Fill any /* TODO: provide ... */ placeholders" "White"
377
+ Write-ColorOutput " 3. Run: go build ./provider" "White"
378
+
379
+ } catch {
380
+ Write-ColorOutput "`nERROR: $($_.Exception.Message)" "Red"
381
+ Write-ColorOutput "Stack trace: $($_.ScriptStackTrace)" "Yellow"
382
+ exit 1
383
  }
go.mod CHANGED
@@ -57,6 +57,7 @@ require (
57
  github.com/quic-go/qpack v0.5.1 // indirect
58
  github.com/quic-go/quic-go v0.55.0 // indirect
59
  github.com/rogpeppe/go-internal v1.14.1 // indirect
 
60
  github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
61
  github.com/ugorji/go/codec v1.3.0 // indirect
62
  go.opentelemetry.io/auto/sdk v1.1.0 // indirect
 
57
  github.com/quic-go/qpack v0.5.1 // indirect
58
  github.com/quic-go/quic-go v0.55.0 // indirect
59
  github.com/rogpeppe/go-internal v1.14.1 // indirect
60
+ github.com/supabase-community/storage-go v0.8.1 // indirect
61
  github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
62
  github.com/ugorji/go/codec v1.3.0 // indirect
63
  go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.sum CHANGED
@@ -140,6 +140,8 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o
140
  github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
141
  github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
142
  github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
 
 
143
  github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
144
  github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
145
  github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=
 
140
  github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
141
  github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
142
  github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
143
+ github.com/supabase-community/storage-go v0.8.1 h1:EwD0vr+ADBIjBWH8G69AxWuvdFhifv64cfE/sjRky6I=
144
+ github.com/supabase-community/storage-go v0.8.1/go.mod h1:oBKcJf5rcUXy3Uj9eS5wR6mvpwbmvkjOtAA+4tGcdvQ=
145
  github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
146
  github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
147
  github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=
middleware/authentication_middleware.go CHANGED
@@ -1,48 +1,48 @@
1
- package middleware
2
-
3
- import (
4
- "errors"
5
- "fmt"
6
- "strings"
7
-
8
- http_error "abdanhafidz.com/go-boilerplate/models/error"
9
- "abdanhafidz.com/go-boilerplate/services"
10
- utils "abdanhafidz.com/go-boilerplate/utils"
11
- "github.com/gin-gonic/gin"
12
- )
13
-
14
- type AuthenticationMiddleware interface {
15
- VerifyAccount(ctx *gin.Context)
16
- }
17
- type authenticationMiddleware struct {
18
- jwtService services.JWTService
19
- }
20
-
21
- func NewAuthenticationMiddleware(jwtService services.JWTService) AuthenticationMiddleware {
22
- return &authenticationMiddleware{
23
- jwtService: jwtService,
24
- }
25
- }
26
- func (m *authenticationMiddleware) VerifyAccount(c *gin.Context) {
27
-
28
- authorizationBearer := c.Request.Header["Authorization"]
29
-
30
- if authorizationBearer != nil {
31
- token := strings.Split(authorizationBearer[0], " ")[1]
32
- claim, err := m.jwtService.ValidateToken(c.Request.Context(), token)
33
-
34
- if err != nil && errors.Is(err, http_error.INVALID_TOKEN) {
35
- utils.ResponseFAILED(c, claim, http_error.INVALID_TOKEN)
36
- c.Abort()
37
- return
38
- }
39
- fmt.Println("Claims:", claim)
40
- c.Set("account_id", claim.AccountId)
41
- c.Next()
42
-
43
- } else {
44
- utils.ResponseFAILED(c, "Empty Token", http_error.UNAUTHORIZED)
45
- return
46
- }
47
-
48
- }
 
1
+ package middleware
2
+
3
+ import (
4
+ "errors"
5
+ "fmt"
6
+ "strings"
7
+
8
+ http_error "abdanhafidz.com/go-boilerplate/models/error"
9
+ "abdanhafidz.com/go-boilerplate/services"
10
+ utils "abdanhafidz.com/go-boilerplate/utils"
11
+ "github.com/gin-gonic/gin"
12
+ )
13
+
14
+ type AuthenticationMiddleware interface {
15
+ VerifyAccount(ctx *gin.Context)
16
+ }
17
+ type authenticationMiddleware struct {
18
+ jwtService services.JWTService
19
+ }
20
+
21
+ func NewAuthenticationMiddleware(jwtService services.JWTService) AuthenticationMiddleware {
22
+ return &authenticationMiddleware{
23
+ jwtService: jwtService,
24
+ }
25
+ }
26
+ func (m *authenticationMiddleware) VerifyAccount(c *gin.Context) {
27
+
28
+ authorizationBearer := c.Request.Header["Authorization"]
29
+
30
+ if authorizationBearer != nil {
31
+ token := strings.Split(authorizationBearer[0], " ")[1]
32
+ claim, err := m.jwtService.ValidateToken(c.Request.Context(), token)
33
+
34
+ if err != nil && errors.Is(err, http_error.INVALID_TOKEN) {
35
+ utils.ResponseFAILED(c, claim, http_error.INVALID_TOKEN)
36
+ c.Abort()
37
+ return
38
+ }
39
+ fmt.Println("Claims:", claim)
40
+ c.Set("account_id", claim.AccountId)
41
+ c.Next()
42
+
43
+ } else {
44
+ utils.ResponseFAILED(c, "Empty Token", http_error.UNAUTHORIZED)
45
+ return
46
+ }
47
+
48
+ }
middleware/authorization_middleware.go CHANGED
@@ -1,43 +1,43 @@
1
- package middleware
2
-
3
- import (
4
- http_error "abdanhafidz.com/go-boilerplate/models/error"
5
- "abdanhafidz.com/go-boilerplate/services"
6
- utils "abdanhafidz.com/go-boilerplate/utils"
7
- "github.com/gin-gonic/gin"
8
- "github.com/google/uuid"
9
- )
10
-
11
- type AuthorizationMiddleware interface {
12
- AuthorizeUserToEvent(ctx *gin.Context)
13
- }
14
- type authorizationMiddleware struct {
15
- eventService services.EventService
16
- }
17
-
18
- func NewAuthorizationMiddleware(eventService services.EventService) AuthorizationMiddleware {
19
- return &authorizationMiddleware{
20
- eventService: eventService,
21
- }
22
- }
23
-
24
- func (m *authorizationMiddleware) AuthorizeUserToEvent(c *gin.Context) {
25
-
26
- eventSlug := c.Param("slug")
27
- accountId, exists := c.Get("account_id")
28
- if !exists {
29
- utils.ResponseFAILED(c, eventSlug, http_error.DATA_NOT_FOUND)
30
- c.Abort()
31
- return
32
- }
33
-
34
- err := m.eventService.AuthorizeUserToEvent(c.Request.Context(), eventSlug, accountId.(uuid.UUID))
35
-
36
- if err != nil {
37
- utils.ResponseFAILED(c, eventSlug, err)
38
- c.Abort()
39
- return
40
- }
41
-
42
- c.Next()
43
- }
 
1
+ package middleware
2
+
3
+ import (
4
+ http_error "abdanhafidz.com/go-boilerplate/models/error"
5
+ "abdanhafidz.com/go-boilerplate/services"
6
+ utils "abdanhafidz.com/go-boilerplate/utils"
7
+ "github.com/gin-gonic/gin"
8
+ "github.com/google/uuid"
9
+ )
10
+
11
+ type AuthorizationMiddleware interface {
12
+ AuthorizeUserToEvent(ctx *gin.Context)
13
+ }
14
+ type authorizationMiddleware struct {
15
+ eventService services.EventService
16
+ }
17
+
18
+ func NewAuthorizationMiddleware(eventService services.EventService) AuthorizationMiddleware {
19
+ return &authorizationMiddleware{
20
+ eventService: eventService,
21
+ }
22
+ }
23
+
24
+ func (m *authorizationMiddleware) AuthorizeUserToEvent(c *gin.Context) {
25
+
26
+ eventSlug := c.Param("slug")
27
+ accountId, exists := c.Get("account_id")
28
+ if !exists {
29
+ utils.ResponseFAILED(c, eventSlug, http_error.NOT_FOUND_ERROR)
30
+ c.Abort()
31
+ return
32
+ }
33
+
34
+ err := m.eventService.AuthorizeUserToEvent(c.Request.Context(), eventSlug, accountId.(uuid.UUID))
35
+
36
+ if err != nil {
37
+ utils.ResponseFAILED(c, eventSlug, err)
38
+ c.Abort()
39
+ return
40
+ }
41
+
42
+ c.Next()
43
+ }
middleware/middleware.go CHANGED
@@ -1 +1 @@
1
- package middleware
 
1
+ package middleware
models/dto/academy_dto.go CHANGED
@@ -1,30 +1,30 @@
1
- package dto
2
-
3
- import "github.com/google/uuid"
4
-
5
- type CreateAcademyRequest struct {
6
- Title string `json:"title" binding:"required"`
7
- Slug string `json:"slug"`
8
- Description string `json:"description"`
9
- ImageUrl string `json:"image_url"`
10
- }
11
-
12
- type UpdateAcademyRequest struct {
13
- Title string `json:"title"`
14
- Slug string `json:"slug"`
15
- Description string `json:"description"`
16
- ImageUrl string `json:"image_url"`
17
- }
18
-
19
- type CreateMaterialRequest struct {
20
- AcademyId uuid.UUID `json:"academy_id" binding:"required"`
21
- Title string `json:"title" binding:"required"`
22
- Slug string `json:"slug"`
23
- Description string `json:"description"`
24
- }
25
-
26
- type CreateContentRequest struct {
27
- MaterialId uuid.UUID `json:"material_id" binding:"required"`
28
- Title string `json:"title" binding:"required"`
29
- Contents string `json:"contents"`
30
- }
 
1
+ package dto
2
+
3
+ import "github.com/google/uuid"
4
+
5
+ type CreateAcademyRequest struct {
6
+ Title string `json:"title" binding:"required"`
7
+ Slug string `json:"slug"`
8
+ Description string `json:"description"`
9
+ ImageUrl string `json:"image_url"`
10
+ }
11
+
12
+ type UpdateAcademyRequest struct {
13
+ Title string `json:"title"`
14
+ Slug string `json:"slug"`
15
+ Description string `json:"description"`
16
+ ImageUrl string `json:"image_url"`
17
+ }
18
+
19
+ type CreateMaterialRequest struct {
20
+ AcademyId uuid.UUID `json:"academy_id" binding:"required"`
21
+ Title string `json:"title" binding:"required"`
22
+ Slug string `json:"slug"`
23
+ Description string `json:"description"`
24
+ }
25
+
26
+ type CreateContentRequest struct {
27
+ MaterialId uuid.UUID `json:"material_id" binding:"required"`
28
+ Title string `json:"title" binding:"required"`
29
+ Contents string `json:"contents"`
30
+ }
models/dto/account_details_dto.go CHANGED
@@ -1,19 +1,19 @@
1
- package dto
2
-
3
- import (
4
- entity "abdanhafidz.com/go-boilerplate/models/entity"
5
- )
6
-
7
- type AccountDetailResponse struct {
8
- Account entity.Account `json:"account"`
9
- Details entity.AccountDetail `json:"details"`
10
- }
11
-
12
- type UpdateAccountDetailRequest struct {
13
- FullName *string `json:"full_name"`
14
- SchoolName *string `json:"school_name"`
15
- Province *string `json:"province"`
16
- City *string `json:"city"`
17
- Avatar *string `json:"avatar"`
18
- PhoneNumber *string `json:"phone_number"`
19
- }
 
1
+ package dto
2
+
3
+ import (
4
+ entity "abdanhafidz.com/go-boilerplate/models/entity"
5
+ )
6
+
7
+ type AccountDetailResponse struct {
8
+ Account entity.Account `json:"account"`
9
+ Details entity.AccountDetail `json:"details"`
10
+ }
11
+
12
+ type UpdateAccountDetailRequest struct {
13
+ FullName *string `json:"full_name"`
14
+ SchoolName *string `json:"school_name"`
15
+ Province *string `json:"province"`
16
+ City *string `json:"city"`
17
+ Avatar *string `json:"avatar"`
18
+ PhoneNumber *string `json:"phone_number"`
19
+ }
models/dto/event_dto.go CHANGED
@@ -1,20 +1,20 @@
1
- package dto
2
-
3
- import (
4
- entity "abdanhafidz.com/go-boilerplate/models/entity"
5
- )
6
-
7
- type EventDetailResponse struct {
8
- Data *entity.Events
9
- RegisterStatus int `json:"register_status" binding:"required"`
10
- }
11
-
12
- type JoinEventRequest struct {
13
- EventCode string `json:"event_code" binding:"required"`
14
- }
15
-
16
- type EventStatus struct {
17
- IsHasNotStarted bool
18
- IsOnGoing bool
19
- IsFinished bool
20
- }
 
1
+ package dto
2
+
3
+ import (
4
+ entity "abdanhafidz.com/go-boilerplate/models/entity"
5
+ )
6
+
7
+ type EventDetailResponse struct {
8
+ Data *entity.Events
9
+ RegisterStatus int `json:"register_status" binding:"required"`
10
+ }
11
+
12
+ type JoinEventRequest struct {
13
+ EventCode string `json:"event_code" binding:"required"`
14
+ }
15
+
16
+ type EventStatus struct {
17
+ IsHasNotStarted bool
18
+ IsOnGoing bool
19
+ IsFinished bool
20
+ }
models/dto/exam_dto.go CHANGED
@@ -1,23 +1,23 @@
1
- package dto
2
-
3
- import (
4
- entity "abdanhafidz.com/go-boilerplate/models/entity"
5
- "github.com/google/uuid"
6
- )
7
-
8
- type UserExamStatus struct {
9
- IsNotAttempt bool
10
- IsOnAttempt bool
11
- IsSubmitted bool
12
- IsTimeOut bool
13
- }
14
-
15
- type AnswerWithQuestion struct {
16
- Answer entity.ExamEventAnswer `json:"answer"`
17
- Question entity.Questions `json:"question"`
18
- }
19
-
20
- type AnswerExamEventRequest struct {
21
- QuestionId uuid.UUID `json:"question_id" binding:"required"`
22
- Answer []string `json:"answer"`
23
- }
 
1
+ package dto
2
+
3
+ import (
4
+ entity "abdanhafidz.com/go-boilerplate/models/entity"
5
+ "github.com/google/uuid"
6
+ )
7
+
8
+ type UserExamStatus struct {
9
+ IsNotAttempt bool
10
+ IsOnAttempt bool
11
+ IsSubmitted bool
12
+ IsTimeOut bool
13
+ }
14
+
15
+ type AnswerWithQuestion struct {
16
+ Answer entity.ExamEventAnswer `json:"answer"`
17
+ Question entity.Questions `json:"question"`
18
+ }
19
+
20
+ type AnswerExamEventRequest struct {
21
+ QuestionId uuid.UUID `json:"question_id" binding:"required"`
22
+ Answer []string `json:"answer"`
23
+ }
models/dto/option_dto.go CHANGED
@@ -1,12 +1,12 @@
1
- package dto
2
-
3
- import entity "abdanhafidz.com/go-boilerplate/models/entity"
4
-
5
- type OptionsRequest struct {
6
- OptionName string `json:"option_name" binding:"required"`
7
- OptionValue []string `json:"option_values" binding:"required"`
8
- }
9
-
10
- type OptionsResponse struct {
11
- Options []entity.Options `json:"options"`
12
- }
 
1
+ package dto
2
+
3
+ import entity "abdanhafidz.com/go-boilerplate/models/entity"
4
+
5
+ type OptionsRequest struct {
6
+ OptionName string `json:"option_name" binding:"required"`
7
+ OptionValue []string `json:"option_values" binding:"required"`
8
+ }
9
+
10
+ type OptionsResponse struct {
11
+ Options []entity.Options `json:"options"`
12
+ }
models/dto/upload_dto.go ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package dto
2
+
3
+ import (
4
+ // Gunakan path yang SAMA PERSIS dengan yang ada di OptionsRequest
5
+ entity "abdanhafidz.com/go-boilerplate/models/entity"
6
+
7
+ "github.com/google/uuid"
8
+ "time"
9
+ )
10
+
11
+ type FileResponse struct {
12
+ Id uuid.UUID `json:"id"`
13
+ OriginalName string `json:"original_name"`
14
+ URL string `json:"url"`
15
+ MimeType string `json:"mime_type"`
16
+ Size int64 `json:"size"`
17
+ CreatedAt time.Time `json:"created_at"`
18
+ }
19
+
20
+ type FileUploadResponse struct {
21
+ Status string `json:"status"`
22
+ Message string `json:"message"`
23
+ Data []FileResponse `json:"data"`
24
+ }
25
+
26
+ type FileResponseSingle struct {
27
+ Status string `json:"status"`
28
+ Message string `json:"message"`
29
+ Data FileResponse `json:"data"`
30
+ }
31
+
32
+ func FormatFileResponse(f *entity.File, baseURL string) FileResponse {
33
+ fullURL := baseURL + f.Path
34
+
35
+ return FileResponse{
36
+ Id: f.Id,
37
+ OriginalName: f.OriginalName,
38
+ URL: fullURL,
39
+ MimeType: f.MimeType,
40
+ Size: f.Size,
41
+ CreatedAt: f.CreatedAt,
42
+ }
43
+ }
models/entity/contant.go ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ package models
2
+
3
+ const (
4
+ StatusNotStarted = "NOT_STARTED"
5
+ StatusInProgress = "IN_PROGRESS"
6
+ StatusCompleted = "COMPLETED"
7
+ )
models/entity/entity.go CHANGED
@@ -332,3 +332,18 @@ type AcademyContentProgress struct {
332
  }
333
 
334
  func (AcademyContentProgress) TableName() string { return "academy_content_progress" }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
332
  }
333
 
334
  func (AcademyContentProgress) TableName() string { return "academy_content_progress" }
335
+
336
+ type File struct {
337
+ Id uuid.UUID `gorm:"type:uuid;primary_key;default:uuid_generate_v4()" json:"id"`
338
+ OriginalName string `json:"original_name,omitempty"`
339
+ StoredName string `json:"stored_name,omitempty"`
340
+ MimeType string `json:"mime_type,omitempty"`
341
+ Size int64 `json:"size,omitempty"`
342
+ Path string `json:"path,omitempty"`
343
+ Context string `json:"context,omitempty"`
344
+ AccountId uuid.UUID `json:"account_id,omitempty"`
345
+ CreatedAt time.Time `json:"created_at,omitempty"`
346
+ Account *Account `gorm:"foreignKey:AccountId" json:"account,omitempty"`
347
+ }
348
+
349
+ func (File) TableName() string { return "files" }
models/error/error.go CHANGED
@@ -3,25 +3,49 @@ package http_error
3
  import "errors"
4
 
5
  var (
6
- BAD_REQUEST_ERROR = errors.New("Invalid Request Format !")
7
- INTERNAL_SERVER_ERROR = errors.New("Internal Server Error!")
8
- UNAUTHORIZED = errors.New("Unauthorized, you don't have permission to access this service!")
9
- DATA_NOT_FOUND = errors.New("There is not data with given credential / given parameter!")
10
- TIMEOUT = errors.New("Server took to long respond!")
11
- EXISTING_ACCOUNT = errors.New("There is existing account!")
12
- INVALID_TOKEN = errors.New("Invalid Authentication Payload!")
13
- DUPLICATE_DATA = errors.New("Duplicate data !")
14
- ACCOUNT_NOT_FOUND = errors.New("There is no account with given credential!")
15
- WRONG_PASSWORD = errors.New("Your password is wrong for given account credential, please recheck!")
16
- INVALID_ACCOUNT_DIGITS = errors.New("Your account 3 digits is not found in account number data")
17
- EXPIRED_TOKEN = errors.New("Token expired")
 
 
 
 
 
 
 
 
18
  ALREADY_REGISTERED_TO_EVENT = errors.New("Account already registered to this event")
19
- EMAIL_ALREADY_EXISTS = errors.New("Email already registered")
20
  NOT_REGISTERED_TO_EVENT = errors.New("Account is not registered to this event")
21
- INVALID_OTP = errors.New("Invalid OTP Code")
22
  ERR_PROBLEM_SET_NOT_FOUND = errors.New("problem set not found")
23
  ERR_QUESTION_NOT_FOUND = errors.New("question not found")
24
  EVENT_FINISHED = errors.New("The event has ended, you were disallowed to do the exam!")
25
  EVENT_NOT_STARTED = errors.New("Take it easy, event hasn't starting yet! you cannot do the exam!")
26
  EXAMS_SUBMITTED = errors.New("You've submitted the exam, you were diasallowed to answer the question!")
27
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  import "errors"
4
 
5
  var (
6
+ // ================= GENERAL =================
7
+ BAD_REQUEST_ERROR = errors.New("Invalid Request Format !")
8
+ INTERNAL_SERVER_ERROR = errors.New("Internal Server Error!")
9
+ TIMEOUT = errors.New("Server took to long respond!")
10
+ NOT_FOUND_ERROR = errors.New("Resource not found.")
11
+ DUPLICATE_DATA = errors.New("Duplicate data !")
12
+ INVALID_DATA_PAYLOAD = errors.New("Invalid data payload provided.")
13
+
14
+ // ================= AUTH & ACCOUNT =================
15
+ UNAUTHORIZED = errors.New("Unauthorized, you don't have permission to access this service!")
16
+ EXISTING_ACCOUNT = errors.New("There is existing account!")
17
+ INVALID_TOKEN = errors.New("Invalid Authentication Payload!")
18
+ ACCOUNT_NOT_FOUND = errors.New("There is no account with given credential!")
19
+ WRONG_PASSWORD = errors.New("Your password is wrong for given account credential, please recheck!")
20
+ INVALID_ACCOUNT_DIGITS = errors.New("Your account 3 digits is not found in account number data")
21
+ EXPIRED_TOKEN = errors.New("Token expired")
22
+ INVALID_OTP = errors.New("Invalid OTP Code")
23
+ EMAIL_ALREADY_EXISTS = errors.New("Email already registered")
24
+
25
+ // ================= EVENT & EXAM =================
26
  ALREADY_REGISTERED_TO_EVENT = errors.New("Account already registered to this event")
 
27
  NOT_REGISTERED_TO_EVENT = errors.New("Account is not registered to this event")
 
28
  ERR_PROBLEM_SET_NOT_FOUND = errors.New("problem set not found")
29
  ERR_QUESTION_NOT_FOUND = errors.New("question not found")
30
  EVENT_FINISHED = errors.New("The event has ended, you were disallowed to do the exam!")
31
  EVENT_NOT_STARTED = errors.New("Take it easy, event hasn't starting yet! you cannot do the exam!")
32
  EXAMS_SUBMITTED = errors.New("You've submitted the exam, you were diasallowed to answer the question!")
33
+
34
+ // ================= FILE UPLOAD =================
35
+ FILE_TOO_LARGE = errors.New("File size exceeds the maximum limit!")
36
+ INVALID_FILE_TYPE = errors.New("File type is not permitted for the selected context.")
37
+ UPLOAD_FAILED = errors.New("Failed to upload file to storage provider.")
38
+ PARTIAL_UPLOAD_FAILURE = errors.New("Some files failed validation or upload.")
39
+
40
+ // ================= ACADEMY =================
41
+ TITLE_REQUIRED = errors.New("Title cannot be empty!")
42
+ SLUG_REQUIRED = errors.New("Slug cannot be empty!")
43
+ ACADEMY_ID_REQUIRED = errors.New("Academy ID is required!")
44
+ MATERIAL_ID_REQUIRED = errors.New("Material ID is required!")
45
+
46
+ ACADEMY_NOT_FOUND = errors.New("Academy not found!")
47
+ MATERIAL_NOT_FOUND = errors.New("Material not found!")
48
+ CONTENT_NOT_FOUND = errors.New("Content not found!")
49
+ ACADEMY_HAS_MATERIALS = errors.New("Cannot delete academy because it still has materials!")
50
+ MATERIAL_HAS_CONTENTS = errors.New("Cannot delete material because it still has contents!")
51
+ )
provider/config_provider.go CHANGED
@@ -1,38 +1,59 @@
1
- package provider
2
-
3
- import "abdanhafidz.com/go-boilerplate/config"
4
-
5
- type ConfigProvider interface {
6
- ProvideJWTConfig() config.JWTConfig
7
- ProvideEnvConfig() config.EnvConfig
8
- ProvideDatabaseConfig() config.DatabaseConfig
9
- }
10
-
11
- type configProvider struct {
12
- jWTConfig config.JWTConfig
13
- envConfig config.EnvConfig
14
- databaseConfig config.DatabaseConfig
15
- }
16
-
17
- func NewConfigProvider() ConfigProvider {
18
- envConfig := config.NewEnvConfig("Asia/Jakarta")
19
- jWTConfig := config.NewJWTConfig(envConfig.GetSalt())
20
- databaseConfig := config.NewDatabaseConfig(envConfig.GetDatabaseHost(), envConfig.GetDatabaseUser(), envConfig.GetDatabasePassword(), envConfig.GetDatabaseName(), envConfig.GetDatabasePort())
21
- return &configProvider{
22
- jWTConfig: jWTConfig,
23
- envConfig: envConfig,
24
- databaseConfig: databaseConfig,
25
- }
26
- }
27
-
28
- func (c *configProvider) ProvideJWTConfig() config.JWTConfig {
29
- return c.jWTConfig
30
- }
31
-
32
- func (c *configProvider) ProvideEnvConfig() config.EnvConfig {
33
- return c.envConfig
34
- }
35
-
36
- func (c *configProvider) ProvideDatabaseConfig() config.DatabaseConfig {
37
- return c.databaseConfig
38
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package provider
2
+
3
+ import "abdanhafidz.com/go-boilerplate/config"
4
+
5
+ type ConfigProvider interface {
6
+ ProvideJWTConfig() config.JWTConfig
7
+ ProvideEnvConfig() config.EnvConfig
8
+ ProvideDatabaseConfig() config.DatabaseConfig
9
+ ProvideSupabaseConfig() config.SupabaseConfig
10
+ }
11
+
12
+ type configProvider struct {
13
+ jWTConfig config.JWTConfig
14
+ envConfig config.EnvConfig
15
+ databaseConfig config.DatabaseConfig
16
+ supabaseConfig config.SupabaseConfig
17
+ }
18
+
19
+ func NewConfigProvider() ConfigProvider {
20
+ envConfig := config.NewEnvConfig("Asia/Jakarta")
21
+ jWTConfig := config.NewJWTConfig(envConfig.GetSalt())
22
+
23
+ databaseConfig := config.NewDatabaseConfig(
24
+ envConfig.GetDatabaseHost(),
25
+ envConfig.GetDatabaseUser(),
26
+ envConfig.GetDatabasePassword(),
27
+ envConfig.GetDatabaseName(),
28
+ envConfig.GetDatabasePort(),
29
+ )
30
+
31
+ supabaseConfig := config.NewSupabaseConfig(
32
+ envConfig.GetSupabaseURL(),
33
+ envConfig.GetSupabaseKey(),
34
+ envConfig.GetSupabaseBucket(),
35
+ )
36
+
37
+ return &configProvider{
38
+ jWTConfig: jWTConfig,
39
+ envConfig: envConfig,
40
+ databaseConfig: databaseConfig,
41
+ supabaseConfig: supabaseConfig,
42
+ }
43
+ }
44
+
45
+ func (c *configProvider) ProvideJWTConfig() config.JWTConfig {
46
+ return c.jWTConfig
47
+ }
48
+
49
+ func (c *configProvider) ProvideEnvConfig() config.EnvConfig {
50
+ return c.envConfig
51
+ }
52
+
53
+ func (c *configProvider) ProvideDatabaseConfig() config.DatabaseConfig {
54
+ return c.databaseConfig
55
+ }
56
+
57
+ func (c *configProvider) ProvideSupabaseConfig() config.SupabaseConfig {
58
+ return c.supabaseConfig
59
+ }
provider/controller_provider.go CHANGED
@@ -1,90 +1,106 @@
1
- package provider
2
-
3
- import "abdanhafidz.com/go-boilerplate/controllers"
4
-
5
- type ControllerProvider interface {
6
- ProvideAcademyController() controllers.AcademyController
7
- ProvideAccountDetailController() controllers.AccountDetailController
8
- ProvideAuthenticationController() controllers.AuthenticationController
9
- ProvideEmailVerificationController() controllers.EmailVerificationController
10
- ProvideEventController() controllers.EventController
11
- ProvideExamController() controllers.ExamController
12
- ProvideForgotPasswordController() controllers.ForgotPasswordController
13
- ProvideOptionController() controllers.OptionController
14
- ProvideRegionController() controllers.RegionController
15
- }
16
-
17
- type controllerProvider struct {
18
- academyController controllers.AcademyController
19
- accountDetailController controllers.AccountDetailController
20
- authenticationController controllers.AuthenticationController
21
- emailVerificationController controllers.EmailVerificationController
22
- eventController controllers.EventController
23
- examController controllers.ExamController
24
- forgotPasswordController controllers.ForgotPasswordController
25
- optionController controllers.OptionController
26
- regionController controllers.RegionController
27
- }
28
-
29
- func NewControllerProvider(servicesProvider ServicesProvider) ControllerProvider {
30
-
31
- academyController := controllers.NewAcademyController(servicesProvider.ProvideAcademyService())
32
- accountDetailController := controllers.NewAccountDetailController(servicesProvider.ProvideAccountService())
33
- authenticationController := controllers.NewAuthenticationController(servicesProvider.ProvideAccountService(), servicesProvider.ProvideExternalAuthService())
34
- emailVerificationController := controllers.NewEmailVerificationController(servicesProvider.ProvideEmailVerificationService())
35
- eventController := controllers.NewEventController(servicesProvider.ProvideEventService())
36
- examController := controllers.NewExamController(servicesProvider.ProvideExamService())
37
- forgotPasswordController := controllers.NewForgotPasswordController(servicesProvider.ProvideForgotPasswordService())
38
- optionController := controllers.NewOptionController(servicesProvider.ProvideOptionService())
39
- regionController := controllers.NewRegionController(servicesProvider.ProvideRegionService())
40
- return &controllerProvider{
41
- academyController: academyController,
42
- accountDetailController: accountDetailController,
43
- authenticationController: authenticationController,
44
- emailVerificationController: emailVerificationController,
45
- eventController: eventController,
46
- examController: examController,
47
- forgotPasswordController: forgotPasswordController,
48
- optionController: optionController,
49
- regionController: regionController,
50
- }
51
- }
52
-
53
- // --- Getter Methods ---
54
-
55
- func (c *controllerProvider) ProvideAcademyController() controllers.AcademyController {
56
- return c.academyController
57
- }
58
-
59
- func (c *controllerProvider) ProvideAccountDetailController() controllers.AccountDetailController {
60
- return c.accountDetailController
61
- }
62
-
63
- func (c *controllerProvider) ProvideAuthenticationController() controllers.AuthenticationController {
64
- return c.authenticationController
65
- }
66
-
67
- func (c *controllerProvider) ProvideEmailVerificationController() controllers.EmailVerificationController {
68
- return c.emailVerificationController
69
- }
70
-
71
- func (c *controllerProvider) ProvideEventController() controllers.EventController {
72
- return c.eventController
73
- }
74
-
75
- func (c *controllerProvider) ProvideExamController() controllers.ExamController {
76
- return c.examController
77
- }
78
-
79
- func (c *controllerProvider) ProvideForgotPasswordController() controllers.ForgotPasswordController {
80
- return c.forgotPasswordController
81
- }
82
-
83
- func (c *controllerProvider) ProvideOptionController() controllers.OptionController {
84
- return c.optionController
85
- }
86
-
87
- func (c *controllerProvider) ProvideRegionController() controllers.RegionController {
88
- return c.regionController
89
- }
90
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package provider
2
+
3
+ import "abdanhafidz.com/go-boilerplate/controllers"
4
+
5
+ type ControllerProvider interface {
6
+ ProvideAcademyController() controllers.AcademyController
7
+ ProvideAccountDetailController() controllers.AccountDetailController
8
+ ProvideAuthenticationController() controllers.AuthenticationController
9
+ ProvideEmailVerificationController() controllers.EmailVerificationController
10
+ ProvideEventController() controllers.EventController
11
+ ProvideExamController() controllers.ExamController
12
+ ProvideForgotPasswordController() controllers.ForgotPasswordController
13
+ ProvideOptionController() controllers.OptionController
14
+ ProvideRegionController() controllers.RegionController
15
+
16
+ // UPDATE: Menggunakan Pointer (*)
17
+ ProvideUploadController() *controllers.UploadController
18
+ }
19
+
20
+ type controllerProvider struct {
21
+ academyController controllers.AcademyController
22
+ accountDetailController controllers.AccountDetailController
23
+ authenticationController controllers.AuthenticationController
24
+ emailVerificationController controllers.EmailVerificationController
25
+ eventController controllers.EventController
26
+ examController controllers.ExamController
27
+ forgotPasswordController controllers.ForgotPasswordController
28
+ optionController controllers.OptionController
29
+ regionController controllers.RegionController
30
+
31
+ // UPDATE: Menggunakan Pointer (*)
32
+ uploadController *controllers.UploadController
33
+ }
34
+
35
+ func NewControllerProvider(servicesProvider ServicesProvider) ControllerProvider {
36
+
37
+ academyController := controllers.NewAcademyController(servicesProvider.ProvideAcademyService())
38
+ accountDetailController := controllers.NewAccountDetailController(servicesProvider.ProvideAccountService())
39
+ authenticationController := controllers.NewAuthenticationController(servicesProvider.ProvideAccountService(), servicesProvider.ProvideExternalAuthService())
40
+ emailVerificationController := controllers.NewEmailVerificationController(servicesProvider.ProvideEmailVerificationService())
41
+ eventController := controllers.NewEventController(servicesProvider.ProvideEventService())
42
+ examController := controllers.NewExamController(servicesProvider.ProvideExamService())
43
+ forgotPasswordController := controllers.NewForgotPasswordController(servicesProvider.ProvideForgotPasswordService())
44
+ optionController := controllers.NewOptionController(servicesProvider.ProvideOptionService())
45
+ regionController := controllers.NewRegionController(servicesProvider.ProvideRegionService())
46
+
47
+ // UPDATE: Inisialisasi Upload Controller
48
+ // servicesProvider.ProvideUploadService() sekarang sudah return Pointer (*), jadi aman.
49
+ uploadController := controllers.NewUploadController(servicesProvider.ProvideUploadService())
50
+
51
+ return &controllerProvider{
52
+ academyController: academyController,
53
+ accountDetailController: accountDetailController,
54
+ authenticationController: authenticationController,
55
+ emailVerificationController: emailVerificationController,
56
+ eventController: eventController,
57
+ examController: examController,
58
+ forgotPasswordController: forgotPasswordController,
59
+ optionController: optionController,
60
+ regionController: regionController,
61
+ uploadController: uploadController, // Pointer assign ke Pointer
62
+ }
63
+ }
64
+
65
+ // --- Getter Methods ---
66
+
67
+ func (c *controllerProvider) ProvideAcademyController() controllers.AcademyController {
68
+ return c.academyController
69
+ }
70
+
71
+ func (c *controllerProvider) ProvideAccountDetailController() controllers.AccountDetailController {
72
+ return c.accountDetailController
73
+ }
74
+
75
+ func (c *controllerProvider) ProvideAuthenticationController() controllers.AuthenticationController {
76
+ return c.authenticationController
77
+ }
78
+
79
+ func (c *controllerProvider) ProvideEmailVerificationController() controllers.EmailVerificationController {
80
+ return c.emailVerificationController
81
+ }
82
+
83
+ func (c *controllerProvider) ProvideEventController() controllers.EventController {
84
+ return c.eventController
85
+ }
86
+
87
+ func (c *controllerProvider) ProvideExamController() controllers.ExamController {
88
+ return c.examController
89
+ }
90
+
91
+ func (c *controllerProvider) ProvideForgotPasswordController() controllers.ForgotPasswordController {
92
+ return c.forgotPasswordController
93
+ }
94
+
95
+ func (c *controllerProvider) ProvideOptionController() controllers.OptionController {
96
+ return c.optionController
97
+ }
98
+
99
+ func (c *controllerProvider) ProvideRegionController() controllers.RegionController {
100
+ return c.regionController
101
+ }
102
+
103
+ // UPDATE: Return Pointer (*)
104
+ func (c *controllerProvider) ProvideUploadController() *controllers.UploadController {
105
+ return c.uploadController
106
+ }
provider/middleware_provider.go CHANGED
@@ -1,30 +1,30 @@
1
- package provider
2
-
3
- import "abdanhafidz.com/go-boilerplate/middleware"
4
-
5
- type MiddlewareProvider interface {
6
- ProvideAuthenticationMiddleware() middleware.AuthenticationMiddleware
7
- ProvideAuthorizationMiddleware() middleware.AuthorizationMiddleware
8
- }
9
-
10
- type middlewareProvider struct {
11
- authenticationMiddleware middleware.AuthenticationMiddleware
12
- authorizationMiddleware middleware.AuthorizationMiddleware
13
- }
14
-
15
- func NewMiddlewareProvider(servicesProvider ServicesProvider) MiddlewareProvider {
16
- authenticationMiddleware := middleware.NewAuthenticationMiddleware(servicesProvider.ProvideJWTService())
17
- authorizationMiddleware := middleware.NewAuthorizationMiddleware(servicesProvider.ProvideEventService())
18
- return &middlewareProvider{
19
- authenticationMiddleware: authenticationMiddleware,
20
- authorizationMiddleware: authorizationMiddleware,
21
- }
22
- }
23
-
24
- func (p *middlewareProvider) ProvideAuthenticationMiddleware() middleware.AuthenticationMiddleware {
25
- return p.authenticationMiddleware
26
- }
27
-
28
- func (p *middlewareProvider) ProvideAuthorizationMiddleware() middleware.AuthorizationMiddleware {
29
- return p.authorizationMiddleware
30
- }
 
1
+ package provider
2
+
3
+ import "abdanhafidz.com/go-boilerplate/middleware"
4
+
5
+ type MiddlewareProvider interface {
6
+ ProvideAuthenticationMiddleware() middleware.AuthenticationMiddleware
7
+ ProvideAuthorizationMiddleware() middleware.AuthorizationMiddleware
8
+ }
9
+
10
+ type middlewareProvider struct {
11
+ authenticationMiddleware middleware.AuthenticationMiddleware
12
+ authorizationMiddleware middleware.AuthorizationMiddleware
13
+ }
14
+
15
+ func NewMiddlewareProvider(servicesProvider ServicesProvider) MiddlewareProvider {
16
+ authenticationMiddleware := middleware.NewAuthenticationMiddleware(servicesProvider.ProvideJWTService())
17
+ authorizationMiddleware := middleware.NewAuthorizationMiddleware(servicesProvider.ProvideEventService())
18
+ return &middlewareProvider{
19
+ authenticationMiddleware: authenticationMiddleware,
20
+ authorizationMiddleware: authorizationMiddleware,
21
+ }
22
+ }
23
+
24
+ func (p *middlewareProvider) ProvideAuthenticationMiddleware() middleware.AuthenticationMiddleware {
25
+ return p.authenticationMiddleware
26
+ }
27
+
28
+ func (p *middlewareProvider) ProvideAuthorizationMiddleware() middleware.AuthorizationMiddleware {
29
+ return p.authorizationMiddleware
30
+ }
provider/provider.go CHANGED
@@ -1,87 +1,102 @@
1
  package provider
2
 
3
  import (
4
- entity "abdanhafidz.com/go-boilerplate/models/entity"
5
- "github.com/gin-gonic/gin"
 
6
  )
7
 
8
  type AppProvider interface {
9
- ProvideRouter() *gin.Engine
10
- ProvideConfig() ConfigProvider
11
- ProvideRepositories() RepositoriesProvider
12
- ProvideServices() ServicesProvider
13
- ProvideControllers() ControllerProvider
14
- ProvideMiddlewares() MiddlewareProvider
15
  }
 
16
  type appProvider struct {
17
- ginRouter *gin.Engine
18
- configProvider ConfigProvider
19
- repositoriesProvider RepositoriesProvider
20
- servicesProvider ServicesProvider
21
- controllerProvider ControllerProvider
22
- middlewareProvider MiddlewareProvider
23
  }
24
 
25
  func NewAppProvider() AppProvider {
26
- ginRouter := gin.Default()
27
- configProvider := NewConfigProvider()
28
- repositoriesProvider := NewRepositoriesProvider(configProvider)
29
- servicesProvider := NewServicesProvider(repositoriesProvider, configProvider)
30
- controllerProvider := NewControllerProvider(servicesProvider)
31
- middlewareProvider := NewMiddlewareProvider(servicesProvider)
32
- configProvider.ProvideDatabaseConfig().AutoMigrateAll(
33
- // Accounts & Auth
34
- &entity.Account{},
35
- &entity.AccountDetail{},
36
- &entity.EmailVerification{},
37
- &entity.ExternalAuth{},
38
- &entity.FCM{},
39
- &entity.ForgotPassword{},
40
-
41
- // Events
42
- &entity.Events{},
43
- &entity.EventAssign{},
44
- &entity.Announcement{},
45
-
46
- // Problemset & Exam
47
- &entity.ProblemSet{},
48
- &entity.Questions{},
49
- &entity.Exam{},
50
- &entity.ProblemSetExamAssign{},
51
- &entity.ExamEventAssign{},
52
-
53
- // Exam Attempt & Result
54
- &entity.ExamEventAnswer{},
55
- &entity.ExamEventAttempt{},
56
- &entity.Result{},
57
-
58
- // Academy LMS
59
- &entity.Academy{},
60
- &entity.AcademyMaterial{},
61
- &entity.AcademyContent{},
62
- &entity.AcademyMaterialProgress{},
63
- &entity.AcademyContentProgress{},
64
- &entity.AcademyProgress{},
65
-
66
- // Options & Regions
67
- &entity.OptionCategory{},
68
- &entity.OptionValues{},
69
- &entity.RegionProvince{},
70
- &entity.RegionCity{},
71
- )
72
-
73
- return &appProvider{
74
- ginRouter: ginRouter,
75
- configProvider: configProvider,
76
- repositoriesProvider: repositoriesProvider,
77
- servicesProvider: servicesProvider,
78
- controllerProvider: controllerProvider,
79
- middlewareProvider: middlewareProvider,
80
- }
 
 
 
 
 
 
 
 
 
 
 
81
  }
 
82
  func (a *appProvider) ProvideRouter() *gin.Engine {
83
  return a.ginRouter
84
  }
 
85
  func (a *appProvider) ProvideConfig() ConfigProvider {
86
  return a.configProvider
87
  }
@@ -100,4 +115,4 @@ func (a *appProvider) ProvideControllers() ControllerProvider {
100
 
101
  func (a *appProvider) ProvideMiddlewares() MiddlewareProvider {
102
  return a.middlewareProvider
103
- }
 
1
  package provider
2
 
3
  import (
4
+ "log"
5
+ entity "abdanhafidz.com/go-boilerplate/models/entity"
6
+ "github.com/gin-gonic/gin"
7
  )
8
 
9
  type AppProvider interface {
10
+ ProvideRouter() *gin.Engine
11
+ ProvideConfig() ConfigProvider
12
+ ProvideRepositories() RepositoriesProvider
13
+ ProvideServices() ServicesProvider
14
+ ProvideControllers() ControllerProvider
15
+ ProvideMiddlewares() MiddlewareProvider
16
  }
17
+
18
  type appProvider struct {
19
+ ginRouter *gin.Engine
20
+ configProvider ConfigProvider
21
+ repositoriesProvider RepositoriesProvider
22
+ servicesProvider ServicesProvider
23
+ controllerProvider ControllerProvider
24
+ middlewareProvider MiddlewareProvider
25
  }
26
 
27
  func NewAppProvider() AppProvider {
28
+ ginRouter := gin.Default()
29
+ configProvider := NewConfigProvider()
30
+ repositoriesProvider := NewRepositoriesProvider(configProvider)
31
+ supabaseCfg := configProvider.ProvideSupabaseConfig()
32
+ storageDriver := NewSupabaseStorage(supabaseCfg.URL, supabaseCfg.ServiceKey, supabaseCfg.BucketName)
33
+ servicesProvider := NewServicesProvider(repositoriesProvider, configProvider, storageDriver)
34
+ controllerProvider := NewControllerProvider(servicesProvider)
35
+ middlewareProvider := NewMiddlewareProvider(servicesProvider)
36
+
37
+ // Database Migrations with error handling
38
+ err := configProvider.ProvideDatabaseConfig().AutoMigrateAll(
39
+ // Accounts & Auth
40
+ &entity.Account{},
41
+ &entity.AccountDetail{},
42
+ &entity.EmailVerification{},
43
+ &entity.ExternalAuth{},
44
+ &entity.FCM{},
45
+ &entity.ForgotPassword{},
46
+
47
+ // Events
48
+ &entity.Events{},
49
+ &entity.EventAssign{},
50
+ &entity.Announcement{},
51
+
52
+ // Problemset & Exam
53
+ &entity.ProblemSet{},
54
+ &entity.Questions{},
55
+ &entity.Exam{},
56
+ &entity.ProblemSetExamAssign{},
57
+ &entity.ExamEventAssign{},
58
+
59
+ // Exam Attempt & Result
60
+ &entity.ExamEventAnswer{},
61
+ &entity.ExamEventAttempt{},
62
+ &entity.Result{},
63
+
64
+ // Academy LMS
65
+ &entity.Academy{},
66
+ &entity.AcademyMaterial{},
67
+ &entity.AcademyContent{},
68
+ &entity.AcademyMaterialProgress{},
69
+ &entity.AcademyContentProgress{},
70
+ &entity.AcademyProgress{},
71
+
72
+ // Options & Regions
73
+ &entity.OptionCategory{},
74
+ &entity.OptionValues{},
75
+ &entity.RegionProvince{},
76
+ &entity.RegionCity{},
77
+
78
+ // Files Storage
79
+ &entity.File{},
80
+ )
81
+
82
+ if err != nil {
83
+ log.Fatalf("Database migration failed: %v", err)
84
+ }
85
+
86
+ return &appProvider{
87
+ ginRouter: ginRouter,
88
+ configProvider: configProvider,
89
+ repositoriesProvider: repositoriesProvider,
90
+ servicesProvider: servicesProvider,
91
+ controllerProvider: controllerProvider,
92
+ middlewareProvider: middlewareProvider,
93
+ }
94
  }
95
+
96
  func (a *appProvider) ProvideRouter() *gin.Engine {
97
  return a.ginRouter
98
  }
99
+
100
  func (a *appProvider) ProvideConfig() ConfigProvider {
101
  return a.configProvider
102
  }
 
115
 
116
  func (a *appProvider) ProvideMiddlewares() MiddlewareProvider {
117
  return a.middlewareProvider
118
+ }
provider/repositories_provider.go CHANGED
@@ -1,170 +1,178 @@
1
- package provider
2
-
3
- import "abdanhafidz.com/go-boilerplate/repositories"
4
-
5
- type RepositoriesProvider interface {
6
- ProvideAcademyRepository() repositories.AcademyRepository
7
- ProvideAccountDetailRepository() repositories.AccountDetailRepository
8
- ProvideAccountRepository() repositories.AccountRepository
9
- ProvideEmailVerificationRepository() repositories.EmailVerificationRepository
10
- ProvideEventAssignRepository() repositories.EventAssignRepository
11
- ProvideEventsRepository() repositories.EventsRepository
12
- ProvideExamEventAnswerRepository() repositories.ExamEventAnswerRepository
13
- ProvideExamEventAssignRepository() repositories.ExamEventAssignRepository
14
- ProvideExamEventAttemptRepository() repositories.ExamEventAttemptRepository
15
- ProvideExamRepository() repositories.ExamRepository
16
- ProvideExternalAuthRepository() repositories.ExternalAuthRepository
17
- ProvideFCMRepository() repositories.FCMRepository
18
- ProvideForgotPasswordRepository() repositories.ForgotPasswordRepository
19
- ProvideOptionRepository() repositories.OptionRepository
20
- ProvideProblemSetExamAssignRepository() repositories.ProblemSetExamAssignRepository
21
- ProvideProblemSetRepository() repositories.ProblemSetRepository
22
- ProvideQuestionsRepository() repositories.QuestionsRepository
23
- ProvideRegionRepository() repositories.RegionRepository
24
- ProvideResultRepository() repositories.ResultRepository
25
- }
26
-
27
- type repositoriesProvider struct {
28
- academyRepository repositories.AcademyRepository
29
- accountDetailRepository repositories.AccountDetailRepository
30
- accountRepository repositories.AccountRepository
31
- emailVerificationRepository repositories.EmailVerificationRepository
32
- eventAssignRepository repositories.EventAssignRepository
33
- eventsRepository repositories.EventsRepository
34
- examEventAnswerRepository repositories.ExamEventAnswerRepository
35
- examEventAssignRepository repositories.ExamEventAssignRepository
36
- examEventAttemptRepository repositories.ExamEventAttemptRepository
37
- examRepository repositories.ExamRepository
38
- externalAuthRepository repositories.ExternalAuthRepository
39
- fCMRepository repositories.FCMRepository
40
- forgotPasswordRepository repositories.ForgotPasswordRepository
41
- optionRepository repositories.OptionRepository
42
- problemSetExamAssignRepository repositories.ProblemSetExamAssignRepository
43
- problemSetRepository repositories.ProblemSetRepository
44
- questionsRepository repositories.QuestionsRepository
45
- regionRepository repositories.RegionRepository
46
- resultRepository repositories.ResultRepository
47
- }
48
-
49
- func NewRepositoriesProvider(cfg ConfigProvider) RepositoriesProvider {
50
- dbConfig := cfg.ProvideDatabaseConfig()
51
- db := dbConfig.GetInstance()
52
-
53
- academyRepository := repositories.NewAcademyRepository(db)
54
- accountDetailRepository := repositories.NewAccountDetailRepository(db)
55
- accountRepository := repositories.NewAccountRepository(db)
56
- emailVerificationRepository := repositories.NewEmailVerificationRepository(db)
57
- eventAssignRepository := repositories.NewEventAssignRepository(db)
58
- eventsRepository := repositories.NewEventsRepository(db)
59
- examEventAnswerRepository := repositories.NewExamEventAnswerRepository(db)
60
- examEventAssignRepository := repositories.NewExamEventAssignRepository(db)
61
- examEventAttemptRepository := repositories.NewExamEventAttemptRepository(db)
62
- examRepository := repositories.NewExamRepository(db)
63
- externalAuthRepository := repositories.NewExternalAuthRepository(db)
64
- fCMRepository := repositories.NewFCMRepository(db)
65
- forgotPasswordRepository := repositories.NewForgotPasswordRepository(db)
66
- optionRepository := repositories.NewOptionRepository(db)
67
- problemSetExamAssignRepository := repositories.NewProblemSetExamAssignRepository(db)
68
- problemSetRepository := repositories.NewProblemSetRepository(db)
69
- questionsRepository := repositories.NewQuestionsRepository(db)
70
- regionRepository := repositories.NewRegionRepository(db)
71
- resultRepository := repositories.NewResultRepository(db)
72
-
73
- return &repositoriesProvider{
74
- academyRepository: academyRepository,
75
- accountDetailRepository: accountDetailRepository,
76
- accountRepository: accountRepository,
77
- emailVerificationRepository: emailVerificationRepository,
78
- eventAssignRepository: eventAssignRepository,
79
- eventsRepository: eventsRepository,
80
- examEventAnswerRepository: examEventAnswerRepository,
81
- examEventAssignRepository: examEventAssignRepository,
82
- examEventAttemptRepository: examEventAttemptRepository,
83
- examRepository: examRepository,
84
- externalAuthRepository: externalAuthRepository,
85
- fCMRepository: fCMRepository,
86
- forgotPasswordRepository: forgotPasswordRepository,
87
- optionRepository: optionRepository,
88
- problemSetExamAssignRepository: problemSetExamAssignRepository,
89
- problemSetRepository: problemSetRepository,
90
- questionsRepository: questionsRepository,
91
- regionRepository: regionRepository,
92
- resultRepository: resultRepository,
93
- }
94
- }
95
-
96
- func (r *repositoriesProvider) ProvideAcademyRepository() repositories.AcademyRepository {
97
- return r.academyRepository
98
- }
99
-
100
- func (r *repositoriesProvider) ProvideAccountDetailRepository() repositories.AccountDetailRepository {
101
- return r.accountDetailRepository
102
- }
103
-
104
- func (r *repositoriesProvider) ProvideAccountRepository() repositories.AccountRepository {
105
- return r.accountRepository
106
- }
107
-
108
- func (r *repositoriesProvider) ProvideEmailVerificationRepository() repositories.EmailVerificationRepository {
109
- return r.emailVerificationRepository
110
- }
111
-
112
- func (r *repositoriesProvider) ProvideEventAssignRepository() repositories.EventAssignRepository {
113
- return r.eventAssignRepository
114
- }
115
-
116
- func (r *repositoriesProvider) ProvideEventsRepository() repositories.EventsRepository {
117
- return r.eventsRepository
118
- }
119
-
120
- func (r *repositoriesProvider) ProvideExamEventAnswerRepository() repositories.ExamEventAnswerRepository {
121
- return r.examEventAnswerRepository
122
- }
123
-
124
- func (r *repositoriesProvider) ProvideExamEventAssignRepository() repositories.ExamEventAssignRepository {
125
- return r.examEventAssignRepository
126
- }
127
-
128
- func (r *repositoriesProvider) ProvideExamEventAttemptRepository() repositories.ExamEventAttemptRepository {
129
- return r.examEventAttemptRepository
130
- }
131
-
132
- func (r *repositoriesProvider) ProvideExamRepository() repositories.ExamRepository {
133
- return r.examRepository
134
- }
135
-
136
- func (r *repositoriesProvider) ProvideExternalAuthRepository() repositories.ExternalAuthRepository {
137
- return r.externalAuthRepository
138
- }
139
-
140
- func (r *repositoriesProvider) ProvideFCMRepository() repositories.FCMRepository {
141
- return r.fCMRepository
142
- }
143
-
144
- func (r *repositoriesProvider) ProvideForgotPasswordRepository() repositories.ForgotPasswordRepository {
145
- return r.forgotPasswordRepository
146
- }
147
-
148
- func (r *repositoriesProvider) ProvideOptionRepository() repositories.OptionRepository {
149
- return r.optionRepository
150
- }
151
-
152
- func (r *repositoriesProvider) ProvideProblemSetExamAssignRepository() repositories.ProblemSetExamAssignRepository {
153
- return r.problemSetExamAssignRepository
154
- }
155
-
156
- func (r *repositoriesProvider) ProvideProblemSetRepository() repositories.ProblemSetRepository {
157
- return r.problemSetRepository
158
- }
159
-
160
- func (r *repositoriesProvider) ProvideQuestionsRepository() repositories.QuestionsRepository {
161
- return r.questionsRepository
162
- }
163
-
164
- func (r *repositoriesProvider) ProvideRegionRepository() repositories.RegionRepository {
165
- return r.regionRepository
166
- }
167
-
168
- func (r *repositoriesProvider) ProvideResultRepository() repositories.ResultRepository {
169
- return r.resultRepository
170
- }
 
 
 
 
 
 
 
 
 
1
+ package provider
2
+
3
+ import "abdanhafidz.com/go-boilerplate/repositories"
4
+
5
+ type RepositoriesProvider interface {
6
+ ProvideAcademyRepository() repositories.AcademyRepository
7
+ ProvideAccountDetailRepository() repositories.AccountDetailRepository
8
+ ProvideAccountRepository() repositories.AccountRepository
9
+ ProvideEmailVerificationRepository() repositories.EmailVerificationRepository
10
+ ProvideEventAssignRepository() repositories.EventAssignRepository
11
+ ProvideEventsRepository() repositories.EventsRepository
12
+ ProvideExamEventAnswerRepository() repositories.ExamEventAnswerRepository
13
+ ProvideExamEventAssignRepository() repositories.ExamEventAssignRepository
14
+ ProvideExamEventAttemptRepository() repositories.ExamEventAttemptRepository
15
+ ProvideExamRepository() repositories.ExamRepository
16
+ ProvideExternalAuthRepository() repositories.ExternalAuthRepository
17
+ ProvideFCMRepository() repositories.FCMRepository
18
+ ProvideForgotPasswordRepository() repositories.ForgotPasswordRepository
19
+ ProvideOptionRepository() repositories.OptionRepository
20
+ ProvideProblemSetExamAssignRepository() repositories.ProblemSetExamAssignRepository
21
+ ProvideProblemSetRepository() repositories.ProblemSetRepository
22
+ ProvideQuestionsRepository() repositories.QuestionsRepository
23
+ ProvideRegionRepository() repositories.RegionRepository
24
+ ProvideResultRepository() repositories.ResultRepository
25
+ ProvideFileRepository() repositories.FileRepository
26
+ }
27
+
28
+ type repositoriesProvider struct {
29
+ academyRepository repositories.AcademyRepository
30
+ accountDetailRepository repositories.AccountDetailRepository
31
+ accountRepository repositories.AccountRepository
32
+ emailVerificationRepository repositories.EmailVerificationRepository
33
+ eventAssignRepository repositories.EventAssignRepository
34
+ eventsRepository repositories.EventsRepository
35
+ examEventAnswerRepository repositories.ExamEventAnswerRepository
36
+ examEventAssignRepository repositories.ExamEventAssignRepository
37
+ examEventAttemptRepository repositories.ExamEventAttemptRepository
38
+ examRepository repositories.ExamRepository
39
+ externalAuthRepository repositories.ExternalAuthRepository
40
+ fCMRepository repositories.FCMRepository
41
+ forgotPasswordRepository repositories.ForgotPasswordRepository
42
+ optionRepository repositories.OptionRepository
43
+ problemSetExamAssignRepository repositories.ProblemSetExamAssignRepository
44
+ problemSetRepository repositories.ProblemSetRepository
45
+ questionsRepository repositories.QuestionsRepository
46
+ regionRepository repositories.RegionRepository
47
+ resultRepository repositories.ResultRepository
48
+ fileRepository repositories.FileRepository // Added field
49
+ }
50
+
51
+ func NewRepositoriesProvider(cfg ConfigProvider) RepositoriesProvider {
52
+ dbConfig := cfg.ProvideDatabaseConfig()
53
+ db := dbConfig.GetInstance()
54
+
55
+ academyRepository := repositories.NewAcademyRepository(db)
56
+ accountDetailRepository := repositories.NewAccountDetailRepository(db)
57
+ accountRepository := repositories.NewAccountRepository(db)
58
+ emailVerificationRepository := repositories.NewEmailVerificationRepository(db)
59
+ eventAssignRepository := repositories.NewEventAssignRepository(db)
60
+ eventsRepository := repositories.NewEventsRepository(db)
61
+ examEventAnswerRepository := repositories.NewExamEventAnswerRepository(db)
62
+ examEventAssignRepository := repositories.NewExamEventAssignRepository(db)
63
+ examEventAttemptRepository := repositories.NewExamEventAttemptRepository(db)
64
+ examRepository := repositories.NewExamRepository(db)
65
+ externalAuthRepository := repositories.NewExternalAuthRepository(db)
66
+ fCMRepository := repositories.NewFCMRepository(db)
67
+ forgotPasswordRepository := repositories.NewForgotPasswordRepository(db)
68
+ optionRepository := repositories.NewOptionRepository(db)
69
+ problemSetExamAssignRepository := repositories.NewProblemSetExamAssignRepository(db)
70
+ problemSetRepository := repositories.NewProblemSetRepository(db)
71
+ questionsRepository := repositories.NewQuestionsRepository(db)
72
+ regionRepository := repositories.NewRegionRepository(db)
73
+ resultRepository := repositories.NewResultRepository(db)
74
+ fileRepository := repositories.NewFileRepository(db) // Init here
75
+
76
+ return &repositoriesProvider{
77
+ academyRepository: academyRepository,
78
+ accountDetailRepository: accountDetailRepository,
79
+ accountRepository: accountRepository,
80
+ emailVerificationRepository: emailVerificationRepository,
81
+ eventAssignRepository: eventAssignRepository,
82
+ eventsRepository: eventsRepository,
83
+ examEventAnswerRepository: examEventAnswerRepository,
84
+ examEventAssignRepository: examEventAssignRepository,
85
+ examEventAttemptRepository: examEventAttemptRepository,
86
+ examRepository: examRepository,
87
+ externalAuthRepository: externalAuthRepository,
88
+ fCMRepository: fCMRepository,
89
+ forgotPasswordRepository: forgotPasswordRepository,
90
+ optionRepository: optionRepository,
91
+ problemSetExamAssignRepository: problemSetExamAssignRepository,
92
+ problemSetRepository: problemSetRepository,
93
+ questionsRepository: questionsRepository,
94
+ regionRepository: regionRepository,
95
+ resultRepository: resultRepository,
96
+ fileRepository: fileRepository, // Assign here
97
+ }
98
+ }
99
+
100
+ func (r *repositoriesProvider) ProvideAcademyRepository() repositories.AcademyRepository {
101
+ return r.academyRepository
102
+ }
103
+
104
+ func (r *repositoriesProvider) ProvideAccountDetailRepository() repositories.AccountDetailRepository {
105
+ return r.accountDetailRepository
106
+ }
107
+
108
+ func (r *repositoriesProvider) ProvideAccountRepository() repositories.AccountRepository {
109
+ return r.accountRepository
110
+ }
111
+
112
+ func (r *repositoriesProvider) ProvideEmailVerificationRepository() repositories.EmailVerificationRepository {
113
+ return r.emailVerificationRepository
114
+ }
115
+
116
+ func (r *repositoriesProvider) ProvideEventAssignRepository() repositories.EventAssignRepository {
117
+ return r.eventAssignRepository
118
+ }
119
+
120
+ func (r *repositoriesProvider) ProvideEventsRepository() repositories.EventsRepository {
121
+ return r.eventsRepository
122
+ }
123
+
124
+ func (r *repositoriesProvider) ProvideExamEventAnswerRepository() repositories.ExamEventAnswerRepository {
125
+ return r.examEventAnswerRepository
126
+ }
127
+
128
+ func (r *repositoriesProvider) ProvideExamEventAssignRepository() repositories.ExamEventAssignRepository {
129
+ return r.examEventAssignRepository
130
+ }
131
+
132
+ func (r *repositoriesProvider) ProvideExamEventAttemptRepository() repositories.ExamEventAttemptRepository {
133
+ return r.examEventAttemptRepository
134
+ }
135
+
136
+ func (r *repositoriesProvider) ProvideExamRepository() repositories.ExamRepository {
137
+ return r.examRepository
138
+ }
139
+
140
+ func (r *repositoriesProvider) ProvideExternalAuthRepository() repositories.ExternalAuthRepository {
141
+ return r.externalAuthRepository
142
+ }
143
+
144
+ func (r *repositoriesProvider) ProvideFCMRepository() repositories.FCMRepository {
145
+ return r.fCMRepository
146
+ }
147
+
148
+ func (r *repositoriesProvider) ProvideForgotPasswordRepository() repositories.ForgotPasswordRepository {
149
+ return r.forgotPasswordRepository
150
+ }
151
+
152
+ func (r *repositoriesProvider) ProvideOptionRepository() repositories.OptionRepository {
153
+ return r.optionRepository
154
+ }
155
+
156
+ func (r *repositoriesProvider) ProvideProblemSetExamAssignRepository() repositories.ProblemSetExamAssignRepository {
157
+ return r.problemSetExamAssignRepository
158
+ }
159
+
160
+ func (r *repositoriesProvider) ProvideProblemSetRepository() repositories.ProblemSetRepository {
161
+ return r.problemSetRepository
162
+ }
163
+
164
+ func (r *repositoriesProvider) ProvideQuestionsRepository() repositories.QuestionsRepository {
165
+ return r.questionsRepository
166
+ }
167
+
168
+ func (r *repositoriesProvider) ProvideRegionRepository() repositories.RegionRepository {
169
+ return r.regionRepository
170
+ }
171
+
172
+ func (r *repositoriesProvider) ProvideResultRepository() repositories.ResultRepository {
173
+ return r.resultRepository
174
+ }
175
+
176
+ func (r *repositoriesProvider) ProvideFileRepository() repositories.FileRepository {
177
+ return r.fileRepository
178
+ }
provider/services_provider.go CHANGED
@@ -1,103 +1,121 @@
1
- package provider
2
-
3
- import "abdanhafidz.com/go-boilerplate/services"
4
-
5
- type ServicesProvider interface {
6
- ProvideEventService() services.EventService
7
- ProvideAcademyService() services.AcademyService
8
- ProvideProblemSetService() services.ProblemSetService
9
- ProvideJWTService() services.JWTService
10
- ProvideRegionService() services.RegionService
11
- ProvideOptionService() services.OptionService
12
- ProvideExamService() services.ExamService
13
- ProvideAccountService() services.AccountService
14
- ProvideForgotPasswordService() services.ForgotPasswordService
15
- ProvideEmailVerificationService() services.EmailVerificationService
16
- ProvideExternalAuthService() services.ExternalAuthService
17
- }
18
-
19
- type servicesProvider struct {
20
- eventService services.EventService
21
- academyService services.AcademyService
22
- problemSetService services.ProblemSetService
23
- jWTService services.JWTService
24
- regionService services.RegionService
25
- optionService services.OptionService
26
- examService services.ExamService
27
- accountService services.AccountService
28
- forgotPasswordService services.ForgotPasswordService
29
- emailVerificationService services.EmailVerificationService
30
- externalAuthService services.ExternalAuthService
31
- }
32
-
33
- func NewServicesProvider(repoProvider RepositoriesProvider, configProvider ConfigProvider) ServicesProvider {
34
- eventService := services.NewEventService(repoProvider.ProvideEventsRepository(), repoProvider.ProvideEventAssignRepository())
35
- academyService := services.NewAcademyService(repoProvider.ProvideAcademyRepository())
36
- problemSetService := services.NewProblemSetService(repoProvider.ProvideProblemSetRepository(), repoProvider.ProvideQuestionsRepository(), repoProvider.ProvideProblemSetExamAssignRepository())
37
- jWTService := services.NewJWTService(configProvider.ProvideJWTConfig().GetSecretKey())
38
- regionService := services.NewRegionService(repoProvider.ProvideRegionRepository())
39
- optionService := services.NewOptionService(repoProvider.ProvideOptionRepository())
40
- examService := services.NewExamService(eventService, problemSetService, repoProvider.ProvideProblemSetExamAssignRepository(), repoProvider.ProvideExamRepository(), repoProvider.ProvideExamEventAttemptRepository(), repoProvider.ProvideExamEventAssignRepository(), repoProvider.ProvideExamEventAnswerRepository(), repoProvider.ProvideResultRepository())
41
- accountService := services.NewAccountService(jWTService, repoProvider.ProvideAccountRepository(), repoProvider.ProvideAccountDetailRepository())
42
- forgotPasswordService := services.NewForgotPasswordService(jWTService, repoProvider.ProvideAccountRepository(), repoProvider.ProvideForgotPasswordRepository())
43
- emailVerificationService := services.NewEmailVerificationService(accountService, repoProvider.ProvideEmailVerificationRepository())
44
- externalAuthService := services.NewExternalAuthService(jWTService, accountService, repoProvider.ProvideExternalAuthRepository())
45
-
46
- return &servicesProvider{
47
- eventService: eventService,
48
- academyService: academyService,
49
- problemSetService: problemSetService,
50
- jWTService: jWTService,
51
- regionService: regionService,
52
- optionService: optionService,
53
- examService: examService,
54
- accountService: accountService,
55
- forgotPasswordService: forgotPasswordService,
56
- emailVerificationService: emailVerificationService,
57
- externalAuthService: externalAuthService,
58
- }
59
- }
60
-
61
- func (s *servicesProvider) ProvideEventService() services.EventService {
62
- return s.eventService
63
- }
64
-
65
- func (s *servicesProvider) ProvideAcademyService() services.AcademyService {
66
- return s.academyService
67
- }
68
-
69
- func (s *servicesProvider) ProvideProblemSetService() services.ProblemSetService {
70
- return s.problemSetService
71
- }
72
-
73
- func (s *servicesProvider) ProvideJWTService() services.JWTService {
74
- return s.jWTService
75
- }
76
-
77
- func (s *servicesProvider) ProvideRegionService() services.RegionService {
78
- return s.regionService
79
- }
80
-
81
- func (s *servicesProvider) ProvideOptionService() services.OptionService {
82
- return s.optionService
83
- }
84
-
85
- func (s *servicesProvider) ProvideExamService() services.ExamService {
86
- return s.examService
87
- }
88
-
89
- func (s *servicesProvider) ProvideAccountService() services.AccountService {
90
- return s.accountService
91
- }
92
-
93
- func (s *servicesProvider) ProvideForgotPasswordService() services.ForgotPasswordService {
94
- return s.forgotPasswordService
95
- }
96
-
97
- func (s *servicesProvider) ProvideEmailVerificationService() services.EmailVerificationService {
98
- return s.emailVerificationService
99
- }
100
-
101
- func (s *servicesProvider) ProvideExternalAuthService() services.ExternalAuthService {
102
- return s.externalAuthService
103
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package provider
2
+
3
+ import (
4
+ // Pastikan path ini sesuai dengan go.mod kamu (misal: quzuu-backend-v2/services)
5
+ "abdanhafidz.com/go-boilerplate/services"
6
+ )
7
+
8
+ type ServicesProvider interface {
9
+ ProvideEventService() services.EventService
10
+ ProvideAcademyService() services.AcademyService
11
+ ProvideProblemSetService() services.ProblemSetService
12
+ ProvideJWTService() services.JWTService
13
+ ProvideRegionService() services.RegionService
14
+ ProvideOptionService() services.OptionService
15
+ ProvideExamService() services.ExamService
16
+ ProvideAccountService() services.AccountService
17
+ ProvideForgotPasswordService() services.ForgotPasswordService
18
+ ProvideEmailVerificationService() services.EmailVerificationService
19
+ ProvideExternalAuthService() services.ExternalAuthService
20
+
21
+ // UPDATE: Mengembalikan pointer karena UploadService adalah struct, bukan interface
22
+ ProvideUploadService() *services.UploadService
23
+ }
24
+
25
+ type servicesProvider struct {
26
+ eventService services.EventService
27
+ academyService services.AcademyService
28
+ problemSetService services.ProblemSetService
29
+ jWTService services.JWTService
30
+ regionService services.RegionService
31
+ optionService services.OptionService
32
+ examService services.ExamService
33
+ accountService services.AccountService
34
+ forgotPasswordService services.ForgotPasswordService
35
+ emailVerificationService services.EmailVerificationService
36
+ externalAuthService services.ExternalAuthService
37
+
38
+ // Field untuk menyimpan instance UploadService
39
+ uploadService *services.UploadService
40
+ }
41
+
42
+ func NewServicesProvider(
43
+ repoProvider RepositoriesProvider,
44
+ configProvider ConfigProvider,
45
+ storageProvider services.StorageProvider, // Didapat dari main/wire
46
+ ) ServicesProvider {
47
+
48
+ // Inisialisasi service lain...
49
+ eventService := services.NewEventService(repoProvider.ProvideEventsRepository(), repoProvider.ProvideEventAssignRepository())
50
+ academyService := services.NewAcademyService(repoProvider.ProvideAcademyRepository())
51
+ problemSetService := services.NewProblemSetService(repoProvider.ProvideProblemSetRepository(), repoProvider.ProvideQuestionsRepository(), repoProvider.ProvideProblemSetExamAssignRepository())
52
+ jWTService := services.NewJWTService(configProvider.ProvideJWTConfig().GetSecretKey())
53
+ regionService := services.NewRegionService(repoProvider.ProvideRegionRepository())
54
+ optionService := services.NewOptionService(repoProvider.ProvideOptionRepository())
55
+ examService := services.NewExamService(eventService, problemSetService, repoProvider.ProvideProblemSetExamAssignRepository(), repoProvider.ProvideExamRepository(), repoProvider.ProvideExamEventAttemptRepository(), repoProvider.ProvideExamEventAssignRepository(), repoProvider.ProvideExamEventAnswerRepository(), repoProvider.ProvideResultRepository())
56
+ accountService := services.NewAccountService(jWTService, repoProvider.ProvideAccountRepository(), repoProvider.ProvideAccountDetailRepository())
57
+ forgotPasswordService := services.NewForgotPasswordService(jWTService, repoProvider.ProvideAccountRepository(), repoProvider.ProvideForgotPasswordRepository())
58
+ emailVerificationService := services.NewEmailVerificationService(accountService, repoProvider.ProvideEmailVerificationRepository())
59
+ externalAuthService := services.NewExternalAuthService(jWTService, accountService, repoProvider.ProvideExternalAuthRepository())
60
+
61
+
62
+ uploadService := services.NewUploadService(
63
+ storageProvider,
64
+ repoProvider.ProvideFileRepository(),
65
+ )
66
+
67
+ return &servicesProvider{
68
+ eventService: eventService,
69
+ academyService: academyService,
70
+ problemSetService: problemSetService,
71
+ jWTService: jWTService,
72
+ regionService: regionService,
73
+ optionService: optionService,
74
+ examService: examService,
75
+ accountService: accountService,
76
+ forgotPasswordService: forgotPasswordService,
77
+ emailVerificationService: emailVerificationService,
78
+ externalAuthService: externalAuthService,
79
+ uploadService: uploadService,
80
+ }
81
+ }
82
+
83
+ // ... (Getter method lainnya tetap sama) ...
84
+
85
+ func (s *servicesProvider) ProvideEventService() services.EventService {
86
+ return s.eventService
87
+ }
88
+ func (s *servicesProvider) ProvideAcademyService() services.AcademyService {
89
+ return s.academyService
90
+ }
91
+ func (s *servicesProvider) ProvideProblemSetService() services.ProblemSetService {
92
+ return s.problemSetService
93
+ }
94
+ func (s *servicesProvider) ProvideJWTService() services.JWTService {
95
+ return s.jWTService
96
+ }
97
+ func (s *servicesProvider) ProvideRegionService() services.RegionService {
98
+ return s.regionService
99
+ }
100
+ func (s *servicesProvider) ProvideOptionService() services.OptionService {
101
+ return s.optionService
102
+ }
103
+ func (s *servicesProvider) ProvideExamService() services.ExamService {
104
+ return s.examService
105
+ }
106
+ func (s *servicesProvider) ProvideAccountService() services.AccountService {
107
+ return s.accountService
108
+ }
109
+ func (s *servicesProvider) ProvideForgotPasswordService() services.ForgotPasswordService {
110
+ return s.forgotPasswordService
111
+ }
112
+ func (s *servicesProvider) ProvideEmailVerificationService() services.EmailVerificationService {
113
+ return s.emailVerificationService
114
+ }
115
+ func (s *servicesProvider) ProvideExternalAuthService() services.ExternalAuthService {
116
+ return s.externalAuthService
117
+ }
118
+
119
+ func (s *servicesProvider) ProvideUploadService() *services.UploadService {
120
+ return s.uploadService
121
+ }
provider/storage_interface.go ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package provider
2
+
3
+ import (
4
+ "context"
5
+ "mime/multipart"
6
+ )
7
+
8
+ // StorageProvider defines the behavior for file operations
9
+ type StorageProvider interface {
10
+ UploadFile(ctx context.Context, file multipart.File, header *multipart.FileHeader) (string, error)
11
+ GetFileURL(path string) (string, error)
12
+ DeleteFile(path string) error
13
+ }
provider/supabase_storage.go ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package provider
2
+
3
+ import (
4
+ "context"
5
+ "fmt"
6
+ "io"
7
+
8
+ // Pastikan import library supabase client yang Anda pakai benar
9
+ storage_go "github.com/supabase-community/storage-go"
10
+ )
11
+
12
+ type SupabaseStorage struct {
13
+ client *storage_go.Client
14
+ bucketName string
15
+ url string
16
+ }
17
+
18
+ func NewSupabaseStorage(url string, key string, bucketName string) *SupabaseStorage {
19
+ client := storage_go.NewClient(url+"/storage/v1", key, nil)
20
+ return &SupabaseStorage{
21
+ client: client,
22
+ bucketName: bucketName,
23
+ url: url,
24
+ }
25
+ }
26
+
27
+ func (s *SupabaseStorage) UploadFile(ctx context.Context, file io.Reader, destinationPath string, contentType string) (string, error) {
28
+ _, err := s.client.UploadFile(s.bucketName, destinationPath, file, storage_go.FileOptions{
29
+ ContentType: &contentType,
30
+ Upsert: new(bool), // Use new(bool) to create a pointer to false
31
+ })
32
+
33
+ if err != nil {
34
+ return "", err
35
+ }
36
+ publicURL := s.client.GetPublicUrl(s.bucketName, destinationPath).SignedURL
37
+ if publicURL == "" {
38
+ publicURL = fmt.Sprintf("%s/storage/v1/object/public/%s/%s", s.url, s.bucketName, destinationPath)
39
+ }
40
+
41
+ return publicURL, nil
42
+ }
repositories/academy_repository.go CHANGED
@@ -7,56 +7,58 @@ import (
7
  entity "abdanhafidz.com/go-boilerplate/models/entity"
8
  "github.com/google/uuid"
9
  "gorm.io/gorm"
 
10
  )
11
 
12
  type AcademyRepository interface {
13
- // Academy
 
14
  GetAcademyByID(ctx context.Context, id uuid.UUID) (entity.Academy, error)
15
  GetAcademyBySlug(ctx context.Context, slug string) (entity.Academy, error)
16
  GetAcademyWithProgress(ctx context.Context, accountId uuid.UUID, slug string) (entity.Academy, error)
17
-
18
  CreateAcademy(ctx context.Context, a entity.Academy) (entity.Academy, error)
19
  UpdateAcademy(ctx context.Context, a entity.Academy) (entity.Academy, error)
20
  DeleteAcademy(ctx context.Context, id uuid.UUID) error
21
-
22
  ListAcademy(ctx context.Context, accountId uuid.UUID) ([]entity.Academy, error)
23
  GetAcademyWithMaterials(ctx context.Context, id uuid.UUID) (entity.Academy, []entity.AcademyMaterial, error)
24
  CountMaterialsByAcademyID(ctx context.Context, academyId uuid.UUID) (int64, error)
25
 
26
- // Material
27
  GetMaterialBySlug(ctx context.Context, academy_id uuid.UUID, materialSlug string) (entity.AcademyMaterial, error)
28
  GetMaterialByID(ctx context.Context, id uuid.UUID) (entity.AcademyMaterial, error)
29
  GetMaterialWithProgress(ctx context.Context, accountId uuid.UUID, academyId uuid.UUID, slug string) (entity.AcademyMaterial, error)
30
-
31
  CreateMaterial(ctx context.Context, m entity.AcademyMaterial) (entity.AcademyMaterial, error)
32
  UpdateMaterial(ctx context.Context, m entity.AcademyMaterial) (entity.AcademyMaterial, error)
33
  DeleteMaterial(ctx context.Context, id uuid.UUID) error
34
-
35
  ListMaterials(ctx context.Context, academyId uuid.UUID) ([]entity.AcademyMaterial, error)
36
  GetMaterialWithContents(ctx context.Context, id uuid.UUID) (entity.AcademyMaterial, []entity.AcademyContent, error)
37
 
38
- // Content
39
  GetContentBySlug(ctx context.Context, materialId uuid.UUID, order uint) (entity.AcademyContent, error)
40
  GetContentByID(ctx context.Context, id uuid.UUID) (entity.AcademyContent, error)
41
- GetContentWithProgress(ctx context.Context, accountId uuid.UUID, academyId uuid.UUID,materialId uuid.UUID, order uint) (entity.AcademyContent, error)
42
-
43
  CreateContent(ctx context.Context, c entity.AcademyContent) (entity.AcademyContent, error)
44
  UpdateContent(ctx context.Context, c entity.AcademyContent) (entity.AcademyContent, error)
45
  DeleteContent(ctx context.Context, id uuid.UUID) error
46
-
47
  CountContentsByMaterialID(ctx context.Context, materialId uuid.UUID) (int64, error)
48
 
49
- // Progress
50
  GetAcademyProgress(ctx context.Context, accountId uuid.UUID, academyId uuid.UUID) (entity.AcademyProgress, error)
51
  GetMaterialProgress(ctx context.Context, accountId uuid.UUID, academyId uuid.UUID, materialId uuid.UUID) (entity.AcademyMaterialProgress, error)
52
  GetContentProgress(ctx context.Context, accountId uuid.UUID, academyId uuid.UUID, materialId uuid.UUID, contentId uuid.UUID) (entity.AcademyContentProgress, error)
53
-
54
  UpsertAcademyProgress(ctx context.Context, p entity.AcademyProgress) (entity.AcademyProgress, error)
55
  UpsertMaterialProgress(ctx context.Context, p entity.AcademyMaterialProgress) (entity.AcademyMaterialProgress, error)
56
  UpsertContentProgress(ctx context.Context, p entity.AcademyContentProgress) (entity.AcademyContentProgress, error)
 
 
 
57
 
58
  CountCompletedContentsByMaterialAndAccount(ctx context.Context, accountId uuid.UUID, materialId uuid.UUID) (int64, error)
59
  CountCompletedMaterialsByAcademyAndAccount(ctx context.Context, accountId uuid.UUID, academyId uuid.UUID) (int64, error)
 
 
 
 
 
 
 
60
  }
61
 
62
  type academyRepository struct{ db *gorm.DB }
@@ -65,16 +67,21 @@ func NewAcademyRepository(db *gorm.DB) AcademyRepository {
65
  return &academyRepository{db: db}
66
  }
67
 
68
- // ========== ACADEMY ==========
 
 
 
 
 
 
69
  func (r *academyRepository) GetAcademyWithMaterials(ctx context.Context, id uuid.UUID) (entity.Academy, []entity.AcademyMaterial, error) {
70
  var a entity.Academy
71
- err := r.db.WithContext(ctx).First(&a, "id = ?", id).Error
72
- if err != nil {
73
  return entity.Academy{}, nil, err
74
  }
75
-
76
  var m []entity.AcademyMaterial
77
- return a, m, r.db.WithContext(ctx).Where("academy_id = ?", id).Find(&m).Error
 
78
  }
79
 
80
  func (r *academyRepository) GetAcademyByID(ctx context.Context, id uuid.UUID) (entity.Academy, error) {
@@ -88,23 +95,15 @@ func (r *academyRepository) GetAcademyBySlug(ctx context.Context, slug string) (
88
  }
89
 
90
  func (r *academyRepository) GetAcademyWithProgress(ctx context.Context, accountId uuid.UUID, slug string) (entity.Academy, error) {
91
- var a entity.Academy
92
- var err error
93
- a, err = r.GetAcademyBySlug(ctx, slug)
94
  if err != nil {
95
  return a, err
96
  }
97
-
98
- academyId := a.Id
99
-
100
- var ap entity.AcademyProgress
101
-
102
- ap, err = r.GetAcademyProgress(ctx, accountId, academyId)
103
- if err != nil && err != gorm.ErrRecordNotFound {
104
  return a, err
105
  }
106
  a.AcademyProgresss = ap
107
-
108
  return a, nil
109
  }
110
 
@@ -122,37 +121,51 @@ func (r *academyRepository) DeleteAcademy(ctx context.Context, id uuid.UUID) err
122
 
123
  func (r *academyRepository) ListAcademy(ctx context.Context, accountId uuid.UUID) ([]entity.Academy, error) {
124
  var list []entity.Academy
125
-
126
  if err := r.db.WithContext(ctx).Find(&list).Error; err != nil {
127
  return nil, err
128
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
129
 
130
  for i := range list {
131
- academyId := list[i].Id
132
- ap, err := r.GetAcademyProgress(ctx, accountId, academyId)
133
- if err != nil && err != gorm.ErrRecordNotFound {
134
- return nil, err
 
 
 
 
135
  }
136
- list[i].AcademyProgresss = ap
137
  }
138
-
139
  return list, nil
140
  }
141
 
142
  func (r *academyRepository) CountMaterialsByAcademyID(ctx context.Context, academyId uuid.UUID) (int64, error) {
143
  var count int64
144
-
145
- query := r.db.WithContext(ctx).
146
- Where("academy_id = ?", academyId)
147
-
148
- err := query.Model(&entity.AcademyMaterial{}).
149
- Count(&count).
150
- Error
151
-
152
  return count, err
153
  }
154
 
155
- // ========== MATERIAL ==========
156
  func (r *academyRepository) GetMaterialByID(ctx context.Context, id uuid.UUID) (entity.AcademyMaterial, error) {
157
  var m entity.AcademyMaterial
158
  return m, r.db.WithContext(ctx).First(&m, "id = ?", id).Error
@@ -165,33 +178,24 @@ func (r *academyRepository) GetMaterialBySlug(ctx context.Context, academy_id uu
165
 
166
  func (r *academyRepository) GetMaterialWithContents(ctx context.Context, id uuid.UUID) (entity.AcademyMaterial, []entity.AcademyContent, error) {
167
  var m entity.AcademyMaterial
168
- err := r.db.WithContext(ctx).First(&m, "id = ?", id).Error
169
- if err != nil {
170
  return entity.AcademyMaterial{}, nil, err
171
  }
172
-
173
  var c []entity.AcademyContent
174
- return m, c, r.db.WithContext(ctx).Where("material_id = ?", id).Order("order asc").Find(&c).Error
 
175
  }
176
 
177
- func (r *academyRepository) GetMaterialWithProgress(ctx context.Context, accountId uuid.UUID, academyId uuid.UUID, slug string) (entity.AcademyMaterial, error){
178
- var m entity.AcademyMaterial
179
- var err error
180
- m, err = r.GetMaterialBySlug(ctx, academyId, slug)
181
  if err != nil {
182
  return m, err
183
  }
184
-
185
- MaterialId := m.Id
186
-
187
- var ap entity.AcademyMaterialProgress
188
-
189
- ap, err = r.GetMaterialProgress(ctx, accountId,academyId, MaterialId)
190
- if err != nil && err != gorm.ErrRecordNotFound {
191
  return m, err
192
  }
193
  m.AcademyMaterialProgress = ap
194
-
195
  return m, nil
196
  }
197
 
@@ -201,7 +205,7 @@ func (r *academyRepository) CreateMaterial(ctx context.Context, m entity.Academy
201
 
202
  func (r *academyRepository) ListMaterials(ctx context.Context, academyId uuid.UUID) ([]entity.AcademyMaterial, error) {
203
  var list []entity.AcademyMaterial
204
- return list, r.db.WithContext(ctx).Where("academy_id = ?", academyId).Find(&list).Error
205
  }
206
 
207
  func (r *academyRepository) UpdateMaterial(ctx context.Context, m entity.AcademyMaterial) (entity.AcademyMaterial, error) {
@@ -212,8 +216,6 @@ func (r *academyRepository) DeleteMaterial(ctx context.Context, id uuid.UUID) er
212
  return r.db.WithContext(ctx).Delete(&entity.AcademyMaterial{}, "id = ?", id).Error
213
  }
214
 
215
- // ========== CONTENT ==========
216
-
217
  func (r *academyRepository) GetContentByID(ctx context.Context, id uuid.UUID) (entity.AcademyContent, error) {
218
  var c entity.AcademyContent
219
  return c, r.db.WithContext(ctx).First(&c, "id = ?", id).Error
@@ -221,31 +223,20 @@ func (r *academyRepository) GetContentByID(ctx context.Context, id uuid.UUID) (e
221
 
222
  func (r *academyRepository) GetContentBySlug(ctx context.Context, materialId uuid.UUID, order uint) (entity.AcademyContent, error) {
223
  var c entity.AcademyContent
224
- result := r.db.WithContext(ctx).
225
- // Escape "order" with backslashes and double quotes: \"order\"
226
- Where("\"order\" = ?", order).
227
- Where("material_id = ?", materialId).
228
- First(&c)
229
-
230
- return c, result.Error
231
  }
232
 
233
- func (r *academyRepository) GetContentWithProgress(ctx context.Context, accountId uuid.UUID, academyId uuid.UUID,materialId uuid.UUID, order uint) (entity.AcademyContent, error){
234
- var c entity.AcademyContent
235
- var err error
236
- c, err = r.GetContentBySlug(ctx,materialId,order)
237
  if err != nil {
238
  return c, err
239
  }
240
-
241
- var ap entity.AcademyContentProgress
242
-
243
- ap, err = r.GetContentProgress(ctx, accountId, academyId, materialId,c.Id)
244
- if err != nil && err != gorm.ErrRecordNotFound {
245
  return c, err
246
  }
247
  c.AcademyContentProgress = ap
248
-
249
  return c, nil
250
  }
251
 
@@ -263,89 +254,56 @@ func (r *academyRepository) DeleteContent(ctx context.Context, id uuid.UUID) err
263
 
264
  func (r *academyRepository) CountContentsByMaterialID(ctx context.Context, materialId uuid.UUID) (int64, error) {
265
  var count int64
266
-
267
- query := r.db.WithContext(ctx).
268
- Where("material_id = ?", materialId)
269
-
270
- err := query.Model(&entity.AcademyContent{}).
271
- Count(&count).
272
- Error
273
-
274
  return count, err
275
  }
276
 
277
- // ========== PROGRESS ==========
278
-
279
  func (r *academyRepository) GetAcademyProgress(ctx context.Context, accountId uuid.UUID, academyId uuid.UUID) (entity.AcademyProgress, error) {
280
  var existing entity.AcademyProgress
281
-
282
- err := r.db.WithContext(ctx).
283
- Where("account_id = ? AND academy_id = ?", accountId, academyId).
284
- First(&existing).Error
285
 
286
  if errors.Is(err, gorm.ErrRecordNotFound) {
287
  return entity.AcademyProgress{
288
- AccountId: accountId,
289
- AcademyId: academyId,
290
- Status: "NOT_STARTED",
291
- Progress: 0,
292
- TotalCompletedMaterials: 0,
293
  }, nil
294
  }
295
-
296
- if err != nil {
297
- return existing, err
298
- }
299
- return existing, nil
300
  }
301
 
302
  func (r *academyRepository) UpsertAcademyProgress(ctx context.Context, p entity.AcademyProgress) (entity.AcademyProgress, error) {
303
- var existing entity.AcademyProgress
304
- err := r.db.WithContext(ctx).First(&existing, "account_id = ? AND academy_id = ?", p.AccountId, p.AcademyId).Error
305
- if err == gorm.ErrRecordNotFound {
306
- return p, r.db.WithContext(ctx).Create(&p).Error
307
- }
308
- return p, r.db.WithContext(ctx).Model(&existing).Updates(p).Error
309
  }
310
 
311
  func (r *academyRepository) GetMaterialProgress(ctx context.Context, accountId uuid.UUID, academyId uuid.UUID, materialId uuid.UUID) (entity.AcademyMaterialProgress, error) {
312
  var existing entity.AcademyMaterialProgress
313
-
314
- err := r.db.WithContext(ctx).First(&existing, "account_id = ? AND academy_id = ? AND material_id = ?", accountId, academyId, materialId).Error
315
 
316
  if errors.Is(err, gorm.ErrRecordNotFound) {
317
-
318
  return entity.AcademyMaterialProgress{
319
- AccountId: accountId,
320
- AcademyId: academyId,
321
- MaterialId: materialId,
322
- Progress: 0,
323
- TotalCompletedContents: 0,
324
- Status: "NOT_STARTED",
325
  }, nil
326
  }
327
-
328
- if err != nil {
329
- return existing, err
330
- }
331
- return existing, nil
332
  }
333
 
334
  func (r *academyRepository) UpsertMaterialProgress(ctx context.Context, p entity.AcademyMaterialProgress) (entity.AcademyMaterialProgress, error) {
335
- var existing entity.AcademyMaterialProgress
336
- err := r.db.WithContext(ctx).First(&existing, "account_id = ? AND academy_id = ? AND material_id = ?", p.AccountId, p.AcademyId, p.MaterialId).Error
337
-
338
- if err == gorm.ErrRecordNotFound {
339
- return p, r.db.WithContext(ctx).Create(&p).Error
340
- }
341
-
342
- return p, r.db.WithContext(ctx).Model(&existing).Updates(p).Error
343
  }
344
 
345
  func (r *academyRepository) GetContentProgress(ctx context.Context, accountId uuid.UUID, academyId uuid.UUID, materialId uuid.UUID, contentId uuid.UUID) (entity.AcademyContentProgress, error) {
346
  var existing entity.AcademyContentProgress
347
-
348
- err := r.db.WithContext(ctx).First(&existing, "account_id = ? AND academy_id = ? AND material_id = ? AND content_id = ?", accountId, academyId, materialId,contentId).Error
349
 
350
  if errors.Is(err, gorm.ErrRecordNotFound) {
351
  return entity.AcademyContentProgress{
@@ -353,45 +311,162 @@ func (r *academyRepository) GetContentProgress(ctx context.Context, accountId uu
353
  AcademyId: academyId,
354
  MaterialId: materialId,
355
  ContentId: contentId,
356
- Status: "NOT_STARTED",
357
  }, nil
358
  }
359
-
360
- if err != nil {
361
- return existing, err
362
- }
363
- return existing, nil
364
  }
365
 
366
  func (r *academyRepository) UpsertContentProgress(ctx context.Context, p entity.AcademyContentProgress) (entity.AcademyContentProgress, error) {
367
- var existing entity.AcademyContentProgress
368
- err := r.db.WithContext(ctx).First(&existing, "account_id = ? AND academy_id = ? AND material_id = ? AND content_id = ?", p.AccountId, p.AcademyId, p.MaterialId, p.ContentId).Error
 
 
 
369
 
370
- if err == gorm.ErrRecordNotFound {
371
- return p, r.db.WithContext(ctx).Create(&p).Error
372
- }
373
 
374
- return p, r.db.WithContext(ctx).Model(&existing).Updates(p).Error
 
375
  }
376
 
377
- // UTILS
 
 
378
 
379
  func (r *academyRepository) CountCompletedContentsByMaterialAndAccount(ctx context.Context, accountId uuid.UUID, materialId uuid.UUID) (int64, error) {
380
  var count int64
381
- query := r.db.WithContext(ctx).
382
- Where("account_id = ? AND material_id = ? AND status = ?", accountId, materialId, "COMPLETED")
383
- err := query.Model(&entity.AcademyContentProgress{}).
384
- Count(&count).
385
- Error
386
  return count, err
387
  }
388
 
389
  func (r *academyRepository) CountCompletedMaterialsByAcademyAndAccount(ctx context.Context, accountId uuid.UUID, academyId uuid.UUID) (int64, error) {
390
  var count int64
391
- query := r.db.WithContext(ctx).
392
- Where("account_id = ? AND academy_id = ? AND status = ?", accountId, academyId, "COMPLETED")
393
- err := query.Model(&entity.AcademyMaterialProgress{}).
394
- Count(&count).
395
- Error
 
 
 
 
 
 
396
  return count, err
397
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
  entity "abdanhafidz.com/go-boilerplate/models/entity"
8
  "github.com/google/uuid"
9
  "gorm.io/gorm"
10
+ "gorm.io/gorm/clause"
11
  )
12
 
13
  type AcademyRepository interface {
14
+ Atomic(ctx context.Context, fn func(r AcademyRepository) error) error
15
+
16
  GetAcademyByID(ctx context.Context, id uuid.UUID) (entity.Academy, error)
17
  GetAcademyBySlug(ctx context.Context, slug string) (entity.Academy, error)
18
  GetAcademyWithProgress(ctx context.Context, accountId uuid.UUID, slug string) (entity.Academy, error)
 
19
  CreateAcademy(ctx context.Context, a entity.Academy) (entity.Academy, error)
20
  UpdateAcademy(ctx context.Context, a entity.Academy) (entity.Academy, error)
21
  DeleteAcademy(ctx context.Context, id uuid.UUID) error
 
22
  ListAcademy(ctx context.Context, accountId uuid.UUID) ([]entity.Academy, error)
23
  GetAcademyWithMaterials(ctx context.Context, id uuid.UUID) (entity.Academy, []entity.AcademyMaterial, error)
24
  CountMaterialsByAcademyID(ctx context.Context, academyId uuid.UUID) (int64, error)
25
 
 
26
  GetMaterialBySlug(ctx context.Context, academy_id uuid.UUID, materialSlug string) (entity.AcademyMaterial, error)
27
  GetMaterialByID(ctx context.Context, id uuid.UUID) (entity.AcademyMaterial, error)
28
  GetMaterialWithProgress(ctx context.Context, accountId uuid.UUID, academyId uuid.UUID, slug string) (entity.AcademyMaterial, error)
 
29
  CreateMaterial(ctx context.Context, m entity.AcademyMaterial) (entity.AcademyMaterial, error)
30
  UpdateMaterial(ctx context.Context, m entity.AcademyMaterial) (entity.AcademyMaterial, error)
31
  DeleteMaterial(ctx context.Context, id uuid.UUID) error
 
32
  ListMaterials(ctx context.Context, academyId uuid.UUID) ([]entity.AcademyMaterial, error)
33
  GetMaterialWithContents(ctx context.Context, id uuid.UUID) (entity.AcademyMaterial, []entity.AcademyContent, error)
34
 
 
35
  GetContentBySlug(ctx context.Context, materialId uuid.UUID, order uint) (entity.AcademyContent, error)
36
  GetContentByID(ctx context.Context, id uuid.UUID) (entity.AcademyContent, error)
37
+ GetContentWithProgress(ctx context.Context, accountId uuid.UUID, academyId uuid.UUID, materialId uuid.UUID, order uint) (entity.AcademyContent, error)
 
38
  CreateContent(ctx context.Context, c entity.AcademyContent) (entity.AcademyContent, error)
39
  UpdateContent(ctx context.Context, c entity.AcademyContent) (entity.AcademyContent, error)
40
  DeleteContent(ctx context.Context, id uuid.UUID) error
 
41
  CountContentsByMaterialID(ctx context.Context, materialId uuid.UUID) (int64, error)
42
 
 
43
  GetAcademyProgress(ctx context.Context, accountId uuid.UUID, academyId uuid.UUID) (entity.AcademyProgress, error)
44
  GetMaterialProgress(ctx context.Context, accountId uuid.UUID, academyId uuid.UUID, materialId uuid.UUID) (entity.AcademyMaterialProgress, error)
45
  GetContentProgress(ctx context.Context, accountId uuid.UUID, academyId uuid.UUID, materialId uuid.UUID, contentId uuid.UUID) (entity.AcademyContentProgress, error)
 
46
  UpsertAcademyProgress(ctx context.Context, p entity.AcademyProgress) (entity.AcademyProgress, error)
47
  UpsertMaterialProgress(ctx context.Context, p entity.AcademyMaterialProgress) (entity.AcademyMaterialProgress, error)
48
  UpsertContentProgress(ctx context.Context, p entity.AcademyContentProgress) (entity.AcademyContentProgress, error)
49
+ DeleteContentProgressByContentID(ctx context.Context, contentId uuid.UUID) error
50
+ DeleteContentProgressByMaterialID(ctx context.Context, materialId uuid.UUID) error
51
+ DeleteMaterialProgressByMaterialID(ctx context.Context, materialId uuid.UUID) error
52
 
53
  CountCompletedContentsByMaterialAndAccount(ctx context.Context, accountId uuid.UUID, materialId uuid.UUID) (int64, error)
54
  CountCompletedMaterialsByAcademyAndAccount(ctx context.Context, accountId uuid.UUID, academyId uuid.UUID) (int64, error)
55
+ CountStartedMaterialsByAcademyAndAccount(ctx context.Context, accountId uuid.UUID, academyId uuid.UUID) (int64, error)
56
+ DecrementMaterialOrdersGreaterThan(ctx context.Context, academyId uuid.UUID, order uint) error
57
+ DecrementContentOrdersGreaterThan(ctx context.Context, materialId uuid.UUID, order uint) error
58
+ GetAccumulatedMaterialProgress(ctx context.Context, accountId uuid.UUID, academyId uuid.UUID) (float64, error)
59
+
60
+ BatchRecalculateMaterialProgress(ctx context.Context, materialId uuid.UUID) error
61
+ BatchRecalculateAcademyProgress(ctx context.Context, academyId uuid.UUID) error
62
  }
63
 
64
  type academyRepository struct{ db *gorm.DB }
 
67
  return &academyRepository{db: db}
68
  }
69
 
70
+ func (r *academyRepository) Atomic(ctx context.Context, fn func(r AcademyRepository) error) error {
71
+ return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
72
+ txRepo := NewAcademyRepository(tx)
73
+ return fn(txRepo)
74
+ })
75
+ }
76
+
77
  func (r *academyRepository) GetAcademyWithMaterials(ctx context.Context, id uuid.UUID) (entity.Academy, []entity.AcademyMaterial, error) {
78
  var a entity.Academy
79
+ if err := r.db.WithContext(ctx).First(&a, "id = ?", id).Error; err != nil {
 
80
  return entity.Academy{}, nil, err
81
  }
 
82
  var m []entity.AcademyMaterial
83
+ err := r.db.WithContext(ctx).Where("academy_id = ?", id).Order("\"order\" ASC").Find(&m).Error
84
+ return a, m, err
85
  }
86
 
87
  func (r *academyRepository) GetAcademyByID(ctx context.Context, id uuid.UUID) (entity.Academy, error) {
 
95
  }
96
 
97
  func (r *academyRepository) GetAcademyWithProgress(ctx context.Context, accountId uuid.UUID, slug string) (entity.Academy, error) {
98
+ a, err := r.GetAcademyBySlug(ctx, slug)
 
 
99
  if err != nil {
100
  return a, err
101
  }
102
+ ap, err := r.GetAcademyProgress(ctx, accountId, a.Id)
103
+ if err != nil {
 
 
 
 
 
104
  return a, err
105
  }
106
  a.AcademyProgresss = ap
 
107
  return a, nil
108
  }
109
 
 
121
 
122
  func (r *academyRepository) ListAcademy(ctx context.Context, accountId uuid.UUID) ([]entity.Academy, error) {
123
  var list []entity.Academy
 
124
  if err := r.db.WithContext(ctx).Find(&list).Error; err != nil {
125
  return nil, err
126
  }
127
+ if len(list) == 0 {
128
+ return list, nil
129
+ }
130
+
131
+ academyIDs := make([]uuid.UUID, len(list))
132
+ for i, ac := range list {
133
+ academyIDs[i] = ac.Id
134
+ }
135
+
136
+ var progressList []entity.AcademyProgress
137
+ if err := r.db.WithContext(ctx).
138
+ Where("account_id = ?", accountId).
139
+ Where("academy_id IN ?", academyIDs).
140
+ Find(&progressList).Error; err != nil {
141
+ return nil, err
142
+ }
143
+
144
+ progressMap := make(map[uuid.UUID]entity.AcademyProgress)
145
+ for _, p := range progressList {
146
+ progressMap[p.AcademyId] = p
147
+ }
148
 
149
  for i := range list {
150
+ if p, exists := progressMap[list[i].Id]; exists {
151
+ list[i].AcademyProgresss = p
152
+ } else {
153
+ list[i].AcademyProgresss = entity.AcademyProgress{
154
+ AccountId: accountId,
155
+ AcademyId: list[i].Id,
156
+ Status: entity.StatusNotStarted,
157
+ }
158
  }
 
159
  }
 
160
  return list, nil
161
  }
162
 
163
  func (r *academyRepository) CountMaterialsByAcademyID(ctx context.Context, academyId uuid.UUID) (int64, error) {
164
  var count int64
165
+ err := r.db.WithContext(ctx).Model(&entity.AcademyMaterial{}).Where("academy_id = ?", academyId).Count(&count).Error
 
 
 
 
 
 
 
166
  return count, err
167
  }
168
 
 
169
  func (r *academyRepository) GetMaterialByID(ctx context.Context, id uuid.UUID) (entity.AcademyMaterial, error) {
170
  var m entity.AcademyMaterial
171
  return m, r.db.WithContext(ctx).First(&m, "id = ?", id).Error
 
178
 
179
  func (r *academyRepository) GetMaterialWithContents(ctx context.Context, id uuid.UUID) (entity.AcademyMaterial, []entity.AcademyContent, error) {
180
  var m entity.AcademyMaterial
181
+ if err := r.db.WithContext(ctx).First(&m, "id = ?", id).Error; err != nil {
 
182
  return entity.AcademyMaterial{}, nil, err
183
  }
 
184
  var c []entity.AcademyContent
185
+ err := r.db.WithContext(ctx).Where("material_id = ?", id).Order("\"order\" ASC").Find(&c).Error
186
+ return m, c, err
187
  }
188
 
189
+ func (r *academyRepository) GetMaterialWithProgress(ctx context.Context, accountId uuid.UUID, academyId uuid.UUID, slug string) (entity.AcademyMaterial, error) {
190
+ m, err := r.GetMaterialBySlug(ctx, academyId, slug)
 
 
191
  if err != nil {
192
  return m, err
193
  }
194
+ ap, err := r.GetMaterialProgress(ctx, accountId, academyId, m.Id)
195
+ if err != nil {
 
 
 
 
 
196
  return m, err
197
  }
198
  m.AcademyMaterialProgress = ap
 
199
  return m, nil
200
  }
201
 
 
205
 
206
  func (r *academyRepository) ListMaterials(ctx context.Context, academyId uuid.UUID) ([]entity.AcademyMaterial, error) {
207
  var list []entity.AcademyMaterial
208
+ return list, r.db.WithContext(ctx).Where("academy_id = ?", academyId).Order("\"order\" ASC").Find(&list).Error
209
  }
210
 
211
  func (r *academyRepository) UpdateMaterial(ctx context.Context, m entity.AcademyMaterial) (entity.AcademyMaterial, error) {
 
216
  return r.db.WithContext(ctx).Delete(&entity.AcademyMaterial{}, "id = ?", id).Error
217
  }
218
 
 
 
219
  func (r *academyRepository) GetContentByID(ctx context.Context, id uuid.UUID) (entity.AcademyContent, error) {
220
  var c entity.AcademyContent
221
  return c, r.db.WithContext(ctx).First(&c, "id = ?", id).Error
 
223
 
224
  func (r *academyRepository) GetContentBySlug(ctx context.Context, materialId uuid.UUID, order uint) (entity.AcademyContent, error) {
225
  var c entity.AcademyContent
226
+ err := r.db.WithContext(ctx).Where("\"order\" = ? AND material_id = ?", order, materialId).First(&c).Error
227
+ return c, err
 
 
 
 
 
228
  }
229
 
230
+ func (r *academyRepository) GetContentWithProgress(ctx context.Context, accountId uuid.UUID, academyId uuid.UUID, materialId uuid.UUID, order uint) (entity.AcademyContent, error) {
231
+ c, err := r.GetContentBySlug(ctx, materialId, order)
 
 
232
  if err != nil {
233
  return c, err
234
  }
235
+ ap, err := r.GetContentProgress(ctx, accountId, academyId, materialId, c.Id)
236
+ if err != nil {
 
 
 
237
  return c, err
238
  }
239
  c.AcademyContentProgress = ap
 
240
  return c, nil
241
  }
242
 
 
254
 
255
  func (r *academyRepository) CountContentsByMaterialID(ctx context.Context, materialId uuid.UUID) (int64, error) {
256
  var count int64
257
+ err := r.db.WithContext(ctx).Model(&entity.AcademyContent{}).Where("material_id = ?", materialId).Count(&count).Error
 
 
 
 
 
 
 
258
  return count, err
259
  }
260
 
 
 
261
  func (r *academyRepository) GetAcademyProgress(ctx context.Context, accountId uuid.UUID, academyId uuid.UUID) (entity.AcademyProgress, error) {
262
  var existing entity.AcademyProgress
263
+ err := r.db.WithContext(ctx).Where("account_id = ? AND academy_id = ?", accountId, academyId).First(&existing).Error
 
 
 
264
 
265
  if errors.Is(err, gorm.ErrRecordNotFound) {
266
  return entity.AcademyProgress{
267
+ AccountId: accountId,
268
+ AcademyId: academyId,
269
+ Status: entity.StatusNotStarted,
 
 
270
  }, nil
271
  }
272
+ return existing, err
 
 
 
 
273
  }
274
 
275
  func (r *academyRepository) UpsertAcademyProgress(ctx context.Context, p entity.AcademyProgress) (entity.AcademyProgress, error) {
276
+ return p, r.db.WithContext(ctx).Clauses(clause.OnConflict{
277
+ Columns: []clause.Column{{Name: "id"}},
278
+ UpdateAll: true,
279
+ }).Save(&p).Error
 
 
280
  }
281
 
282
  func (r *academyRepository) GetMaterialProgress(ctx context.Context, accountId uuid.UUID, academyId uuid.UUID, materialId uuid.UUID) (entity.AcademyMaterialProgress, error) {
283
  var existing entity.AcademyMaterialProgress
284
+ err := r.db.WithContext(ctx).Where("account_id = ? AND material_id = ?", accountId, materialId).First(&existing).Error
 
285
 
286
  if errors.Is(err, gorm.ErrRecordNotFound) {
 
287
  return entity.AcademyMaterialProgress{
288
+ AccountId: accountId,
289
+ AcademyId: academyId,
290
+ MaterialId: materialId,
291
+ Status: entity.StatusNotStarted,
 
 
292
  }, nil
293
  }
294
+ return existing, err
 
 
 
 
295
  }
296
 
297
  func (r *academyRepository) UpsertMaterialProgress(ctx context.Context, p entity.AcademyMaterialProgress) (entity.AcademyMaterialProgress, error) {
298
+ return p, r.db.WithContext(ctx).Clauses(clause.OnConflict{
299
+ Columns: []clause.Column{{Name: "id"}},
300
+ UpdateAll: true,
301
+ }).Save(&p).Error
 
 
 
 
302
  }
303
 
304
  func (r *academyRepository) GetContentProgress(ctx context.Context, accountId uuid.UUID, academyId uuid.UUID, materialId uuid.UUID, contentId uuid.UUID) (entity.AcademyContentProgress, error) {
305
  var existing entity.AcademyContentProgress
306
+ err := r.db.WithContext(ctx).Where("account_id = ? AND content_id = ?", accountId, contentId).First(&existing).Error
 
307
 
308
  if errors.Is(err, gorm.ErrRecordNotFound) {
309
  return entity.AcademyContentProgress{
 
311
  AcademyId: academyId,
312
  MaterialId: materialId,
313
  ContentId: contentId,
314
+ Status: entity.StatusNotStarted,
315
  }, nil
316
  }
317
+ return existing, err
 
 
 
 
318
  }
319
 
320
  func (r *academyRepository) UpsertContentProgress(ctx context.Context, p entity.AcademyContentProgress) (entity.AcademyContentProgress, error) {
321
+ return p, r.db.WithContext(ctx).Clauses(clause.OnConflict{
322
+ Columns: []clause.Column{{Name: "id"}},
323
+ UpdateAll: true,
324
+ }).Save(&p).Error
325
+ }
326
 
327
+ func (r *academyRepository) DeleteContentProgressByContentID(ctx context.Context, contentId uuid.UUID) error {
328
+ return r.db.WithContext(ctx).Where("content_id = ?", contentId).Delete(&entity.AcademyContentProgress{}).Error
329
+ }
330
 
331
+ func (r *academyRepository) DeleteContentProgressByMaterialID(ctx context.Context, materialId uuid.UUID) error {
332
+ return r.db.WithContext(ctx).Where("material_id = ?", materialId).Delete(&entity.AcademyContentProgress{}).Error
333
  }
334
 
335
+ func (r *academyRepository) DeleteMaterialProgressByMaterialID(ctx context.Context, materialId uuid.UUID) error {
336
+ return r.db.WithContext(ctx).Where("material_id = ?", materialId).Delete(&entity.AcademyMaterialProgress{}).Error
337
+ }
338
 
339
  func (r *academyRepository) CountCompletedContentsByMaterialAndAccount(ctx context.Context, accountId uuid.UUID, materialId uuid.UUID) (int64, error) {
340
  var count int64
341
+ err := r.db.WithContext(ctx).Model(&entity.AcademyContentProgress{}).
342
+ Where("account_id = ? AND material_id = ? AND status = ?", accountId, materialId, entity.StatusCompleted).
343
+ Count(&count).Error
 
 
344
  return count, err
345
  }
346
 
347
  func (r *academyRepository) CountCompletedMaterialsByAcademyAndAccount(ctx context.Context, accountId uuid.UUID, academyId uuid.UUID) (int64, error) {
348
  var count int64
349
+ err := r.db.WithContext(ctx).Model(&entity.AcademyMaterialProgress{}).
350
+ Where("account_id = ? AND academy_id = ? AND status = ?", accountId, academyId, entity.StatusCompleted).
351
+ Count(&count).Error
352
+ return count, err
353
+ }
354
+
355
+ func (r *academyRepository) CountStartedMaterialsByAcademyAndAccount(ctx context.Context, accountId uuid.UUID, academyId uuid.UUID) (int64, error) {
356
+ var count int64
357
+ err := r.db.WithContext(ctx).Model(&entity.AcademyMaterialProgress{}).
358
+ Where("account_id = ? AND academy_id = ? AND status IN ?", accountId, academyId, []string{entity.StatusInProgress, entity.StatusCompleted}).
359
+ Count(&count).Error
360
  return count, err
361
  }
362
+
363
+ func (r *academyRepository) DecrementMaterialOrdersGreaterThan(ctx context.Context, academyId uuid.UUID, order uint) error {
364
+ return r.db.WithContext(ctx).Model(&entity.AcademyMaterial{}).
365
+ Where("academy_id = ? AND \"order\" > ?", academyId, order).
366
+ Update("\"order\"", gorm.Expr("\"order\" - 1")).Error
367
+ }
368
+
369
+ func (r *academyRepository) DecrementContentOrdersGreaterThan(ctx context.Context, materialId uuid.UUID, order uint) error {
370
+ return r.db.WithContext(ctx).Model(&entity.AcademyContent{}).
371
+ Where("material_id = ? AND \"order\" > ?", materialId, order).
372
+ Update("\"order\"", gorm.Expr("\"order\" - 1")).Error
373
+ }
374
+
375
+ func (r *academyRepository) GetAccumulatedMaterialProgress(ctx context.Context, accountId uuid.UUID, academyId uuid.UUID) (float64, error) {
376
+ var total float64
377
+ err := r.db.WithContext(ctx).Model(&entity.AcademyMaterialProgress{}).
378
+ Where("account_id = ? AND academy_id = ?", accountId, academyId).
379
+ Select("COALESCE(SUM(progress), 0)").
380
+ Scan(&total).Error
381
+ return total, err
382
+ }
383
+
384
+ func (r *academyRepository) BatchRecalculateMaterialProgress(ctx context.Context, materialId uuid.UUID) error {
385
+ totalContents, err := r.CountContentsByMaterialID(ctx, materialId)
386
+ if err != nil {
387
+ return err
388
+ }
389
+
390
+ if totalContents == 0 {
391
+ return r.db.WithContext(ctx).Model(&entity.AcademyMaterialProgress{}).
392
+ Where("material_id = ?", materialId).
393
+ Updates(map[string]interface{}{
394
+ "progress": 100,
395
+ "status": entity.StatusCompleted,
396
+ "total_completed_contents": 0,
397
+ }).Error
398
+ }
399
+
400
+ return r.db.WithContext(ctx).Exec(`
401
+ UPDATE academy_material_progresses amp
402
+ SET
403
+ total_completed_contents = (
404
+ SELECT COUNT(id) FROM academy_content_progresses acp
405
+ WHERE acp.material_id = amp.material_id AND acp.account_id = amp.account_id AND acp.status = 'COMPLETED'
406
+ ),
407
+ progress = (
408
+ SELECT COUNT(id) FROM academy_content_progresses acp
409
+ WHERE acp.material_id = amp.material_id AND acp.account_id = amp.account_id AND acp.status = 'COMPLETED'
410
+ )::float / ? * 100,
411
+ status = CASE
412
+ WHEN (
413
+ SELECT COUNT(id) FROM academy_content_progresses acp
414
+ WHERE acp.material_id = amp.material_id AND acp.account_id = amp.account_id AND acp.status = 'COMPLETED'
415
+ ) >= ? THEN 'COMPLETED'
416
+ ELSE 'IN_PROGRESS'
417
+ END,
418
+ completed_at = CASE
419
+ WHEN (
420
+ SELECT COUNT(id) FROM academy_content_progresses acp
421
+ WHERE acp.material_id = amp.material_id AND acp.account_id = amp.account_id AND acp.status = 'COMPLETED'
422
+ ) >= ? THEN NOW()
423
+ ELSE NULL
424
+ END
425
+ WHERE amp.material_id = ?
426
+ `, totalContents, totalContents, totalContents, materialId).Error
427
+ }
428
+
429
+ func (r *academyRepository) BatchRecalculateAcademyProgress(ctx context.Context, academyId uuid.UUID) error {
430
+ totalMaterials, err := r.CountMaterialsByAcademyID(ctx, academyId)
431
+ if err != nil {
432
+ return err
433
+ }
434
+
435
+ if totalMaterials == 0 {
436
+ return r.db.WithContext(ctx).Model(&entity.AcademyProgress{}).
437
+ Where("academy_id = ?", academyId).
438
+ Updates(map[string]interface{}{
439
+ "progress": 100,
440
+ "status": entity.StatusCompleted,
441
+ "total_completed_materials": 0,
442
+ }).Error
443
+ }
444
+
445
+ return r.db.WithContext(ctx).Exec(`
446
+ UPDATE academy_progress ap
447
+ SET
448
+ progress = (
449
+ SELECT COALESCE(SUM(amp.progress), 0) FROM academy_material_progresses amp
450
+ WHERE amp.academy_id = ap.academy_id AND amp.account_id = ap.account_id
451
+ )::float / ?,
452
+ total_completed_materials = (
453
+ SELECT COUNT(id) FROM academy_material_progresses amp
454
+ WHERE amp.academy_id = ap.academy_id AND amp.account_id = ap.account_id AND amp.status = 'COMPLETED'
455
+ ),
456
+ status = CASE
457
+ WHEN (
458
+ SELECT COUNT(id) FROM academy_material_progresses amp
459
+ WHERE amp.academy_id = ap.academy_id AND amp.account_id = ap.account_id AND amp.status = 'COMPLETED'
460
+ ) >= ? THEN 'COMPLETED'
461
+ ELSE 'IN_PROGRESS'
462
+ END,
463
+ completed_at = CASE
464
+ WHEN (
465
+ SELECT COUNT(id) FROM academy_material_progresses amp
466
+ WHERE amp.academy_id = ap.academy_id AND amp.account_id = ap.account_id AND amp.status = 'COMPLETED'
467
+ ) >= ? THEN NOW()
468
+ ELSE NULL
469
+ END
470
+ WHERE ap.academy_id = ?
471
+ `, totalMaterials, totalMaterials, totalMaterials, academyId).Error
472
+ }
repositories/exam_event_answer_repository.go CHANGED
@@ -1,59 +1,59 @@
1
- package repositories
2
-
3
- import (
4
- "context"
5
-
6
- entity "abdanhafidz.com/go-boilerplate/models/entity"
7
- "github.com/google/uuid"
8
- "gorm.io/gorm"
9
- )
10
-
11
- type ExamEventAnswerRepository interface {
12
- Create(ctx context.Context, ans *entity.ExamEventAnswer) error
13
- Update(ctx context.Context, ans *entity.ExamEventAnswer) error
14
- GetByAttemptAndQuestion(ctx context.Context, attemptId uuid.UUID, questionId uuid.UUID) (entity.ExamEventAnswer, error)
15
- ListByAttempt(ctx context.Context, attemptId uuid.UUID) ([]entity.ExamEventAnswer, error)
16
- DeleteByAttempt(ctx context.Context, attemptId uuid.UUID) error
17
-
18
- // decomposed result (answer + question)
19
- }
20
-
21
- type examEventAnswerRepository struct {
22
- db *gorm.DB
23
- }
24
-
25
- func NewExamEventAnswerRepository(db *gorm.DB) ExamEventAnswerRepository {
26
- return &examEventAnswerRepository{db: db}
27
- }
28
-
29
- func (r *examEventAnswerRepository) Create(ctx context.Context, ans *entity.ExamEventAnswer) error {
30
- return r.db.WithContext(ctx).Create(ans).Error
31
- }
32
-
33
- func (r *examEventAnswerRepository) Update(ctx context.Context, ans *entity.ExamEventAnswer) error {
34
- return r.db.WithContext(ctx).
35
- Where("attempt_id = ? AND question_id = ?", ans.AttemptId, ans.QuestionId).
36
- Updates(ans).Error
37
- }
38
-
39
- func (r *examEventAnswerRepository) GetByAttemptAndQuestion(ctx context.Context, attemptId uuid.UUID, questionId uuid.UUID) (entity.ExamEventAnswer, error) {
40
- var ans entity.ExamEventAnswer
41
- err := r.db.WithContext(ctx).
42
- Where("attempt_id = ? AND question_id = ?", attemptId, questionId).
43
- First(&ans).Error
44
- return ans, err
45
- }
46
-
47
- func (r *examEventAnswerRepository) ListByAttempt(ctx context.Context, attemptId uuid.UUID) ([]entity.ExamEventAnswer, error) {
48
- var answers []entity.ExamEventAnswer
49
- err := r.db.WithContext(ctx).
50
- Where("attempt_id = ?", attemptId).
51
- Find(&answers).Error
52
- return answers, err
53
- }
54
-
55
- func (r *examEventAnswerRepository) DeleteByAttempt(ctx context.Context, attemptId uuid.UUID) error {
56
- return r.db.WithContext(ctx).
57
- Where("attempt_id = ?", attemptId).
58
- Delete(&entity.ExamEventAnswer{}).Error
59
- }
 
1
+ package repositories
2
+
3
+ import (
4
+ "context"
5
+
6
+ entity "abdanhafidz.com/go-boilerplate/models/entity"
7
+ "github.com/google/uuid"
8
+ "gorm.io/gorm"
9
+ )
10
+
11
+ type ExamEventAnswerRepository interface {
12
+ Create(ctx context.Context, ans *entity.ExamEventAnswer) error
13
+ Update(ctx context.Context, ans *entity.ExamEventAnswer) error
14
+ GetByAttemptAndQuestion(ctx context.Context, attemptId uuid.UUID, questionId uuid.UUID) (entity.ExamEventAnswer, error)
15
+ ListByAttempt(ctx context.Context, attemptId uuid.UUID) ([]entity.ExamEventAnswer, error)
16
+ DeleteByAttempt(ctx context.Context, attemptId uuid.UUID) error
17
+
18
+ // decomposed result (answer + question)
19
+ }
20
+
21
+ type examEventAnswerRepository struct {
22
+ db *gorm.DB
23
+ }
24
+
25
+ func NewExamEventAnswerRepository(db *gorm.DB) ExamEventAnswerRepository {
26
+ return &examEventAnswerRepository{db: db}
27
+ }
28
+
29
+ func (r *examEventAnswerRepository) Create(ctx context.Context, ans *entity.ExamEventAnswer) error {
30
+ return r.db.WithContext(ctx).Create(ans).Error
31
+ }
32
+
33
+ func (r *examEventAnswerRepository) Update(ctx context.Context, ans *entity.ExamEventAnswer) error {
34
+ return r.db.WithContext(ctx).
35
+ Where("attempt_id = ? AND question_id = ?", ans.AttemptId, ans.QuestionId).
36
+ Updates(ans).Error
37
+ }
38
+
39
+ func (r *examEventAnswerRepository) GetByAttemptAndQuestion(ctx context.Context, attemptId uuid.UUID, questionId uuid.UUID) (entity.ExamEventAnswer, error) {
40
+ var ans entity.ExamEventAnswer
41
+ err := r.db.WithContext(ctx).
42
+ Where("attempt_id = ? AND question_id = ?", attemptId, questionId).
43
+ First(&ans).Error
44
+ return ans, err
45
+ }
46
+
47
+ func (r *examEventAnswerRepository) ListByAttempt(ctx context.Context, attemptId uuid.UUID) ([]entity.ExamEventAnswer, error) {
48
+ var answers []entity.ExamEventAnswer
49
+ err := r.db.WithContext(ctx).
50
+ Where("attempt_id = ?", attemptId).
51
+ Find(&answers).Error
52
+ return answers, err
53
+ }
54
+
55
+ func (r *examEventAnswerRepository) DeleteByAttempt(ctx context.Context, attemptId uuid.UUID) error {
56
+ return r.db.WithContext(ctx).
57
+ Where("attempt_id = ?", attemptId).
58
+ Delete(&entity.ExamEventAnswer{}).Error
59
+ }
repositories/exam_event_assign_repository.go CHANGED
@@ -1,47 +1,47 @@
1
- package repositories
2
-
3
- import (
4
- "context"
5
-
6
- entity "abdanhafidz.com/go-boilerplate/models/entity"
7
- "github.com/google/uuid"
8
- "gorm.io/gorm"
9
- )
10
-
11
- type ExamEventAssignRepository interface {
12
- Create(ctx context.Context, m entity.ExamEventAssign) error
13
- ListByEvent(ctx context.Context, eventId uuid.UUID) ([]entity.ExamEventAssign, error)
14
- Delete(ctx context.Context, id uuid.UUID) error
15
- Check(ctx context.Context, eventId uuid.UUID, examId uuid.UUID) error
16
- }
17
-
18
- type examEventAssignRepository struct{ db *gorm.DB }
19
-
20
- func NewExamEventAssignRepository(db *gorm.DB) ExamEventAssignRepository {
21
- return &examEventAssignRepository{db}
22
- }
23
-
24
- func (r *examEventAssignRepository) Check(ctx context.Context, eventId uuid.UUID, examId uuid.UUID) error {
25
- return r.db.WithContext(ctx).
26
- Where("event_id = ?", eventId).
27
- Where("exam_id = ?", examId).
28
- First(&entity.ExamEventAssign{}).Error
29
- }
30
- func (r *examEventAssignRepository) Create(ctx context.Context, m entity.ExamEventAssign) error {
31
- return r.db.WithContext(ctx).Create(&m).Error
32
- }
33
-
34
- func (r *examEventAssignRepository) ListByEvent(ctx context.Context, eventId uuid.UUID) ([]entity.ExamEventAssign, error) {
35
- var items []entity.ExamEventAssign
36
- err := r.db.WithContext(ctx).
37
- Where("event_id = ?", eventId).
38
- Preload("Exam").
39
- Find(&items).Error
40
- return items, err
41
- }
42
-
43
- func (r *examEventAssignRepository) Delete(ctx context.Context, id uuid.UUID) error {
44
- return r.db.WithContext(ctx).
45
- Where("id = ?", id).
46
- Delete(&entity.ExamEventAssign{}).Error
47
- }
 
1
+ package repositories
2
+
3
+ import (
4
+ "context"
5
+
6
+ entity "abdanhafidz.com/go-boilerplate/models/entity"
7
+ "github.com/google/uuid"
8
+ "gorm.io/gorm"
9
+ )
10
+
11
+ type ExamEventAssignRepository interface {
12
+ Create(ctx context.Context, m entity.ExamEventAssign) error
13
+ ListByEvent(ctx context.Context, eventId uuid.UUID) ([]entity.ExamEventAssign, error)
14
+ Delete(ctx context.Context, id uuid.UUID) error
15
+ Check(ctx context.Context, eventId uuid.UUID, examId uuid.UUID) error
16
+ }
17
+
18
+ type examEventAssignRepository struct{ db *gorm.DB }
19
+
20
+ func NewExamEventAssignRepository(db *gorm.DB) ExamEventAssignRepository {
21
+ return &examEventAssignRepository{db}
22
+ }
23
+
24
+ func (r *examEventAssignRepository) Check(ctx context.Context, eventId uuid.UUID, examId uuid.UUID) error {
25
+ return r.db.WithContext(ctx).
26
+ Where("event_id = ?", eventId).
27
+ Where("exam_id = ?", examId).
28
+ First(&entity.ExamEventAssign{}).Error
29
+ }
30
+ func (r *examEventAssignRepository) Create(ctx context.Context, m entity.ExamEventAssign) error {
31
+ return r.db.WithContext(ctx).Create(&m).Error
32
+ }
33
+
34
+ func (r *examEventAssignRepository) ListByEvent(ctx context.Context, eventId uuid.UUID) ([]entity.ExamEventAssign, error) {
35
+ var items []entity.ExamEventAssign
36
+ err := r.db.WithContext(ctx).
37
+ Where("event_id = ?", eventId).
38
+ Preload("Exam").
39
+ Find(&items).Error
40
+ return items, err
41
+ }
42
+
43
+ func (r *examEventAssignRepository) Delete(ctx context.Context, id uuid.UUID) error {
44
+ return r.db.WithContext(ctx).
45
+ Where("id = ?", id).
46
+ Delete(&entity.ExamEventAssign{}).Error
47
+ }
repositories/exam_event_attempt_repository.go CHANGED
@@ -1,55 +1,55 @@
1
- package repositories
2
-
3
- import (
4
- "context"
5
-
6
- entity "abdanhafidz.com/go-boilerplate/models/entity"
7
- "github.com/google/uuid"
8
- "gorm.io/gorm"
9
- )
10
-
11
- type ExamEventAttemptRepository interface {
12
- Create(ctx context.Context, a *entity.ExamEventAttempt) error
13
- GetById(ctx context.Context, attemptId uuid.UUID) (entity.ExamEventAttempt, error)
14
- GetByExamEvent(ctx context.Context, eventId uuid.UUID, examId uuid.UUID, accountId uuid.UUID) (entity.ExamEventAttempt, error)
15
- Update(ctx context.Context, a *entity.ExamEventAttempt) error
16
- }
17
-
18
- type examEventAttemptRepository struct{ db *gorm.DB }
19
-
20
- func NewExamEventAttemptRepository(db *gorm.DB) ExamEventAttemptRepository {
21
- return &examEventAttemptRepository{db}
22
- }
23
-
24
- func (r *examEventAttemptRepository) Create(ctx context.Context, a *entity.ExamEventAttempt) error {
25
- return r.db.WithContext(ctx).Create(a).Error
26
- }
27
-
28
- func (r *examEventAttemptRepository) GetById(ctx context.Context, attemptId uuid.UUID) (entity.ExamEventAttempt, error) {
29
- var a entity.ExamEventAttempt
30
- err := r.db.WithContext(ctx).
31
- Preload("Answers").
32
- First(&a, "id = ?", attemptId).Error
33
- return a, err
34
- }
35
-
36
- func (r *examEventAttemptRepository) GetByExamEvent(ctx context.Context, eventId uuid.UUID, examId uuid.UUID, accountId uuid.UUID) (entity.ExamEventAttempt, error) {
37
-
38
- var attempt entity.ExamEventAttempt
39
-
40
- err := r.db.WithContext(ctx).
41
- Preload("Answers").
42
- Where("event_id = ?", eventId).
43
- Where("exam_id = ?", examId).
44
- Where("account_id = ?", accountId).
45
- First(&attempt).Error
46
-
47
- return attempt, err
48
- }
49
-
50
- func (r *examEventAttemptRepository) Update(ctx context.Context, a *entity.ExamEventAttempt) error {
51
- return r.db.WithContext(ctx).
52
- Model(&entity.ExamEventAttempt{}).
53
- Where("id = ?", a.Id).
54
- Updates(a).Error
55
- }
 
1
+ package repositories
2
+
3
+ import (
4
+ "context"
5
+
6
+ entity "abdanhafidz.com/go-boilerplate/models/entity"
7
+ "github.com/google/uuid"
8
+ "gorm.io/gorm"
9
+ )
10
+
11
+ type ExamEventAttemptRepository interface {
12
+ Create(ctx context.Context, a *entity.ExamEventAttempt) error
13
+ GetById(ctx context.Context, attemptId uuid.UUID) (entity.ExamEventAttempt, error)
14
+ GetByExamEvent(ctx context.Context, eventId uuid.UUID, examId uuid.UUID, accountId uuid.UUID) (entity.ExamEventAttempt, error)
15
+ Update(ctx context.Context, a *entity.ExamEventAttempt) error
16
+ }
17
+
18
+ type examEventAttemptRepository struct{ db *gorm.DB }
19
+
20
+ func NewExamEventAttemptRepository(db *gorm.DB) ExamEventAttemptRepository {
21
+ return &examEventAttemptRepository{db}
22
+ }
23
+
24
+ func (r *examEventAttemptRepository) Create(ctx context.Context, a *entity.ExamEventAttempt) error {
25
+ return r.db.WithContext(ctx).Create(a).Error
26
+ }
27
+
28
+ func (r *examEventAttemptRepository) GetById(ctx context.Context, attemptId uuid.UUID) (entity.ExamEventAttempt, error) {
29
+ var a entity.ExamEventAttempt
30
+ err := r.db.WithContext(ctx).
31
+ Preload("Answers").
32
+ First(&a, "id = ?", attemptId).Error
33
+ return a, err
34
+ }
35
+
36
+ func (r *examEventAttemptRepository) GetByExamEvent(ctx context.Context, eventId uuid.UUID, examId uuid.UUID, accountId uuid.UUID) (entity.ExamEventAttempt, error) {
37
+
38
+ var attempt entity.ExamEventAttempt
39
+
40
+ err := r.db.WithContext(ctx).
41
+ Preload("Answers").
42
+ Where("event_id = ?", eventId).
43
+ Where("exam_id = ?", examId).
44
+ Where("account_id = ?", accountId).
45
+ First(&attempt).Error
46
+
47
+ return attempt, err
48
+ }
49
+
50
+ func (r *examEventAttemptRepository) Update(ctx context.Context, a *entity.ExamEventAttempt) error {
51
+ return r.db.WithContext(ctx).
52
+ Model(&entity.ExamEventAttempt{}).
53
+ Where("id = ?", a.Id).
54
+ Updates(a).Error
55
+ }
repositories/exam_event_repository.go CHANGED
@@ -1,85 +1,85 @@
1
- package repositories
2
-
3
- import (
4
- "context"
5
-
6
- entity "abdanhafidz.com/go-boilerplate/models/entity"
7
- "github.com/google/uuid"
8
- "gorm.io/gorm"
9
- )
10
-
11
- type ExamRepository interface {
12
- Create(ctx context.Context, e entity.Exam) error
13
- Get(ctx context.Context, id uuid.UUID) (entity.Exam, error)
14
- GetBySlug(ctx context.Context, slug string) (entity.Exam, error)
15
- Update(ctx context.Context, e entity.Exam) error
16
- Delete(ctx context.Context, id uuid.UUID) error
17
- List(ctx context.Context) ([]entity.Exam, error)
18
-
19
- // Additional business need
20
- ListByEvent(ctx context.Context, eventId uuid.UUID) ([]entity.Exam, error)
21
- }
22
-
23
- type examRepository struct {
24
- db *gorm.DB
25
- }
26
-
27
- func NewExamRepository(db *gorm.DB) ExamRepository {
28
- return &examRepository{db}
29
- }
30
-
31
- // ================= CRUD =================
32
-
33
- func (r *examRepository) Create(ctx context.Context, e entity.Exam) error {
34
- return r.db.WithContext(ctx).Create(&e).Error
35
- }
36
-
37
- func (r *examRepository) Get(ctx context.Context, id uuid.UUID) (entity.Exam, error) {
38
- var e entity.Exam
39
- err := r.db.WithContext(ctx).
40
- First(&e, "exam_id = ?", id).Error
41
- return e, err
42
- }
43
-
44
- func (r *examRepository) GetBySlug(ctx context.Context, slug string) (entity.Exam, error) {
45
- var e entity.Exam
46
- err := r.db.WithContext(ctx).
47
- Where("slug = ?", slug).
48
- First(&e).Error
49
- return e, err
50
- }
51
-
52
- func (r *examRepository) Update(ctx context.Context, e entity.Exam) error {
53
- return r.db.WithContext(ctx).
54
- Model(&entity.Exam{}).
55
- Where("exam_id = ?", e.Id).
56
- Updates(e).Error
57
- }
58
-
59
- func (r *examRepository) Delete(ctx context.Context, id uuid.UUID) error {
60
- return r.db.WithContext(ctx).
61
- Where("exam_id = ?", id).
62
- Delete(&entity.Exam{}).Error
63
- }
64
-
65
- func (r *examRepository) List(ctx context.Context) ([]entity.Exam, error) {
66
- var list []entity.Exam
67
- err := r.db.WithContext(ctx).
68
- Order("created_at DESC").
69
- Find(&list).Error
70
- return list, err
71
- }
72
-
73
- // =========== Business Specific ============
74
-
75
- func (r *examRepository) ListByEvent(ctx context.Context, eventId uuid.UUID) ([]entity.Exam, error) {
76
- var exams []entity.Exam
77
-
78
- err := r.db.WithContext(ctx).
79
- Table("exam").
80
- Joins(`JOIN exam_event_assign ON exam_event_assign.exam_id = exam.id`).
81
- Where("exam_event_assign.event_id = ?", eventId).
82
- Find(&exams).Error
83
-
84
- return exams, err
85
- }
 
1
+ package repositories
2
+
3
+ import (
4
+ "context"
5
+
6
+ entity "abdanhafidz.com/go-boilerplate/models/entity"
7
+ "github.com/google/uuid"
8
+ "gorm.io/gorm"
9
+ )
10
+
11
+ type ExamRepository interface {
12
+ Create(ctx context.Context, e entity.Exam) error
13
+ Get(ctx context.Context, id uuid.UUID) (entity.Exam, error)
14
+ GetBySlug(ctx context.Context, slug string) (entity.Exam, error)
15
+ Update(ctx context.Context, e entity.Exam) error
16
+ Delete(ctx context.Context, id uuid.UUID) error
17
+ List(ctx context.Context) ([]entity.Exam, error)
18
+
19
+ // Additional business need
20
+ ListByEvent(ctx context.Context, eventId uuid.UUID) ([]entity.Exam, error)
21
+ }
22
+
23
+ type examRepository struct {
24
+ db *gorm.DB
25
+ }
26
+
27
+ func NewExamRepository(db *gorm.DB) ExamRepository {
28
+ return &examRepository{db}
29
+ }
30
+
31
+ // ================= CRUD =================
32
+
33
+ func (r *examRepository) Create(ctx context.Context, e entity.Exam) error {
34
+ return r.db.WithContext(ctx).Create(&e).Error
35
+ }
36
+
37
+ func (r *examRepository) Get(ctx context.Context, id uuid.UUID) (entity.Exam, error) {
38
+ var e entity.Exam
39
+ err := r.db.WithContext(ctx).
40
+ First(&e, "exam_id = ?", id).Error
41
+ return e, err
42
+ }
43
+
44
+ func (r *examRepository) GetBySlug(ctx context.Context, slug string) (entity.Exam, error) {
45
+ var e entity.Exam
46
+ err := r.db.WithContext(ctx).
47
+ Where("slug = ?", slug).
48
+ First(&e).Error
49
+ return e, err
50
+ }
51
+
52
+ func (r *examRepository) Update(ctx context.Context, e entity.Exam) error {
53
+ return r.db.WithContext(ctx).
54
+ Model(&entity.Exam{}).
55
+ Where("exam_id = ?", e.Id).
56
+ Updates(e).Error
57
+ }
58
+
59
+ func (r *examRepository) Delete(ctx context.Context, id uuid.UUID) error {
60
+ return r.db.WithContext(ctx).
61
+ Where("exam_id = ?", id).
62
+ Delete(&entity.Exam{}).Error
63
+ }
64
+
65
+ func (r *examRepository) List(ctx context.Context) ([]entity.Exam, error) {
66
+ var list []entity.Exam
67
+ err := r.db.WithContext(ctx).
68
+ Order("created_at DESC").
69
+ Find(&list).Error
70
+ return list, err
71
+ }
72
+
73
+ // =========== Business Specific ============
74
+
75
+ func (r *examRepository) ListByEvent(ctx context.Context, eventId uuid.UUID) ([]entity.Exam, error) {
76
+ var exams []entity.Exam
77
+
78
+ err := r.db.WithContext(ctx).
79
+ Table("exam").
80
+ Joins(`JOIN exam_event_assign ON exam_event_assign.exam_id = exam.id`).
81
+ Where("exam_event_assign.event_id = ?", eventId).
82
+ Find(&exams).Error
83
+
84
+ return exams, err
85
+ }
repositories/file_repository.go ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package repositories
2
+
3
+ import (
4
+ "context"
5
+ "errors"
6
+
7
+ "github.com/google/uuid"
8
+ "gorm.io/gorm"
9
+
10
+ entity "abdanhafidz.com/go-boilerplate/models/entity"
11
+ http_error "abdanhafidz.com/go-boilerplate/models/error"
12
+ )
13
+
14
+ type FileRepository interface {
15
+ Create(ctx context.Context, file *entity.File) error
16
+ FindByID(ctx context.Context, id uuid.UUID) (*entity.File, error)
17
+ }
18
+
19
+ type fileRepository struct {
20
+ db *gorm.DB
21
+ }
22
+
23
+ func NewFileRepository(db *gorm.DB) FileRepository {
24
+ return &fileRepository{db: db}
25
+ }
26
+
27
+ func (r *fileRepository) Create(ctx context.Context, file *entity.File) error {
28
+ return r.db.WithContext(ctx).Create(file).Error
29
+ }
30
+
31
+ func (r *fileRepository) FindByID(ctx context.Context, id uuid.UUID) (*entity.File, error) {
32
+ var file entity.File
33
+ result := r.db.WithContext(ctx).First(&file, "id = ?", id)
34
+
35
+ if result.Error != nil {
36
+ if errors.Is(result.Error, gorm.ErrRecordNotFound) {
37
+ return nil, http_error.NOT_FOUND_ERROR
38
+ }
39
+ return nil, result.Error
40
+ }
41
+
42
+ return &file, nil
43
+ }
repositories/problem_set_exam_assign_repository.go CHANGED
@@ -1,40 +1,40 @@
1
- package repositories
2
-
3
- import (
4
- "context"
5
-
6
- entity "abdanhafidz.com/go-boilerplate/models/entity"
7
- "github.com/google/uuid"
8
- "gorm.io/gorm"
9
- )
10
-
11
- type ProblemSetExamAssignRepository interface {
12
- Create(ctx context.Context, m entity.ProblemSetExamAssign) error
13
- GetByExam(ctx context.Context, examId uuid.UUID) (entity.ProblemSetExamAssign, error)
14
- Delete(ctx context.Context, id uuid.UUID) error
15
- }
16
-
17
- type problemSetExamAssignRepository struct{ db *gorm.DB }
18
-
19
- func NewProblemSetExamAssignRepository(db *gorm.DB) ProblemSetExamAssignRepository {
20
- return &problemSetExamAssignRepository{db}
21
- }
22
-
23
- func (r *problemSetExamAssignRepository) Create(ctx context.Context, m entity.ProblemSetExamAssign) error {
24
- return r.db.WithContext(ctx).Create(&m).Error
25
- }
26
-
27
- func (r *problemSetExamAssignRepository) GetByExam(ctx context.Context, examId uuid.UUID) (entity.ProblemSetExamAssign, error) {
28
- var items entity.ProblemSetExamAssign
29
- err := r.db.WithContext(ctx).
30
- Where("exam_id = ?", examId).
31
- Preload("ProblemSet").
32
- First(&items).Error
33
- return items, err
34
- }
35
-
36
- func (r *problemSetExamAssignRepository) Delete(ctx context.Context, id uuid.UUID) error {
37
- return r.db.WithContext(ctx).
38
- Where("id = ?", id).
39
- Delete(&entity.ProblemSetExamAssign{}).Error
40
- }
 
1
+ package repositories
2
+
3
+ import (
4
+ "context"
5
+
6
+ entity "abdanhafidz.com/go-boilerplate/models/entity"
7
+ "github.com/google/uuid"
8
+ "gorm.io/gorm"
9
+ )
10
+
11
+ type ProblemSetExamAssignRepository interface {
12
+ Create(ctx context.Context, m entity.ProblemSetExamAssign) error
13
+ GetByExam(ctx context.Context, examId uuid.UUID) (entity.ProblemSetExamAssign, error)
14
+ Delete(ctx context.Context, id uuid.UUID) error
15
+ }
16
+
17
+ type problemSetExamAssignRepository struct{ db *gorm.DB }
18
+
19
+ func NewProblemSetExamAssignRepository(db *gorm.DB) ProblemSetExamAssignRepository {
20
+ return &problemSetExamAssignRepository{db}
21
+ }
22
+
23
+ func (r *problemSetExamAssignRepository) Create(ctx context.Context, m entity.ProblemSetExamAssign) error {
24
+ return r.db.WithContext(ctx).Create(&m).Error
25
+ }
26
+
27
+ func (r *problemSetExamAssignRepository) GetByExam(ctx context.Context, examId uuid.UUID) (entity.ProblemSetExamAssign, error) {
28
+ var items entity.ProblemSetExamAssign
29
+ err := r.db.WithContext(ctx).
30
+ Where("exam_id = ?", examId).
31
+ Preload("ProblemSet").
32
+ First(&items).Error
33
+ return items, err
34
+ }
35
+
36
+ func (r *problemSetExamAssignRepository) Delete(ctx context.Context, id uuid.UUID) error {
37
+ return r.db.WithContext(ctx).
38
+ Where("id = ?", id).
39
+ Delete(&entity.ProblemSetExamAssign{}).Error
40
+ }
repositories/problem_set_repository.go CHANGED
@@ -1,57 +1,57 @@
1
- package repositories
2
-
3
- import (
4
- "context"
5
-
6
- entity "abdanhafidz.com/go-boilerplate/models/entity"
7
- "github.com/google/uuid"
8
- "gorm.io/gorm"
9
- )
10
-
11
- type ProblemSetRepository interface {
12
- Create(ctx context.Context, ps entity.ProblemSet) error
13
- Get(ctx context.Context, id uuid.UUID) (entity.ProblemSet, error)
14
- Update(ctx context.Context, ps entity.ProblemSet) error
15
- Delete(ctx context.Context, id uuid.UUID) error
16
- List(ctx context.Context) ([]entity.ProblemSet, error)
17
- }
18
-
19
- type problemSetRepository struct {
20
- db *gorm.DB
21
- }
22
-
23
- func NewProblemSetRepository(db *gorm.DB) ProblemSetRepository {
24
- return &problemSetRepository{db: db}
25
- }
26
-
27
- func (r *problemSetRepository) Create(ctx context.Context, ps entity.ProblemSet) error {
28
- return r.db.WithContext(ctx).Create(&ps).Error
29
- }
30
-
31
- func (r *problemSetRepository) Get(ctx context.Context, id uuid.UUID) (entity.ProblemSet, error) {
32
- var ps entity.ProblemSet
33
- err := r.db.WithContext(ctx).
34
- First(&ps, "id = ?", id).Error
35
- return ps, err
36
- }
37
-
38
- func (r *problemSetRepository) List(ctx context.Context) ([]entity.ProblemSet, error) {
39
- var list []entity.ProblemSet
40
- err := r.db.WithContext(ctx).
41
- Order("title").
42
- Find(&list).Error
43
- return list, err
44
- }
45
-
46
- func (r *problemSetRepository) Update(ctx context.Context, ps entity.ProblemSet) error {
47
- return r.db.WithContext(ctx).
48
- Model(&entity.ProblemSet{}).
49
- Where("id = ?", ps.Id).
50
- Updates(ps).Error
51
- }
52
-
53
- func (r *problemSetRepository) Delete(ctx context.Context, id uuid.UUID) error {
54
- return r.db.WithContext(ctx).
55
- Where("id = ?", id).
56
- Delete(&entity.ProblemSet{}).Error
57
- }
 
1
+ package repositories
2
+
3
+ import (
4
+ "context"
5
+
6
+ entity "abdanhafidz.com/go-boilerplate/models/entity"
7
+ "github.com/google/uuid"
8
+ "gorm.io/gorm"
9
+ )
10
+
11
+ type ProblemSetRepository interface {
12
+ Create(ctx context.Context, ps entity.ProblemSet) error
13
+ Get(ctx context.Context, id uuid.UUID) (entity.ProblemSet, error)
14
+ Update(ctx context.Context, ps entity.ProblemSet) error
15
+ Delete(ctx context.Context, id uuid.UUID) error
16
+ List(ctx context.Context) ([]entity.ProblemSet, error)
17
+ }
18
+
19
+ type problemSetRepository struct {
20
+ db *gorm.DB
21
+ }
22
+
23
+ func NewProblemSetRepository(db *gorm.DB) ProblemSetRepository {
24
+ return &problemSetRepository{db: db}
25
+ }
26
+
27
+ func (r *problemSetRepository) Create(ctx context.Context, ps entity.ProblemSet) error {
28
+ return r.db.WithContext(ctx).Create(&ps).Error
29
+ }
30
+
31
+ func (r *problemSetRepository) Get(ctx context.Context, id uuid.UUID) (entity.ProblemSet, error) {
32
+ var ps entity.ProblemSet
33
+ err := r.db.WithContext(ctx).
34
+ First(&ps, "id = ?", id).Error
35
+ return ps, err
36
+ }
37
+
38
+ func (r *problemSetRepository) List(ctx context.Context) ([]entity.ProblemSet, error) {
39
+ var list []entity.ProblemSet
40
+ err := r.db.WithContext(ctx).
41
+ Order("title").
42
+ Find(&list).Error
43
+ return list, err
44
+ }
45
+
46
+ func (r *problemSetRepository) Update(ctx context.Context, ps entity.ProblemSet) error {
47
+ return r.db.WithContext(ctx).
48
+ Model(&entity.ProblemSet{}).
49
+ Where("id = ?", ps.Id).
50
+ Updates(ps).Error
51
+ }
52
+
53
+ func (r *problemSetRepository) Delete(ctx context.Context, id uuid.UUID) error {
54
+ return r.db.WithContext(ctx).
55
+ Where("id = ?", id).
56
+ Delete(&entity.ProblemSet{}).Error
57
+ }
repositories/question_repository.go CHANGED
@@ -1,55 +1,55 @@
1
- package repositories
2
-
3
- import (
4
- "context"
5
-
6
- entity "abdanhafidz.com/go-boilerplate/models/entity"
7
- "github.com/google/uuid"
8
- "gorm.io/gorm"
9
- )
10
-
11
- type QuestionsRepository interface {
12
- Create(ctx context.Context, q entity.Questions) error
13
- Get(ctx context.Context, id uuid.UUID) (entity.Questions, error)
14
- Update(ctx context.Context, q entity.Questions) error
15
- Delete(ctx context.Context, id uuid.UUID) error
16
- ListByProblemSet(ctx context.Context, problemSetId uuid.UUID) ([]entity.Questions, error)
17
- }
18
-
19
- type questionsRepository struct{ db *gorm.DB }
20
-
21
- func NewQuestionsRepository(db *gorm.DB) QuestionsRepository {
22
- return &questionsRepository{db}
23
- }
24
-
25
- func (r *questionsRepository) Create(ctx context.Context, q entity.Questions) error {
26
- return r.db.WithContext(ctx).Create(&q).Error
27
- }
28
-
29
- func (r *questionsRepository) Get(ctx context.Context, id uuid.UUID) (entity.Questions, error) {
30
- var q entity.Questions
31
- err := r.db.WithContext(ctx).First(&q, "id = ?", id).Error
32
- return q, err
33
- }
34
-
35
- func (r *questionsRepository) Update(ctx context.Context, q entity.Questions) error {
36
- return r.db.WithContext(ctx).
37
- Model(&entity.Questions{}).
38
- Where("id = ?", q.Id).
39
- Updates(q).Error
40
- }
41
-
42
- func (r *questionsRepository) Delete(ctx context.Context, id uuid.UUID) error {
43
- return r.db.WithContext(ctx).
44
- Where("id = ?", id).
45
- Delete(&entity.Questions{}).Error
46
- }
47
-
48
- func (r *questionsRepository) ListByProblemSet(ctx context.Context, problemSetId uuid.UUID) ([]entity.Questions, error) {
49
- var q []entity.Questions
50
- err := r.db.WithContext(ctx).
51
- Where("problem_set_id = ?", problemSetId).
52
- Order("id").
53
- Find(&q).Error
54
- return q, err
55
- }
 
1
+ package repositories
2
+
3
+ import (
4
+ "context"
5
+
6
+ entity "abdanhafidz.com/go-boilerplate/models/entity"
7
+ "github.com/google/uuid"
8
+ "gorm.io/gorm"
9
+ )
10
+
11
+ type QuestionsRepository interface {
12
+ Create(ctx context.Context, q entity.Questions) error
13
+ Get(ctx context.Context, id uuid.UUID) (entity.Questions, error)
14
+ Update(ctx context.Context, q entity.Questions) error
15
+ Delete(ctx context.Context, id uuid.UUID) error
16
+ ListByProblemSet(ctx context.Context, problemSetId uuid.UUID) ([]entity.Questions, error)
17
+ }
18
+
19
+ type questionsRepository struct{ db *gorm.DB }
20
+
21
+ func NewQuestionsRepository(db *gorm.DB) QuestionsRepository {
22
+ return &questionsRepository{db}
23
+ }
24
+
25
+ func (r *questionsRepository) Create(ctx context.Context, q entity.Questions) error {
26
+ return r.db.WithContext(ctx).Create(&q).Error
27
+ }
28
+
29
+ func (r *questionsRepository) Get(ctx context.Context, id uuid.UUID) (entity.Questions, error) {
30
+ var q entity.Questions
31
+ err := r.db.WithContext(ctx).First(&q, "id = ?", id).Error
32
+ return q, err
33
+ }
34
+
35
+ func (r *questionsRepository) Update(ctx context.Context, q entity.Questions) error {
36
+ return r.db.WithContext(ctx).
37
+ Model(&entity.Questions{}).
38
+ Where("id = ?", q.Id).
39
+ Updates(q).Error
40
+ }
41
+
42
+ func (r *questionsRepository) Delete(ctx context.Context, id uuid.UUID) error {
43
+ return r.db.WithContext(ctx).
44
+ Where("id = ?", id).
45
+ Delete(&entity.Questions{}).Error
46
+ }
47
+
48
+ func (r *questionsRepository) ListByProblemSet(ctx context.Context, problemSetId uuid.UUID) ([]entity.Questions, error) {
49
+ var q []entity.Questions
50
+ err := r.db.WithContext(ctx).
51
+ Where("problem_set_id = ?", problemSetId).
52
+ Order("id").
53
+ Find(&q).Error
54
+ return q, err
55
+ }
repositories/result_repository.go CHANGED
@@ -1,60 +1,60 @@
1
- package repositories
2
-
3
- import (
4
- "context"
5
-
6
- entity "abdanhafidz.com/go-boilerplate/models/entity"
7
- "github.com/google/uuid"
8
- "gorm.io/gorm"
9
- )
10
-
11
- type ResultRepository interface {
12
- Create(ctx context.Context, r *entity.Result) error
13
- GetById(ctx context.Context, id uuid.UUID) (entity.Result, error)
14
- Update(ctx context.Context, r *entity.Result) error
15
- ListByEvent(ctx context.Context, eventId uuid.UUID) ([]entity.Result, error)
16
- GetByAttemptId(ctx context.Context, attemptId uuid.UUID) (entity.Result, error)
17
- }
18
-
19
- type resultRepository struct{ db *gorm.DB }
20
-
21
- func NewResultRepository(db *gorm.DB) ResultRepository {
22
- return &resultRepository{db}
23
- }
24
-
25
- func (r *resultRepository) Create(ctx context.Context, rs *entity.Result) error {
26
- return r.db.WithContext(ctx).Create(rs).Error
27
- }
28
- func (r *resultRepository) GetByAttemptId(ctx context.Context, attemptId uuid.UUID) (entity.Result, error) {
29
- var rs entity.Result
30
- err := r.db.WithContext(ctx).
31
- Preload("ExamEventAttempt").
32
- First(&rs, "attempt_id = ?", attemptId).Error
33
- return rs, err
34
- }
35
- func (r *resultRepository) GetById(ctx context.Context, id uuid.UUID) (entity.Result, error) {
36
- var rs entity.Result
37
- err := r.db.WithContext(ctx).
38
- Preload("Account").
39
- Preload("Event").
40
- Preload("ProblemSet").
41
- Preload("ExamEventAttempt").
42
- First(&rs, "id = ?", id).Error
43
- return rs, err
44
- }
45
-
46
- func (r *resultRepository) Update(ctx context.Context, rs *entity.Result) error {
47
- return r.db.WithContext(ctx).
48
- Model(&entity.Result{}).
49
- Where("id = ?", rs.Id).
50
- Updates(rs).Error
51
- }
52
-
53
- func (r *resultRepository) ListByEvent(ctx context.Context, eventId uuid.UUID) ([]entity.Result, error) {
54
- var list []entity.Result
55
- err := r.db.WithContext(ctx).
56
- Where("event_id = ?", eventId).
57
- Preload("Account").
58
- Find(&list).Error
59
- return list, err
60
- }
 
1
+ package repositories
2
+
3
+ import (
4
+ "context"
5
+
6
+ entity "abdanhafidz.com/go-boilerplate/models/entity"
7
+ "github.com/google/uuid"
8
+ "gorm.io/gorm"
9
+ )
10
+
11
+ type ResultRepository interface {
12
+ Create(ctx context.Context, r *entity.Result) error
13
+ GetById(ctx context.Context, id uuid.UUID) (entity.Result, error)
14
+ Update(ctx context.Context, r *entity.Result) error
15
+ ListByEvent(ctx context.Context, eventId uuid.UUID) ([]entity.Result, error)
16
+ GetByAttemptId(ctx context.Context, attemptId uuid.UUID) (entity.Result, error)
17
+ }
18
+
19
+ type resultRepository struct{ db *gorm.DB }
20
+
21
+ func NewResultRepository(db *gorm.DB) ResultRepository {
22
+ return &resultRepository{db}
23
+ }
24
+
25
+ func (r *resultRepository) Create(ctx context.Context, rs *entity.Result) error {
26
+ return r.db.WithContext(ctx).Create(rs).Error
27
+ }
28
+ func (r *resultRepository) GetByAttemptId(ctx context.Context, attemptId uuid.UUID) (entity.Result, error) {
29
+ var rs entity.Result
30
+ err := r.db.WithContext(ctx).
31
+ Preload("ExamEventAttempt").
32
+ First(&rs, "attempt_id = ?", attemptId).Error
33
+ return rs, err
34
+ }
35
+ func (r *resultRepository) GetById(ctx context.Context, id uuid.UUID) (entity.Result, error) {
36
+ var rs entity.Result
37
+ err := r.db.WithContext(ctx).
38
+ Preload("Account").
39
+ Preload("Event").
40
+ Preload("ProblemSet").
41
+ Preload("ExamEventAttempt").
42
+ First(&rs, "id = ?", id).Error
43
+ return rs, err
44
+ }
45
+
46
+ func (r *resultRepository) Update(ctx context.Context, rs *entity.Result) error {
47
+ return r.db.WithContext(ctx).
48
+ Model(&entity.Result{}).
49
+ Where("id = ?", rs.Id).
50
+ Updates(rs).Error
51
+ }
52
+
53
+ func (r *resultRepository) ListByEvent(ctx context.Context, eventId uuid.UUID) ([]entity.Result, error) {
54
+ var list []entity.Result
55
+ err := r.db.WithContext(ctx).
56
+ Where("event_id = ?", eventId).
57
+ Preload("Account").
58
+ Find(&list).Error
59
+ return list, err
60
+ }
router/academy_router.go CHANGED
@@ -1,39 +1,45 @@
1
- package router
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 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
- // ADMIN
19
- adminGroup := routerGroup.Group("/admin", authenticationMiddleware.VerifyAccount)
20
- {
21
- // CRUD for Academies
22
- adminGroup.POST("/", academyController.CreateAcademy)
23
- adminGroup.GET("/id/:id/detail", academyController.GetAcademyDetail)
24
- adminGroup.PUT("/id/:id", academyController.UpdateAcademy)
25
- adminGroup.DELETE("/id/:id", academyController.DeleteAcademy)
26
-
27
- // Creation of Sub-resources
28
- adminGroup.POST("/materials", academyController.CreateMaterial)
29
- adminGroup.POST("/contents", academyController.CreateContent)
30
- }
31
-
32
- // USER
33
- routerGroup.GET("/", authenticationMiddleware.VerifyAccount, academyController.ListAcademies)
34
- routerGroup.GET("/:academy_slug", authenticationMiddleware.VerifyAccount, academyController.GetAcademy)
35
- routerGroup.GET("/:academy_slug/:material_slug", authenticationMiddleware.VerifyAccount, academyController.GetMaterial)
36
- routerGroup.GET("/:academy_slug/:material_slug/:order", authenticationMiddleware.VerifyAccount, academyController.GetContent)
37
- routerGroup.POST("/:academy_slug/:material_slug/:order", authenticationMiddleware.VerifyAccount, academyController.UpdateContentProgress)
38
- }
 
 
 
 
 
 
39
  }
 
1
+ package router
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 AcademyRouter(router *gin.Engine, middleware provider.MiddlewareProvider, controller provider.ControllerProvider) {
10
+ academyController := controller.ProvideAcademyController()
11
+ authenticationMiddleware := middleware.ProvideAuthenticationMiddleware()
12
+ routerGroup := router.Group("/api/v1/academy")
13
+
14
+ routerGroup.Use(gzip.Gzip(gzip.DefaultCompression))
15
+
16
+ {
17
+ // ================= ADMIN SECTION =================
18
+ adminGroup := routerGroup.Group("/admin", authenticationMiddleware.VerifyAccount)
19
+ {
20
+ // CRUD for Academies
21
+ adminGroup.POST("/", academyController.CreateAcademy)
22
+ adminGroup.GET("/id/:id/detail", academyController.GetAcademyDetail)
23
+ adminGroup.PUT("/id/:id", academyController.UpdateAcademy)
24
+ adminGroup.DELETE("/id/:id", academyController.DeleteAcademy)
25
+
26
+ // Materials
27
+ adminGroup.POST("/materials", academyController.CreateMaterial)
28
+ adminGroup.DELETE("/materials/:id", academyController.DeleteMaterial)
29
+
30
+ // Contents
31
+ adminGroup.POST("/contents", academyController.CreateContent)
32
+ adminGroup.DELETE("/contents/:id", academyController.DeleteContent)
33
+ }
34
+
35
+ // ================= USER SECTION =================
36
+ // Public/Student endpoints (Authenticated)
37
+ routerGroup.GET("/", authenticationMiddleware.VerifyAccount, academyController.ListAcademies)
38
+ routerGroup.GET("/:academy_slug", authenticationMiddleware.VerifyAccount, academyController.GetAcademy)
39
+ routerGroup.GET("/:academy_slug/:material_slug", authenticationMiddleware.VerifyAccount, academyController.GetMaterial)
40
+ routerGroup.GET("/:academy_slug/:material_slug/:order", authenticationMiddleware.VerifyAccount, academyController.GetContent)
41
+
42
+ // Update Progress
43
+ routerGroup.POST("/:academy_slug/:material_slug/:order", authenticationMiddleware.VerifyAccount, academyController.UpdateContentProgress)
44
+ }
45
  }
router/account_detail_router.go CHANGED
@@ -1,18 +1,18 @@
1
- package router
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 AccountDetailRouter(router *gin.Engine, middleware provider.MiddlewareProvider, controller provider.ControllerProvider) {
10
- routerGroup := router.Group("/api/v1/account")
11
- accountDetailController := controller.ProvideAccountDetailController()
12
- authenticationMiddleware := middleware.ProvideAuthenticationMiddleware()
13
- routerGroup.Use(gzip.Gzip(gzip.DefaultCompression))
14
- {
15
- routerGroup.GET("/me", authenticationMiddleware.VerifyAccount, accountDetailController.GetDetail)
16
- routerGroup.PUT("/me", authenticationMiddleware.VerifyAccount, accountDetailController.UpdateDetail)
17
- }
18
- }
 
1
+ package router
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 AccountDetailRouter(router *gin.Engine, middleware provider.MiddlewareProvider, controller provider.ControllerProvider) {
10
+ routerGroup := router.Group("/api/v1/account")
11
+ accountDetailController := controller.ProvideAccountDetailController()
12
+ authenticationMiddleware := middleware.ProvideAuthenticationMiddleware()
13
+ routerGroup.Use(gzip.Gzip(gzip.DefaultCompression))
14
+ {
15
+ routerGroup.GET("/me", authenticationMiddleware.VerifyAccount, accountDetailController.GetDetail)
16
+ routerGroup.PUT("/me", authenticationMiddleware.VerifyAccount, accountDetailController.UpdateDetail)
17
+ }
18
+ }
router/email_router.go CHANGED
@@ -1,17 +1,17 @@
1
- package router
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 EmailVerificationRouter(router *gin.Engine, controller provider.ControllerProvider) {
10
- emailVerificationController := controller.ProvideEmailVerificationController()
11
- routerGroup := router.Group("/api/v1/email")
12
- routerGroup.Use(gzip.Gzip(gzip.DefaultCompression))
13
- {
14
- routerGroup.POST("/verify", emailVerificationController.Validate)
15
- routerGroup.POST("/create-verification", emailVerificationController.Create)
16
- }
17
- }
 
1
+ package router
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 EmailVerificationRouter(router *gin.Engine, controller provider.ControllerProvider) {
10
+ emailVerificationController := controller.ProvideEmailVerificationController()
11
+ routerGroup := router.Group("/api/v1/email")
12
+ routerGroup.Use(gzip.Gzip(gzip.DefaultCompression))
13
+ {
14
+ routerGroup.POST("/verify", emailVerificationController.Validate)
15
+ routerGroup.POST("/create-verification", emailVerificationController.Create)
16
+ }
17
+ }
router/event_router.go CHANGED
@@ -1,19 +1,19 @@
1
- package router
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
- routerGroup := router.Group("api/v1/events")
13
- routerGroup.Use(gzip.Gzip(gzip.DefaultCompression))
14
- {
15
- routerGroup.GET("/", authenticationMiddleware.VerifyAccount, eventController.List)
16
- routerGroup.GET("/:event_slug", authenticationMiddleware.VerifyAccount, eventController.DetailBySlug)
17
- routerGroup.POST("/register-event", authenticationMiddleware.VerifyAccount, eventController.Join)
18
- }
19
- }
 
1
+ package router
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
+ routerGroup := router.Group("api/v1/events")
13
+ routerGroup.Use(gzip.Gzip(gzip.DefaultCompression))
14
+ {
15
+ routerGroup.GET("/", authenticationMiddleware.VerifyAccount, eventController.List)
16
+ routerGroup.GET("/:event_slug", authenticationMiddleware.VerifyAccount, eventController.DetailBySlug)
17
+ routerGroup.POST("/register-event", authenticationMiddleware.VerifyAccount, eventController.Join)
18
+ }
19
+ }