lifedebugger commited on
Commit
c606a38
·
1 Parent(s): ff2bb22

Deploy files from GitHub repository

Browse files
Quzuu_API_Collection.postman_collection.json CHANGED
@@ -55,7 +55,7 @@
55
  ],
56
  "body": {
57
  "mode": "raw",
58
- "raw": "{\n \"provider\": \"google\",\n \"token\": \"external_token_here\"\n}"
59
  },
60
  "url": {
61
  "raw": "{{base_url}}/api/v1/authentication/external-login",
@@ -79,7 +79,7 @@
79
  ],
80
  "body": {
81
  "mode": "raw",
82
- "raw": "{\n \"email\": \"user@example.com\",\n \"password\": \"password123\"\n}"
83
  },
84
  "url": {
85
  "raw": "{{base_url}}/api/v1/authentication/login",
@@ -103,7 +103,7 @@
103
  ],
104
  "body": {
105
  "mode": "raw",
106
- "raw": "{\n \"email\": \"newuser@example.com\",\n \"password\": \"password123\",\n \"full_name\": \"New User\"\n}"
107
  },
108
  "url": {
109
  "raw": "{{base_url}}/api/v1/authentication/register",
@@ -215,7 +215,7 @@
215
  ],
216
  "body": {
217
  "mode": "raw",
218
- "raw": "{\n \"full_name\": \"Updated Name\",\n \"phone\": \"08123456789\",\n \"address\": \"Jl. Contoh No. 1\"\n}"
219
  },
220
  "url": {
221
  "raw": "{{base_url}}/api/v1/account/me",
@@ -268,7 +268,7 @@
268
  ],
269
  "body": {
270
  "mode": "raw",
271
- "raw": "{\n \"email\": \"user@example.com\",\n \"code\": \"123456\"\n}"
272
  },
273
  "url": {
274
  "raw": "{{base_url}}/api/v1/email/verify",
@@ -438,7 +438,7 @@
438
  ],
439
  "body": {
440
  "mode": "raw",
441
- "raw": "{\n \"question_id\": \"question_id\",\n \"answer\": \"answer_text_or_option_id\"\n}"
442
  },
443
  "url": {
444
  "raw": "{{base_url}}/api/v1/events/:event_slug/exam/:attempt_id/answer_question",
@@ -521,7 +521,7 @@
521
  ],
522
  "body": {
523
  "mode": "raw",
524
- "raw": "{\n \"event_slug\": \"event_slug\",\n \"exam_slug\": \"exam_slug\",\n \"log_data\": \"proctoring_event_data\"\n}"
525
  },
526
  "url": {
527
  "raw": "{{base_url}}/api/v1/proctoring/:event_slug/exam/:exam_slug/proctoring",
@@ -808,7 +808,7 @@
808
  ],
809
  "body": {
810
  "mode": "raw",
811
- "raw": "{\n \"question_id\": \"question_id\",\n \"answer\": \"answer_text_or_option_id\"\n}"
812
  },
813
  "url": {
814
  "raw": "{{base_url}}/api/v1/academy/:academy_slug/exam/:attempt_id/answer_question",
@@ -1066,34 +1066,54 @@
1066
  "name": "Admin",
1067
  "item": [
1068
  {
1069
- "name": "Events Management",
1070
  "item": [
1071
  {
1072
- "name": "Create Event",
1073
  "request": {
1074
- "method": "POST",
1075
  "header": [
1076
- {
1077
- "key": "Content-Type",
1078
- "value": "application/json",
1079
- "type": "text"
1080
- },
1081
  {
1082
  "key": "Authorization",
1083
  "value": "Bearer {{access_token}}",
1084
  "type": "text"
1085
  }
1086
  ],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1087
  "body": {
1088
  "mode": "raw",
1089
- "raw": "{\n \"name\": \"Event Name\",\n \"slug\": \"event-slug\",\n \"description\": \"Event description\",\n \"start_date\": \"2024-03-15\",\n \"end_date\": \"2024-03-20\"\n}"
1090
  },
1091
  "url": {
1092
  "raw": "{{base_url}}/api/v1/admin/events/",
1093
  "host": ["{{base_url}}"],
1094
  "path": ["api", "v1", "admin", "events", ""]
1095
  },
1096
- "description": "Create new event (admin)"
1097
  },
1098
  "response": []
1099
  },
@@ -1102,59 +1122,200 @@
1102
  "request": {
1103
  "method": "PUT",
1104
  "header": [
1105
- {
1106
- "key": "Content-Type",
1107
- "value": "application/json",
1108
- "type": "text"
1109
- },
1110
- {
1111
- "key": "Authorization",
1112
- "value": "Bearer {{access_token}}",
1113
- "type": "text"
1114
- }
1115
  ],
1116
  "body": {
1117
  "mode": "raw",
1118
- "raw": "{\n \"name\": \"Updated Event Name\",\n \"description\": \"Updated description\"\n}"
1119
  },
1120
  "url": {
1121
- "raw": "{{base_url}}/api/v1/admin/events/:id",
1122
  "host": ["{{base_url}}"],
1123
- "path": ["api", "v1", "admin", "events", ":id"],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1124
  "variable": [
1125
- {
1126
- "key": "id",
1127
- "value": ""
1128
- }
1129
  ]
1130
  },
1131
- "description": "Update event (admin)"
1132
  },
1133
  "response": []
1134
  },
1135
  {
1136
- "name": "Delete Event",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1137
  "request": {
1138
  "method": "DELETE",
1139
  "header": [
1140
- {
1141
- "key": "Authorization",
1142
- "value": "Bearer {{access_token}}",
1143
- "type": "text"
1144
- }
1145
  ],
1146
  "url": {
1147
- "raw": "{{base_url}}/api/v1/admin/events/:id",
1148
  "host": ["{{base_url}}"],
1149
- "path": ["api", "v1", "admin", "events", ":id"],
1150
  "variable": [
1151
- {
1152
- "key": "id",
1153
- "value": ""
1154
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1155
  ]
1156
  },
1157
- "description": "Delete event (admin)"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1158
  },
1159
  "response": []
1160
  }
@@ -1181,7 +1342,7 @@
1181
  ],
1182
  "body": {
1183
  "mode": "raw",
1184
- "raw": "{\n \"name\": \"Academy Name\",\n \"slug\": \"academy-slug\",\n \"description\": \"Academy description\"\n}"
1185
  },
1186
  "url": {
1187
  "raw": "{{base_url}}/api/v1/admin/academy/",
@@ -1236,7 +1397,7 @@
1236
  ],
1237
  "body": {
1238
  "mode": "raw",
1239
- "raw": "{\n \"name\": \"Updated Academy Name\",\n \"description\": \"Updated description\"\n}"
1240
  },
1241
  "url": {
1242
  "raw": "{{base_url}}/api/v1/admin/academy/id/:id",
@@ -1297,7 +1458,7 @@
1297
  ],
1298
  "body": {
1299
  "mode": "raw",
1300
- "raw": "{\n \"academy_id\": \"academy_id\",\n \"name\": \"Material Name\",\n \"slug\": \"material-slug\",\n \"description\": \"Material description\"\n}"
1301
  },
1302
  "url": {
1303
  "raw": "{{base_url}}/api/v1/admin/academy/materials",
@@ -1352,7 +1513,7 @@
1352
  ],
1353
  "body": {
1354
  "mode": "raw",
1355
- "raw": "{\n \"material_id\": \"material_id\",\n \"title\": \"Content Title\",\n \"content\": \"Content data\",\n \"order\": 1\n}"
1356
  },
1357
  "url": {
1358
  "raw": "{{base_url}}/api/v1/admin/academy/contents",
@@ -1617,225 +1778,233 @@
1617
  "response": []
1618
  }
1619
  ]
1620
- }
1621
- ]
1622
- },
1623
- {
1624
- "name": "Exam Management",
1625
- "item": [
1626
  {
1627
- "name": "Create Exam",
1628
- "request": {
1629
- "method": "POST",
1630
- "header": [
1631
- {
1632
- "key": "Content-Type",
1633
- "value": "application/json",
1634
- "type": "text"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1635
  },
1636
- {
1637
- "key": "Authorization",
1638
- "value": "Bearer {{access_token}}",
1639
- "type": "text"
1640
- }
1641
- ],
1642
- "body": {
1643
- "mode": "raw",
1644
- "raw": "{\n \"title\": \"Exam Title\",\n \"slug\": \"exam-slug\",\n \"description\": \"Exam description\",\n \"duration_minutes\": 120\n}"
1645
- },
1646
- "url": {
1647
- "raw": "{{base_url}}/api/v1/admin/exam",
1648
- "host": ["{{base_url}}"],
1649
- "path": ["api", "v1", "admin", "exam"]
1650
  },
1651
- "description": "Create new exam (admin)"
1652
- },
1653
- "response": []
1654
- },
1655
- {
1656
- "name": "List Exams",
1657
- "request": {
1658
- "method": "GET",
1659
- "header": [
1660
- {
1661
- "key": "Authorization",
1662
- "value": "Bearer {{access_token}}",
1663
- "type": "text"
1664
- }
1665
- ],
1666
- "url": {
1667
- "raw": "{{base_url}}/api/v1/admin/exam",
1668
- "host": ["{{base_url}}"],
1669
- "path": ["api", "v1", "admin", "exam"]
 
1670
  },
1671
- "description": "List all exams (admin)"
1672
- },
1673
- "response": []
1674
- },
1675
- {
1676
- "name": "Update Exam",
1677
- "request": {
1678
- "method": "PUT",
1679
- "header": [
1680
- {
1681
- "key": "Content-Type",
1682
- "value": "application/json",
1683
- "type": "text"
 
1684
  },
1685
- {
1686
- "key": "Authorization",
1687
- "value": "Bearer {{access_token}}",
1688
- "type": "text"
1689
- }
1690
- ],
1691
- "body": {
1692
- "mode": "raw",
1693
- "raw": "{\n \"title\": \"Updated Exam Title\",\n \"description\": \"Updated description\"\n}"
1694
  },
1695
- "url": {
1696
- "raw": "{{base_url}}/api/v1/admin/exam/:id",
1697
- "host": ["{{base_url}}"],
1698
- "path": ["api", "v1", "admin", "exam", ":id"],
1699
- "variable": [
1700
- {
1701
- "key": "id",
1702
- "value": ""
1703
- }
1704
- ]
 
 
 
 
 
 
 
 
 
 
 
1705
  },
1706
- "description": "Update exam (admin)"
1707
- },
1708
- "response": []
1709
- },
1710
- {
1711
- "name": "Delete Exam",
1712
- "request": {
1713
- "method": "DELETE",
1714
- "header": [
1715
- {
1716
- "key": "Authorization",
1717
- "value": "Bearer {{access_token}}",
1718
- "type": "text"
1719
- }
1720
- ],
1721
- "url": {
1722
- "raw": "{{base_url}}/api/v1/admin/exam/:id",
1723
- "host": ["{{base_url}}"],
1724
- "path": ["api", "v1", "admin", "exam", ":id"],
1725
- "variable": [
1726
- {
1727
- "key": "id",
1728
- "value": ""
1729
- }
1730
- ]
1731
  },
1732
- "description": "Delete exam (admin)"
1733
- },
1734
- "response": []
1735
- },
1736
- {
1737
- "name": "Get Exam Detail",
1738
- "request": {
1739
- "method": "GET",
1740
- "header": [
1741
- {
1742
- "key": "Authorization",
1743
- "value": "Bearer {{access_token}}",
1744
- "type": "text"
1745
- }
1746
- ],
1747
- "url": {
1748
- "raw": "{{base_url}}/api/v1/admin/exam/:id",
1749
- "host": ["{{base_url}}"],
1750
- "path": ["api", "v1", "admin", "exam", ":id"],
1751
- "variable": [
1752
- {
1753
- "key": "id",
1754
- "value": ""
1755
- }
1756
- ]
1757
  },
1758
- "description": "Get exam detail (admin)"
1759
- },
1760
- "response": []
1761
- },
1762
- {
1763
- "name": "Assign Exam to Event",
1764
- "request": {
1765
- "method": "POST",
1766
- "header": [
1767
- {
1768
- "key": "Content-Type",
1769
- "value": "application/json",
1770
- "type": "text"
 
 
 
 
 
 
 
 
1771
  },
1772
- {
1773
- "key": "Authorization",
1774
- "value": "Bearer {{access_token}}",
1775
- "type": "text"
1776
- }
1777
- ],
1778
- "body": {
1779
- "mode": "raw",
1780
- "raw": "{}"
1781
  },
1782
- "url": {
1783
- "raw": "{{base_url}}/api/v1/admin/exam/:exam_id/event/:event_id",
1784
- "host": ["{{base_url}}"],
1785
- "path": ["api", "v1", "admin", "exam", ":exam_id", "event", ":event_id"],
1786
- "variable": [
1787
- {
1788
- "key": "exam_id",
1789
- "value": ""
 
 
 
 
 
 
 
1790
  },
1791
- {
1792
- "key": "event_id",
1793
- "value": ""
1794
- }
1795
- ]
1796
  },
1797
- "description": "Assign exam to event (admin)"
1798
- },
1799
- "response": []
1800
- },
1801
- {
1802
- "name": "Assign Exam to Academy",
1803
- "request": {
1804
- "method": "POST",
1805
- "header": [
1806
- {
1807
- "key": "Content-Type",
1808
- "value": "application/json",
1809
- "type": "text"
 
1810
  },
1811
- {
1812
- "key": "Authorization",
1813
- "value": "Bearer {{access_token}}",
1814
- "type": "text"
1815
- }
1816
- ],
1817
- "body": {
1818
- "mode": "raw",
1819
- "raw": "{}"
1820
  },
1821
- "url": {
1822
- "raw": "{{base_url}}/api/v1/admin/exam/:exam_id/academy/:academy_id",
1823
- "host": ["{{base_url}}"],
1824
- "path": ["api", "v1", "admin", "exam", ":exam_id", "academy", ":academy_id"],
1825
- "variable": [
1826
- {
1827
- "key": "exam_id",
1828
- "value": ""
 
 
1829
  },
1830
- {
1831
- "key": "academy_id",
1832
- "value": ""
1833
- }
1834
- ]
 
 
 
 
 
 
 
1835
  },
1836
- "description": "Assign exam to academy (admin)"
1837
- },
1838
- "response": []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1839
  }
1840
  ]
1841
  },
@@ -1883,7 +2052,7 @@
1883
  ],
1884
  "body": {
1885
  "mode": "raw",
1886
- "raw": "{\n \"email\": \"newuser@example.com\",\n \"password\": \"password123\",\n \"full_name\": \"New User\",\n \"role\": \"user\"\n}"
1887
  },
1888
  "url": {
1889
  "raw": "{{base_url}}/api/v1/super-admin/users/",
@@ -1912,7 +2081,7 @@
1912
  ],
1913
  "body": {
1914
  "mode": "raw",
1915
- "raw": "{\n \"users\": [\n {\n \"email\": \"user1@example.com\",\n \"password\": \"password123\",\n \"full_name\": \"User One\",\n \"role\": \"user\"\n },\n {\n \"email\": \"user2@example.com\",\n \"password\": \"password123\",\n \"full_name\": \"User Two\",\n \"role\": \"user\"\n }\n ]\n}"
1916
  },
1917
  "url": {
1918
  "raw": "{{base_url}}/api/v1/super-admin/users/bulk",
@@ -1941,7 +2110,7 @@
1941
  ],
1942
  "body": {
1943
  "mode": "raw",
1944
- "raw": "{\n \"full_name\": \"Updated Name\",\n \"email\": \"newemail@example.com\"\n}"
1945
  },
1946
  "url": {
1947
  "raw": "{{base_url}}/api/v1/super-admin/users/:id",
@@ -2073,6 +2242,36 @@
2073
  "key": "province_id",
2074
  "value": "",
2075
  "type": "string"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2076
  }
2077
  ]
2078
  }
 
55
  ],
56
  "body": {
57
  "mode": "raw",
58
+ "raw": "{\n \"oauth_id\": \"google_user_id_here\",\n \"oauth_provider\": \"google\"\n}"
59
  },
60
  "url": {
61
  "raw": "{{base_url}}/api/v1/authentication/external-login",
 
79
  ],
80
  "body": {
81
  "mode": "raw",
82
+ "raw": "{\n \"email_or_username\": \"user@example.com\",\n \"password\": \"password123\"\n}"
83
  },
84
  "url": {
85
  "raw": "{{base_url}}/api/v1/authentication/login",
 
103
  ],
104
  "body": {
105
  "mode": "raw",
106
+ "raw": "{\n \"name\": \"New User\",\n \"email\": \"newuser@example.com\",\n \"username\": \"newuser\",\n \"password\": \"password123\"\n}"
107
  },
108
  "url": {
109
  "raw": "{{base_url}}/api/v1/authentication/register",
 
215
  ],
216
  "body": {
217
  "mode": "raw",
218
+ "raw": "{\n \"full_name\": \"Updated Name\",\n \"school_name\": \"School Name\",\n \"province\": \"Province Name\",\n \"city\": \"City Name\",\n \"avatar\": \"https://example.com/avatar.jpg\",\n \"phone_number\": \"08123456789\"\n}"
219
  },
220
  "url": {
221
  "raw": "{{base_url}}/api/v1/account/me",
 
268
  ],
269
  "body": {
270
  "mode": "raw",
271
+ "raw": "{\n \"email\": \"user@example.com\",\n \"token\": 123456\n}"
272
  },
273
  "url": {
274
  "raw": "{{base_url}}/api/v1/email/verify",
 
438
  ],
439
  "body": {
440
  "mode": "raw",
441
+ "raw": "{\n \"question_id\": \"00000000-0000-0000-0000-000000000000\",\n \"answer\": [\"option_id_or_text\"]\n}"
442
  },
443
  "url": {
444
  "raw": "{{base_url}}/api/v1/events/:event_slug/exam/:attempt_id/answer_question",
 
521
  ],
522
  "body": {
523
  "mode": "raw",
524
+ "raw": "{\n \"violation_score\": 1,\n \"violation_category\": \"tab_switch\",\n \"attachement\": \"https://example.com/screenshot.jpg\"\n}"
525
  },
526
  "url": {
527
  "raw": "{{base_url}}/api/v1/proctoring/:event_slug/exam/:exam_slug/proctoring",
 
808
  ],
809
  "body": {
810
  "mode": "raw",
811
+ "raw": "{\n \"question_id\": \"00000000-0000-0000-0000-000000000000\",\n \"answer\": [\"option_id_or_text\"]\n}"
812
  },
813
  "url": {
814
  "raw": "{{base_url}}/api/v1/academy/:academy_slug/exam/:attempt_id/answer_question",
 
1066
  "name": "Admin",
1067
  "item": [
1068
  {
1069
+ "name": "Admin Event Management",
1070
  "item": [
1071
  {
1072
+ "name": "List Events (Admin View)",
1073
  "request": {
1074
+ "method": "GET",
1075
  "header": [
 
 
 
 
 
1076
  {
1077
  "key": "Authorization",
1078
  "value": "Bearer {{access_token}}",
1079
  "type": "text"
1080
  }
1081
  ],
1082
+ "url": {
1083
+ "raw": "{{base_url}}/api/v1/admin/events/?page=1&limit=10&search=&sortBy=created_at&order=desc",
1084
+ "host": ["{{base_url}}"],
1085
+ "path": ["api", "v1", "admin", "events", ""],
1086
+ "query": [
1087
+ { "key": "page", "value": "1" },
1088
+ { "key": "limit", "value": "10" },
1089
+ { "key": "search", "value": "" },
1090
+ { "key": "sortBy", "value": "created_at" },
1091
+ { "key": "order", "value": "desc" },
1092
+ { "key": "status", "value": "", "disabled": true }
1093
+ ]
1094
+ },
1095
+ "description": "Admin view: list all events with participant_count and exam_count. Supports pagination and search."
1096
+ },
1097
+ "response": []
1098
+ },
1099
+ {
1100
+ "name": "Create Event",
1101
+ "request": {
1102
+ "method": "POST",
1103
+ "header": [
1104
+ { "key": "Content-Type", "value": "application/json", "type": "text" },
1105
+ { "key": "Authorization", "value": "Bearer {{access_token}}", "type": "text" }
1106
+ ],
1107
  "body": {
1108
  "mode": "raw",
1109
+ "raw": "{\n \"title\": \"National Olympiad 2026\",\n \"start_event\": \"2026-04-01T08:00:00Z\",\n \"end_event\": \"2026-04-02T17:00:00Z\",\n \"overview\": \"Annual national programming olympiad\",\n \"img_banner\": \"https://example.com/banner.jpg\",\n \"event_code\": \"NAT2026\",\n \"is_public\": true\n}"
1110
  },
1111
  "url": {
1112
  "raw": "{{base_url}}/api/v1/admin/events/",
1113
  "host": ["{{base_url}}"],
1114
  "path": ["api", "v1", "admin", "events", ""]
1115
  },
1116
+ "description": "Create a new event."
1117
  },
1118
  "response": []
1119
  },
 
1122
  "request": {
1123
  "method": "PUT",
1124
  "header": [
1125
+ { "key": "Content-Type", "value": "application/json", "type": "text" },
1126
+ { "key": "Authorization", "value": "Bearer {{access_token}}", "type": "text" }
 
 
 
 
 
 
 
 
1127
  ],
1128
  "body": {
1129
  "mode": "raw",
1130
+ "raw": "{\n \"title\": \"Updated Event Title\",\n \"overview\": \"Updated overview text\",\n \"is_public\": true\n}"
1131
  },
1132
  "url": {
1133
+ "raw": "{{base_url}}/api/v1/admin/events/:event_id",
1134
  "host": ["{{base_url}}"],
1135
+ "path": ["api", "v1", "admin", "events", ":event_id"],
1136
+ "variable": [{ "key": "event_id", "value": "{{event_id}}" }]
1137
+ },
1138
+ "description": "Update an existing event by ID."
1139
+ },
1140
+ "response": []
1141
+ },
1142
+ {
1143
+ "name": "Delete Event",
1144
+ "request": {
1145
+ "method": "DELETE",
1146
+ "header": [
1147
+ { "key": "Authorization", "value": "Bearer {{access_token}}", "type": "text" }
1148
+ ],
1149
+ "url": {
1150
+ "raw": "{{base_url}}/api/v1/admin/events/:event_id",
1151
+ "host": ["{{base_url}}"],
1152
+ "path": ["api", "v1", "admin", "events", ":event_id"],
1153
+ "variable": [{ "key": "event_id", "value": "{{event_id}}" }]
1154
+ },
1155
+ "description": "Delete an event by ID."
1156
+ },
1157
+ "response": []
1158
+ },
1159
+ {
1160
+ "name": "Participants - List",
1161
+ "request": {
1162
+ "method": "GET",
1163
+ "header": [
1164
+ { "key": "Authorization", "value": "Bearer {{access_token}}", "type": "text" }
1165
+ ],
1166
+ "url": {
1167
+ "raw": "{{base_url}}/api/v1/admin/events/:event_id/participants",
1168
+ "host": ["{{base_url}}"],
1169
+ "path": ["api", "v1", "admin", "events", ":event_id", "participants"],
1170
+ "variable": [{ "key": "event_id", "value": "{{event_id}}" }]
1171
+ },
1172
+ "description": "List all participants (with account details) assigned to an event."
1173
+ },
1174
+ "response": []
1175
+ },
1176
+ {
1177
+ "name": "Participants - Add (Manual Assign)",
1178
+ "request": {
1179
+ "method": "POST",
1180
+ "header": [
1181
+ { "key": "Content-Type", "value": "application/json", "type": "text" },
1182
+ { "key": "Authorization", "value": "Bearer {{access_token}}", "type": "text" }
1183
+ ],
1184
+ "body": {
1185
+ "mode": "raw",
1186
+ "raw": "{\n \"user_id\": \"{{user_id}}\"\n}"
1187
+ },
1188
+ "url": {
1189
+ "raw": "{{base_url}}/api/v1/admin/events/:event_id/participants",
1190
+ "host": ["{{base_url}}"],
1191
+ "path": ["api", "v1", "admin", "events", ":event_id", "participants"],
1192
+ "variable": [{ "key": "event_id", "value": "{{event_id}}" }]
1193
+ },
1194
+ "description": "Manually assign a user to an event. Bypasses payment."
1195
+ },
1196
+ "response": []
1197
+ },
1198
+ {
1199
+ "name": "Participants - Remove",
1200
+ "request": {
1201
+ "method": "DELETE",
1202
+ "header": [
1203
+ { "key": "Authorization", "value": "Bearer {{access_token}}", "type": "text" }
1204
+ ],
1205
+ "url": {
1206
+ "raw": "{{base_url}}/api/v1/admin/events/:event_id/participants/:user_id",
1207
+ "host": ["{{base_url}}"],
1208
+ "path": ["api", "v1", "admin", "events", ":event_id", "participants", ":user_id"],
1209
  "variable": [
1210
+ { "key": "event_id", "value": "{{event_id}}" },
1211
+ { "key": "user_id", "value": "{{user_id}}" }
 
 
1212
  ]
1213
  },
1214
+ "description": "Unassign a user from an event."
1215
  },
1216
  "response": []
1217
  },
1218
  {
1219
+ "name": "Exams - List Event Exams",
1220
+ "request": {
1221
+ "method": "GET",
1222
+ "header": [
1223
+ { "key": "Authorization", "value": "Bearer {{access_token}}", "type": "text" }
1224
+ ],
1225
+ "url": {
1226
+ "raw": "{{base_url}}/api/v1/admin/events/:event_id/exams",
1227
+ "host": ["{{base_url}}"],
1228
+ "path": ["api", "v1", "admin", "events", ":event_id", "exams"],
1229
+ "variable": [{ "key": "event_id", "value": "{{event_id}}" }]
1230
+ },
1231
+ "description": "List all exams assigned to an event, with exam details."
1232
+ },
1233
+ "response": []
1234
+ },
1235
+ {
1236
+ "name": "Exams - Remove Exam from Event",
1237
  "request": {
1238
  "method": "DELETE",
1239
  "header": [
1240
+ { "key": "Authorization", "value": "Bearer {{access_token}}", "type": "text" }
 
 
 
 
1241
  ],
1242
  "url": {
1243
+ "raw": "{{base_url}}/api/v1/admin/events/:event_id/exams/:exam_id",
1244
  "host": ["{{base_url}}"],
1245
+ "path": ["api", "v1", "admin", "events", ":event_id", "exams", ":exam_id"],
1246
  "variable": [
1247
+ { "key": "event_id", "value": "{{event_id}}" },
1248
+ { "key": "exam_id", "value": "{{exam_id}}" }
1249
+ ]
1250
+ },
1251
+ "description": "Unassign an exam from an event."
1252
+ },
1253
+ "response": []
1254
+ },
1255
+ {
1256
+ "name": "Results - List Exam Results",
1257
+ "request": {
1258
+ "method": "GET",
1259
+ "header": [
1260
+ { "key": "Authorization", "value": "Bearer {{access_token}}", "type": "text" }
1261
+ ],
1262
+ "url": {
1263
+ "raw": "{{base_url}}/api/v1/admin/events/:event_id/exam/:exam_id/results",
1264
+ "host": ["{{base_url}}"],
1265
+ "path": ["api", "v1", "admin", "events", ":event_id", "exam", ":exam_id", "results"],
1266
+ "variable": [
1267
+ { "key": "event_id", "value": "{{event_id}}" },
1268
+ { "key": "exam_id", "value": "{{exam_id}}" }
1269
  ]
1270
  },
1271
+ "description": "List all participant results for a specific exam within an event."
1272
+ },
1273
+ "response": []
1274
+ },
1275
+ {
1276
+ "name": "Results - Edit Result",
1277
+ "request": {
1278
+ "method": "PUT",
1279
+ "header": [
1280
+ { "key": "Content-Type", "value": "application/json", "type": "text" },
1281
+ { "key": "Authorization", "value": "Bearer {{access_token}}", "type": "text" }
1282
+ ],
1283
+ "body": {
1284
+ "mode": "raw",
1285
+ "raw": "{\n \"final_score\": 95.5\n}"
1286
+ },
1287
+ "url": {
1288
+ "raw": "{{base_url}}/api/v1/admin/events/:event_id/exam/:exam_id/results/:result_id",
1289
+ "host": ["{{base_url}}"],
1290
+ "path": ["api", "v1", "admin", "events", ":event_id", "exam", ":exam_id", "results", ":result_id"],
1291
+ "variable": [
1292
+ { "key": "event_id", "value": "{{event_id}}" },
1293
+ { "key": "exam_id", "value": "{{exam_id}}" },
1294
+ { "key": "result_id", "value": "{{result_id}}" }
1295
+ ]
1296
+ },
1297
+ "description": "Edit the final score of a participant's exam result."
1298
+ },
1299
+ "response": []
1300
+ },
1301
+ {
1302
+ "name": "Results - Delete Result",
1303
+ "request": {
1304
+ "method": "DELETE",
1305
+ "header": [
1306
+ { "key": "Authorization", "value": "Bearer {{access_token}}", "type": "text" }
1307
+ ],
1308
+ "url": {
1309
+ "raw": "{{base_url}}/api/v1/admin/events/:event_id/exam/:exam_id/results/:result_id",
1310
+ "host": ["{{base_url}}"],
1311
+ "path": ["api", "v1", "admin", "events", ":event_id", "exam", ":exam_id", "results", ":result_id"],
1312
+ "variable": [
1313
+ { "key": "event_id", "value": "{{event_id}}" },
1314
+ { "key": "exam_id", "value": "{{exam_id}}" },
1315
+ { "key": "result_id", "value": "{{result_id}}" }
1316
+ ]
1317
+ },
1318
+ "description": "Delete a participant's exam result."
1319
  },
1320
  "response": []
1321
  }
 
1342
  ],
1343
  "body": {
1344
  "mode": "raw",
1345
+ "raw": "{\n \"title\": \"Academy Title\",\n \"slug\": \"academy-slug\",\n \"code\": \"ACAD01\",\n \"is_public\": true,\n \"description\": \"Academy description\",\n \"image_url\": \"https://example.com/image.jpg\"\n}"
1346
  },
1347
  "url": {
1348
  "raw": "{{base_url}}/api/v1/admin/academy/",
 
1397
  ],
1398
  "body": {
1399
  "mode": "raw",
1400
+ "raw": "{\n \"title\": \"Updated Academy Title\",\n \"slug\": \"updated-academy-slug\",\n \"description\": \"Updated description\",\n \"image_url\": \"https://example.com/image.jpg\",\n \"is_public\": true\n}"
1401
  },
1402
  "url": {
1403
  "raw": "{{base_url}}/api/v1/admin/academy/id/:id",
 
1458
  ],
1459
  "body": {
1460
  "mode": "raw",
1461
+ "raw": "{\n \"academy_id\": \"{{academy_id}}\",\n \"title\": \"Material Title\",\n \"slug\": \"material-slug\",\n \"description\": \"Material description\"\n}"
1462
  },
1463
  "url": {
1464
  "raw": "{{base_url}}/api/v1/admin/academy/materials",
 
1513
  ],
1514
  "body": {
1515
  "mode": "raw",
1516
+ "raw": "{\n \"material_id\": \"{{material_id}}\",\n \"title\": \"Content Title\",\n \"contents\": \"Content body text here\"\n}"
1517
  },
1518
  "url": {
1519
  "raw": "{{base_url}}/api/v1/admin/academy/contents",
 
1778
  "response": []
1779
  }
1780
  ]
1781
+ },
 
 
 
 
 
1782
  {
1783
+ "name": "Admin Exam Management",
1784
+ "item": [
1785
+ {
1786
+ "name": "List Exams",
1787
+ "request": {
1788
+ "method": "GET",
1789
+ "header": [
1790
+ { "key": "Authorization", "value": "Bearer {{access_token}}", "type": "text" }
1791
+ ],
1792
+ "url": {
1793
+ "raw": "{{base_url}}/api/v1/admin/exam?page=1&limit=10&search=&sortBy=created_at&order=desc",
1794
+ "host": ["{{base_url}}"],
1795
+ "path": ["api", "v1", "admin", "exam"],
1796
+ "query": [
1797
+ { "key": "page", "value": "1" },
1798
+ { "key": "limit", "value": "10" },
1799
+ { "key": "search", "value": "" },
1800
+ { "key": "sortBy", "value": "created_at" },
1801
+ { "key": "order", "value": "desc" }
1802
+ ]
1803
+ },
1804
+ "description": "Paginated list of all exams with event_count and academy_count. Sort by: title, slug, created_at, duration, event_count, academy_count."
1805
  },
1806
+ "response": []
 
 
 
 
 
 
 
 
 
 
 
 
 
1807
  },
1808
+ {
1809
+ "name": "Create Exam",
1810
+ "request": {
1811
+ "method": "POST",
1812
+ "header": [
1813
+ { "key": "Content-Type", "value": "application/json", "type": "text" },
1814
+ { "key": "Authorization", "value": "Bearer {{access_token}}", "type": "text" }
1815
+ ],
1816
+ "body": {
1817
+ "mode": "raw",
1818
+ "raw": "{\n \"slug\": \"exam-slug\",\n \"title\": \"Exam Title\",\n \"description\": \"Exam description\",\n \"duration\": 7200000000000,\n \"randomize\": 0,\n \"allow_retake\": false,\n \"allow_review\": true,\n \"enable_timer\": true,\n \"enable_webcam\": false,\n \"enable_vad\": false,\n \"enable_tab_block\": false,\n \"enable_full_screen\": false,\n \"enable_eye_tracking\": false,\n \"disable_copy_paste\": false,\n \"enable_exam_browser\": false\n}"
1819
+ },
1820
+ "url": {
1821
+ "raw": "{{base_url}}/api/v1/admin/exam",
1822
+ "host": ["{{base_url}}"],
1823
+ "path": ["api", "v1", "admin", "exam"]
1824
+ },
1825
+ "description": "Create a new exam with configuration and proctoring settings."
1826
+ },
1827
+ "response": []
1828
  },
1829
+ {
1830
+ "name": "Get Exam Detail",
1831
+ "request": {
1832
+ "method": "GET",
1833
+ "header": [
1834
+ { "key": "Authorization", "value": "Bearer {{access_token}}", "type": "text" }
1835
+ ],
1836
+ "url": {
1837
+ "raw": "{{base_url}}/api/v1/admin/exam/:id",
1838
+ "host": ["{{base_url}}"],
1839
+ "path": ["api", "v1", "admin", "exam", ":id"],
1840
+ "variable": [{ "key": "id", "value": "{{exam_id}}" }]
1841
+ },
1842
+ "description": "Retrieve full exam details including configuration and proctoring settings."
1843
  },
1844
+ "response": []
 
 
 
 
 
 
 
 
1845
  },
1846
+ {
1847
+ "name": "Update Exam",
1848
+ "request": {
1849
+ "method": "PUT",
1850
+ "header": [
1851
+ { "key": "Content-Type", "value": "application/json", "type": "text" },
1852
+ { "key": "Authorization", "value": "Bearer {{access_token}}", "type": "text" }
1853
+ ],
1854
+ "body": {
1855
+ "mode": "raw",
1856
+ "raw": "{\n \"slug\": \"updated-exam-slug\",\n \"title\": \"Updated Exam Title\",\n \"description\": \"Updated description\",\n \"duration\": 7200000000000,\n \"randomize\": 0,\n \"allow_retake\": false,\n \"allow_review\": true,\n \"enable_timer\": true,\n \"enable_webcam\": false,\n \"enable_vad\": false,\n \"enable_tab_block\": false,\n \"enable_full_screen\": false,\n \"enable_eye_tracking\": false,\n \"disable_copy_paste\": false,\n \"enable_exam_browser\": false\n}"
1857
+ },
1858
+ "url": {
1859
+ "raw": "{{base_url}}/api/v1/admin/exam/:id",
1860
+ "host": ["{{base_url}}"],
1861
+ "path": ["api", "v1", "admin", "exam", ":id"],
1862
+ "variable": [{ "key": "id", "value": "{{exam_id}}" }]
1863
+ },
1864
+ "description": "Update an existing exam including configuration and proctoring settings."
1865
+ },
1866
+ "response": []
1867
  },
1868
+ {
1869
+ "name": "Delete Exam",
1870
+ "request": {
1871
+ "method": "DELETE",
1872
+ "header": [
1873
+ { "key": "Authorization", "value": "Bearer {{access_token}}", "type": "text" }
1874
+ ],
1875
+ "url": {
1876
+ "raw": "{{base_url}}/api/v1/admin/exam/:id",
1877
+ "host": ["{{base_url}}"],
1878
+ "path": ["api", "v1", "admin", "exam", ":id"],
1879
+ "variable": [{ "key": "id", "value": "{{exam_id}}" }]
1880
+ },
1881
+ "description": "Soft delete an exam by ID."
1882
+ },
1883
+ "response": []
 
 
 
 
 
 
 
 
 
1884
  },
1885
+ {
1886
+ "name": "Events - List Events by Exam",
1887
+ "request": {
1888
+ "method": "GET",
1889
+ "header": [
1890
+ { "key": "Authorization", "value": "Bearer {{access_token}}", "type": "text" }
1891
+ ],
1892
+ "url": {
1893
+ "raw": "{{base_url}}/api/v1/admin/exam/:id/events",
1894
+ "host": ["{{base_url}}"],
1895
+ "path": ["api", "v1", "admin", "exam", ":id", "events"],
1896
+ "variable": [{ "key": "id", "value": "{{exam_id}}" }]
1897
+ },
1898
+ "description": "List all events that have this exam assigned."
1899
+ },
1900
+ "response": []
 
 
 
 
 
 
 
 
 
1901
  },
1902
+ {
1903
+ "name": "Events - Assign Exam to Event",
1904
+ "request": {
1905
+ "method": "POST",
1906
+ "header": [
1907
+ { "key": "Authorization", "value": "Bearer {{access_token}}", "type": "text" }
1908
+ ],
1909
+ "body": {
1910
+ "mode": "raw",
1911
+ "raw": ""
1912
+ },
1913
+ "url": {
1914
+ "raw": "{{base_url}}/api/v1/admin/exam/:id/events/:event_id",
1915
+ "host": ["{{base_url}}"],
1916
+ "path": ["api", "v1", "admin", "exam", ":id", "events", ":event_id"],
1917
+ "variable": [
1918
+ { "key": "id", "value": "{{exam_id}}" },
1919
+ { "key": "event_id", "value": "{{event_id}}" }
1920
+ ]
1921
+ },
1922
+ "description": "Assign an exam to an event. Returns DUPLICATE_DATA if already assigned."
1923
  },
1924
+ "response": []
 
 
 
 
 
 
 
 
1925
  },
1926
+ {
1927
+ "name": "Events - Unassign Exam from Event",
1928
+ "request": {
1929
+ "method": "DELETE",
1930
+ "header": [
1931
+ { "key": "Authorization", "value": "Bearer {{access_token}}", "type": "text" }
1932
+ ],
1933
+ "url": {
1934
+ "raw": "{{base_url}}/api/v1/admin/exam/:id/events/:event_id",
1935
+ "host": ["{{base_url}}"],
1936
+ "path": ["api", "v1", "admin", "exam", ":id", "events", ":event_id"],
1937
+ "variable": [
1938
+ { "key": "id", "value": "{{exam_id}}" },
1939
+ { "key": "event_id", "value": "{{event_id}}" }
1940
+ ]
1941
  },
1942
+ "description": "Remove an exam assignment from an event. Returns DATA_NOT_FOUND if not assigned."
1943
+ },
1944
+ "response": []
 
 
1945
  },
1946
+ {
1947
+ "name": "Academies - List Academies by Exam",
1948
+ "request": {
1949
+ "method": "GET",
1950
+ "header": [
1951
+ { "key": "Authorization", "value": "Bearer {{access_token}}", "type": "text" }
1952
+ ],
1953
+ "url": {
1954
+ "raw": "{{base_url}}/api/v1/admin/exam/:id/academies",
1955
+ "host": ["{{base_url}}"],
1956
+ "path": ["api", "v1", "admin", "exam", ":id", "academies"],
1957
+ "variable": [{ "key": "id", "value": "{{exam_id}}" }]
1958
+ },
1959
+ "description": "List all academies that have this exam assigned."
1960
  },
1961
+ "response": []
 
 
 
 
 
 
 
 
1962
  },
1963
+ {
1964
+ "name": "Academies - Assign Exam to Academy",
1965
+ "request": {
1966
+ "method": "POST",
1967
+ "header": [
1968
+ { "key": "Authorization", "value": "Bearer {{access_token}}", "type": "text" }
1969
+ ],
1970
+ "body": {
1971
+ "mode": "raw",
1972
+ "raw": ""
1973
  },
1974
+ "url": {
1975
+ "raw": "{{base_url}}/api/v1/admin/exam/:id/academies/:academy_id",
1976
+ "host": ["{{base_url}}"],
1977
+ "path": ["api", "v1", "admin", "exam", ":id", "academies", ":academy_id"],
1978
+ "variable": [
1979
+ { "key": "id", "value": "{{exam_id}}" },
1980
+ { "key": "academy_id", "value": "{{academy_id}}" }
1981
+ ]
1982
+ },
1983
+ "description": "Assign an exam to an academy. Returns DUPLICATE_DATA if already assigned."
1984
+ },
1985
+ "response": []
1986
  },
1987
+ {
1988
+ "name": "Academies - Unassign Exam from Academy",
1989
+ "request": {
1990
+ "method": "DELETE",
1991
+ "header": [
1992
+ { "key": "Authorization", "value": "Bearer {{access_token}}", "type": "text" }
1993
+ ],
1994
+ "url": {
1995
+ "raw": "{{base_url}}/api/v1/admin/exam/:id/academies/:academy_id",
1996
+ "host": ["{{base_url}}"],
1997
+ "path": ["api", "v1", "admin", "exam", ":id", "academies", ":academy_id"],
1998
+ "variable": [
1999
+ { "key": "id", "value": "{{exam_id}}" },
2000
+ { "key": "academy_id", "value": "{{academy_id}}" }
2001
+ ]
2002
+ },
2003
+ "description": "Remove an exam assignment from an academy. Returns DATA_NOT_FOUND if not assigned."
2004
+ },
2005
+ "response": []
2006
+ }
2007
+ ]
2008
  }
2009
  ]
2010
  },
 
2052
  ],
2053
  "body": {
2054
  "mode": "raw",
2055
+ "raw": "{\n \"name\": \"New User\",\n \"email\": \"newuser@example.com\",\n \"username\": \"newuser\",\n \"password\": \"password123\",\n \"role\": \"user\"\n}"
2056
  },
2057
  "url": {
2058
  "raw": "{{base_url}}/api/v1/super-admin/users/",
 
2081
  ],
2082
  "body": {
2083
  "mode": "raw",
2084
+ "raw": "[\n {\n \"name\": \"User One\",\n \"email\": \"user1@example.com\",\n \"username\": \"userone\",\n \"password\": \"password123\",\n \"role\": \"user\"\n },\n {\n \"name\": \"User Two\",\n \"email\": \"user2@example.com\",\n \"username\": \"usertwo\",\n \"password\": \"password123\",\n \"role\": \"user\"\n }\n]"
2085
  },
2086
  "url": {
2087
  "raw": "{{base_url}}/api/v1/super-admin/users/bulk",
 
2110
  ],
2111
  "body": {
2112
  "mode": "raw",
2113
+ "raw": "{\n \"name\": \"Updated Name\",\n \"email\": \"newemail@example.com\",\n \"username\": \"updatedusername\",\n \"role\": \"user\"\n}"
2114
  },
2115
  "url": {
2116
  "raw": "{{base_url}}/api/v1/super-admin/users/:id",
 
2242
  "key": "province_id",
2243
  "value": "",
2244
  "type": "string"
2245
+ },
2246
+ {
2247
+ "key": "exam_id",
2248
+ "value": "",
2249
+ "type": "string"
2250
+ },
2251
+ {
2252
+ "key": "event_id",
2253
+ "value": "",
2254
+ "type": "string"
2255
+ },
2256
+ {
2257
+ "key": "academy_id",
2258
+ "value": "",
2259
+ "type": "string"
2260
+ },
2261
+ {
2262
+ "key": "result_id",
2263
+ "value": "",
2264
+ "type": "string"
2265
+ },
2266
+ {
2267
+ "key": "user_id",
2268
+ "value": "",
2269
+ "type": "string"
2270
+ },
2271
+ {
2272
+ "key": "material_id",
2273
+ "value": "",
2274
+ "type": "string"
2275
  }
2276
  ]
2277
  }
controllers/admin_event_controller.go ADDED
@@ -0,0 +1,396 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package controllers
2
+
3
+ import (
4
+ "strconv"
5
+
6
+ "abdanhafidz.com/go-boilerplate/models/dto"
7
+ entity "abdanhafidz.com/go-boilerplate/models/entity"
8
+ http_error "abdanhafidz.com/go-boilerplate/models/error"
9
+ "abdanhafidz.com/go-boilerplate/services"
10
+ "github.com/gin-gonic/gin"
11
+ "github.com/google/uuid"
12
+ )
13
+
14
+ type AdminEventController interface {
15
+ ListEvents(ctx *gin.Context)
16
+ CreateEvent(ctx *gin.Context)
17
+ UpdateEvent(ctx *gin.Context)
18
+ DeleteEvent(ctx *gin.Context)
19
+
20
+ ListParticipants(ctx *gin.Context)
21
+ AddParticipant(ctx *gin.Context)
22
+ RemoveParticipant(ctx *gin.Context)
23
+
24
+ ListEventExams(ctx *gin.Context)
25
+ RemoveExam(ctx *gin.Context)
26
+
27
+ ListResults(ctx *gin.Context)
28
+ UpdateResult(ctx *gin.Context)
29
+ DeleteResult(ctx *gin.Context)
30
+ }
31
+
32
+ type adminEventController struct {
33
+ adminEventService services.AdminEventService
34
+ }
35
+
36
+ func NewAdminEventController(adminEventService services.AdminEventService) AdminEventController {
37
+ return &adminEventController{adminEventService: adminEventService}
38
+ }
39
+
40
+ // ListEvents godoc
41
+ // @Summary Admin: List Events
42
+ // @Description Admin view of all events with participant count, exam count and optional filters/pagination
43
+ // @Tags AdminEvent
44
+ // @Accept json
45
+ // @Produce json
46
+ // @Security BearerAuth
47
+ // @Param limit query int false "Items per page" default(10)
48
+ // @Param page query int false "Page number" default(1)
49
+ // @Param search query string false "Search by title / slug / event code"
50
+ // @Param sortBy query string false "Sort field (title, start_event, end_event, created_at, participant_count, exam_count)"
51
+ // @Param order query string false "Sort direction (asc / desc)"
52
+ // @Param status query string false "Filter by status (UPCOMING, ONGOING, ENDED)"
53
+ // @Success 200 {object} dto.SuccessResponse[[]dto.AdminEventResponse]
54
+ // @Failure 400 {object} dto.ErrorResponse
55
+ // @Failure 401 {object} dto.ErrorResponse
56
+ // @Failure 403 {object} dto.ErrorResponse
57
+ // @Router /api/v1/admin/events [get]
58
+ func (c *adminEventController) ListEvents(ctx *gin.Context) {
59
+ limit, _ := strconv.Atoi(ctx.DefaultQuery("limit", "10"))
60
+ page, _ := strconv.Atoi(ctx.DefaultQuery("page", "1"))
61
+ search := ctx.DefaultQuery("search", "")
62
+ sortBy := ctx.DefaultQuery("sortBy", "")
63
+ order := ctx.DefaultQuery("order", "")
64
+
65
+ if limit < 1 {
66
+ limit = 10
67
+ } else if limit > 100 {
68
+ limit = 100
69
+ }
70
+ if page < 1 {
71
+ page = 1
72
+ }
73
+
74
+ var status *string
75
+ if val := ctx.Query("status"); val != "" {
76
+ if val == entity.EventStatusUpcoming || val == entity.EventStatusOngoing || val == entity.EventStatusEnded {
77
+ status = &val
78
+ }
79
+ }
80
+
81
+ offset := (page - 1) * limit
82
+ p := entity.Pagination{
83
+ Limit: limit,
84
+ Offset: offset,
85
+ Search: search,
86
+ SortBy: sortBy,
87
+ Order: order,
88
+ Status: status,
89
+ }
90
+
91
+ list, total, err := c.adminEventService.ListEvents(ctx.Request.Context(), p)
92
+ if err != nil {
93
+ ResponseJSON[any, any](ctx, nil, nil, err)
94
+ return
95
+ }
96
+
97
+ var totalPages int
98
+ if total == 0 {
99
+ totalPages = 1
100
+ } else {
101
+ totalPages = int((total + int64(limit) - 1) / int64(limit))
102
+ }
103
+ if page > totalPages {
104
+ page = totalPages
105
+ }
106
+
107
+ meta := gin.H{
108
+ "totalItems": total,
109
+ "totalPages": totalPages,
110
+ "currentPage": page,
111
+ "limit": limit,
112
+ }
113
+ ResponseJSON(ctx, meta, list, nil)
114
+ }
115
+
116
+ // CreateEvent godoc
117
+ // @Summary Admin: Create Event
118
+ // @Description Create a new event
119
+ // @Tags AdminEvent
120
+ // @Accept json
121
+ // @Produce json
122
+ // @Security BearerAuth
123
+ // @Param request body dto.CreateEventRequest true "Create Event Request"
124
+ // @Success 200 {object} dto.SuccessResponse[entity.Events]
125
+ // @Failure 400 {object} dto.ErrorResponse
126
+ // @Failure 401 {object} dto.ErrorResponse
127
+ // @Failure 403 {object} dto.ErrorResponse
128
+ // @Router /api/v1/admin/events [post]
129
+ func (c *adminEventController) CreateEvent(ctx *gin.Context) {
130
+ req := RequestJSON[dto.CreateEventRequest](ctx)
131
+ res, err := c.adminEventService.CreateEvent(ctx.Request.Context(), req)
132
+ ResponseJSON(ctx, req, res, err)
133
+ }
134
+
135
+ // UpdateEvent godoc
136
+ // @Summary Admin: Update Event
137
+ // @Description Update an existing event by ID
138
+ // @Tags AdminEvent
139
+ // @Accept json
140
+ // @Produce json
141
+ // @Security BearerAuth
142
+ // @Param event_id path string true "Event ID"
143
+ // @Param request body dto.UpdateEventRequest true "Update Event Request"
144
+ // @Success 200 {object} dto.SuccessResponse[entity.Events]
145
+ // @Failure 400 {object} dto.ErrorResponse
146
+ // @Failure 401 {object} dto.ErrorResponse
147
+ // @Failure 403 {object} dto.ErrorResponse
148
+ // @Router /api/v1/admin/events/{event_id} [put]
149
+ func (c *adminEventController) UpdateEvent(ctx *gin.Context) {
150
+ id, err := uuid.Parse(ctx.Param("event_id"))
151
+ if err != nil {
152
+ ResponseJSON[any](ctx, gin.H{"event_id": ctx.Param("event_id")}, nil, http_error.INVALID_TOKEN)
153
+ return
154
+ }
155
+ req := RequestJSON[dto.UpdateEventRequest](ctx)
156
+ res, err := c.adminEventService.UpdateEvent(ctx.Request.Context(), id, req)
157
+ ResponseJSON(ctx, req, res, err)
158
+ }
159
+
160
+ // DeleteEvent godoc
161
+ // @Summary Admin: Delete Event
162
+ // @Description Delete an event by ID
163
+ // @Tags AdminEvent
164
+ // @Accept json
165
+ // @Produce json
166
+ // @Security BearerAuth
167
+ // @Param event_id path string true "Event ID"
168
+ // @Success 200 {object} dto.SuccessResponse[map[string]bool]
169
+ // @Failure 400 {object} dto.ErrorResponse
170
+ // @Failure 401 {object} dto.ErrorResponse
171
+ // @Failure 403 {object} dto.ErrorResponse
172
+ // @Router /api/v1/admin/events/{event_id} [delete]
173
+ func (c *adminEventController) DeleteEvent(ctx *gin.Context) {
174
+ id, err := uuid.Parse(ctx.Param("event_id"))
175
+ if err != nil {
176
+ ResponseJSON[any](ctx, gin.H{"event_id": ctx.Param("event_id")}, nil, http_error.INVALID_TOKEN)
177
+ return
178
+ }
179
+ delErr := c.adminEventService.DeleteEvent(ctx.Request.Context(), id)
180
+ ResponseJSON(ctx, gin.H{"id": id}, gin.H{"deleted": delErr == nil}, delErr)
181
+ }
182
+
183
+ // ListParticipants godoc
184
+ // @Summary Admin: List Event Participants
185
+ // @Description List all participants assigned to an event
186
+ // @Tags AdminEvent
187
+ // @Accept json
188
+ // @Produce json
189
+ // @Security BearerAuth
190
+ // @Param event_id path string true "Event ID"
191
+ // @Success 200 {object} dto.SuccessResponse[[]entity.EventAssign]
192
+ // @Failure 400 {object} dto.ErrorResponse
193
+ // @Failure 401 {object} dto.ErrorResponse
194
+ // @Failure 403 {object} dto.ErrorResponse
195
+ // @Router /api/v1/admin/events/{event_id}/participants [get]
196
+ func (c *adminEventController) ListParticipants(ctx *gin.Context) {
197
+ eventId, err := uuid.Parse(ctx.Param("event_id"))
198
+ if err != nil {
199
+ ResponseJSON[any](ctx, gin.H{"event_id": ctx.Param("event_id")}, nil, http_error.INVALID_TOKEN)
200
+ return
201
+ }
202
+ list, err := c.adminEventService.ListParticipants(ctx.Request.Context(), eventId)
203
+ ResponseJSON(ctx, gin.H{"event_id": eventId}, list, err)
204
+ }
205
+
206
+ // AddParticipant godoc
207
+ // @Summary Admin: Add Participant to Event
208
+ // @Description Manually assign a user to an event (bypasses payment)
209
+ // @Tags AdminEvent
210
+ // @Accept json
211
+ // @Produce json
212
+ // @Security BearerAuth
213
+ // @Param event_id path string true "Event ID"
214
+ // @Param request body dto.AddParticipantRequest true "Add Participant Request"
215
+ // @Success 200 {object} dto.SuccessResponse[entity.EventAssign]
216
+ // @Failure 400 {object} dto.ErrorResponse
217
+ // @Failure 401 {object} dto.ErrorResponse
218
+ // @Failure 403 {object} dto.ErrorResponse
219
+ // @Router /api/v1/admin/events/{event_id}/participants [post]
220
+ func (c *adminEventController) AddParticipant(ctx *gin.Context) {
221
+ eventId, err := uuid.Parse(ctx.Param("event_id"))
222
+ if err != nil {
223
+ ResponseJSON[any](ctx, gin.H{"event_id": ctx.Param("event_id")}, nil, http_error.INVALID_TOKEN)
224
+ return
225
+ }
226
+ req := RequestJSON[dto.AddParticipantRequest](ctx)
227
+ userId, parseErr := uuid.Parse(req.UserId)
228
+ if parseErr != nil {
229
+ ResponseJSON[any](ctx, req, nil, http_error.INVALID_TOKEN)
230
+ return
231
+ }
232
+ assign, err := c.adminEventService.AddParticipant(ctx.Request.Context(), eventId, userId)
233
+ ResponseJSON(ctx, req, assign, err)
234
+ }
235
+
236
+ // RemoveParticipant godoc
237
+ // @Summary Admin: Remove Participant from Event
238
+ // @Description Unassign a user from an event
239
+ // @Tags AdminEvent
240
+ // @Accept json
241
+ // @Produce json
242
+ // @Security BearerAuth
243
+ // @Param event_id path string true "Event ID"
244
+ // @Param user_id path string true "User ID"
245
+ // @Success 200 {object} dto.SuccessResponse[map[string]bool]
246
+ // @Failure 400 {object} dto.ErrorResponse
247
+ // @Failure 401 {object} dto.ErrorResponse
248
+ // @Failure 403 {object} dto.ErrorResponse
249
+ // @Router /api/v1/admin/events/{event_id}/participants/{user_id} [delete]
250
+ func (c *adminEventController) RemoveParticipant(ctx *gin.Context) {
251
+ eventId, err := uuid.Parse(ctx.Param("event_id"))
252
+ if err != nil {
253
+ ResponseJSON[any](ctx, gin.H{"event_id": ctx.Param("event_id")}, nil, http_error.INVALID_TOKEN)
254
+ return
255
+ }
256
+ userId, err := uuid.Parse(ctx.Param("user_id"))
257
+ if err != nil {
258
+ ResponseJSON[any](ctx, gin.H{"user_id": ctx.Param("user_id")}, nil, http_error.INVALID_TOKEN)
259
+ return
260
+ }
261
+ delErr := c.adminEventService.RemoveParticipant(ctx.Request.Context(), eventId, userId)
262
+ ResponseJSON(ctx, gin.H{"event_id": eventId, "user_id": userId}, gin.H{"removed": delErr == nil}, delErr)
263
+ }
264
+
265
+ // ListEventExams godoc
266
+ // @Summary Admin: List Event Exams
267
+ // @Description List all exams assigned to an event
268
+ // @Tags AdminEvent
269
+ // @Accept json
270
+ // @Produce json
271
+ // @Security BearerAuth
272
+ // @Param event_id path string true "Event ID"
273
+ // @Success 200 {object} dto.SuccessResponse[[]entity.EventExamAssign]
274
+ // @Failure 400 {object} dto.ErrorResponse
275
+ // @Failure 401 {object} dto.ErrorResponse
276
+ // @Failure 403 {object} dto.ErrorResponse
277
+ // @Router /api/v1/admin/events/{event_id}/exams [get]
278
+ func (c *adminEventController) ListEventExams(ctx *gin.Context) {
279
+ eventId, err := uuid.Parse(ctx.Param("event_id"))
280
+ if err != nil {
281
+ ResponseJSON[any](ctx, gin.H{"event_id": ctx.Param("event_id")}, nil, http_error.INVALID_TOKEN)
282
+ return
283
+ }
284
+ list, err := c.adminEventService.ListEventExams(ctx.Request.Context(), eventId)
285
+ ResponseJSON(ctx, gin.H{"event_id": eventId}, list, err)
286
+ }
287
+
288
+ // RemoveExam godoc
289
+ // @Summary Admin: Remove Exam from Event
290
+ // @Description Unassign an exam from an event
291
+ // @Tags AdminEvent
292
+ // @Accept json
293
+ // @Produce json
294
+ // @Security BearerAuth
295
+ // @Param event_id path string true "Event ID"
296
+ // @Param exam_id path string true "Exam ID"
297
+ // @Success 200 {object} dto.SuccessResponse[map[string]bool]
298
+ // @Failure 400 {object} dto.ErrorResponse
299
+ // @Failure 401 {object} dto.ErrorResponse
300
+ // @Failure 403 {object} dto.ErrorResponse
301
+ // @Router /api/v1/admin/events/{event_id}/exams/{exam_id} [delete]
302
+ func (c *adminEventController) RemoveExam(ctx *gin.Context) {
303
+ eventId, err := uuid.Parse(ctx.Param("event_id"))
304
+ if err != nil {
305
+ ResponseJSON[any](ctx, gin.H{"event_id": ctx.Param("event_id")}, nil, http_error.INVALID_TOKEN)
306
+ return
307
+ }
308
+ examId, err := uuid.Parse(ctx.Param("exam_id"))
309
+ if err != nil {
310
+ ResponseJSON[any](ctx, gin.H{"exam_id": ctx.Param("exam_id")}, nil, http_error.INVALID_TOKEN)
311
+ return
312
+ }
313
+ delErr := c.adminEventService.RemoveExam(ctx.Request.Context(), eventId, examId)
314
+ ResponseJSON(ctx, gin.H{"event_id": eventId, "exam_id": examId}, gin.H{"removed": delErr == nil}, delErr)
315
+ }
316
+
317
+ // ListResults godoc
318
+ // @Summary Admin: List Exam Results for an Event
319
+ // @Description Retrieve all participant results for a specific exam within an event
320
+ // @Tags AdminEvent
321
+ // @Accept json
322
+ // @Produce json
323
+ // @Security BearerAuth
324
+ // @Param event_id path string true "Event ID"
325
+ // @Param exam_id path string true "Exam ID"
326
+ // @Success 200 {object} dto.SuccessResponse[[]entity.Result]
327
+ // @Failure 400 {object} dto.ErrorResponse
328
+ // @Failure 401 {object} dto.ErrorResponse
329
+ // @Failure 403 {object} dto.ErrorResponse
330
+ // @Router /api/v1/admin/events/{event_id}/exam/{exam_id}/results [get]
331
+ func (c *adminEventController) ListResults(ctx *gin.Context) {
332
+ eventId, err := uuid.Parse(ctx.Param("event_id"))
333
+ if err != nil {
334
+ ResponseJSON[any](ctx, gin.H{"event_id": ctx.Param("event_id")}, nil, http_error.INVALID_TOKEN)
335
+ return
336
+ }
337
+ examId, err := uuid.Parse(ctx.Param("exam_id"))
338
+ if err != nil {
339
+ ResponseJSON[any](ctx, gin.H{"exam_id": ctx.Param("exam_id")}, nil, http_error.INVALID_TOKEN)
340
+ return
341
+ }
342
+ list, err := c.adminEventService.ListResults(ctx.Request.Context(), eventId, examId)
343
+ ResponseJSON(ctx, gin.H{"event_id": eventId, "exam_id": examId}, list, err)
344
+ }
345
+
346
+ // UpdateResult godoc
347
+ // @Summary Admin: Update Exam Result
348
+ // @Description Edit the final score of a participant's exam result
349
+ // @Tags AdminEvent
350
+ // @Accept json
351
+ // @Produce json
352
+ // @Security BearerAuth
353
+ // @Param event_id path string true "Event ID"
354
+ // @Param exam_id path string true "Exam ID"
355
+ // @Param result_id path string true "Result ID"
356
+ // @Param request body dto.UpdateResultRequest true "Update Result Request"
357
+ // @Success 200 {object} dto.SuccessResponse[entity.Result]
358
+ // @Failure 400 {object} dto.ErrorResponse
359
+ // @Failure 401 {object} dto.ErrorResponse
360
+ // @Failure 403 {object} dto.ErrorResponse
361
+ // @Router /api/v1/admin/events/{event_id}/exam/{exam_id}/results/{result_id} [put]
362
+ func (c *adminEventController) UpdateResult(ctx *gin.Context) {
363
+ resultId, err := uuid.Parse(ctx.Param("result_id"))
364
+ if err != nil {
365
+ ResponseJSON[any](ctx, gin.H{"result_id": ctx.Param("result_id")}, nil, http_error.INVALID_TOKEN)
366
+ return
367
+ }
368
+ req := RequestJSON[dto.UpdateResultRequest](ctx)
369
+ res, err := c.adminEventService.UpdateResult(ctx.Request.Context(), resultId, req)
370
+ ResponseJSON(ctx, req, res, err)
371
+ }
372
+
373
+ // DeleteResult godoc
374
+ // @Summary Admin: Delete Exam Result
375
+ // @Description Delete a participant's exam result
376
+ // @Tags AdminEvent
377
+ // @Accept json
378
+ // @Produce json
379
+ // @Security BearerAuth
380
+ // @Param event_id path string true "Event ID"
381
+ // @Param exam_id path string true "Exam ID"
382
+ // @Param result_id path string true "Result ID"
383
+ // @Success 200 {object} dto.SuccessResponse[map[string]bool]
384
+ // @Failure 400 {object} dto.ErrorResponse
385
+ // @Failure 401 {object} dto.ErrorResponse
386
+ // @Failure 403 {object} dto.ErrorResponse
387
+ // @Router /api/v1/admin/events/{event_id}/exam/{exam_id}/results/{result_id} [delete]
388
+ func (c *adminEventController) DeleteResult(ctx *gin.Context) {
389
+ resultId, err := uuid.Parse(ctx.Param("result_id"))
390
+ if err != nil {
391
+ ResponseJSON[any](ctx, gin.H{"result_id": ctx.Param("result_id")}, nil, http_error.INVALID_TOKEN)
392
+ return
393
+ }
394
+ delErr := c.adminEventService.DeleteResult(ctx.Request.Context(), resultId)
395
+ ResponseJSON(ctx, gin.H{"result_id": resultId}, gin.H{"deleted": delErr == nil}, delErr)
396
+ }
controllers/admin_exam_controller.go ADDED
@@ -0,0 +1,351 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package controllers
2
+
3
+ import (
4
+ "strconv"
5
+
6
+ "abdanhafidz.com/go-boilerplate/models/dto"
7
+ entity "abdanhafidz.com/go-boilerplate/models/entity"
8
+ http_error "abdanhafidz.com/go-boilerplate/models/error"
9
+ "abdanhafidz.com/go-boilerplate/services"
10
+ "github.com/gin-gonic/gin"
11
+ "github.com/google/uuid"
12
+ )
13
+
14
+ type AdminExamController interface {
15
+ CreateExam(ctx *gin.Context)
16
+ UpdateExam(ctx *gin.Context)
17
+ DeleteExam(ctx *gin.Context)
18
+ GetExamDetail(ctx *gin.Context)
19
+ ListExams(ctx *gin.Context)
20
+ AssignToEvent(ctx *gin.Context)
21
+ UnassignFromEvent(ctx *gin.Context)
22
+ AssignToAcademy(ctx *gin.Context)
23
+ UnassignFromAcademy(ctx *gin.Context)
24
+ ListEventsByExam(ctx *gin.Context)
25
+ ListAcademiesByExam(ctx *gin.Context)
26
+ }
27
+
28
+ type adminExamController struct {
29
+ adminExamService services.AdminExamService
30
+ }
31
+
32
+ func NewAdminExamController(adminExamService services.AdminExamService) AdminExamController {
33
+ return &adminExamController{adminExamService: adminExamService}
34
+ }
35
+
36
+ // CreateExam godoc
37
+ // @Summary Admin: Create Exam
38
+ // @Description Create a new exam with configuration and proctoring settings
39
+ // @Tags AdminExam
40
+ // @Accept json
41
+ // @Produce json
42
+ // @Security BearerAuth
43
+ // @Param request body dto.CreateExamRequest true "Create Exam Request"
44
+ // @Success 200 {object} dto.SuccessResponse[entity.Exam]
45
+ // @Failure 400 {object} dto.ErrorResponse
46
+ // @Failure 401 {object} dto.ErrorResponse
47
+ // @Failure 403 {object} dto.ErrorResponse
48
+ // @Router /api/v1/admin/exam [post]
49
+ func (c *adminExamController) CreateExam(ctx *gin.Context) {
50
+ req := RequestJSON[dto.CreateExamRequest](ctx)
51
+ res, err := c.adminExamService.CreateExam(ctx.Request.Context(), req)
52
+ ResponseJSON(ctx, req, res, err)
53
+ }
54
+
55
+ // UpdateExam godoc
56
+ // @Summary Admin: Update Exam
57
+ // @Description Update an existing exam including configuration and proctoring settings
58
+ // @Tags AdminExam
59
+ // @Accept json
60
+ // @Produce json
61
+ // @Security BearerAuth
62
+ // @Param id path string true "Exam ID"
63
+ // @Param request body dto.CreateExamRequest true "Update Exam Request"
64
+ // @Success 200 {object} dto.SuccessResponse[entity.Exam]
65
+ // @Failure 400 {object} dto.ErrorResponse
66
+ // @Failure 401 {object} dto.ErrorResponse
67
+ // @Failure 403 {object} dto.ErrorResponse
68
+ // @Router /api/v1/admin/exam/{id} [put]
69
+ func (c *adminExamController) UpdateExam(ctx *gin.Context) {
70
+ id, err := uuid.Parse(ctx.Param("id"))
71
+ if err != nil {
72
+ ResponseJSON[any](ctx, gin.H{"id": ctx.Param("id")}, nil, http_error.INVALID_TOKEN)
73
+ return
74
+ }
75
+ req := RequestJSON[dto.CreateExamRequest](ctx)
76
+ res, updateErr := c.adminExamService.UpdateExam(ctx.Request.Context(), id, req)
77
+ ResponseJSON(ctx, req, res, updateErr)
78
+ }
79
+
80
+ // DeleteExam godoc
81
+ // @Summary Admin: Delete Exam
82
+ // @Description Soft delete an exam by ID
83
+ // @Tags AdminExam
84
+ // @Accept json
85
+ // @Produce json
86
+ // @Security BearerAuth
87
+ // @Param id path string true "Exam ID"
88
+ // @Success 200 {object} dto.SuccessResponse[map[string]bool]
89
+ // @Failure 400 {object} dto.ErrorResponse
90
+ // @Failure 401 {object} dto.ErrorResponse
91
+ // @Failure 403 {object} dto.ErrorResponse
92
+ // @Router /api/v1/admin/exam/{id} [delete]
93
+ func (c *adminExamController) DeleteExam(ctx *gin.Context) {
94
+ id, err := uuid.Parse(ctx.Param("id"))
95
+ if err != nil {
96
+ ResponseJSON[any](ctx, gin.H{"id": ctx.Param("id")}, nil, http_error.INVALID_TOKEN)
97
+ return
98
+ }
99
+ delErr := c.adminExamService.DeleteExam(ctx.Request.Context(), id)
100
+ ResponseJSON(ctx, gin.H{"id": id}, gin.H{"deleted": delErr == nil}, delErr)
101
+ }
102
+
103
+ // GetExamDetail godoc
104
+ // @Summary Admin: Get Exam Detail
105
+ // @Description Retrieve full exam details including configuration and proctoring
106
+ // @Tags AdminExam
107
+ // @Accept json
108
+ // @Produce json
109
+ // @Security BearerAuth
110
+ // @Param id path string true "Exam ID"
111
+ // @Success 200 {object} dto.SuccessResponse[entity.Exam]
112
+ // @Failure 400 {object} dto.ErrorResponse
113
+ // @Failure 401 {object} dto.ErrorResponse
114
+ // @Failure 403 {object} dto.ErrorResponse
115
+ // @Router /api/v1/admin/exam/{id} [get]
116
+ func (c *adminExamController) GetExamDetail(ctx *gin.Context) {
117
+ id, err := uuid.Parse(ctx.Param("id"))
118
+ if err != nil {
119
+ ResponseJSON[any](ctx, gin.H{"id": ctx.Param("id")}, nil, http_error.INVALID_TOKEN)
120
+ return
121
+ }
122
+ res, getErr := c.adminExamService.GetExamDetail(ctx.Request.Context(), id)
123
+ ResponseJSON(ctx, gin.H{"id": id}, res, getErr)
124
+ }
125
+
126
+ // ListExams godoc
127
+ // @Summary Admin: List Exams
128
+ // @Description Retrieve a paginated list of exams with event and academy assignment counts
129
+ // @Tags AdminExam
130
+ // @Accept json
131
+ // @Produce json
132
+ // @Security BearerAuth
133
+ // @Param limit query int false "Items per page" default(10)
134
+ // @Param page query int false "Page number" default(1)
135
+ // @Param search query string false "Search by title or slug"
136
+ // @Param sortBy query string false "Sort field (title, slug, created_at, duration, event_count, academy_count)"
137
+ // @Param order query string false "Sort direction (asc / desc)"
138
+ // @Success 200 {object} dto.SuccessResponse[[]dto.AdminExamResponse]
139
+ // @Failure 400 {object} dto.ErrorResponse
140
+ // @Failure 401 {object} dto.ErrorResponse
141
+ // @Failure 403 {object} dto.ErrorResponse
142
+ // @Router /api/v1/admin/exam [get]
143
+ func (c *adminExamController) ListExams(ctx *gin.Context) {
144
+ limit, _ := strconv.Atoi(ctx.DefaultQuery("limit", "10"))
145
+ page, _ := strconv.Atoi(ctx.DefaultQuery("page", "1"))
146
+ search := ctx.DefaultQuery("search", "")
147
+ sortBy := ctx.DefaultQuery("sortBy", "")
148
+ order := ctx.DefaultQuery("order", "")
149
+
150
+ if limit < 1 {
151
+ limit = 10
152
+ } else if limit > 100 {
153
+ limit = 100
154
+ }
155
+ if page < 1 {
156
+ page = 1
157
+ }
158
+
159
+ offset := (page - 1) * limit
160
+ p := entity.Pagination{
161
+ Limit: limit,
162
+ Offset: offset,
163
+ Search: search,
164
+ SortBy: sortBy,
165
+ Order: order,
166
+ }
167
+
168
+ list, total, err := c.adminExamService.ListExams(ctx.Request.Context(), p)
169
+ if err != nil {
170
+ ResponseJSON[any, any](ctx, nil, nil, err)
171
+ return
172
+ }
173
+
174
+ totalPages := int((total + int64(limit) - 1) / int64(limit))
175
+ if total == 0 {
176
+ totalPages = 1
177
+ }
178
+ if page > totalPages {
179
+ page = totalPages
180
+ }
181
+
182
+ meta := gin.H{
183
+ "totalItems": total,
184
+ "totalPages": totalPages,
185
+ "currentPage": page,
186
+ "limit": limit,
187
+ }
188
+ ResponseJSON(ctx, meta, list, nil)
189
+ }
190
+
191
+ // AssignToEvent godoc
192
+ // @Summary Admin: Assign Exam to Event
193
+ // @Description Assign an exam to an event
194
+ // @Tags AdminExam
195
+ // @Accept json
196
+ // @Produce json
197
+ // @Security BearerAuth
198
+ // @Param id path string true "Exam ID"
199
+ // @Param event_id path string true "Event ID"
200
+ // @Success 200 {object} dto.SuccessResponse[map[string]bool]
201
+ // @Failure 400 {object} dto.ErrorResponse
202
+ // @Failure 401 {object} dto.ErrorResponse
203
+ // @Failure 403 {object} dto.ErrorResponse
204
+ // @Router /api/v1/admin/exam/{id}/events/{event_id} [post]
205
+ func (c *adminExamController) AssignToEvent(ctx *gin.Context) {
206
+ examId, err := uuid.Parse(ctx.Param("id"))
207
+ if err != nil {
208
+ ResponseJSON[any](ctx, gin.H{"id": ctx.Param("id")}, nil, http_error.INVALID_TOKEN)
209
+ return
210
+ }
211
+ eventId, err := uuid.Parse(ctx.Param("event_id"))
212
+ if err != nil {
213
+ ResponseJSON[any](ctx, gin.H{"event_id": ctx.Param("event_id")}, nil, http_error.INVALID_TOKEN)
214
+ return
215
+ }
216
+ assignErr := c.adminExamService.AssignToEvent(ctx.Request.Context(), examId, eventId)
217
+ ResponseJSON(ctx, gin.H{"exam_id": examId, "event_id": eventId}, gin.H{"assigned": assignErr == nil}, assignErr)
218
+ }
219
+
220
+ // UnassignFromEvent godoc
221
+ // @Summary Admin: Unassign Exam from Event
222
+ // @Description Remove an exam assignment from an event
223
+ // @Tags AdminExam
224
+ // @Accept json
225
+ // @Produce json
226
+ // @Security BearerAuth
227
+ // @Param id path string true "Exam ID"
228
+ // @Param event_id path string true "Event ID"
229
+ // @Success 200 {object} dto.SuccessResponse[map[string]bool]
230
+ // @Failure 400 {object} dto.ErrorResponse
231
+ // @Failure 401 {object} dto.ErrorResponse
232
+ // @Failure 403 {object} dto.ErrorResponse
233
+ // @Router /api/v1/admin/exam/{id}/events/{event_id} [delete]
234
+ func (c *adminExamController) UnassignFromEvent(ctx *gin.Context) {
235
+ examId, err := uuid.Parse(ctx.Param("id"))
236
+ if err != nil {
237
+ ResponseJSON[any](ctx, gin.H{"id": ctx.Param("id")}, nil, http_error.INVALID_TOKEN)
238
+ return
239
+ }
240
+ eventId, err := uuid.Parse(ctx.Param("event_id"))
241
+ if err != nil {
242
+ ResponseJSON[any](ctx, gin.H{"event_id": ctx.Param("event_id")}, nil, http_error.INVALID_TOKEN)
243
+ return
244
+ }
245
+ delErr := c.adminExamService.UnassignFromEvent(ctx.Request.Context(), examId, eventId)
246
+ ResponseJSON(ctx, gin.H{"exam_id": examId, "event_id": eventId}, gin.H{"removed": delErr == nil}, delErr)
247
+ }
248
+
249
+ // AssignToAcademy godoc
250
+ // @Summary Admin: Assign Exam to Academy
251
+ // @Description Assign an exam to an academy
252
+ // @Tags AdminExam
253
+ // @Accept json
254
+ // @Produce json
255
+ // @Security BearerAuth
256
+ // @Param id path string true "Exam ID"
257
+ // @Param academy_id path string true "Academy ID"
258
+ // @Success 200 {object} dto.SuccessResponse[map[string]bool]
259
+ // @Failure 400 {object} dto.ErrorResponse
260
+ // @Failure 401 {object} dto.ErrorResponse
261
+ // @Failure 403 {object} dto.ErrorResponse
262
+ // @Router /api/v1/admin/exam/{id}/academies/{academy_id} [post]
263
+ func (c *adminExamController) AssignToAcademy(ctx *gin.Context) {
264
+ examId, err := uuid.Parse(ctx.Param("id"))
265
+ if err != nil {
266
+ ResponseJSON[any](ctx, gin.H{"id": ctx.Param("id")}, nil, http_error.INVALID_TOKEN)
267
+ return
268
+ }
269
+ academyId, err := uuid.Parse(ctx.Param("academy_id"))
270
+ if err != nil {
271
+ ResponseJSON[any](ctx, gin.H{"academy_id": ctx.Param("academy_id")}, nil, http_error.INVALID_TOKEN)
272
+ return
273
+ }
274
+ assignErr := c.adminExamService.AssignToAcademy(ctx.Request.Context(), examId, academyId)
275
+ ResponseJSON(ctx, gin.H{"exam_id": examId, "academy_id": academyId}, gin.H{"assigned": assignErr == nil}, assignErr)
276
+ }
277
+
278
+ // UnassignFromAcademy godoc
279
+ // @Summary Admin: Unassign Exam from Academy
280
+ // @Description Remove an exam assignment from an academy
281
+ // @Tags AdminExam
282
+ // @Accept json
283
+ // @Produce json
284
+ // @Security BearerAuth
285
+ // @Param id path string true "Exam ID"
286
+ // @Param academy_id path string true "Academy ID"
287
+ // @Success 200 {object} dto.SuccessResponse[map[string]bool]
288
+ // @Failure 400 {object} dto.ErrorResponse
289
+ // @Failure 401 {object} dto.ErrorResponse
290
+ // @Failure 403 {object} dto.ErrorResponse
291
+ // @Router /api/v1/admin/exam/{id}/academies/{academy_id} [delete]
292
+ func (c *adminExamController) UnassignFromAcademy(ctx *gin.Context) {
293
+ examId, err := uuid.Parse(ctx.Param("id"))
294
+ if err != nil {
295
+ ResponseJSON[any](ctx, gin.H{"id": ctx.Param("id")}, nil, http_error.INVALID_TOKEN)
296
+ return
297
+ }
298
+ academyId, err := uuid.Parse(ctx.Param("academy_id"))
299
+ if err != nil {
300
+ ResponseJSON[any](ctx, gin.H{"academy_id": ctx.Param("academy_id")}, nil, http_error.INVALID_TOKEN)
301
+ return
302
+ }
303
+ delErr := c.adminExamService.UnassignFromAcademy(ctx.Request.Context(), examId, academyId)
304
+ ResponseJSON(ctx, gin.H{"exam_id": examId, "academy_id": academyId}, gin.H{"removed": delErr == nil}, delErr)
305
+ }
306
+
307
+ // ListEventsByExam godoc
308
+ // @Summary Admin: List Events by Exam
309
+ // @Description List all events that have this exam assigned
310
+ // @Tags AdminExam
311
+ // @Accept json
312
+ // @Produce json
313
+ // @Security BearerAuth
314
+ // @Param id path string true "Exam ID"
315
+ // @Success 200 {object} dto.SuccessResponse[[]entity.EventExamAssign]
316
+ // @Failure 400 {object} dto.ErrorResponse
317
+ // @Failure 401 {object} dto.ErrorResponse
318
+ // @Failure 403 {object} dto.ErrorResponse
319
+ // @Router /api/v1/admin/exam/{id}/events [get]
320
+ func (c *adminExamController) ListEventsByExam(ctx *gin.Context) {
321
+ examId, err := uuid.Parse(ctx.Param("id"))
322
+ if err != nil {
323
+ ResponseJSON[any](ctx, gin.H{"id": ctx.Param("id")}, nil, http_error.INVALID_TOKEN)
324
+ return
325
+ }
326
+ list, listErr := c.adminExamService.ListEventsByExam(ctx.Request.Context(), examId)
327
+ ResponseJSON(ctx, gin.H{"exam_id": examId}, list, listErr)
328
+ }
329
+
330
+ // ListAcademiesByExam godoc
331
+ // @Summary Admin: List Academies by Exam
332
+ // @Description List all academies that have this exam assigned
333
+ // @Tags AdminExam
334
+ // @Accept json
335
+ // @Produce json
336
+ // @Security BearerAuth
337
+ // @Param id path string true "Exam ID"
338
+ // @Success 200 {object} dto.SuccessResponse[[]entity.AcademyExamAssign]
339
+ // @Failure 400 {object} dto.ErrorResponse
340
+ // @Failure 401 {object} dto.ErrorResponse
341
+ // @Failure 403 {object} dto.ErrorResponse
342
+ // @Router /api/v1/admin/exam/{id}/academies [get]
343
+ func (c *adminExamController) ListAcademiesByExam(ctx *gin.Context) {
344
+ examId, err := uuid.Parse(ctx.Param("id"))
345
+ if err != nil {
346
+ ResponseJSON[any](ctx, gin.H{"id": ctx.Param("id")}, nil, http_error.INVALID_TOKEN)
347
+ return
348
+ }
349
+ list, listErr := c.adminExamService.ListAcademiesByExam(ctx.Request.Context(), examId)
350
+ ResponseJSON(ctx, gin.H{"exam_id": examId}, list, listErr)
351
+ }
controllers/event_controller.go CHANGED
@@ -16,9 +16,6 @@ type EventController interface {
16
  List(ctx *gin.Context)
17
  DetailBySlug(ctx *gin.Context)
18
  Join(ctx *gin.Context)
19
- CreateEvent(ctx *gin.Context)
20
- UpdateEvent(ctx *gin.Context)
21
- DeleteEvent(ctx *gin.Context)
22
  }
23
 
24
  type eventController struct {
@@ -149,53 +146,3 @@ func (c *eventController) Join(ctx *gin.Context) {
149
 
150
  ResponseJSON(ctx, req, res, err)
151
  }
152
-
153
- // Create Event godoc
154
- // @Summary Create Event
155
- // @Description Create a new event with the provided details
156
- // @Tags Event
157
- // @Accept json
158
- // @Produce json
159
- // @Param request body dto.CreateEventRequest true "Create Event Request"
160
- // @Success 200 {object} dto.SuccessResponse[dto.EventDetailResponse]
161
- // @Failure 400 {object} dto.ErrorResponse
162
- // @Router /api/v1/admin/events [post]
163
- func (c *eventController) CreateEvent(ctx *gin.Context) {
164
- req := RequestJSON[dto.CreateEventRequest](ctx)
165
- res, err := c.eventService.CreateEvent(ctx.Request.Context(), req)
166
- ResponseJSON(ctx, req, res, err)
167
- }
168
-
169
- // Update Event godoc
170
- // @Summary Update Event
171
- // @Description Update an existing event with the provided details
172
- // @Tags Event
173
- // @Accept json
174
- // @Produce json
175
- // @Param id path string true "Event ID"
176
- // @Param request body dto.UpdateEventRequest true "Update Event Request"
177
- // @Success 200 {object} dto.SuccessResponse[entity.Events]
178
- // @Failure 400 {object} dto.ErrorResponse
179
- // @Router /api/v1/admin/events/{id} [put]
180
- func (c *eventController) UpdateEvent(ctx *gin.Context) {
181
- id := ParseUUID(ctx, "id")
182
- req := RequestJSON[dto.UpdateEventRequest](ctx)
183
- res, err := c.eventService.UpdateEvent(ctx.Request.Context(), id, req)
184
- ResponseJSON(ctx, req, res, err)
185
- }
186
-
187
- // Delete Event godoc
188
- // @Summary Delete Event
189
- // @Description Delete an existing event by its ID
190
- // @Tags Event
191
- // @Accept json
192
- // @Produce json
193
- // @Param id path string true "Event ID"
194
- // @Success 200 {object} dto.SuccessResponse[map[string]bool]
195
- // @Failure 400 {object} dto.ErrorResponse
196
- // @Router /api/v1/admin/events/{id} [delete]
197
- func (c *eventController) DeleteEvent(ctx *gin.Context) {
198
- id := ParseUUID(ctx, "id")
199
- err := c.eventService.DeleteEvent(ctx.Request.Context(), id)
200
- ResponseJSON(ctx, gin.H{"id": id}, gin.H{"deleted": true}, err)
201
- }
 
16
  List(ctx *gin.Context)
17
  DetailBySlug(ctx *gin.Context)
18
  Join(ctx *gin.Context)
 
 
 
19
  }
20
 
21
  type eventController struct {
 
146
 
147
  ResponseJSON(ctx, req, res, err)
148
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
go.mod CHANGED
@@ -8,6 +8,7 @@ require (
8
  github.com/golang-jwt/jwt/v4 v4.5.2
9
  github.com/google/uuid v1.6.0
10
  github.com/gosimple/slug v1.15.0
 
11
  github.com/joho/godotenv v1.5.1
12
  github.com/lib/pq v1.1.1
13
  github.com/supabase-community/storage-go v0.8.1
@@ -56,7 +57,6 @@ require (
56
  github.com/gosimple/unidecode v1.0.1 // indirect
57
  github.com/jackc/pgpassfile v1.0.0 // indirect
58
  github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
59
- github.com/jackc/pgx/v5 v5.7.5 // indirect
60
  github.com/jackc/puddle/v2 v2.2.2 // indirect
61
  github.com/jinzhu/inflection v1.0.0 // indirect
62
  github.com/jinzhu/now v1.1.5 // indirect
 
8
  github.com/golang-jwt/jwt/v4 v4.5.2
9
  github.com/google/uuid v1.6.0
10
  github.com/gosimple/slug v1.15.0
11
+ github.com/jackc/pgx/v5 v5.7.5
12
  github.com/joho/godotenv v1.5.1
13
  github.com/lib/pq v1.1.1
14
  github.com/supabase-community/storage-go v0.8.1
 
57
  github.com/gosimple/unidecode v1.0.1 // indirect
58
  github.com/jackc/pgpassfile v1.0.0 // indirect
59
  github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
 
60
  github.com/jackc/puddle/v2 v2.2.2 // indirect
61
  github.com/jinzhu/inflection v1.0.0 // indirect
62
  github.com/jinzhu/now v1.1.5 // indirect
models/dto/event_dto.go CHANGED
@@ -66,3 +66,21 @@ type ScoreboardItem struct {
66
  TotalScore float32 `json:"total_score"`
67
  AverageScore float32 `json:"average_score"`
68
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
  TotalScore float32 `json:"total_score"`
67
  AverageScore float32 `json:"average_score"`
68
  }
69
+
70
+ // AdminEventResponse is the detailed event response for admin views,
71
+ // including aggregate counts for participants and assigned exams.
72
+ type AdminEventResponse struct {
73
+ entity.Events
74
+ ParticipantCount int64 `json:"participant_count"`
75
+ ExamCount int64 `json:"exam_count"`
76
+ }
77
+
78
+ // AddParticipantRequest is the request body for manually assigning a user to an event.
79
+ type AddParticipantRequest struct {
80
+ UserId string `json:"user_id" binding:"required"`
81
+ }
82
+
83
+ // UpdateResultRequest is the request body for editing an exam result.
84
+ type UpdateResultRequest struct {
85
+ FinalScore float32 `json:"final_score" binding:"required"`
86
+ }
models/dto/exam_dto.go CHANGED
@@ -41,3 +41,9 @@ type CreateExamRequest struct {
41
  DisableCopyPaste bool `json:"disable_copy_paste,omitempty"`
42
  EnableExamBrowser bool `json:"enable_exam_browser,omitempty"`
43
  }
 
 
 
 
 
 
 
41
  DisableCopyPaste bool `json:"disable_copy_paste,omitempty"`
42
  EnableExamBrowser bool `json:"enable_exam_browser,omitempty"`
43
  }
44
+
45
+ type AdminExamResponse struct {
46
+ entity.Exam
47
+ EventCount int64 `json:"event_count"`
48
+ AcademyCount int64 `json:"academy_count"`
49
+ }
provider/controller_provider.go CHANGED
@@ -3,6 +3,8 @@ package provider
3
  import "abdanhafidz.com/go-boilerplate/controllers"
4
 
5
  type ControllerProvider interface {
 
 
6
  ProvideAcademyController() controllers.AcademyController
7
  ProvideAcademyExamController() controllers.AcademyExamController
8
  ProvideAccountDetailController() controllers.AccountDetailController
@@ -21,6 +23,8 @@ type ControllerProvider interface {
21
  }
22
 
23
  type controllerProvider struct {
 
 
24
  academyController controllers.AcademyController
25
  academyExamController controllers.AcademyExamController
26
  accountDetailController controllers.AccountDetailController
@@ -40,6 +44,8 @@ type controllerProvider struct {
40
 
41
  func NewControllerProvider(servicesProvider ServicesProvider) ControllerProvider {
42
 
 
 
43
  academyController := controllers.NewAcademyController(servicesProvider.ProvideAcademyService())
44
  academyExamController := controllers.NewAcademyExamController(servicesProvider.ProvideAcademyExamService())
45
  accountDetailController := controllers.NewAccountDetailController(servicesProvider.ProvideAccountService())
@@ -60,6 +66,8 @@ func NewControllerProvider(servicesProvider ServicesProvider) ControllerProvider
60
  uploadController := controllers.NewUploadController(servicesProvider.ProvideUploadService())
61
  userController := controllers.NewUserController(servicesProvider.ProvideAccountService())
62
  return &controllerProvider{
 
 
63
  academyController: academyController,
64
  academyExamController: academyExamController,
65
  accountDetailController: accountDetailController,
@@ -80,6 +88,14 @@ func NewControllerProvider(servicesProvider ServicesProvider) ControllerProvider
80
 
81
  // --- Getter Methods ---
82
 
 
 
 
 
 
 
 
 
83
  func (c *controllerProvider) ProvideAcademyController() controllers.AcademyController {
84
  return c.academyController
85
  }
 
3
  import "abdanhafidz.com/go-boilerplate/controllers"
4
 
5
  type ControllerProvider interface {
6
+ ProvideAdminEventController() controllers.AdminEventController
7
+ ProvideAdminExamController() controllers.AdminExamController
8
  ProvideAcademyController() controllers.AcademyController
9
  ProvideAcademyExamController() controllers.AcademyExamController
10
  ProvideAccountDetailController() controllers.AccountDetailController
 
23
  }
24
 
25
  type controllerProvider struct {
26
+ adminEventController controllers.AdminEventController
27
+ adminExamController controllers.AdminExamController
28
  academyController controllers.AcademyController
29
  academyExamController controllers.AcademyExamController
30
  accountDetailController controllers.AccountDetailController
 
44
 
45
  func NewControllerProvider(servicesProvider ServicesProvider) ControllerProvider {
46
 
47
+ adminEventController := controllers.NewAdminEventController(servicesProvider.ProvideAdminEventService())
48
+ adminExamController := controllers.NewAdminExamController(servicesProvider.ProvideAdminExamService())
49
  academyController := controllers.NewAcademyController(servicesProvider.ProvideAcademyService())
50
  academyExamController := controllers.NewAcademyExamController(servicesProvider.ProvideAcademyExamService())
51
  accountDetailController := controllers.NewAccountDetailController(servicesProvider.ProvideAccountService())
 
66
  uploadController := controllers.NewUploadController(servicesProvider.ProvideUploadService())
67
  userController := controllers.NewUserController(servicesProvider.ProvideAccountService())
68
  return &controllerProvider{
69
+ adminEventController: adminEventController,
70
+ adminExamController: adminExamController,
71
  academyController: academyController,
72
  academyExamController: academyExamController,
73
  accountDetailController: accountDetailController,
 
88
 
89
  // --- Getter Methods ---
90
 
91
+ func (c *controllerProvider) ProvideAdminEventController() controllers.AdminEventController {
92
+ return c.adminEventController
93
+ }
94
+
95
+ func (c *controllerProvider) ProvideAdminExamController() controllers.AdminExamController {
96
+ return c.adminExamController
97
+ }
98
+
99
  func (c *controllerProvider) ProvideAcademyController() controllers.AcademyController {
100
  return c.academyController
101
  }
provider/repositories_provider.go CHANGED
@@ -3,6 +3,8 @@ package provider
3
  import "abdanhafidz.com/go-boilerplate/repositories"
4
 
5
  type RepositoriesProvider interface {
 
 
6
  ProvideAcademyPaymentRepository() repositories.AcademyPaymentRepository
7
  ProvideAcademyRepository() repositories.AcademyRepository
8
  ProvideAcademyResultRepository() repositories.AcademyResultRepository
@@ -33,6 +35,8 @@ type RepositoriesProvider interface {
33
  }
34
 
35
  type repositoriesProvider struct {
 
 
36
  academyPaymentRepository repositories.AcademyPaymentRepository
37
  academyRepository repositories.AcademyRepository
38
  academyResultRepository repositories.AcademyResultRepository
@@ -66,6 +70,8 @@ func NewRepositoriesProvider(cfg ConfigProvider) RepositoriesProvider {
66
  dbConfig := cfg.ProvideDatabaseConfig()
67
  db := dbConfig.GetInstance()
68
 
 
 
69
  academyPaymentRepository := repositories.NewAcaddemyPaymentRepository(db)
70
  academyRepository := repositories.NewAcademyRepository(db)
71
  academyResultRepository := repositories.NewAcademyResultRepository(db)
@@ -95,6 +101,8 @@ func NewRepositoriesProvider(cfg ConfigProvider) RepositoriesProvider {
95
  resultRepository := repositories.NewResultRepository(db)
96
 
97
  return &repositoriesProvider{
 
 
98
  academyPaymentRepository: academyPaymentRepository,
99
  academyRepository: academyRepository,
100
  academyResultRepository: academyResultRepository,
@@ -125,6 +133,14 @@ func NewRepositoriesProvider(cfg ConfigProvider) RepositoriesProvider {
125
  }
126
  }
127
 
 
 
 
 
 
 
 
 
128
  func (r *repositoriesProvider) ProvideAcademyPaymentRepository() repositories.AcademyPaymentRepository {
129
  return r.academyPaymentRepository
130
  }
 
3
  import "abdanhafidz.com/go-boilerplate/repositories"
4
 
5
  type RepositoriesProvider interface {
6
+ ProvideAdminEventRepository() repositories.AdminEventRepository
7
+ ProvideAdminExamRepository() repositories.AdminExamRepository
8
  ProvideAcademyPaymentRepository() repositories.AcademyPaymentRepository
9
  ProvideAcademyRepository() repositories.AcademyRepository
10
  ProvideAcademyResultRepository() repositories.AcademyResultRepository
 
35
  }
36
 
37
  type repositoriesProvider struct {
38
+ adminEventRepository repositories.AdminEventRepository
39
+ adminExamRepository repositories.AdminExamRepository
40
  academyPaymentRepository repositories.AcademyPaymentRepository
41
  academyRepository repositories.AcademyRepository
42
  academyResultRepository repositories.AcademyResultRepository
 
70
  dbConfig := cfg.ProvideDatabaseConfig()
71
  db := dbConfig.GetInstance()
72
 
73
+ adminEventRepository := repositories.NewAdminEventRepository(db)
74
+ adminExamRepository := repositories.NewAdminExamRepository(db)
75
  academyPaymentRepository := repositories.NewAcaddemyPaymentRepository(db)
76
  academyRepository := repositories.NewAcademyRepository(db)
77
  academyResultRepository := repositories.NewAcademyResultRepository(db)
 
101
  resultRepository := repositories.NewResultRepository(db)
102
 
103
  return &repositoriesProvider{
104
+ adminEventRepository: adminEventRepository,
105
+ adminExamRepository: adminExamRepository,
106
  academyPaymentRepository: academyPaymentRepository,
107
  academyRepository: academyRepository,
108
  academyResultRepository: academyResultRepository,
 
133
  }
134
  }
135
 
136
+ func (r *repositoriesProvider) ProvideAdminEventRepository() repositories.AdminEventRepository {
137
+ return r.adminEventRepository
138
+ }
139
+
140
+ func (r *repositoriesProvider) ProvideAdminExamRepository() repositories.AdminExamRepository {
141
+ return r.adminExamRepository
142
+ }
143
+
144
  func (r *repositoriesProvider) ProvideAcademyPaymentRepository() repositories.AcademyPaymentRepository {
145
  return r.academyPaymentRepository
146
  }
provider/services_provider.go CHANGED
@@ -6,6 +6,8 @@ import (
6
  )
7
 
8
  type ServicesProvider interface {
 
 
9
  ProvideRegionService() services.RegionService
10
  ProvideJWTService() services.JWTService
11
  ProvideAcademyService() services.AcademyService
@@ -25,6 +27,8 @@ type ServicesProvider interface {
25
  }
26
 
27
  type servicesProvider struct {
 
 
28
  regionService services.RegionService
29
  jWTService services.JWTService
30
  academyService services.AcademyService
@@ -67,7 +71,12 @@ func NewServicesProvider(repoProvider RepositoriesProvider, configProvider Confi
67
  eventExamProctoringService := services.NewEventExamProctoringService(eventExamService, uploadService, repoProvider.ProvideEventExamProctoringRepository())
68
  examService := services.NewExamService(repoProvider.ProvideExamRepository(), repoProvider.ProvideEventExamAssignRepository(), repoProvider.ProvideAcademyExamAssignRepository())
69
 
 
 
 
70
  return &servicesProvider{
 
 
71
  regionService: regionService,
72
  jWTService: jWTService,
73
  academyService: academyService,
@@ -87,6 +96,14 @@ func NewServicesProvider(repoProvider RepositoriesProvider, configProvider Confi
87
  }
88
  }
89
 
 
 
 
 
 
 
 
 
90
  func (s *servicesProvider) ProvideRegionService() services.RegionService {
91
  return s.regionService
92
  }
 
6
  )
7
 
8
  type ServicesProvider interface {
9
+ ProvideAdminEventService() services.AdminEventService
10
+ ProvideAdminExamService() services.AdminExamService
11
  ProvideRegionService() services.RegionService
12
  ProvideJWTService() services.JWTService
13
  ProvideAcademyService() services.AcademyService
 
27
  }
28
 
29
  type servicesProvider struct {
30
+ adminEventService services.AdminEventService
31
+ adminExamService services.AdminExamService
32
  regionService services.RegionService
33
  jWTService services.JWTService
34
  academyService services.AcademyService
 
71
  eventExamProctoringService := services.NewEventExamProctoringService(eventExamService, uploadService, repoProvider.ProvideEventExamProctoringRepository())
72
  examService := services.NewExamService(repoProvider.ProvideExamRepository(), repoProvider.ProvideEventExamAssignRepository(), repoProvider.ProvideAcademyExamAssignRepository())
73
 
74
+ adminEventService := services.NewAdminEventService(eventService, repoProvider.ProvideAdminEventRepository(), repoProvider.ProvideEventAssignRepository(), repoProvider.ProvideEventExamAssignRepository(), repoProvider.ProvideResultRepository())
75
+ adminExamService := services.NewAdminExamService(repoProvider.ProvideAdminExamRepository())
76
+
77
  return &servicesProvider{
78
+ adminEventService: adminEventService,
79
+ adminExamService: adminExamService,
80
  regionService: regionService,
81
  jWTService: jWTService,
82
  academyService: academyService,
 
96
  }
97
  }
98
 
99
+ func (s *servicesProvider) ProvideAdminEventService() services.AdminEventService {
100
+ return s.adminEventService
101
+ }
102
+
103
+ func (s *servicesProvider) ProvideAdminExamService() services.AdminExamService {
104
+ return s.adminExamService
105
+ }
106
+
107
  func (s *servicesProvider) ProvideRegionService() services.RegionService {
108
  return s.regionService
109
  }
repositories/admin_event_repository.go ADDED
@@ -0,0 +1,155 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package repositories
2
+
3
+ import (
4
+ "context"
5
+ "strings"
6
+
7
+ dto "abdanhafidz.com/go-boilerplate/models/dto"
8
+ entity "abdanhafidz.com/go-boilerplate/models/entity"
9
+ "github.com/google/uuid"
10
+ "gorm.io/gorm"
11
+ )
12
+
13
+ type AdminEventRepository interface {
14
+ ListEventsWithStats(ctx context.Context, p entity.Pagination) ([]dto.AdminEventResponse, int64, error)
15
+ ListParticipantsWithDetails(ctx context.Context, eventId uuid.UUID) ([]entity.EventAssign, error)
16
+ RemoveParticipant(ctx context.Context, eventId uuid.UUID, accountId uuid.UUID) error
17
+ RemoveExamFromEvent(ctx context.Context, eventId uuid.UUID, examId uuid.UUID) error
18
+ ListResultsByEventAndExam(ctx context.Context, eventId uuid.UUID, examId uuid.UUID) ([]entity.Result, error)
19
+ DeleteResult(ctx context.Context, resultId uuid.UUID) error
20
+ }
21
+
22
+ type adminEventRepository struct{ db *gorm.DB }
23
+
24
+ func NewAdminEventRepository(db *gorm.DB) AdminEventRepository {
25
+ return &adminEventRepository{db: db}
26
+ }
27
+
28
+ func (r *adminEventRepository) ListEventsWithStats(ctx context.Context, p entity.Pagination) ([]dto.AdminEventResponse, int64, error) {
29
+ type rawRow struct {
30
+ entity.Events
31
+ ParticipantCount int64 `gorm:"column:participant_count"`
32
+ ExamCount int64 `gorm:"column:exam_count"`
33
+ }
34
+
35
+ q := r.db.WithContext(ctx).
36
+ Table("events").
37
+ Select(`events.*,
38
+ COUNT(DISTINCT ea.id) AS participant_count,
39
+ COUNT(DISTINCT exa.id) AS exam_count`).
40
+ Joins("LEFT JOIN event_assign ea ON ea.event_id = events.id").
41
+ Joins("LEFT JOIN exam_event_assign exa ON exa.event_id = events.id").
42
+ Where("events.deleted_at IS NULL").
43
+ Group("events.id")
44
+
45
+ if s := strings.TrimSpace(p.Search); s != "" {
46
+ s = strings.Trim(s, "\"'")
47
+ s = strings.ToLower(s)
48
+ like := "%" + s + "%"
49
+ q = q.Where("LOWER(events.title) LIKE ? OR LOWER(events.slug) LIKE ? OR LOWER(events.event_code) LIKE ?", like, like, like)
50
+ }
51
+
52
+ if p.Status != nil {
53
+ switch *p.Status {
54
+ case entity.EventStatusUpcoming:
55
+ q = q.Where("events.start_event > NOW()")
56
+ case entity.EventStatusOngoing:
57
+ q = q.Where("events.start_event <= NOW() AND events.end_event >= NOW()")
58
+ case entity.EventStatusEnded:
59
+ q = q.Where("events.end_event < NOW()")
60
+ }
61
+ }
62
+
63
+ col := strings.ToLower(strings.TrimSpace(p.SortBy))
64
+ ord := strings.ToLower(strings.TrimSpace(p.Order))
65
+ if col == "" {
66
+ col = "created_at"
67
+ }
68
+ if ord != "asc" {
69
+ ord = "desc"
70
+ }
71
+ switch col {
72
+ case "title", "start_event", "end_event", "created_at", "is_public":
73
+ q = q.Order("events." + col + " " + ord)
74
+ case "participant_count", "exam_count":
75
+ q = q.Order(col + " " + ord)
76
+ default:
77
+ q = q.Order("events.created_at " + ord)
78
+ }
79
+
80
+ var total int64
81
+ countQ := r.db.WithContext(ctx).Table("events").
82
+ Where("deleted_at IS NULL")
83
+ if s := strings.TrimSpace(p.Search); s != "" {
84
+ s = strings.Trim(s, "\"'")
85
+ s = strings.ToLower(s)
86
+ like := "%" + s + "%"
87
+ countQ = countQ.Where("LOWER(title) LIKE ? OR LOWER(slug) LIKE ? OR LOWER(event_code) LIKE ?", like, like, like)
88
+ }
89
+ if err := countQ.Count(&total).Error; err != nil {
90
+ return nil, 0, err
91
+ }
92
+
93
+ if p.Limit > 0 {
94
+ q = q.Limit(p.Limit)
95
+ }
96
+ if p.Offset > 0 {
97
+ q = q.Offset(p.Offset)
98
+ }
99
+
100
+ var rows []rawRow
101
+ if err := q.Scan(&rows).Error; err != nil {
102
+ return nil, 0, err
103
+ }
104
+
105
+ result := make([]dto.AdminEventResponse, 0, len(rows))
106
+ for _, row := range rows {
107
+ result = append(result, dto.AdminEventResponse{
108
+ Events: row.Events,
109
+ ParticipantCount: row.ParticipantCount,
110
+ ExamCount: row.ExamCount,
111
+ })
112
+ }
113
+ return result, total, nil
114
+ }
115
+
116
+ func (r *adminEventRepository) ListParticipantsWithDetails(ctx context.Context, eventId uuid.UUID) ([]entity.EventAssign, error) {
117
+ var list []entity.EventAssign
118
+ err := r.db.WithContext(ctx).
119
+ Where("event_id = ?", eventId).
120
+ Preload("Account").
121
+ Find(&list).Error
122
+ return list, err
123
+ }
124
+
125
+ func (r *adminEventRepository) RemoveParticipant(ctx context.Context, eventId uuid.UUID, accountId uuid.UUID) error {
126
+ return r.db.WithContext(ctx).
127
+ Where("event_id = ? AND account_id = ?", eventId, accountId).
128
+ Delete(&entity.EventAssign{}).Error
129
+ }
130
+
131
+ func (r *adminEventRepository) RemoveExamFromEvent(ctx context.Context, eventId uuid.UUID, examId uuid.UUID) error {
132
+ return r.db.WithContext(ctx).
133
+ Where("event_id = ? AND exam_id = ?", eventId, examId).
134
+ Delete(&entity.EventExamAssign{}).Error
135
+ }
136
+
137
+ func (r *adminEventRepository) ListResultsByEventAndExam(ctx context.Context, eventId uuid.UUID, examId uuid.UUID) ([]entity.Result, error) {
138
+ var list []entity.Result
139
+ err := r.db.WithContext(ctx).
140
+ Table("result").
141
+ Joins("JOIN exam_event_attempt ON exam_event_attempt.id = result.attempt_id").
142
+ Where("exam_event_attempt.event_id = ?", eventId).
143
+ Where("exam_event_attempt.exam_id = ?", examId).
144
+ Preload("EventExamAttempt").
145
+ Preload("EventExamAttempt.Account").
146
+ Preload("EventExamAttempt.Exam").
147
+ Find(&list).Error
148
+ return list, err
149
+ }
150
+
151
+ func (r *adminEventRepository) DeleteResult(ctx context.Context, resultId uuid.UUID) error {
152
+ return r.db.WithContext(ctx).
153
+ Where("id = ?", resultId).
154
+ Delete(&entity.Result{}).Error
155
+ }
repositories/admin_exam_repository.go ADDED
@@ -0,0 +1,242 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package repositories
2
+
3
+ import (
4
+ "context"
5
+ "strings"
6
+
7
+ dto "abdanhafidz.com/go-boilerplate/models/dto"
8
+ entity "abdanhafidz.com/go-boilerplate/models/entity"
9
+ http_error "abdanhafidz.com/go-boilerplate/models/error"
10
+ "abdanhafidz.com/go-boilerplate/utils"
11
+ "github.com/google/uuid"
12
+ "gorm.io/gorm"
13
+ )
14
+
15
+ type AdminExamRepository interface {
16
+ Create(ctx context.Context, exam *entity.Exam) error
17
+ Get(ctx context.Context, id uuid.UUID) (entity.Exam, error)
18
+ Update(ctx context.Context, exam entity.Exam) error
19
+ Delete(ctx context.Context, id uuid.UUID) error
20
+ List(ctx context.Context, p entity.Pagination) ([]dto.AdminExamResponse, int64, error)
21
+ AssignToEvent(ctx context.Context, assign entity.EventExamAssign) error
22
+ UnassignFromEvent(ctx context.Context, examId uuid.UUID, eventId uuid.UUID) error
23
+ AssignToAcademy(ctx context.Context, assign entity.AcademyExamAssign) error
24
+ UnassignFromAcademy(ctx context.Context, examId uuid.UUID, academyId uuid.UUID) error
25
+ IsAssignedToEvent(ctx context.Context, examId uuid.UUID, eventId uuid.UUID) bool
26
+ IsAssignedToAcademy(ctx context.Context, examId uuid.UUID, academyId uuid.UUID) bool
27
+ ListEventsByExam(ctx context.Context, examId uuid.UUID) ([]entity.EventExamAssign, error)
28
+ ListAcademiesByExam(ctx context.Context, examId uuid.UUID) ([]entity.AcademyExamAssign, error)
29
+ }
30
+
31
+ type adminExamRepository struct{ db *gorm.DB }
32
+
33
+ func NewAdminExamRepository(db *gorm.DB) AdminExamRepository {
34
+ return &adminExamRepository{db: db}
35
+ }
36
+
37
+ func (r *adminExamRepository) Create(ctx context.Context, exam *entity.Exam) error {
38
+ tx := r.db.WithContext(ctx).Begin()
39
+ if tx.Error != nil {
40
+ return tx.Error
41
+ }
42
+
43
+ if err := tx.Omit("Configuration", "Proctoring").Create(exam).Error; err != nil {
44
+ tx.Rollback()
45
+ if utils.IsDuplicateKeyError(err) {
46
+ return http_error.DUPLICATE_DATA
47
+ }
48
+ return err
49
+ }
50
+
51
+ exam.Configuration.ExamId = exam.Id
52
+ exam.Proctoring.ExamId = exam.Id
53
+
54
+ if err := tx.Create(&exam.Configuration).Error; err != nil {
55
+ tx.Rollback()
56
+ if utils.IsDuplicateKeyError(err) {
57
+ return http_error.DUPLICATE_DATA
58
+ }
59
+ return err
60
+ }
61
+
62
+ if err := tx.Create(&exam.Proctoring).Error; err != nil {
63
+ tx.Rollback()
64
+ if utils.IsDuplicateKeyError(err) {
65
+ return http_error.DUPLICATE_DATA
66
+ }
67
+ return err
68
+ }
69
+
70
+ return tx.Commit().Error
71
+ }
72
+
73
+ func (r *adminExamRepository) Get(ctx context.Context, id uuid.UUID) (entity.Exam, error) {
74
+ var exam entity.Exam
75
+ err := r.db.WithContext(ctx).
76
+ Preload("Configuration").
77
+ Preload("Proctoring").
78
+ First(&exam, "id = ?", id).Error
79
+ return exam, err
80
+ }
81
+
82
+ func (r *adminExamRepository) Update(ctx context.Context, exam entity.Exam) error {
83
+ tx := r.db.WithContext(ctx).Begin()
84
+ if tx.Error != nil {
85
+ return tx.Error
86
+ }
87
+
88
+ if err := tx.Model(&entity.Exam{}).Where("id = ?", exam.Id).Updates(map[string]interface{}{
89
+ "slug": exam.Slug,
90
+ "title": exam.Title,
91
+ "description": exam.Description,
92
+ "duration": exam.Duration,
93
+ "randomize": exam.Randomize,
94
+ }).Error; err != nil {
95
+ tx.Rollback()
96
+ return err
97
+ }
98
+
99
+ if err := tx.Save(&exam.Configuration).Error; err != nil {
100
+ tx.Rollback()
101
+ return err
102
+ }
103
+
104
+ if err := tx.Save(&exam.Proctoring).Error; err != nil {
105
+ tx.Rollback()
106
+ return err
107
+ }
108
+
109
+ return tx.Commit().Error
110
+ }
111
+
112
+ func (r *adminExamRepository) Delete(ctx context.Context, id uuid.UUID) error {
113
+ return r.db.WithContext(ctx).Where("id = ?", id).Delete(&entity.Exam{}).Error
114
+ }
115
+
116
+ func (r *adminExamRepository) List(ctx context.Context, p entity.Pagination) ([]dto.AdminExamResponse, int64, error) {
117
+ type rawRow struct {
118
+ entity.Exam
119
+ EventCount int64 `gorm:"column:event_count"`
120
+ AcademyCount int64 `gorm:"column:academy_count"`
121
+ }
122
+
123
+ countQ := r.db.WithContext(ctx).Table("exam").Where("exam.deleted_at IS NULL")
124
+
125
+ if s := strings.TrimSpace(p.Search); s != "" {
126
+ like := "%" + strings.ToLower(s) + "%"
127
+ countQ = countQ.Where("LOWER(exam.title) LIKE ? OR LOWER(exam.slug) LIKE ?", like, like)
128
+ }
129
+
130
+ var total int64
131
+ if err := countQ.Count(&total).Error; err != nil {
132
+ return nil, 0, err
133
+ }
134
+
135
+ q := r.db.WithContext(ctx).
136
+ Table("exam").
137
+ Select(`exam.*, COUNT(DISTINCT eea.id) AS event_count, COUNT(DISTINCT aea.id) AS academy_count`).
138
+ Joins("LEFT JOIN exam_event_assign eea ON eea.exam_id = exam.id").
139
+ Joins("LEFT JOIN exam_academy_assign aea ON aea.exam_id = exam.id").
140
+ Where("exam.deleted_at IS NULL").
141
+ Group("exam.id")
142
+
143
+ if s := strings.TrimSpace(p.Search); s != "" {
144
+ like := "%" + strings.ToLower(s) + "%"
145
+ q = q.Where("LOWER(exam.title) LIKE ? OR LOWER(exam.slug) LIKE ?", like, like)
146
+ }
147
+
148
+ col := strings.ToLower(strings.TrimSpace(p.SortBy))
149
+ ord := strings.ToLower(strings.TrimSpace(p.Order))
150
+ if col == "" {
151
+ col = "created_at"
152
+ }
153
+ if ord != "asc" {
154
+ ord = "desc"
155
+ }
156
+ switch col {
157
+ case "title", "slug", "created_at", "duration":
158
+ q = q.Order("exam." + col + " " + ord)
159
+ case "event_count", "academy_count":
160
+ q = q.Order(col + " " + ord)
161
+ default:
162
+ q = q.Order("exam.created_at " + ord)
163
+ }
164
+
165
+ if p.Limit > 0 {
166
+ q = q.Limit(p.Limit)
167
+ }
168
+ if p.Offset > 0 {
169
+ q = q.Offset(p.Offset)
170
+ }
171
+
172
+ var rows []rawRow
173
+ if err := q.Find(&rows).Error; err != nil {
174
+ return nil, 0, err
175
+ }
176
+
177
+ result := make([]dto.AdminExamResponse, 0, len(rows))
178
+ for _, row := range rows {
179
+ result = append(result, dto.AdminExamResponse{
180
+ Exam: row.Exam,
181
+ EventCount: row.EventCount,
182
+ AcademyCount: row.AcademyCount,
183
+ })
184
+ }
185
+ return result, total, nil
186
+ }
187
+
188
+ func (r *adminExamRepository) AssignToEvent(ctx context.Context, assign entity.EventExamAssign) error {
189
+ return r.db.WithContext(ctx).Create(&assign).Error
190
+ }
191
+
192
+ func (r *adminExamRepository) UnassignFromEvent(ctx context.Context, examId uuid.UUID, eventId uuid.UUID) error {
193
+ return r.db.WithContext(ctx).
194
+ Where("exam_id = ? AND event_id = ?", examId, eventId).
195
+ Delete(&entity.EventExamAssign{}).Error
196
+ }
197
+
198
+ func (r *adminExamRepository) AssignToAcademy(ctx context.Context, assign entity.AcademyExamAssign) error {
199
+ return r.db.WithContext(ctx).Create(&assign).Error
200
+ }
201
+
202
+ func (r *adminExamRepository) UnassignFromAcademy(ctx context.Context, examId uuid.UUID, academyId uuid.UUID) error {
203
+ return r.db.WithContext(ctx).
204
+ Where("exam_id = ? AND academy_id = ?", examId, academyId).
205
+ Delete(&entity.AcademyExamAssign{}).Error
206
+ }
207
+
208
+ func (r *adminExamRepository) IsAssignedToEvent(ctx context.Context, examId uuid.UUID, eventId uuid.UUID) bool {
209
+ var count int64
210
+ r.db.WithContext(ctx).
211
+ Model(&entity.EventExamAssign{}).
212
+ Where("exam_id = ? AND event_id = ?", examId, eventId).
213
+ Count(&count)
214
+ return count > 0
215
+ }
216
+
217
+ func (r *adminExamRepository) IsAssignedToAcademy(ctx context.Context, examId uuid.UUID, academyId uuid.UUID) bool {
218
+ var count int64
219
+ r.db.WithContext(ctx).
220
+ Model(&entity.AcademyExamAssign{}).
221
+ Where("exam_id = ? AND academy_id = ?", examId, academyId).
222
+ Count(&count)
223
+ return count > 0
224
+ }
225
+
226
+ func (r *adminExamRepository) ListEventsByExam(ctx context.Context, examId uuid.UUID) ([]entity.EventExamAssign, error) {
227
+ var items []entity.EventExamAssign
228
+ err := r.db.WithContext(ctx).
229
+ Where("exam_id = ?", examId).
230
+ Preload("Event").
231
+ Find(&items).Error
232
+ return items, err
233
+ }
234
+
235
+ func (r *adminExamRepository) ListAcademiesByExam(ctx context.Context, examId uuid.UUID) ([]entity.AcademyExamAssign, error) {
236
+ var items []entity.AcademyExamAssign
237
+ err := r.db.WithContext(ctx).
238
+ Where("exam_id = ?", examId).
239
+ Preload("Academy").
240
+ Find(&items).Error
241
+ return items, err
242
+ }
repositories/exam_repository.go CHANGED
@@ -4,6 +4,8 @@ import (
4
  "context"
5
 
6
  entity "abdanhafidz.com/go-boilerplate/models/entity"
 
 
7
  "github.com/google/uuid"
8
  "gorm.io/gorm"
9
  )
@@ -37,25 +39,30 @@ func (r *examRepository) Create(ctx context.Context, e *entity.Exam) error {
37
  return tx.Error
38
  }
39
 
40
- // 1. Create Exam
41
- if err := tx.Create(&e).Error; err != nil {
42
  tx.Rollback()
 
 
 
43
  return err
44
  }
45
 
46
- // 2. Inject ExamId
47
  e.Configuration.ExamId = e.Id
48
  e.Proctoring.ExamId = e.Id
49
 
50
- // 3. Create Configuration
51
  if err := tx.Create(&e.Configuration).Error; err != nil {
52
  tx.Rollback()
 
 
 
53
  return err
54
  }
55
 
56
- // 4. Create Proctoring
57
  if err := tx.Create(&e.Proctoring).Error; err != nil {
58
  tx.Rollback()
 
 
 
59
  return err
60
  }
61
 
 
4
  "context"
5
 
6
  entity "abdanhafidz.com/go-boilerplate/models/entity"
7
+ http_error "abdanhafidz.com/go-boilerplate/models/error"
8
+ "abdanhafidz.com/go-boilerplate/utils"
9
  "github.com/google/uuid"
10
  "gorm.io/gorm"
11
  )
 
39
  return tx.Error
40
  }
41
 
42
+ if err := tx.Omit("Configuration", "Proctoring").Create(&e).Error; err != nil {
 
43
  tx.Rollback()
44
+ if utils.IsDuplicateKeyError(err) {
45
+ return http_error.DUPLICATE_DATA
46
+ }
47
  return err
48
  }
49
 
 
50
  e.Configuration.ExamId = e.Id
51
  e.Proctoring.ExamId = e.Id
52
 
 
53
  if err := tx.Create(&e.Configuration).Error; err != nil {
54
  tx.Rollback()
55
+ if utils.IsDuplicateKeyError(err) {
56
+ return http_error.DUPLICATE_DATA
57
+ }
58
  return err
59
  }
60
 
 
61
  if err := tx.Create(&e.Proctoring).Error; err != nil {
62
  tx.Rollback()
63
+ if utils.IsDuplicateKeyError(err) {
64
+ return http_error.DUPLICATE_DATA
65
+ }
66
  return err
67
  }
68
 
router/admin_event_router.go ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package router
2
+
3
+ import (
4
+ "abdanhafidz.com/go-boilerplate/provider"
5
+ "github.com/gin-gonic/gin"
6
+ )
7
+
8
+ func AdminEventRouter(router *gin.Engine, middleware provider.MiddlewareProvider, controller provider.ControllerProvider) {
9
+ authenticationMiddleware := middleware.ProvideAuthenticationMiddleware()
10
+ authorizationMiddleware := middleware.ProvideAuthorizationMiddleware()
11
+
12
+ adminEventController := controller.ProvideAdminEventController()
13
+
14
+ adminEventGroup := router.Group("/api/v1/admin/events", authenticationMiddleware.VerifyAccount, authorizationMiddleware.VerifyAdmin)
15
+ {
16
+ // Event CRUD
17
+ adminEventGroup.GET("/", adminEventController.ListEvents)
18
+ adminEventGroup.POST("/", adminEventController.CreateEvent)
19
+ adminEventGroup.PUT("/:event_id", adminEventController.UpdateEvent)
20
+ adminEventGroup.DELETE("/:event_id", adminEventController.DeleteEvent)
21
+
22
+ // Participant management
23
+ adminEventGroup.GET("/:event_id/participants", adminEventController.ListParticipants)
24
+ adminEventGroup.POST("/:event_id/participants", adminEventController.AddParticipant)
25
+ adminEventGroup.DELETE("/:event_id/participants/:user_id", adminEventController.RemoveParticipant)
26
+
27
+ // Exam management
28
+ adminEventGroup.GET("/:event_id/exams", adminEventController.ListEventExams)
29
+ adminEventGroup.DELETE("/:event_id/exams/:exam_id", adminEventController.RemoveExam)
30
+
31
+ // Result management
32
+ adminEventGroup.GET("/:event_id/exam/:exam_id/results", adminEventController.ListResults)
33
+ adminEventGroup.PUT("/:event_id/exam/:exam_id/results/:result_id", adminEventController.UpdateResult)
34
+ adminEventGroup.DELETE("/:event_id/exam/:exam_id/results/:result_id", adminEventController.DeleteResult)
35
+ }
36
+ }
router/admin_exam_router.go ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package router
2
+
3
+ import (
4
+ "abdanhafidz.com/go-boilerplate/provider"
5
+ "github.com/gin-gonic/gin"
6
+ )
7
+
8
+ func AdminExamRouter(router *gin.Engine, middleware provider.MiddlewareProvider, controller provider.ControllerProvider) {
9
+ authenticationMiddleware := middleware.ProvideAuthenticationMiddleware()
10
+ authorizationMiddleware := middleware.ProvideAuthorizationMiddleware()
11
+
12
+ adminExamCtrl := controller.ProvideAdminExamController()
13
+
14
+ adminExam := router.Group("/api/v1/admin/exam", authenticationMiddleware.VerifyAccount, authorizationMiddleware.VerifyAdmin)
15
+ {
16
+ adminExam.GET("", adminExamCtrl.ListExams)
17
+ adminExam.POST("", adminExamCtrl.CreateExam)
18
+ adminExam.GET("/:id", adminExamCtrl.GetExamDetail)
19
+ adminExam.PUT("/:id", adminExamCtrl.UpdateExam)
20
+ adminExam.DELETE("/:id", adminExamCtrl.DeleteExam)
21
+
22
+ adminExam.GET("/:id/events", adminExamCtrl.ListEventsByExam)
23
+ adminExam.POST("/:id/events/:event_id", adminExamCtrl.AssignToEvent)
24
+ adminExam.DELETE("/:id/events/:event_id", adminExamCtrl.UnassignFromEvent)
25
+
26
+ adminExam.GET("/:id/academies", adminExamCtrl.ListAcademiesByExam)
27
+ adminExam.POST("/:id/academies/:academy_id", adminExamCtrl.AssignToAcademy)
28
+ adminExam.DELETE("/:id/academies/:academy_id", adminExamCtrl.UnassignFromAcademy)
29
+ }
30
+ }
router/admin_router.go CHANGED
@@ -9,18 +9,9 @@ func AdminRouter(router *gin.Engine, middleware provider.MiddlewareProvider, con
9
  authenticationMiddleware := middleware.ProvideAuthenticationMiddleware()
10
  authorizationMiddleware := middleware.ProvideAuthorizationMiddleware()
11
 
12
- eventController := controller.ProvideEventController()
13
  academyController := controller.ProvideAcademyController()
14
  authenticationController := controller.ProvideAuthenticationController()
15
 
16
- // Event Admin Routes
17
- eventAdminGroup := router.Group("api/v1/admin/events", authenticationMiddleware.VerifyAccount, authorizationMiddleware.VerifyAdmin)
18
- {
19
- eventAdminGroup.POST("/", eventController.CreateEvent)
20
- eventAdminGroup.PUT("/:id", eventController.UpdateEvent)
21
- eventAdminGroup.DELETE("/:id", eventController.DeleteEvent)
22
- }
23
-
24
  // Academy Admin Routes
25
  academyAdminGroup := router.Group("/api/v1/admin/academy", authenticationMiddleware.VerifyAccount, authorizationMiddleware.VerifyAdmin)
26
  {
 
9
  authenticationMiddleware := middleware.ProvideAuthenticationMiddleware()
10
  authorizationMiddleware := middleware.ProvideAuthorizationMiddleware()
11
 
 
12
  academyController := controller.ProvideAcademyController()
13
  authenticationController := controller.ProvideAuthenticationController()
14
 
 
 
 
 
 
 
 
 
15
  // Academy Admin Routes
16
  academyAdminGroup := router.Group("/api/v1/admin/academy", authenticationMiddleware.VerifyAccount, authorizationMiddleware.VerifyAdmin)
17
  {
router/exam_router.go CHANGED
@@ -7,42 +7,18 @@ import (
7
 
8
  func ExamRouter(router *gin.Engine, middleware provider.MiddlewareProvider, controller provider.ControllerProvider) {
9
  examCtrl := controller.ProvideExamController()
10
- authMiddleware := middleware.ProvideAuthenticationMiddleware()
 
11
 
12
- v1 := router.Group("/api/v1")
13
  {
14
- admin := v1.Group("/admin")
15
- admin.Use(authMiddleware.VerifyAccount) // Assuming VerifyAccount or similar check if admin constraint needed.
16
- // Note: Code snippet for admin_router.go used RolesEnum.Admin. Let's assume Middleware works as in my previous artifact or check admin_router usage.
17
- // Checking admin_router.go usage in router.go: AdminRouter(router, middleware, controller).
18
- // My previous artifact used `admin.Use(Middleware(entity.RolesEnum.Admin))`.
19
- // existing router/academy_router.go uses `authenticationMiddleware.VerifyAccount`.
20
- // Let's check if there is a role based middleware.
21
- // `ProvideAuthenticationMiddleware` likely gives `VerifyAccount`.
22
- // existing `router/admin_router.go` probably has `VerifyAdmin` or similar?
23
- // I'll stick to VerifyAccount for now or check admin_router.go.
24
- // BUT the user asked for "router".
25
- // Let's assume `VerifyAccount` is enough for now or use `VerifyAccount` + Role check if I see how used.
26
- // Actually, `admin_router.go` usage would be helpful.
27
- // I will use `VerifyAccount` for now as in AcademyRouter.
28
- // Wait, `Exam` operations seem admin related (`/api/v1/admin/exam`).
29
 
30
- // Let's try to match strict pattern.
31
- // If I use `authenticationMiddleware.VerifyAccount`, it's safe.
32
-
33
- admin.Use(authMiddleware.VerifyAccount)
34
- {
35
- exam := admin.Group("/exam")
36
- {
37
- exam.POST("", examCtrl.CreateExam)
38
- exam.GET("", examCtrl.ListExam)
39
- exam.PUT("/:id", examCtrl.UpdateExam)
40
- exam.DELETE("/:id", examCtrl.DeleteExam)
41
- exam.GET("/:id", examCtrl.GetExamDetail)
42
-
43
- exam.POST("/:exam_id/event/:event_id", examCtrl.AssignExamToEvent)
44
- exam.POST("/:exam_id/academy/:academy_id", examCtrl.AssignExamToAcademy)
45
- }
46
- }
47
  }
48
  }
 
7
 
8
  func ExamRouter(router *gin.Engine, middleware provider.MiddlewareProvider, controller provider.ControllerProvider) {
9
  examCtrl := controller.ProvideExamController()
10
+ authenticationMiddleware := middleware.ProvideAuthenticationMiddleware()
11
+ authorizationMiddleware := middleware.ProvideAuthorizationMiddleware()
12
 
13
+ exam := router.Group("/api/v1/admin/exam", authenticationMiddleware.VerifyAccount, authorizationMiddleware.VerifyAdmin)
14
  {
15
+ exam.POST("", examCtrl.CreateExam)
16
+ exam.GET("", examCtrl.ListExam)
17
+ exam.PUT("/:id", examCtrl.UpdateExam)
18
+ exam.DELETE("/:id", examCtrl.DeleteExam)
19
+ exam.GET("/:id", examCtrl.GetExamDetail)
 
 
 
 
 
 
 
 
 
 
20
 
21
+ exam.POST("/:exam_id/event/:event_id", examCtrl.AssignExamToEvent)
22
+ exam.POST("/:exam_id/academy/:academy_id", examCtrl.AssignExamToAcademy)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  }
24
  }
router/router.go CHANGED
@@ -35,8 +35,9 @@ func RunRouter(appProvider provider.AppProvider) {
35
  AcademyExamRouter(router, middleware, controller)
36
  UploadRouter(router, middleware, controller)
37
  AdminRouter(router, middleware, controller)
 
 
38
  SuperAdminRouter(router, middleware, controller)
39
- ExamRouter(router, middleware, controller)
40
  PaymentCallbackRouter(router, controller)
41
  SwaggerRouter(router)
42
  router.Run(config.ProvideEnvConfig().GetTCPAddress())
 
35
  AcademyExamRouter(router, middleware, controller)
36
  UploadRouter(router, middleware, controller)
37
  AdminRouter(router, middleware, controller)
38
+ AdminEventRouter(router, middleware, controller)
39
+ AdminExamRouter(router, middleware, controller)
40
  SuperAdminRouter(router, middleware, controller)
 
41
  PaymentCallbackRouter(router, controller)
42
  SwaggerRouter(router)
43
  router.Run(config.ProvideEnvConfig().GetTCPAddress())
services/admin_event_service.go ADDED
@@ -0,0 +1,145 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package services
2
+
3
+ import (
4
+ "context"
5
+
6
+ dto "abdanhafidz.com/go-boilerplate/models/dto"
7
+ entity "abdanhafidz.com/go-boilerplate/models/entity"
8
+ http_error "abdanhafidz.com/go-boilerplate/models/error"
9
+ "abdanhafidz.com/go-boilerplate/repositories"
10
+ "github.com/google/uuid"
11
+ )
12
+
13
+ type AdminEventService interface {
14
+ // Event CRUD (delegates to EventService)
15
+ CreateEvent(ctx context.Context, req dto.CreateEventRequest) (entity.Events, error)
16
+ UpdateEvent(ctx context.Context, id uuid.UUID, req dto.UpdateEventRequest) (entity.Events, error)
17
+ DeleteEvent(ctx context.Context, id uuid.UUID) error
18
+
19
+ // Admin-specific event listing with stats
20
+ ListEvents(ctx context.Context, p entity.Pagination) ([]dto.AdminEventResponse, int64, error)
21
+
22
+ // Participant management
23
+ ListParticipants(ctx context.Context, eventId uuid.UUID) ([]entity.EventAssign, error)
24
+ AddParticipant(ctx context.Context, eventId uuid.UUID, userId uuid.UUID) (entity.EventAssign, error)
25
+ RemoveParticipant(ctx context.Context, eventId uuid.UUID, userId uuid.UUID) error
26
+
27
+ // Event exam management
28
+ ListEventExams(ctx context.Context, eventId uuid.UUID) ([]entity.EventExamAssign, error)
29
+ RemoveExam(ctx context.Context, eventId uuid.UUID, examId uuid.UUID) error
30
+
31
+ // Result management
32
+ ListResults(ctx context.Context, eventId uuid.UUID, examId uuid.UUID) ([]entity.Result, error)
33
+ UpdateResult(ctx context.Context, resultId uuid.UUID, req dto.UpdateResultRequest) (entity.Result, error)
34
+ DeleteResult(ctx context.Context, resultId uuid.UUID) error
35
+ }
36
+
37
+ type adminEventService struct {
38
+ eventService EventService
39
+ adminEventRepo repositories.AdminEventRepository
40
+ eventAssignRepo repositories.EventAssignRepository
41
+ eventExamAssignRepo repositories.EventExamAssignRepository
42
+ resultRepo repositories.ResultRepository
43
+ }
44
+
45
+ func NewAdminEventService(
46
+ eventService EventService,
47
+ adminEventRepo repositories.AdminEventRepository,
48
+ eventAssignRepo repositories.EventAssignRepository,
49
+ eventExamAssignRepo repositories.EventExamAssignRepository,
50
+ resultRepo repositories.ResultRepository,
51
+ ) AdminEventService {
52
+ return &adminEventService{
53
+ eventService: eventService,
54
+ adminEventRepo: adminEventRepo,
55
+ eventAssignRepo: eventAssignRepo,
56
+ eventExamAssignRepo: eventExamAssignRepo,
57
+ resultRepo: resultRepo,
58
+ }
59
+ }
60
+
61
+ // --- Event CRUD (delegated) ---
62
+
63
+ func (s *adminEventService) CreateEvent(ctx context.Context, req dto.CreateEventRequest) (entity.Events, error) {
64
+ return s.eventService.CreateEvent(ctx, req)
65
+ }
66
+
67
+ func (s *adminEventService) UpdateEvent(ctx context.Context, id uuid.UUID, req dto.UpdateEventRequest) (entity.Events, error) {
68
+ return s.eventService.UpdateEvent(ctx, id, req)
69
+ }
70
+
71
+ func (s *adminEventService) DeleteEvent(ctx context.Context, id uuid.UUID) error {
72
+ return s.eventService.DeleteEvent(ctx, id)
73
+ }
74
+
75
+ // --- Admin listing with stats ---
76
+
77
+ func (s *adminEventService) ListEvents(ctx context.Context, p entity.Pagination) ([]dto.AdminEventResponse, int64, error) {
78
+ return s.adminEventRepo.ListEventsWithStats(ctx, p)
79
+ }
80
+
81
+ // --- Participant management ---
82
+
83
+ func (s *adminEventService) ListParticipants(ctx context.Context, eventId uuid.UUID) ([]entity.EventAssign, error) {
84
+ return s.adminEventRepo.ListParticipantsWithDetails(ctx, eventId)
85
+ }
86
+
87
+ func (s *adminEventService) AddParticipant(ctx context.Context, eventId uuid.UUID, userId uuid.UUID) (entity.EventAssign, error) {
88
+ existing, err := s.eventAssignRepo.GetByEventAndAccount(ctx, eventId, userId)
89
+ if err == nil && existing.Id != uuid.Nil {
90
+ return entity.EventAssign{}, http_error.ALREADY_REGISTERED_TO_EVENT
91
+ }
92
+
93
+ assign := entity.EventAssign{
94
+ Id: uuid.New(),
95
+ AccountId: userId,
96
+ EventId: eventId,
97
+ }
98
+ return s.eventAssignRepo.Assign(ctx, assign)
99
+ }
100
+
101
+ func (s *adminEventService) RemoveParticipant(ctx context.Context, eventId uuid.UUID, userId uuid.UUID) error {
102
+ existing, err := s.eventAssignRepo.GetByEventAndAccount(ctx, eventId, userId)
103
+ if err != nil || existing.Id == uuid.Nil {
104
+ return http_error.NOT_REGISTERED_TO_EVENT
105
+ }
106
+ return s.adminEventRepo.RemoveParticipant(ctx, eventId, userId)
107
+ }
108
+
109
+ // --- Event exam management ---
110
+
111
+ func (s *adminEventService) ListEventExams(ctx context.Context, eventId uuid.UUID) ([]entity.EventExamAssign, error) {
112
+ return s.eventExamAssignRepo.ListByEvent(ctx, eventId)
113
+ }
114
+
115
+ func (s *adminEventService) RemoveExam(ctx context.Context, eventId uuid.UUID, examId uuid.UUID) error {
116
+ if err := s.eventExamAssignRepo.Check(ctx, eventId, examId); err != nil {
117
+ return http_error.DATA_NOT_FOUND
118
+ }
119
+ return s.adminEventRepo.RemoveExamFromEvent(ctx, eventId, examId)
120
+ }
121
+
122
+ // --- Result management ---
123
+
124
+ func (s *adminEventService) ListResults(ctx context.Context, eventId uuid.UUID, examId uuid.UUID) ([]entity.Result, error) {
125
+ return s.adminEventRepo.ListResultsByEventAndExam(ctx, eventId, examId)
126
+ }
127
+
128
+ func (s *adminEventService) UpdateResult(ctx context.Context, resultId uuid.UUID, req dto.UpdateResultRequest) (entity.Result, error) {
129
+ result, err := s.resultRepo.GetById(ctx, resultId)
130
+ if err != nil {
131
+ return entity.Result{}, http_error.DATA_NOT_FOUND
132
+ }
133
+ result.FinalScore = req.FinalScore
134
+ if err := s.resultRepo.Update(ctx, &result); err != nil {
135
+ return entity.Result{}, err
136
+ }
137
+ return result, nil
138
+ }
139
+
140
+ func (s *adminEventService) DeleteResult(ctx context.Context, resultId uuid.UUID) error {
141
+ if _, err := s.resultRepo.GetById(ctx, resultId); err != nil {
142
+ return http_error.DATA_NOT_FOUND
143
+ }
144
+ return s.adminEventRepo.DeleteResult(ctx, resultId)
145
+ }
services/admin_exam_service.go ADDED
@@ -0,0 +1,155 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package services
2
+
3
+ import (
4
+ "context"
5
+
6
+ dto "abdanhafidz.com/go-boilerplate/models/dto"
7
+ entity "abdanhafidz.com/go-boilerplate/models/entity"
8
+ http_error "abdanhafidz.com/go-boilerplate/models/error"
9
+ "abdanhafidz.com/go-boilerplate/repositories"
10
+ "github.com/google/uuid"
11
+ )
12
+
13
+ type AdminExamService interface {
14
+ CreateExam(ctx context.Context, req dto.CreateExamRequest) (entity.Exam, error)
15
+ UpdateExam(ctx context.Context, id uuid.UUID, req dto.CreateExamRequest) (entity.Exam, error)
16
+ DeleteExam(ctx context.Context, id uuid.UUID) error
17
+ GetExamDetail(ctx context.Context, id uuid.UUID) (entity.Exam, error)
18
+ ListExams(ctx context.Context, p entity.Pagination) ([]dto.AdminExamResponse, int64, error)
19
+ AssignToEvent(ctx context.Context, examId uuid.UUID, eventId uuid.UUID) error
20
+ UnassignFromEvent(ctx context.Context, examId uuid.UUID, eventId uuid.UUID) error
21
+ AssignToAcademy(ctx context.Context, examId uuid.UUID, academyId uuid.UUID) error
22
+ UnassignFromAcademy(ctx context.Context, examId uuid.UUID, academyId uuid.UUID) error
23
+ ListEventsByExam(ctx context.Context, examId uuid.UUID) ([]entity.EventExamAssign, error)
24
+ ListAcademiesByExam(ctx context.Context, examId uuid.UUID) ([]entity.AcademyExamAssign, error)
25
+ }
26
+
27
+ type adminExamService struct {
28
+ adminExamRepo repositories.AdminExamRepository
29
+ }
30
+
31
+ func NewAdminExamService(adminExamRepo repositories.AdminExamRepository) AdminExamService {
32
+ return &adminExamService{adminExamRepo: adminExamRepo}
33
+ }
34
+
35
+ func (s *adminExamService) CreateExam(ctx context.Context, req dto.CreateExamRequest) (entity.Exam, error) {
36
+ exam := entity.Exam{
37
+ Slug: req.Slug,
38
+ Title: req.Title,
39
+ Description: req.Description,
40
+ Duration: req.Duration,
41
+ Randomize: req.Randomize,
42
+ Configuration: entity.ExamConfiguration{
43
+ AllowRetake: req.AllowRetake,
44
+ AllowReview: req.AllowReview,
45
+ EnableTimer: req.EnableTimer,
46
+ },
47
+ Proctoring: entity.ExamProctoring{
48
+ EnableWebCam: req.EnableWebCam,
49
+ EnableVAD: req.EnableVAD,
50
+ EnableTabBlock: req.EnableTabBlock,
51
+ RequiredFullScreen: req.RequiredFullScreen,
52
+ EnableEyeTracking: req.EnableEyeTracking,
53
+ DisableCopyPaste: req.DisableCopyPaste,
54
+ EnableExamBrowser: req.EnableExamBrowser,
55
+ },
56
+ }
57
+
58
+ if err := s.adminExamRepo.Create(ctx, &exam); err != nil {
59
+ return entity.Exam{}, err
60
+ }
61
+
62
+ return exam, nil
63
+ }
64
+
65
+ func (s *adminExamService) UpdateExam(ctx context.Context, id uuid.UUID, req dto.CreateExamRequest) (entity.Exam, error) {
66
+ exam, err := s.adminExamRepo.Get(ctx, id)
67
+ if err != nil {
68
+ return entity.Exam{}, http_error.DATA_NOT_FOUND
69
+ }
70
+
71
+ exam.Slug = req.Slug
72
+ exam.Title = req.Title
73
+ exam.Description = req.Description
74
+ exam.Duration = req.Duration
75
+ exam.Randomize = req.Randomize
76
+ exam.Configuration.AllowRetake = req.AllowRetake
77
+ exam.Configuration.AllowReview = req.AllowReview
78
+ exam.Configuration.EnableTimer = req.EnableTimer
79
+ exam.Proctoring.EnableWebCam = req.EnableWebCam
80
+ exam.Proctoring.EnableVAD = req.EnableVAD
81
+ exam.Proctoring.EnableTabBlock = req.EnableTabBlock
82
+ exam.Proctoring.RequiredFullScreen = req.RequiredFullScreen
83
+ exam.Proctoring.EnableEyeTracking = req.EnableEyeTracking
84
+ exam.Proctoring.DisableCopyPaste = req.DisableCopyPaste
85
+ exam.Proctoring.EnableExamBrowser = req.EnableExamBrowser
86
+
87
+ if err := s.adminExamRepo.Update(ctx, exam); err != nil {
88
+ return entity.Exam{}, err
89
+ }
90
+
91
+ return exam, nil
92
+ }
93
+
94
+ func (s *adminExamService) DeleteExam(ctx context.Context, id uuid.UUID) error {
95
+ if _, err := s.adminExamRepo.Get(ctx, id); err != nil {
96
+ return http_error.DATA_NOT_FOUND
97
+ }
98
+ return s.adminExamRepo.Delete(ctx, id)
99
+ }
100
+
101
+ func (s *adminExamService) GetExamDetail(ctx context.Context, id uuid.UUID) (entity.Exam, error) {
102
+ exam, err := s.adminExamRepo.Get(ctx, id)
103
+ if err != nil {
104
+ return entity.Exam{}, http_error.DATA_NOT_FOUND
105
+ }
106
+ return exam, nil
107
+ }
108
+
109
+ func (s *adminExamService) ListExams(ctx context.Context, p entity.Pagination) ([]dto.AdminExamResponse, int64, error) {
110
+ return s.adminExamRepo.List(ctx, p)
111
+ }
112
+
113
+ func (s *adminExamService) AssignToEvent(ctx context.Context, examId uuid.UUID, eventId uuid.UUID) error {
114
+ if s.adminExamRepo.IsAssignedToEvent(ctx, examId, eventId) {
115
+ return http_error.DUPLICATE_DATA
116
+ }
117
+ assign := entity.EventExamAssign{
118
+ ExamId: examId,
119
+ EventId: eventId,
120
+ }
121
+ return s.adminExamRepo.AssignToEvent(ctx, assign)
122
+ }
123
+
124
+ func (s *adminExamService) UnassignFromEvent(ctx context.Context, examId uuid.UUID, eventId uuid.UUID) error {
125
+ if !s.adminExamRepo.IsAssignedToEvent(ctx, examId, eventId) {
126
+ return http_error.DATA_NOT_FOUND
127
+ }
128
+ return s.adminExamRepo.UnassignFromEvent(ctx, examId, eventId)
129
+ }
130
+
131
+ func (s *adminExamService) AssignToAcademy(ctx context.Context, examId uuid.UUID, academyId uuid.UUID) error {
132
+ if s.adminExamRepo.IsAssignedToAcademy(ctx, examId, academyId) {
133
+ return http_error.DUPLICATE_DATA
134
+ }
135
+ assign := entity.AcademyExamAssign{
136
+ ExamId: examId,
137
+ AcademyId: academyId,
138
+ }
139
+ return s.adminExamRepo.AssignToAcademy(ctx, assign)
140
+ }
141
+
142
+ func (s *adminExamService) UnassignFromAcademy(ctx context.Context, examId uuid.UUID, academyId uuid.UUID) error {
143
+ if !s.adminExamRepo.IsAssignedToAcademy(ctx, examId, academyId) {
144
+ return http_error.DATA_NOT_FOUND
145
+ }
146
+ return s.adminExamRepo.UnassignFromAcademy(ctx, examId, academyId)
147
+ }
148
+
149
+ func (s *adminExamService) ListEventsByExam(ctx context.Context, examId uuid.UUID) ([]entity.EventExamAssign, error) {
150
+ return s.adminExamRepo.ListEventsByExam(ctx, examId)
151
+ }
152
+
153
+ func (s *adminExamService) ListAcademiesByExam(ctx context.Context, examId uuid.UUID) ([]entity.AcademyExamAssign, error) {
154
+ return s.adminExamRepo.ListAcademiesByExam(ctx, examId)
155
+ }
swagger/docs/docs.go CHANGED
@@ -739,8 +739,97 @@ const docTemplate = `{
739
  }
740
  },
741
  "/api/v1/admin/events": {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
742
  "post": {
743
- "description": "Create a new event with the provided details",
 
 
 
 
 
744
  "consumes": [
745
  "application/json"
746
  ],
@@ -748,9 +837,9 @@ const docTemplate = `{
748
  "application/json"
749
  ],
750
  "tags": [
751
- "Event"
752
  ],
753
- "summary": "Create Event",
754
  "parameters": [
755
  {
756
  "description": "Create Event Request",
@@ -766,7 +855,7 @@ const docTemplate = `{
766
  "200": {
767
  "description": "OK",
768
  "schema": {
769
- "$ref": "#/definitions/dto.SuccessResponse-dto_EventDetailResponse"
770
  }
771
  },
772
  "400": {
@@ -774,13 +863,30 @@ const docTemplate = `{
774
  "schema": {
775
  "$ref": "#/definitions/dto.ErrorResponse"
776
  }
 
 
 
 
 
 
 
 
 
 
 
 
777
  }
778
  }
779
  }
780
  },
781
- "/api/v1/admin/events/{id}": {
782
  "put": {
783
- "description": "Update an existing event with the provided details",
 
 
 
 
 
784
  "consumes": [
785
  "application/json"
786
  ],
@@ -788,14 +894,14 @@ const docTemplate = `{
788
  "application/json"
789
  ],
790
  "tags": [
791
- "Event"
792
  ],
793
- "summary": "Update Event",
794
  "parameters": [
795
  {
796
  "type": "string",
797
  "description": "Event ID",
798
- "name": "id",
799
  "in": "path",
800
  "required": true
801
  },
@@ -813,19 +919,532 @@ const docTemplate = `{
813
  "200": {
814
  "description": "OK",
815
  "schema": {
816
- "$ref": "#/definitions/dto.SuccessResponse-models_Events"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
817
  }
818
  },
819
- "400": {
820
- "description": "Bad Request",
821
  "schema": {
822
  "$ref": "#/definitions/dto.ErrorResponse"
823
  }
824
  }
825
  }
826
- },
 
 
827
  "delete": {
828
- "description": "Delete an existing event by its ID",
 
 
 
 
 
829
  "consumes": [
830
  "application/json"
831
  ],
@@ -833,14 +1452,21 @@ const docTemplate = `{
833
  "application/json"
834
  ],
835
  "tags": [
836
- "Event"
837
  ],
838
- "summary": "Delete Event",
839
  "parameters": [
840
  {
841
  "type": "string",
842
  "description": "Event ID",
843
- "name": "id",
 
 
 
 
 
 
 
844
  "in": "path",
845
  "required": true
846
  }
@@ -857,6 +1483,18 @@ const docTemplate = `{
857
  "schema": {
858
  "$ref": "#/definitions/dto.ErrorResponse"
859
  }
 
 
 
 
 
 
 
 
 
 
 
 
860
  }
861
  }
862
  }
@@ -2468,7 +3106,7 @@ const docTemplate = `{
2468
  "BearerAuth": []
2469
  }
2470
  ],
2471
- "description": "Retrieve a list of all users in the system",
2472
  "consumes": [
2473
  "application/json"
2474
  ],
@@ -2476,9 +3114,29 @@ const docTemplate = `{
2476
  "application/json"
2477
  ],
2478
  "tags": [
2479
- "Admin Users"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2480
  ],
2481
- "summary": "List All Users",
2482
  "responses": {
2483
  "200": {
2484
  "description": "OK",
@@ -2486,6 +3144,12 @@ const docTemplate = `{
2486
  "$ref": "#/definitions/dto.SuccessResponse-array_dto_UserResponse"
2487
  }
2488
  },
 
 
 
 
 
 
2489
  "401": {
2490
  "description": "Unauthorized",
2491
  "schema": {
@@ -2514,7 +3178,7 @@ const docTemplate = `{
2514
  "application/json"
2515
  ],
2516
  "tags": [
2517
- "Admin Users"
2518
  ],
2519
  "summary": "Create Single User",
2520
  "parameters": [
@@ -2571,7 +3235,7 @@ const docTemplate = `{
2571
  "application/json"
2572
  ],
2573
  "tags": [
2574
- "Admin Users"
2575
  ],
2576
  "summary": "Bulk Create Users",
2577
  "parameters": [
@@ -2631,7 +3295,7 @@ const docTemplate = `{
2631
  "application/json"
2632
  ],
2633
  "tags": [
2634
- "Admin Users"
2635
  ],
2636
  "summary": "Edit User",
2637
  "parameters": [
@@ -2699,7 +3363,7 @@ const docTemplate = `{
2699
  "application/json"
2700
  ],
2701
  "tags": [
2702
- "Admin Users"
2703
  ],
2704
  "summary": "Delete User",
2705
  "parameters": [
@@ -2775,6 +3439,67 @@ const docTemplate = `{
2775
  }
2776
  }
2777
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2778
  "dto.AnswerEventExamRequest": {
2779
  "type": "object",
2780
  "required": [
@@ -3296,6 +4021,22 @@ const docTemplate = `{
3296
  }
3297
  }
3298
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3299
  "dto.SuccessResponse-array_dto_UserResponse": {
3300
  "type": "object",
3301
  "properties": {
@@ -3328,6 +4069,38 @@ const docTemplate = `{
3328
  }
3329
  }
3330
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3331
  "dto.SuccessResponse-array_models_EventExamProctoringLogs": {
3332
  "type": "object",
3333
  "properties": {
@@ -3408,6 +4181,22 @@ const docTemplate = `{
3408
  }
3409
  }
3410
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3411
  "dto.SuccessResponse-dto_AcademyMiniDetailResponse": {
3412
  "type": "object",
3413
  "properties": {
@@ -3629,6 +4418,19 @@ const docTemplate = `{
3629
  }
3630
  }
3631
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
3632
  "dto.SuccessResponse-models_EventExamAttempt": {
3633
  "type": "object",
3634
  "properties": {
@@ -3707,6 +4509,19 @@ const docTemplate = `{
3707
  }
3708
  }
3709
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
3710
  "dto.SuccessResponse-string": {
3711
  "type": "object",
3712
  "properties": {
@@ -3786,6 +4601,17 @@ const docTemplate = `{
3786
  }
3787
  }
3788
  },
 
 
 
 
 
 
 
 
 
 
 
3789
  "dto.UpdateUserRequest": {
3790
  "type": "object",
3791
  "properties": {
@@ -4291,6 +5117,29 @@ const docTemplate = `{
4291
  }
4292
  }
4293
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4294
  "models.EventExamAnswer": {
4295
  "type": "object",
4296
  "properties": {
@@ -4323,6 +5172,26 @@ const docTemplate = `{
4323
  }
4324
  }
4325
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4326
  "models.EventExamAttempt": {
4327
  "type": "object",
4328
  "properties": {
@@ -4710,6 +5579,23 @@ const docTemplate = `{
4710
  "type": "string"
4711
  }
4712
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4713
  }
4714
  },
4715
  "securityDefinitions": {
 
739
  }
740
  },
741
  "/api/v1/admin/events": {
742
+ "get": {
743
+ "security": [
744
+ {
745
+ "BearerAuth": []
746
+ }
747
+ ],
748
+ "description": "Admin view of all events with participant count, exam count and optional filters/pagination",
749
+ "consumes": [
750
+ "application/json"
751
+ ],
752
+ "produces": [
753
+ "application/json"
754
+ ],
755
+ "tags": [
756
+ "AdminEvent"
757
+ ],
758
+ "summary": "Admin: List Events",
759
+ "parameters": [
760
+ {
761
+ "type": "integer",
762
+ "default": 10,
763
+ "description": "Items per page",
764
+ "name": "limit",
765
+ "in": "query"
766
+ },
767
+ {
768
+ "type": "integer",
769
+ "default": 1,
770
+ "description": "Page number",
771
+ "name": "page",
772
+ "in": "query"
773
+ },
774
+ {
775
+ "type": "string",
776
+ "description": "Search by title / slug / event code",
777
+ "name": "search",
778
+ "in": "query"
779
+ },
780
+ {
781
+ "type": "string",
782
+ "description": "Sort field (title, start_event, end_event, created_at, participant_count, exam_count)",
783
+ "name": "sortBy",
784
+ "in": "query"
785
+ },
786
+ {
787
+ "type": "string",
788
+ "description": "Sort direction (asc / desc)",
789
+ "name": "order",
790
+ "in": "query"
791
+ },
792
+ {
793
+ "type": "string",
794
+ "description": "Filter by status (UPCOMING, ONGOING, ENDED)",
795
+ "name": "status",
796
+ "in": "query"
797
+ }
798
+ ],
799
+ "responses": {
800
+ "200": {
801
+ "description": "OK",
802
+ "schema": {
803
+ "$ref": "#/definitions/dto.SuccessResponse-array_dto_AdminEventResponse"
804
+ }
805
+ },
806
+ "400": {
807
+ "description": "Bad Request",
808
+ "schema": {
809
+ "$ref": "#/definitions/dto.ErrorResponse"
810
+ }
811
+ },
812
+ "401": {
813
+ "description": "Unauthorized",
814
+ "schema": {
815
+ "$ref": "#/definitions/dto.ErrorResponse"
816
+ }
817
+ },
818
+ "403": {
819
+ "description": "Forbidden",
820
+ "schema": {
821
+ "$ref": "#/definitions/dto.ErrorResponse"
822
+ }
823
+ }
824
+ }
825
+ },
826
  "post": {
827
+ "security": [
828
+ {
829
+ "BearerAuth": []
830
+ }
831
+ ],
832
+ "description": "Create a new event",
833
  "consumes": [
834
  "application/json"
835
  ],
 
837
  "application/json"
838
  ],
839
  "tags": [
840
+ "AdminEvent"
841
  ],
842
+ "summary": "Admin: Create Event",
843
  "parameters": [
844
  {
845
  "description": "Create Event Request",
 
855
  "200": {
856
  "description": "OK",
857
  "schema": {
858
+ "$ref": "#/definitions/dto.SuccessResponse-models_Events"
859
  }
860
  },
861
  "400": {
 
863
  "schema": {
864
  "$ref": "#/definitions/dto.ErrorResponse"
865
  }
866
+ },
867
+ "401": {
868
+ "description": "Unauthorized",
869
+ "schema": {
870
+ "$ref": "#/definitions/dto.ErrorResponse"
871
+ }
872
+ },
873
+ "403": {
874
+ "description": "Forbidden",
875
+ "schema": {
876
+ "$ref": "#/definitions/dto.ErrorResponse"
877
+ }
878
  }
879
  }
880
  }
881
  },
882
+ "/api/v1/admin/events/{event_id}": {
883
  "put": {
884
+ "security": [
885
+ {
886
+ "BearerAuth": []
887
+ }
888
+ ],
889
+ "description": "Update an existing event by ID",
890
  "consumes": [
891
  "application/json"
892
  ],
 
894
  "application/json"
895
  ],
896
  "tags": [
897
+ "AdminEvent"
898
  ],
899
+ "summary": "Admin: Update Event",
900
  "parameters": [
901
  {
902
  "type": "string",
903
  "description": "Event ID",
904
+ "name": "event_id",
905
  "in": "path",
906
  "required": true
907
  },
 
919
  "200": {
920
  "description": "OK",
921
  "schema": {
922
+ "$ref": "#/definitions/dto.SuccessResponse-models_Events"
923
+ }
924
+ },
925
+ "400": {
926
+ "description": "Bad Request",
927
+ "schema": {
928
+ "$ref": "#/definitions/dto.ErrorResponse"
929
+ }
930
+ },
931
+ "401": {
932
+ "description": "Unauthorized",
933
+ "schema": {
934
+ "$ref": "#/definitions/dto.ErrorResponse"
935
+ }
936
+ },
937
+ "403": {
938
+ "description": "Forbidden",
939
+ "schema": {
940
+ "$ref": "#/definitions/dto.ErrorResponse"
941
+ }
942
+ }
943
+ }
944
+ },
945
+ "delete": {
946
+ "security": [
947
+ {
948
+ "BearerAuth": []
949
+ }
950
+ ],
951
+ "description": "Delete an event by ID",
952
+ "consumes": [
953
+ "application/json"
954
+ ],
955
+ "produces": [
956
+ "application/json"
957
+ ],
958
+ "tags": [
959
+ "AdminEvent"
960
+ ],
961
+ "summary": "Admin: Delete Event",
962
+ "parameters": [
963
+ {
964
+ "type": "string",
965
+ "description": "Event ID",
966
+ "name": "event_id",
967
+ "in": "path",
968
+ "required": true
969
+ }
970
+ ],
971
+ "responses": {
972
+ "200": {
973
+ "description": "OK",
974
+ "schema": {
975
+ "$ref": "#/definitions/dto.SuccessResponse-map_string_bool"
976
+ }
977
+ },
978
+ "400": {
979
+ "description": "Bad Request",
980
+ "schema": {
981
+ "$ref": "#/definitions/dto.ErrorResponse"
982
+ }
983
+ },
984
+ "401": {
985
+ "description": "Unauthorized",
986
+ "schema": {
987
+ "$ref": "#/definitions/dto.ErrorResponse"
988
+ }
989
+ },
990
+ "403": {
991
+ "description": "Forbidden",
992
+ "schema": {
993
+ "$ref": "#/definitions/dto.ErrorResponse"
994
+ }
995
+ }
996
+ }
997
+ }
998
+ },
999
+ "/api/v1/admin/events/{event_id}/exam/{exam_id}/results": {
1000
+ "get": {
1001
+ "security": [
1002
+ {
1003
+ "BearerAuth": []
1004
+ }
1005
+ ],
1006
+ "description": "Retrieve all participant results for a specific exam within an event",
1007
+ "consumes": [
1008
+ "application/json"
1009
+ ],
1010
+ "produces": [
1011
+ "application/json"
1012
+ ],
1013
+ "tags": [
1014
+ "AdminEvent"
1015
+ ],
1016
+ "summary": "Admin: List Exam Results for an Event",
1017
+ "parameters": [
1018
+ {
1019
+ "type": "string",
1020
+ "description": "Event ID",
1021
+ "name": "event_id",
1022
+ "in": "path",
1023
+ "required": true
1024
+ },
1025
+ {
1026
+ "type": "string",
1027
+ "description": "Exam ID",
1028
+ "name": "exam_id",
1029
+ "in": "path",
1030
+ "required": true
1031
+ }
1032
+ ],
1033
+ "responses": {
1034
+ "200": {
1035
+ "description": "OK",
1036
+ "schema": {
1037
+ "$ref": "#/definitions/dto.SuccessResponse-array_models_Result"
1038
+ }
1039
+ },
1040
+ "400": {
1041
+ "description": "Bad Request",
1042
+ "schema": {
1043
+ "$ref": "#/definitions/dto.ErrorResponse"
1044
+ }
1045
+ },
1046
+ "401": {
1047
+ "description": "Unauthorized",
1048
+ "schema": {
1049
+ "$ref": "#/definitions/dto.ErrorResponse"
1050
+ }
1051
+ },
1052
+ "403": {
1053
+ "description": "Forbidden",
1054
+ "schema": {
1055
+ "$ref": "#/definitions/dto.ErrorResponse"
1056
+ }
1057
+ }
1058
+ }
1059
+ }
1060
+ },
1061
+ "/api/v1/admin/events/{event_id}/exam/{exam_id}/results/{result_id}": {
1062
+ "put": {
1063
+ "security": [
1064
+ {
1065
+ "BearerAuth": []
1066
+ }
1067
+ ],
1068
+ "description": "Edit the final score of a participant's exam result",
1069
+ "consumes": [
1070
+ "application/json"
1071
+ ],
1072
+ "produces": [
1073
+ "application/json"
1074
+ ],
1075
+ "tags": [
1076
+ "AdminEvent"
1077
+ ],
1078
+ "summary": "Admin: Update Exam Result",
1079
+ "parameters": [
1080
+ {
1081
+ "type": "string",
1082
+ "description": "Event ID",
1083
+ "name": "event_id",
1084
+ "in": "path",
1085
+ "required": true
1086
+ },
1087
+ {
1088
+ "type": "string",
1089
+ "description": "Exam ID",
1090
+ "name": "exam_id",
1091
+ "in": "path",
1092
+ "required": true
1093
+ },
1094
+ {
1095
+ "type": "string",
1096
+ "description": "Result ID",
1097
+ "name": "result_id",
1098
+ "in": "path",
1099
+ "required": true
1100
+ },
1101
+ {
1102
+ "description": "Update Result Request",
1103
+ "name": "request",
1104
+ "in": "body",
1105
+ "required": true,
1106
+ "schema": {
1107
+ "$ref": "#/definitions/dto.UpdateResultRequest"
1108
+ }
1109
+ }
1110
+ ],
1111
+ "responses": {
1112
+ "200": {
1113
+ "description": "OK",
1114
+ "schema": {
1115
+ "$ref": "#/definitions/dto.SuccessResponse-models_Result"
1116
+ }
1117
+ },
1118
+ "400": {
1119
+ "description": "Bad Request",
1120
+ "schema": {
1121
+ "$ref": "#/definitions/dto.ErrorResponse"
1122
+ }
1123
+ },
1124
+ "401": {
1125
+ "description": "Unauthorized",
1126
+ "schema": {
1127
+ "$ref": "#/definitions/dto.ErrorResponse"
1128
+ }
1129
+ },
1130
+ "403": {
1131
+ "description": "Forbidden",
1132
+ "schema": {
1133
+ "$ref": "#/definitions/dto.ErrorResponse"
1134
+ }
1135
+ }
1136
+ }
1137
+ },
1138
+ "delete": {
1139
+ "security": [
1140
+ {
1141
+ "BearerAuth": []
1142
+ }
1143
+ ],
1144
+ "description": "Delete a participant's exam result",
1145
+ "consumes": [
1146
+ "application/json"
1147
+ ],
1148
+ "produces": [
1149
+ "application/json"
1150
+ ],
1151
+ "tags": [
1152
+ "AdminEvent"
1153
+ ],
1154
+ "summary": "Admin: Delete Exam Result",
1155
+ "parameters": [
1156
+ {
1157
+ "type": "string",
1158
+ "description": "Event ID",
1159
+ "name": "event_id",
1160
+ "in": "path",
1161
+ "required": true
1162
+ },
1163
+ {
1164
+ "type": "string",
1165
+ "description": "Exam ID",
1166
+ "name": "exam_id",
1167
+ "in": "path",
1168
+ "required": true
1169
+ },
1170
+ {
1171
+ "type": "string",
1172
+ "description": "Result ID",
1173
+ "name": "result_id",
1174
+ "in": "path",
1175
+ "required": true
1176
+ }
1177
+ ],
1178
+ "responses": {
1179
+ "200": {
1180
+ "description": "OK",
1181
+ "schema": {
1182
+ "$ref": "#/definitions/dto.SuccessResponse-map_string_bool"
1183
+ }
1184
+ },
1185
+ "400": {
1186
+ "description": "Bad Request",
1187
+ "schema": {
1188
+ "$ref": "#/definitions/dto.ErrorResponse"
1189
+ }
1190
+ },
1191
+ "401": {
1192
+ "description": "Unauthorized",
1193
+ "schema": {
1194
+ "$ref": "#/definitions/dto.ErrorResponse"
1195
+ }
1196
+ },
1197
+ "403": {
1198
+ "description": "Forbidden",
1199
+ "schema": {
1200
+ "$ref": "#/definitions/dto.ErrorResponse"
1201
+ }
1202
+ }
1203
+ }
1204
+ }
1205
+ },
1206
+ "/api/v1/admin/events/{event_id}/exams": {
1207
+ "get": {
1208
+ "security": [
1209
+ {
1210
+ "BearerAuth": []
1211
+ }
1212
+ ],
1213
+ "description": "List all exams assigned to an event",
1214
+ "consumes": [
1215
+ "application/json"
1216
+ ],
1217
+ "produces": [
1218
+ "application/json"
1219
+ ],
1220
+ "tags": [
1221
+ "AdminEvent"
1222
+ ],
1223
+ "summary": "Admin: List Event Exams",
1224
+ "parameters": [
1225
+ {
1226
+ "type": "string",
1227
+ "description": "Event ID",
1228
+ "name": "event_id",
1229
+ "in": "path",
1230
+ "required": true
1231
+ }
1232
+ ],
1233
+ "responses": {
1234
+ "200": {
1235
+ "description": "OK",
1236
+ "schema": {
1237
+ "$ref": "#/definitions/dto.SuccessResponse-array_models_EventExamAssign"
1238
+ }
1239
+ },
1240
+ "400": {
1241
+ "description": "Bad Request",
1242
+ "schema": {
1243
+ "$ref": "#/definitions/dto.ErrorResponse"
1244
+ }
1245
+ },
1246
+ "401": {
1247
+ "description": "Unauthorized",
1248
+ "schema": {
1249
+ "$ref": "#/definitions/dto.ErrorResponse"
1250
+ }
1251
+ },
1252
+ "403": {
1253
+ "description": "Forbidden",
1254
+ "schema": {
1255
+ "$ref": "#/definitions/dto.ErrorResponse"
1256
+ }
1257
+ }
1258
+ }
1259
+ }
1260
+ },
1261
+ "/api/v1/admin/events/{event_id}/exams/{exam_id}": {
1262
+ "delete": {
1263
+ "security": [
1264
+ {
1265
+ "BearerAuth": []
1266
+ }
1267
+ ],
1268
+ "description": "Unassign an exam from an event",
1269
+ "consumes": [
1270
+ "application/json"
1271
+ ],
1272
+ "produces": [
1273
+ "application/json"
1274
+ ],
1275
+ "tags": [
1276
+ "AdminEvent"
1277
+ ],
1278
+ "summary": "Admin: Remove Exam from Event",
1279
+ "parameters": [
1280
+ {
1281
+ "type": "string",
1282
+ "description": "Event ID",
1283
+ "name": "event_id",
1284
+ "in": "path",
1285
+ "required": true
1286
+ },
1287
+ {
1288
+ "type": "string",
1289
+ "description": "Exam ID",
1290
+ "name": "exam_id",
1291
+ "in": "path",
1292
+ "required": true
1293
+ }
1294
+ ],
1295
+ "responses": {
1296
+ "200": {
1297
+ "description": "OK",
1298
+ "schema": {
1299
+ "$ref": "#/definitions/dto.SuccessResponse-map_string_bool"
1300
+ }
1301
+ },
1302
+ "400": {
1303
+ "description": "Bad Request",
1304
+ "schema": {
1305
+ "$ref": "#/definitions/dto.ErrorResponse"
1306
+ }
1307
+ },
1308
+ "401": {
1309
+ "description": "Unauthorized",
1310
+ "schema": {
1311
+ "$ref": "#/definitions/dto.ErrorResponse"
1312
+ }
1313
+ },
1314
+ "403": {
1315
+ "description": "Forbidden",
1316
+ "schema": {
1317
+ "$ref": "#/definitions/dto.ErrorResponse"
1318
+ }
1319
+ }
1320
+ }
1321
+ }
1322
+ },
1323
+ "/api/v1/admin/events/{event_id}/participants": {
1324
+ "get": {
1325
+ "security": [
1326
+ {
1327
+ "BearerAuth": []
1328
+ }
1329
+ ],
1330
+ "description": "List all participants assigned to an event",
1331
+ "consumes": [
1332
+ "application/json"
1333
+ ],
1334
+ "produces": [
1335
+ "application/json"
1336
+ ],
1337
+ "tags": [
1338
+ "AdminEvent"
1339
+ ],
1340
+ "summary": "Admin: List Event Participants",
1341
+ "parameters": [
1342
+ {
1343
+ "type": "string",
1344
+ "description": "Event ID",
1345
+ "name": "event_id",
1346
+ "in": "path",
1347
+ "required": true
1348
+ }
1349
+ ],
1350
+ "responses": {
1351
+ "200": {
1352
+ "description": "OK",
1353
+ "schema": {
1354
+ "$ref": "#/definitions/dto.SuccessResponse-array_models_EventAssign"
1355
+ }
1356
+ },
1357
+ "400": {
1358
+ "description": "Bad Request",
1359
+ "schema": {
1360
+ "$ref": "#/definitions/dto.ErrorResponse"
1361
+ }
1362
+ },
1363
+ "401": {
1364
+ "description": "Unauthorized",
1365
+ "schema": {
1366
+ "$ref": "#/definitions/dto.ErrorResponse"
1367
+ }
1368
+ },
1369
+ "403": {
1370
+ "description": "Forbidden",
1371
+ "schema": {
1372
+ "$ref": "#/definitions/dto.ErrorResponse"
1373
+ }
1374
+ }
1375
+ }
1376
+ },
1377
+ "post": {
1378
+ "security": [
1379
+ {
1380
+ "BearerAuth": []
1381
+ }
1382
+ ],
1383
+ "description": "Manually assign a user to an event (bypasses payment)",
1384
+ "consumes": [
1385
+ "application/json"
1386
+ ],
1387
+ "produces": [
1388
+ "application/json"
1389
+ ],
1390
+ "tags": [
1391
+ "AdminEvent"
1392
+ ],
1393
+ "summary": "Admin: Add Participant to Event",
1394
+ "parameters": [
1395
+ {
1396
+ "type": "string",
1397
+ "description": "Event ID",
1398
+ "name": "event_id",
1399
+ "in": "path",
1400
+ "required": true
1401
+ },
1402
+ {
1403
+ "description": "Add Participant Request",
1404
+ "name": "request",
1405
+ "in": "body",
1406
+ "required": true,
1407
+ "schema": {
1408
+ "$ref": "#/definitions/dto.AddParticipantRequest"
1409
+ }
1410
+ }
1411
+ ],
1412
+ "responses": {
1413
+ "200": {
1414
+ "description": "OK",
1415
+ "schema": {
1416
+ "$ref": "#/definitions/dto.SuccessResponse-models_EventAssign"
1417
+ }
1418
+ },
1419
+ "400": {
1420
+ "description": "Bad Request",
1421
+ "schema": {
1422
+ "$ref": "#/definitions/dto.ErrorResponse"
1423
+ }
1424
+ },
1425
+ "401": {
1426
+ "description": "Unauthorized",
1427
+ "schema": {
1428
+ "$ref": "#/definitions/dto.ErrorResponse"
1429
  }
1430
  },
1431
+ "403": {
1432
+ "description": "Forbidden",
1433
  "schema": {
1434
  "$ref": "#/definitions/dto.ErrorResponse"
1435
  }
1436
  }
1437
  }
1438
+ }
1439
+ },
1440
+ "/api/v1/admin/events/{event_id}/participants/{user_id}": {
1441
  "delete": {
1442
+ "security": [
1443
+ {
1444
+ "BearerAuth": []
1445
+ }
1446
+ ],
1447
+ "description": "Unassign a user from an event",
1448
  "consumes": [
1449
  "application/json"
1450
  ],
 
1452
  "application/json"
1453
  ],
1454
  "tags": [
1455
+ "AdminEvent"
1456
  ],
1457
+ "summary": "Admin: Remove Participant from Event",
1458
  "parameters": [
1459
  {
1460
  "type": "string",
1461
  "description": "Event ID",
1462
+ "name": "event_id",
1463
+ "in": "path",
1464
+ "required": true
1465
+ },
1466
+ {
1467
+ "type": "string",
1468
+ "description": "User ID",
1469
+ "name": "user_id",
1470
  "in": "path",
1471
  "required": true
1472
  }
 
1483
  "schema": {
1484
  "$ref": "#/definitions/dto.ErrorResponse"
1485
  }
1486
+ },
1487
+ "401": {
1488
+ "description": "Unauthorized",
1489
+ "schema": {
1490
+ "$ref": "#/definitions/dto.ErrorResponse"
1491
+ }
1492
+ },
1493
+ "403": {
1494
+ "description": "Forbidden",
1495
+ "schema": {
1496
+ "$ref": "#/definitions/dto.ErrorResponse"
1497
+ }
1498
  }
1499
  }
1500
  }
 
3106
  "BearerAuth": []
3107
  }
3108
  ],
3109
+ "description": "Retrieve a paginated list of all users with optional role filter. Supports pagination parameters (page, limit) and can filter by user role.",
3110
  "consumes": [
3111
  "application/json"
3112
  ],
 
3114
  "application/json"
3115
  ],
3116
  "tags": [
3117
+ "Super Admin Users Management"
3118
+ ],
3119
+ "summary": "List All Users with Pagination",
3120
+ "parameters": [
3121
+ {
3122
+ "type": "integer",
3123
+ "description": "Page number for pagination. Minimum value is 1. Default is 1.",
3124
+ "name": "page",
3125
+ "in": "query"
3126
+ },
3127
+ {
3128
+ "type": "integer",
3129
+ "description": "Number of items per page. Minimum 1, Maximum 50. Default is 10.",
3130
+ "name": "limit",
3131
+ "in": "query"
3132
+ },
3133
+ {
3134
+ "type": "string",
3135
+ "description": "Filter users by role. Allowed values: user, admin, super_admin. Leave empty for no filter.",
3136
+ "name": "role",
3137
+ "in": "query"
3138
+ }
3139
  ],
 
3140
  "responses": {
3141
  "200": {
3142
  "description": "OK",
 
3144
  "$ref": "#/definitions/dto.SuccessResponse-array_dto_UserResponse"
3145
  }
3146
  },
3147
+ "400": {
3148
+ "description": "Bad Request",
3149
+ "schema": {
3150
+ "$ref": "#/definitions/dto.ErrorResponse"
3151
+ }
3152
+ },
3153
  "401": {
3154
  "description": "Unauthorized",
3155
  "schema": {
 
3178
  "application/json"
3179
  ],
3180
  "tags": [
3181
+ "Super Admin Users Management"
3182
  ],
3183
  "summary": "Create Single User",
3184
  "parameters": [
 
3235
  "application/json"
3236
  ],
3237
  "tags": [
3238
+ "Super Admin Users Management"
3239
  ],
3240
  "summary": "Bulk Create Users",
3241
  "parameters": [
 
3295
  "application/json"
3296
  ],
3297
  "tags": [
3298
+ "Super Admin Users Management"
3299
  ],
3300
  "summary": "Edit User",
3301
  "parameters": [
 
3363
  "application/json"
3364
  ],
3365
  "tags": [
3366
+ "Super Admin Users Management"
3367
  ],
3368
  "summary": "Delete User",
3369
  "parameters": [
 
3439
  }
3440
  }
3441
  },
3442
+ "dto.AddParticipantRequest": {
3443
+ "type": "object",
3444
+ "required": [
3445
+ "user_id"
3446
+ ],
3447
+ "properties": {
3448
+ "user_id": {
3449
+ "type": "string"
3450
+ }
3451
+ }
3452
+ },
3453
+ "dto.AdminEventResponse": {
3454
+ "type": "object",
3455
+ "properties": {
3456
+ "created_at": {
3457
+ "type": "string"
3458
+ },
3459
+ "deleted_at": {
3460
+ "$ref": "#/definitions/gorm.DeletedAt"
3461
+ },
3462
+ "end_event": {
3463
+ "type": "string"
3464
+ },
3465
+ "event_code": {
3466
+ "type": "string"
3467
+ },
3468
+ "exam_count": {
3469
+ "type": "integer"
3470
+ },
3471
+ "id_event": {
3472
+ "type": "string"
3473
+ },
3474
+ "img_banner": {
3475
+ "type": "string"
3476
+ },
3477
+ "is_public": {
3478
+ "type": "boolean"
3479
+ },
3480
+ "overview": {
3481
+ "type": "string"
3482
+ },
3483
+ "participant_count": {
3484
+ "type": "integer"
3485
+ },
3486
+ "price": {
3487
+ "type": "number"
3488
+ },
3489
+ "register_status": {
3490
+ "type": "integer"
3491
+ },
3492
+ "slug": {
3493
+ "type": "string"
3494
+ },
3495
+ "start_event": {
3496
+ "type": "string"
3497
+ },
3498
+ "title": {
3499
+ "type": "string"
3500
+ }
3501
+ }
3502
+ },
3503
  "dto.AnswerEventExamRequest": {
3504
  "type": "object",
3505
  "required": [
 
4021
  }
4022
  }
4023
  },
4024
+ "dto.SuccessResponse-array_dto_AdminEventResponse": {
4025
+ "type": "object",
4026
+ "properties": {
4027
+ "data": {
4028
+ "type": "array",
4029
+ "items": {
4030
+ "$ref": "#/definitions/dto.AdminEventResponse"
4031
+ }
4032
+ },
4033
+ "message": {},
4034
+ "meta_data": {},
4035
+ "status": {
4036
+ "type": "string"
4037
+ }
4038
+ }
4039
+ },
4040
  "dto.SuccessResponse-array_dto_UserResponse": {
4041
  "type": "object",
4042
  "properties": {
 
4069
  }
4070
  }
4071
  },
4072
+ "dto.SuccessResponse-array_models_EventAssign": {
4073
+ "type": "object",
4074
+ "properties": {
4075
+ "data": {
4076
+ "type": "array",
4077
+ "items": {
4078
+ "$ref": "#/definitions/models.EventAssign"
4079
+ }
4080
+ },
4081
+ "message": {},
4082
+ "meta_data": {},
4083
+ "status": {
4084
+ "type": "string"
4085
+ }
4086
+ }
4087
+ },
4088
+ "dto.SuccessResponse-array_models_EventExamAssign": {
4089
+ "type": "object",
4090
+ "properties": {
4091
+ "data": {
4092
+ "type": "array",
4093
+ "items": {
4094
+ "$ref": "#/definitions/models.EventExamAssign"
4095
+ }
4096
+ },
4097
+ "message": {},
4098
+ "meta_data": {},
4099
+ "status": {
4100
+ "type": "string"
4101
+ }
4102
+ }
4103
+ },
4104
  "dto.SuccessResponse-array_models_EventExamProctoringLogs": {
4105
  "type": "object",
4106
  "properties": {
 
4181
  }
4182
  }
4183
  },
4184
+ "dto.SuccessResponse-array_models_Result": {
4185
+ "type": "object",
4186
+ "properties": {
4187
+ "data": {
4188
+ "type": "array",
4189
+ "items": {
4190
+ "$ref": "#/definitions/models.Result"
4191
+ }
4192
+ },
4193
+ "message": {},
4194
+ "meta_data": {},
4195
+ "status": {
4196
+ "type": "string"
4197
+ }
4198
+ }
4199
+ },
4200
  "dto.SuccessResponse-dto_AcademyMiniDetailResponse": {
4201
  "type": "object",
4202
  "properties": {
 
4418
  }
4419
  }
4420
  },
4421
+ "dto.SuccessResponse-models_EventAssign": {
4422
+ "type": "object",
4423
+ "properties": {
4424
+ "data": {
4425
+ "$ref": "#/definitions/models.EventAssign"
4426
+ },
4427
+ "message": {},
4428
+ "meta_data": {},
4429
+ "status": {
4430
+ "type": "string"
4431
+ }
4432
+ }
4433
+ },
4434
  "dto.SuccessResponse-models_EventExamAttempt": {
4435
  "type": "object",
4436
  "properties": {
 
4509
  }
4510
  }
4511
  },
4512
+ "dto.SuccessResponse-models_Result": {
4513
+ "type": "object",
4514
+ "properties": {
4515
+ "data": {
4516
+ "$ref": "#/definitions/models.Result"
4517
+ },
4518
+ "message": {},
4519
+ "meta_data": {},
4520
+ "status": {
4521
+ "type": "string"
4522
+ }
4523
+ }
4524
+ },
4525
  "dto.SuccessResponse-string": {
4526
  "type": "object",
4527
  "properties": {
 
4601
  }
4602
  }
4603
  },
4604
+ "dto.UpdateResultRequest": {
4605
+ "type": "object",
4606
+ "required": [
4607
+ "final_score"
4608
+ ],
4609
+ "properties": {
4610
+ "final_score": {
4611
+ "type": "number"
4612
+ }
4613
+ }
4614
+ },
4615
  "dto.UpdateUserRequest": {
4616
  "type": "object",
4617
  "properties": {
 
5117
  }
5118
  }
5119
  },
5120
+ "models.EventAssign": {
5121
+ "type": "object",
5122
+ "properties": {
5123
+ "account": {
5124
+ "$ref": "#/definitions/models.Account"
5125
+ },
5126
+ "assigned_at": {
5127
+ "type": "string"
5128
+ },
5129
+ "event": {
5130
+ "$ref": "#/definitions/models.Events"
5131
+ },
5132
+ "id_account": {
5133
+ "type": "string"
5134
+ },
5135
+ "id_assign": {
5136
+ "type": "string"
5137
+ },
5138
+ "id_event": {
5139
+ "type": "string"
5140
+ }
5141
+ }
5142
+ },
5143
  "models.EventExamAnswer": {
5144
  "type": "object",
5145
  "properties": {
 
5172
  }
5173
  }
5174
  },
5175
+ "models.EventExamAssign": {
5176
+ "type": "object",
5177
+ "properties": {
5178
+ "event": {
5179
+ "$ref": "#/definitions/models.Events"
5180
+ },
5181
+ "exam": {
5182
+ "$ref": "#/definitions/models.Exam"
5183
+ },
5184
+ "id_event": {
5185
+ "type": "string"
5186
+ },
5187
+ "id_exam": {
5188
+ "type": "string"
5189
+ },
5190
+ "id_exam_event_assign": {
5191
+ "type": "string"
5192
+ }
5193
+ }
5194
+ },
5195
  "models.EventExamAttempt": {
5196
  "type": "object",
5197
  "properties": {
 
5579
  "type": "string"
5580
  }
5581
  }
5582
+ },
5583
+ "models.Result": {
5584
+ "type": "object",
5585
+ "properties": {
5586
+ "exam_attempt": {
5587
+ "$ref": "#/definitions/models.EventExamAttempt"
5588
+ },
5589
+ "final_score": {
5590
+ "type": "number"
5591
+ },
5592
+ "id_attempt": {
5593
+ "type": "string"
5594
+ },
5595
+ "id_result": {
5596
+ "type": "string"
5597
+ }
5598
+ }
5599
  }
5600
  },
5601
  "securityDefinitions": {
swagger/docs/swagger.json CHANGED
@@ -736,8 +736,97 @@
736
  }
737
  },
738
  "/api/v1/admin/events": {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
739
  "post": {
740
- "description": "Create a new event with the provided details",
 
 
 
 
 
741
  "consumes": [
742
  "application/json"
743
  ],
@@ -745,9 +834,9 @@
745
  "application/json"
746
  ],
747
  "tags": [
748
- "Event"
749
  ],
750
- "summary": "Create Event",
751
  "parameters": [
752
  {
753
  "description": "Create Event Request",
@@ -763,7 +852,7 @@
763
  "200": {
764
  "description": "OK",
765
  "schema": {
766
- "$ref": "#/definitions/dto.SuccessResponse-dto_EventDetailResponse"
767
  }
768
  },
769
  "400": {
@@ -771,13 +860,30 @@
771
  "schema": {
772
  "$ref": "#/definitions/dto.ErrorResponse"
773
  }
 
 
 
 
 
 
 
 
 
 
 
 
774
  }
775
  }
776
  }
777
  },
778
- "/api/v1/admin/events/{id}": {
779
  "put": {
780
- "description": "Update an existing event with the provided details",
 
 
 
 
 
781
  "consumes": [
782
  "application/json"
783
  ],
@@ -785,14 +891,14 @@
785
  "application/json"
786
  ],
787
  "tags": [
788
- "Event"
789
  ],
790
- "summary": "Update Event",
791
  "parameters": [
792
  {
793
  "type": "string",
794
  "description": "Event ID",
795
- "name": "id",
796
  "in": "path",
797
  "required": true
798
  },
@@ -810,19 +916,532 @@
810
  "200": {
811
  "description": "OK",
812
  "schema": {
813
- "$ref": "#/definitions/dto.SuccessResponse-models_Events"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
814
  }
815
  },
816
- "400": {
817
- "description": "Bad Request",
818
  "schema": {
819
  "$ref": "#/definitions/dto.ErrorResponse"
820
  }
821
  }
822
  }
823
- },
 
 
824
  "delete": {
825
- "description": "Delete an existing event by its ID",
 
 
 
 
 
826
  "consumes": [
827
  "application/json"
828
  ],
@@ -830,14 +1449,21 @@
830
  "application/json"
831
  ],
832
  "tags": [
833
- "Event"
834
  ],
835
- "summary": "Delete Event",
836
  "parameters": [
837
  {
838
  "type": "string",
839
  "description": "Event ID",
840
- "name": "id",
 
 
 
 
 
 
 
841
  "in": "path",
842
  "required": true
843
  }
@@ -854,6 +1480,18 @@
854
  "schema": {
855
  "$ref": "#/definitions/dto.ErrorResponse"
856
  }
 
 
 
 
 
 
 
 
 
 
 
 
857
  }
858
  }
859
  }
@@ -2465,7 +3103,7 @@
2465
  "BearerAuth": []
2466
  }
2467
  ],
2468
- "description": "Retrieve a list of all users in the system",
2469
  "consumes": [
2470
  "application/json"
2471
  ],
@@ -2473,9 +3111,29 @@
2473
  "application/json"
2474
  ],
2475
  "tags": [
2476
- "Admin Users"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2477
  ],
2478
- "summary": "List All Users",
2479
  "responses": {
2480
  "200": {
2481
  "description": "OK",
@@ -2483,6 +3141,12 @@
2483
  "$ref": "#/definitions/dto.SuccessResponse-array_dto_UserResponse"
2484
  }
2485
  },
 
 
 
 
 
 
2486
  "401": {
2487
  "description": "Unauthorized",
2488
  "schema": {
@@ -2511,7 +3175,7 @@
2511
  "application/json"
2512
  ],
2513
  "tags": [
2514
- "Admin Users"
2515
  ],
2516
  "summary": "Create Single User",
2517
  "parameters": [
@@ -2568,7 +3232,7 @@
2568
  "application/json"
2569
  ],
2570
  "tags": [
2571
- "Admin Users"
2572
  ],
2573
  "summary": "Bulk Create Users",
2574
  "parameters": [
@@ -2628,7 +3292,7 @@
2628
  "application/json"
2629
  ],
2630
  "tags": [
2631
- "Admin Users"
2632
  ],
2633
  "summary": "Edit User",
2634
  "parameters": [
@@ -2696,7 +3360,7 @@
2696
  "application/json"
2697
  ],
2698
  "tags": [
2699
- "Admin Users"
2700
  ],
2701
  "summary": "Delete User",
2702
  "parameters": [
@@ -2772,6 +3436,67 @@
2772
  }
2773
  }
2774
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2775
  "dto.AnswerEventExamRequest": {
2776
  "type": "object",
2777
  "required": [
@@ -3293,6 +4018,22 @@
3293
  }
3294
  }
3295
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3296
  "dto.SuccessResponse-array_dto_UserResponse": {
3297
  "type": "object",
3298
  "properties": {
@@ -3325,6 +4066,38 @@
3325
  }
3326
  }
3327
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3328
  "dto.SuccessResponse-array_models_EventExamProctoringLogs": {
3329
  "type": "object",
3330
  "properties": {
@@ -3405,6 +4178,22 @@
3405
  }
3406
  }
3407
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3408
  "dto.SuccessResponse-dto_AcademyMiniDetailResponse": {
3409
  "type": "object",
3410
  "properties": {
@@ -3626,6 +4415,19 @@
3626
  }
3627
  }
3628
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
3629
  "dto.SuccessResponse-models_EventExamAttempt": {
3630
  "type": "object",
3631
  "properties": {
@@ -3704,6 +4506,19 @@
3704
  }
3705
  }
3706
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
3707
  "dto.SuccessResponse-string": {
3708
  "type": "object",
3709
  "properties": {
@@ -3783,6 +4598,17 @@
3783
  }
3784
  }
3785
  },
 
 
 
 
 
 
 
 
 
 
 
3786
  "dto.UpdateUserRequest": {
3787
  "type": "object",
3788
  "properties": {
@@ -4288,6 +5114,29 @@
4288
  }
4289
  }
4290
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4291
  "models.EventExamAnswer": {
4292
  "type": "object",
4293
  "properties": {
@@ -4320,6 +5169,26 @@
4320
  }
4321
  }
4322
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4323
  "models.EventExamAttempt": {
4324
  "type": "object",
4325
  "properties": {
@@ -4707,6 +5576,23 @@
4707
  "type": "string"
4708
  }
4709
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4710
  }
4711
  },
4712
  "securityDefinitions": {
 
736
  }
737
  },
738
  "/api/v1/admin/events": {
739
+ "get": {
740
+ "security": [
741
+ {
742
+ "BearerAuth": []
743
+ }
744
+ ],
745
+ "description": "Admin view of all events with participant count, exam count and optional filters/pagination",
746
+ "consumes": [
747
+ "application/json"
748
+ ],
749
+ "produces": [
750
+ "application/json"
751
+ ],
752
+ "tags": [
753
+ "AdminEvent"
754
+ ],
755
+ "summary": "Admin: List Events",
756
+ "parameters": [
757
+ {
758
+ "type": "integer",
759
+ "default": 10,
760
+ "description": "Items per page",
761
+ "name": "limit",
762
+ "in": "query"
763
+ },
764
+ {
765
+ "type": "integer",
766
+ "default": 1,
767
+ "description": "Page number",
768
+ "name": "page",
769
+ "in": "query"
770
+ },
771
+ {
772
+ "type": "string",
773
+ "description": "Search by title / slug / event code",
774
+ "name": "search",
775
+ "in": "query"
776
+ },
777
+ {
778
+ "type": "string",
779
+ "description": "Sort field (title, start_event, end_event, created_at, participant_count, exam_count)",
780
+ "name": "sortBy",
781
+ "in": "query"
782
+ },
783
+ {
784
+ "type": "string",
785
+ "description": "Sort direction (asc / desc)",
786
+ "name": "order",
787
+ "in": "query"
788
+ },
789
+ {
790
+ "type": "string",
791
+ "description": "Filter by status (UPCOMING, ONGOING, ENDED)",
792
+ "name": "status",
793
+ "in": "query"
794
+ }
795
+ ],
796
+ "responses": {
797
+ "200": {
798
+ "description": "OK",
799
+ "schema": {
800
+ "$ref": "#/definitions/dto.SuccessResponse-array_dto_AdminEventResponse"
801
+ }
802
+ },
803
+ "400": {
804
+ "description": "Bad Request",
805
+ "schema": {
806
+ "$ref": "#/definitions/dto.ErrorResponse"
807
+ }
808
+ },
809
+ "401": {
810
+ "description": "Unauthorized",
811
+ "schema": {
812
+ "$ref": "#/definitions/dto.ErrorResponse"
813
+ }
814
+ },
815
+ "403": {
816
+ "description": "Forbidden",
817
+ "schema": {
818
+ "$ref": "#/definitions/dto.ErrorResponse"
819
+ }
820
+ }
821
+ }
822
+ },
823
  "post": {
824
+ "security": [
825
+ {
826
+ "BearerAuth": []
827
+ }
828
+ ],
829
+ "description": "Create a new event",
830
  "consumes": [
831
  "application/json"
832
  ],
 
834
  "application/json"
835
  ],
836
  "tags": [
837
+ "AdminEvent"
838
  ],
839
+ "summary": "Admin: Create Event",
840
  "parameters": [
841
  {
842
  "description": "Create Event Request",
 
852
  "200": {
853
  "description": "OK",
854
  "schema": {
855
+ "$ref": "#/definitions/dto.SuccessResponse-models_Events"
856
  }
857
  },
858
  "400": {
 
860
  "schema": {
861
  "$ref": "#/definitions/dto.ErrorResponse"
862
  }
863
+ },
864
+ "401": {
865
+ "description": "Unauthorized",
866
+ "schema": {
867
+ "$ref": "#/definitions/dto.ErrorResponse"
868
+ }
869
+ },
870
+ "403": {
871
+ "description": "Forbidden",
872
+ "schema": {
873
+ "$ref": "#/definitions/dto.ErrorResponse"
874
+ }
875
  }
876
  }
877
  }
878
  },
879
+ "/api/v1/admin/events/{event_id}": {
880
  "put": {
881
+ "security": [
882
+ {
883
+ "BearerAuth": []
884
+ }
885
+ ],
886
+ "description": "Update an existing event by ID",
887
  "consumes": [
888
  "application/json"
889
  ],
 
891
  "application/json"
892
  ],
893
  "tags": [
894
+ "AdminEvent"
895
  ],
896
+ "summary": "Admin: Update Event",
897
  "parameters": [
898
  {
899
  "type": "string",
900
  "description": "Event ID",
901
+ "name": "event_id",
902
  "in": "path",
903
  "required": true
904
  },
 
916
  "200": {
917
  "description": "OK",
918
  "schema": {
919
+ "$ref": "#/definitions/dto.SuccessResponse-models_Events"
920
+ }
921
+ },
922
+ "400": {
923
+ "description": "Bad Request",
924
+ "schema": {
925
+ "$ref": "#/definitions/dto.ErrorResponse"
926
+ }
927
+ },
928
+ "401": {
929
+ "description": "Unauthorized",
930
+ "schema": {
931
+ "$ref": "#/definitions/dto.ErrorResponse"
932
+ }
933
+ },
934
+ "403": {
935
+ "description": "Forbidden",
936
+ "schema": {
937
+ "$ref": "#/definitions/dto.ErrorResponse"
938
+ }
939
+ }
940
+ }
941
+ },
942
+ "delete": {
943
+ "security": [
944
+ {
945
+ "BearerAuth": []
946
+ }
947
+ ],
948
+ "description": "Delete an event by ID",
949
+ "consumes": [
950
+ "application/json"
951
+ ],
952
+ "produces": [
953
+ "application/json"
954
+ ],
955
+ "tags": [
956
+ "AdminEvent"
957
+ ],
958
+ "summary": "Admin: Delete Event",
959
+ "parameters": [
960
+ {
961
+ "type": "string",
962
+ "description": "Event ID",
963
+ "name": "event_id",
964
+ "in": "path",
965
+ "required": true
966
+ }
967
+ ],
968
+ "responses": {
969
+ "200": {
970
+ "description": "OK",
971
+ "schema": {
972
+ "$ref": "#/definitions/dto.SuccessResponse-map_string_bool"
973
+ }
974
+ },
975
+ "400": {
976
+ "description": "Bad Request",
977
+ "schema": {
978
+ "$ref": "#/definitions/dto.ErrorResponse"
979
+ }
980
+ },
981
+ "401": {
982
+ "description": "Unauthorized",
983
+ "schema": {
984
+ "$ref": "#/definitions/dto.ErrorResponse"
985
+ }
986
+ },
987
+ "403": {
988
+ "description": "Forbidden",
989
+ "schema": {
990
+ "$ref": "#/definitions/dto.ErrorResponse"
991
+ }
992
+ }
993
+ }
994
+ }
995
+ },
996
+ "/api/v1/admin/events/{event_id}/exam/{exam_id}/results": {
997
+ "get": {
998
+ "security": [
999
+ {
1000
+ "BearerAuth": []
1001
+ }
1002
+ ],
1003
+ "description": "Retrieve all participant results for a specific exam within an event",
1004
+ "consumes": [
1005
+ "application/json"
1006
+ ],
1007
+ "produces": [
1008
+ "application/json"
1009
+ ],
1010
+ "tags": [
1011
+ "AdminEvent"
1012
+ ],
1013
+ "summary": "Admin: List Exam Results for an Event",
1014
+ "parameters": [
1015
+ {
1016
+ "type": "string",
1017
+ "description": "Event ID",
1018
+ "name": "event_id",
1019
+ "in": "path",
1020
+ "required": true
1021
+ },
1022
+ {
1023
+ "type": "string",
1024
+ "description": "Exam ID",
1025
+ "name": "exam_id",
1026
+ "in": "path",
1027
+ "required": true
1028
+ }
1029
+ ],
1030
+ "responses": {
1031
+ "200": {
1032
+ "description": "OK",
1033
+ "schema": {
1034
+ "$ref": "#/definitions/dto.SuccessResponse-array_models_Result"
1035
+ }
1036
+ },
1037
+ "400": {
1038
+ "description": "Bad Request",
1039
+ "schema": {
1040
+ "$ref": "#/definitions/dto.ErrorResponse"
1041
+ }
1042
+ },
1043
+ "401": {
1044
+ "description": "Unauthorized",
1045
+ "schema": {
1046
+ "$ref": "#/definitions/dto.ErrorResponse"
1047
+ }
1048
+ },
1049
+ "403": {
1050
+ "description": "Forbidden",
1051
+ "schema": {
1052
+ "$ref": "#/definitions/dto.ErrorResponse"
1053
+ }
1054
+ }
1055
+ }
1056
+ }
1057
+ },
1058
+ "/api/v1/admin/events/{event_id}/exam/{exam_id}/results/{result_id}": {
1059
+ "put": {
1060
+ "security": [
1061
+ {
1062
+ "BearerAuth": []
1063
+ }
1064
+ ],
1065
+ "description": "Edit the final score of a participant's exam result",
1066
+ "consumes": [
1067
+ "application/json"
1068
+ ],
1069
+ "produces": [
1070
+ "application/json"
1071
+ ],
1072
+ "tags": [
1073
+ "AdminEvent"
1074
+ ],
1075
+ "summary": "Admin: Update Exam Result",
1076
+ "parameters": [
1077
+ {
1078
+ "type": "string",
1079
+ "description": "Event ID",
1080
+ "name": "event_id",
1081
+ "in": "path",
1082
+ "required": true
1083
+ },
1084
+ {
1085
+ "type": "string",
1086
+ "description": "Exam ID",
1087
+ "name": "exam_id",
1088
+ "in": "path",
1089
+ "required": true
1090
+ },
1091
+ {
1092
+ "type": "string",
1093
+ "description": "Result ID",
1094
+ "name": "result_id",
1095
+ "in": "path",
1096
+ "required": true
1097
+ },
1098
+ {
1099
+ "description": "Update Result Request",
1100
+ "name": "request",
1101
+ "in": "body",
1102
+ "required": true,
1103
+ "schema": {
1104
+ "$ref": "#/definitions/dto.UpdateResultRequest"
1105
+ }
1106
+ }
1107
+ ],
1108
+ "responses": {
1109
+ "200": {
1110
+ "description": "OK",
1111
+ "schema": {
1112
+ "$ref": "#/definitions/dto.SuccessResponse-models_Result"
1113
+ }
1114
+ },
1115
+ "400": {
1116
+ "description": "Bad Request",
1117
+ "schema": {
1118
+ "$ref": "#/definitions/dto.ErrorResponse"
1119
+ }
1120
+ },
1121
+ "401": {
1122
+ "description": "Unauthorized",
1123
+ "schema": {
1124
+ "$ref": "#/definitions/dto.ErrorResponse"
1125
+ }
1126
+ },
1127
+ "403": {
1128
+ "description": "Forbidden",
1129
+ "schema": {
1130
+ "$ref": "#/definitions/dto.ErrorResponse"
1131
+ }
1132
+ }
1133
+ }
1134
+ },
1135
+ "delete": {
1136
+ "security": [
1137
+ {
1138
+ "BearerAuth": []
1139
+ }
1140
+ ],
1141
+ "description": "Delete a participant's exam result",
1142
+ "consumes": [
1143
+ "application/json"
1144
+ ],
1145
+ "produces": [
1146
+ "application/json"
1147
+ ],
1148
+ "tags": [
1149
+ "AdminEvent"
1150
+ ],
1151
+ "summary": "Admin: Delete Exam Result",
1152
+ "parameters": [
1153
+ {
1154
+ "type": "string",
1155
+ "description": "Event ID",
1156
+ "name": "event_id",
1157
+ "in": "path",
1158
+ "required": true
1159
+ },
1160
+ {
1161
+ "type": "string",
1162
+ "description": "Exam ID",
1163
+ "name": "exam_id",
1164
+ "in": "path",
1165
+ "required": true
1166
+ },
1167
+ {
1168
+ "type": "string",
1169
+ "description": "Result ID",
1170
+ "name": "result_id",
1171
+ "in": "path",
1172
+ "required": true
1173
+ }
1174
+ ],
1175
+ "responses": {
1176
+ "200": {
1177
+ "description": "OK",
1178
+ "schema": {
1179
+ "$ref": "#/definitions/dto.SuccessResponse-map_string_bool"
1180
+ }
1181
+ },
1182
+ "400": {
1183
+ "description": "Bad Request",
1184
+ "schema": {
1185
+ "$ref": "#/definitions/dto.ErrorResponse"
1186
+ }
1187
+ },
1188
+ "401": {
1189
+ "description": "Unauthorized",
1190
+ "schema": {
1191
+ "$ref": "#/definitions/dto.ErrorResponse"
1192
+ }
1193
+ },
1194
+ "403": {
1195
+ "description": "Forbidden",
1196
+ "schema": {
1197
+ "$ref": "#/definitions/dto.ErrorResponse"
1198
+ }
1199
+ }
1200
+ }
1201
+ }
1202
+ },
1203
+ "/api/v1/admin/events/{event_id}/exams": {
1204
+ "get": {
1205
+ "security": [
1206
+ {
1207
+ "BearerAuth": []
1208
+ }
1209
+ ],
1210
+ "description": "List all exams assigned to an event",
1211
+ "consumes": [
1212
+ "application/json"
1213
+ ],
1214
+ "produces": [
1215
+ "application/json"
1216
+ ],
1217
+ "tags": [
1218
+ "AdminEvent"
1219
+ ],
1220
+ "summary": "Admin: List Event Exams",
1221
+ "parameters": [
1222
+ {
1223
+ "type": "string",
1224
+ "description": "Event ID",
1225
+ "name": "event_id",
1226
+ "in": "path",
1227
+ "required": true
1228
+ }
1229
+ ],
1230
+ "responses": {
1231
+ "200": {
1232
+ "description": "OK",
1233
+ "schema": {
1234
+ "$ref": "#/definitions/dto.SuccessResponse-array_models_EventExamAssign"
1235
+ }
1236
+ },
1237
+ "400": {
1238
+ "description": "Bad Request",
1239
+ "schema": {
1240
+ "$ref": "#/definitions/dto.ErrorResponse"
1241
+ }
1242
+ },
1243
+ "401": {
1244
+ "description": "Unauthorized",
1245
+ "schema": {
1246
+ "$ref": "#/definitions/dto.ErrorResponse"
1247
+ }
1248
+ },
1249
+ "403": {
1250
+ "description": "Forbidden",
1251
+ "schema": {
1252
+ "$ref": "#/definitions/dto.ErrorResponse"
1253
+ }
1254
+ }
1255
+ }
1256
+ }
1257
+ },
1258
+ "/api/v1/admin/events/{event_id}/exams/{exam_id}": {
1259
+ "delete": {
1260
+ "security": [
1261
+ {
1262
+ "BearerAuth": []
1263
+ }
1264
+ ],
1265
+ "description": "Unassign an exam from an event",
1266
+ "consumes": [
1267
+ "application/json"
1268
+ ],
1269
+ "produces": [
1270
+ "application/json"
1271
+ ],
1272
+ "tags": [
1273
+ "AdminEvent"
1274
+ ],
1275
+ "summary": "Admin: Remove Exam from Event",
1276
+ "parameters": [
1277
+ {
1278
+ "type": "string",
1279
+ "description": "Event ID",
1280
+ "name": "event_id",
1281
+ "in": "path",
1282
+ "required": true
1283
+ },
1284
+ {
1285
+ "type": "string",
1286
+ "description": "Exam ID",
1287
+ "name": "exam_id",
1288
+ "in": "path",
1289
+ "required": true
1290
+ }
1291
+ ],
1292
+ "responses": {
1293
+ "200": {
1294
+ "description": "OK",
1295
+ "schema": {
1296
+ "$ref": "#/definitions/dto.SuccessResponse-map_string_bool"
1297
+ }
1298
+ },
1299
+ "400": {
1300
+ "description": "Bad Request",
1301
+ "schema": {
1302
+ "$ref": "#/definitions/dto.ErrorResponse"
1303
+ }
1304
+ },
1305
+ "401": {
1306
+ "description": "Unauthorized",
1307
+ "schema": {
1308
+ "$ref": "#/definitions/dto.ErrorResponse"
1309
+ }
1310
+ },
1311
+ "403": {
1312
+ "description": "Forbidden",
1313
+ "schema": {
1314
+ "$ref": "#/definitions/dto.ErrorResponse"
1315
+ }
1316
+ }
1317
+ }
1318
+ }
1319
+ },
1320
+ "/api/v1/admin/events/{event_id}/participants": {
1321
+ "get": {
1322
+ "security": [
1323
+ {
1324
+ "BearerAuth": []
1325
+ }
1326
+ ],
1327
+ "description": "List all participants assigned to an event",
1328
+ "consumes": [
1329
+ "application/json"
1330
+ ],
1331
+ "produces": [
1332
+ "application/json"
1333
+ ],
1334
+ "tags": [
1335
+ "AdminEvent"
1336
+ ],
1337
+ "summary": "Admin: List Event Participants",
1338
+ "parameters": [
1339
+ {
1340
+ "type": "string",
1341
+ "description": "Event ID",
1342
+ "name": "event_id",
1343
+ "in": "path",
1344
+ "required": true
1345
+ }
1346
+ ],
1347
+ "responses": {
1348
+ "200": {
1349
+ "description": "OK",
1350
+ "schema": {
1351
+ "$ref": "#/definitions/dto.SuccessResponse-array_models_EventAssign"
1352
+ }
1353
+ },
1354
+ "400": {
1355
+ "description": "Bad Request",
1356
+ "schema": {
1357
+ "$ref": "#/definitions/dto.ErrorResponse"
1358
+ }
1359
+ },
1360
+ "401": {
1361
+ "description": "Unauthorized",
1362
+ "schema": {
1363
+ "$ref": "#/definitions/dto.ErrorResponse"
1364
+ }
1365
+ },
1366
+ "403": {
1367
+ "description": "Forbidden",
1368
+ "schema": {
1369
+ "$ref": "#/definitions/dto.ErrorResponse"
1370
+ }
1371
+ }
1372
+ }
1373
+ },
1374
+ "post": {
1375
+ "security": [
1376
+ {
1377
+ "BearerAuth": []
1378
+ }
1379
+ ],
1380
+ "description": "Manually assign a user to an event (bypasses payment)",
1381
+ "consumes": [
1382
+ "application/json"
1383
+ ],
1384
+ "produces": [
1385
+ "application/json"
1386
+ ],
1387
+ "tags": [
1388
+ "AdminEvent"
1389
+ ],
1390
+ "summary": "Admin: Add Participant to Event",
1391
+ "parameters": [
1392
+ {
1393
+ "type": "string",
1394
+ "description": "Event ID",
1395
+ "name": "event_id",
1396
+ "in": "path",
1397
+ "required": true
1398
+ },
1399
+ {
1400
+ "description": "Add Participant Request",
1401
+ "name": "request",
1402
+ "in": "body",
1403
+ "required": true,
1404
+ "schema": {
1405
+ "$ref": "#/definitions/dto.AddParticipantRequest"
1406
+ }
1407
+ }
1408
+ ],
1409
+ "responses": {
1410
+ "200": {
1411
+ "description": "OK",
1412
+ "schema": {
1413
+ "$ref": "#/definitions/dto.SuccessResponse-models_EventAssign"
1414
+ }
1415
+ },
1416
+ "400": {
1417
+ "description": "Bad Request",
1418
+ "schema": {
1419
+ "$ref": "#/definitions/dto.ErrorResponse"
1420
+ }
1421
+ },
1422
+ "401": {
1423
+ "description": "Unauthorized",
1424
+ "schema": {
1425
+ "$ref": "#/definitions/dto.ErrorResponse"
1426
  }
1427
  },
1428
+ "403": {
1429
+ "description": "Forbidden",
1430
  "schema": {
1431
  "$ref": "#/definitions/dto.ErrorResponse"
1432
  }
1433
  }
1434
  }
1435
+ }
1436
+ },
1437
+ "/api/v1/admin/events/{event_id}/participants/{user_id}": {
1438
  "delete": {
1439
+ "security": [
1440
+ {
1441
+ "BearerAuth": []
1442
+ }
1443
+ ],
1444
+ "description": "Unassign a user from an event",
1445
  "consumes": [
1446
  "application/json"
1447
  ],
 
1449
  "application/json"
1450
  ],
1451
  "tags": [
1452
+ "AdminEvent"
1453
  ],
1454
+ "summary": "Admin: Remove Participant from Event",
1455
  "parameters": [
1456
  {
1457
  "type": "string",
1458
  "description": "Event ID",
1459
+ "name": "event_id",
1460
+ "in": "path",
1461
+ "required": true
1462
+ },
1463
+ {
1464
+ "type": "string",
1465
+ "description": "User ID",
1466
+ "name": "user_id",
1467
  "in": "path",
1468
  "required": true
1469
  }
 
1480
  "schema": {
1481
  "$ref": "#/definitions/dto.ErrorResponse"
1482
  }
1483
+ },
1484
+ "401": {
1485
+ "description": "Unauthorized",
1486
+ "schema": {
1487
+ "$ref": "#/definitions/dto.ErrorResponse"
1488
+ }
1489
+ },
1490
+ "403": {
1491
+ "description": "Forbidden",
1492
+ "schema": {
1493
+ "$ref": "#/definitions/dto.ErrorResponse"
1494
+ }
1495
  }
1496
  }
1497
  }
 
3103
  "BearerAuth": []
3104
  }
3105
  ],
3106
+ "description": "Retrieve a paginated list of all users with optional role filter. Supports pagination parameters (page, limit) and can filter by user role.",
3107
  "consumes": [
3108
  "application/json"
3109
  ],
 
3111
  "application/json"
3112
  ],
3113
  "tags": [
3114
+ "Super Admin Users Management"
3115
+ ],
3116
+ "summary": "List All Users with Pagination",
3117
+ "parameters": [
3118
+ {
3119
+ "type": "integer",
3120
+ "description": "Page number for pagination. Minimum value is 1. Default is 1.",
3121
+ "name": "page",
3122
+ "in": "query"
3123
+ },
3124
+ {
3125
+ "type": "integer",
3126
+ "description": "Number of items per page. Minimum 1, Maximum 50. Default is 10.",
3127
+ "name": "limit",
3128
+ "in": "query"
3129
+ },
3130
+ {
3131
+ "type": "string",
3132
+ "description": "Filter users by role. Allowed values: user, admin, super_admin. Leave empty for no filter.",
3133
+ "name": "role",
3134
+ "in": "query"
3135
+ }
3136
  ],
 
3137
  "responses": {
3138
  "200": {
3139
  "description": "OK",
 
3141
  "$ref": "#/definitions/dto.SuccessResponse-array_dto_UserResponse"
3142
  }
3143
  },
3144
+ "400": {
3145
+ "description": "Bad Request",
3146
+ "schema": {
3147
+ "$ref": "#/definitions/dto.ErrorResponse"
3148
+ }
3149
+ },
3150
  "401": {
3151
  "description": "Unauthorized",
3152
  "schema": {
 
3175
  "application/json"
3176
  ],
3177
  "tags": [
3178
+ "Super Admin Users Management"
3179
  ],
3180
  "summary": "Create Single User",
3181
  "parameters": [
 
3232
  "application/json"
3233
  ],
3234
  "tags": [
3235
+ "Super Admin Users Management"
3236
  ],
3237
  "summary": "Bulk Create Users",
3238
  "parameters": [
 
3292
  "application/json"
3293
  ],
3294
  "tags": [
3295
+ "Super Admin Users Management"
3296
  ],
3297
  "summary": "Edit User",
3298
  "parameters": [
 
3360
  "application/json"
3361
  ],
3362
  "tags": [
3363
+ "Super Admin Users Management"
3364
  ],
3365
  "summary": "Delete User",
3366
  "parameters": [
 
3436
  }
3437
  }
3438
  },
3439
+ "dto.AddParticipantRequest": {
3440
+ "type": "object",
3441
+ "required": [
3442
+ "user_id"
3443
+ ],
3444
+ "properties": {
3445
+ "user_id": {
3446
+ "type": "string"
3447
+ }
3448
+ }
3449
+ },
3450
+ "dto.AdminEventResponse": {
3451
+ "type": "object",
3452
+ "properties": {
3453
+ "created_at": {
3454
+ "type": "string"
3455
+ },
3456
+ "deleted_at": {
3457
+ "$ref": "#/definitions/gorm.DeletedAt"
3458
+ },
3459
+ "end_event": {
3460
+ "type": "string"
3461
+ },
3462
+ "event_code": {
3463
+ "type": "string"
3464
+ },
3465
+ "exam_count": {
3466
+ "type": "integer"
3467
+ },
3468
+ "id_event": {
3469
+ "type": "string"
3470
+ },
3471
+ "img_banner": {
3472
+ "type": "string"
3473
+ },
3474
+ "is_public": {
3475
+ "type": "boolean"
3476
+ },
3477
+ "overview": {
3478
+ "type": "string"
3479
+ },
3480
+ "participant_count": {
3481
+ "type": "integer"
3482
+ },
3483
+ "price": {
3484
+ "type": "number"
3485
+ },
3486
+ "register_status": {
3487
+ "type": "integer"
3488
+ },
3489
+ "slug": {
3490
+ "type": "string"
3491
+ },
3492
+ "start_event": {
3493
+ "type": "string"
3494
+ },
3495
+ "title": {
3496
+ "type": "string"
3497
+ }
3498
+ }
3499
+ },
3500
  "dto.AnswerEventExamRequest": {
3501
  "type": "object",
3502
  "required": [
 
4018
  }
4019
  }
4020
  },
4021
+ "dto.SuccessResponse-array_dto_AdminEventResponse": {
4022
+ "type": "object",
4023
+ "properties": {
4024
+ "data": {
4025
+ "type": "array",
4026
+ "items": {
4027
+ "$ref": "#/definitions/dto.AdminEventResponse"
4028
+ }
4029
+ },
4030
+ "message": {},
4031
+ "meta_data": {},
4032
+ "status": {
4033
+ "type": "string"
4034
+ }
4035
+ }
4036
+ },
4037
  "dto.SuccessResponse-array_dto_UserResponse": {
4038
  "type": "object",
4039
  "properties": {
 
4066
  }
4067
  }
4068
  },
4069
+ "dto.SuccessResponse-array_models_EventAssign": {
4070
+ "type": "object",
4071
+ "properties": {
4072
+ "data": {
4073
+ "type": "array",
4074
+ "items": {
4075
+ "$ref": "#/definitions/models.EventAssign"
4076
+ }
4077
+ },
4078
+ "message": {},
4079
+ "meta_data": {},
4080
+ "status": {
4081
+ "type": "string"
4082
+ }
4083
+ }
4084
+ },
4085
+ "dto.SuccessResponse-array_models_EventExamAssign": {
4086
+ "type": "object",
4087
+ "properties": {
4088
+ "data": {
4089
+ "type": "array",
4090
+ "items": {
4091
+ "$ref": "#/definitions/models.EventExamAssign"
4092
+ }
4093
+ },
4094
+ "message": {},
4095
+ "meta_data": {},
4096
+ "status": {
4097
+ "type": "string"
4098
+ }
4099
+ }
4100
+ },
4101
  "dto.SuccessResponse-array_models_EventExamProctoringLogs": {
4102
  "type": "object",
4103
  "properties": {
 
4178
  }
4179
  }
4180
  },
4181
+ "dto.SuccessResponse-array_models_Result": {
4182
+ "type": "object",
4183
+ "properties": {
4184
+ "data": {
4185
+ "type": "array",
4186
+ "items": {
4187
+ "$ref": "#/definitions/models.Result"
4188
+ }
4189
+ },
4190
+ "message": {},
4191
+ "meta_data": {},
4192
+ "status": {
4193
+ "type": "string"
4194
+ }
4195
+ }
4196
+ },
4197
  "dto.SuccessResponse-dto_AcademyMiniDetailResponse": {
4198
  "type": "object",
4199
  "properties": {
 
4415
  }
4416
  }
4417
  },
4418
+ "dto.SuccessResponse-models_EventAssign": {
4419
+ "type": "object",
4420
+ "properties": {
4421
+ "data": {
4422
+ "$ref": "#/definitions/models.EventAssign"
4423
+ },
4424
+ "message": {},
4425
+ "meta_data": {},
4426
+ "status": {
4427
+ "type": "string"
4428
+ }
4429
+ }
4430
+ },
4431
  "dto.SuccessResponse-models_EventExamAttempt": {
4432
  "type": "object",
4433
  "properties": {
 
4506
  }
4507
  }
4508
  },
4509
+ "dto.SuccessResponse-models_Result": {
4510
+ "type": "object",
4511
+ "properties": {
4512
+ "data": {
4513
+ "$ref": "#/definitions/models.Result"
4514
+ },
4515
+ "message": {},
4516
+ "meta_data": {},
4517
+ "status": {
4518
+ "type": "string"
4519
+ }
4520
+ }
4521
+ },
4522
  "dto.SuccessResponse-string": {
4523
  "type": "object",
4524
  "properties": {
 
4598
  }
4599
  }
4600
  },
4601
+ "dto.UpdateResultRequest": {
4602
+ "type": "object",
4603
+ "required": [
4604
+ "final_score"
4605
+ ],
4606
+ "properties": {
4607
+ "final_score": {
4608
+ "type": "number"
4609
+ }
4610
+ }
4611
+ },
4612
  "dto.UpdateUserRequest": {
4613
  "type": "object",
4614
  "properties": {
 
5114
  }
5115
  }
5116
  },
5117
+ "models.EventAssign": {
5118
+ "type": "object",
5119
+ "properties": {
5120
+ "account": {
5121
+ "$ref": "#/definitions/models.Account"
5122
+ },
5123
+ "assigned_at": {
5124
+ "type": "string"
5125
+ },
5126
+ "event": {
5127
+ "$ref": "#/definitions/models.Events"
5128
+ },
5129
+ "id_account": {
5130
+ "type": "string"
5131
+ },
5132
+ "id_assign": {
5133
+ "type": "string"
5134
+ },
5135
+ "id_event": {
5136
+ "type": "string"
5137
+ }
5138
+ }
5139
+ },
5140
  "models.EventExamAnswer": {
5141
  "type": "object",
5142
  "properties": {
 
5169
  }
5170
  }
5171
  },
5172
+ "models.EventExamAssign": {
5173
+ "type": "object",
5174
+ "properties": {
5175
+ "event": {
5176
+ "$ref": "#/definitions/models.Events"
5177
+ },
5178
+ "exam": {
5179
+ "$ref": "#/definitions/models.Exam"
5180
+ },
5181
+ "id_event": {
5182
+ "type": "string"
5183
+ },
5184
+ "id_exam": {
5185
+ "type": "string"
5186
+ },
5187
+ "id_exam_event_assign": {
5188
+ "type": "string"
5189
+ }
5190
+ }
5191
+ },
5192
  "models.EventExamAttempt": {
5193
  "type": "object",
5194
  "properties": {
 
5576
  "type": "string"
5577
  }
5578
  }
5579
+ },
5580
+ "models.Result": {
5581
+ "type": "object",
5582
+ "properties": {
5583
+ "exam_attempt": {
5584
+ "$ref": "#/definitions/models.EventExamAttempt"
5585
+ },
5586
+ "final_score": {
5587
+ "type": "number"
5588
+ },
5589
+ "id_attempt": {
5590
+ "type": "string"
5591
+ },
5592
+ "id_result": {
5593
+ "type": "string"
5594
+ }
5595
+ }
5596
  }
5597
  },
5598
  "securityDefinitions": {
swagger/docs/swagger.yaml CHANGED
@@ -18,6 +18,46 @@ definitions:
18
  details:
19
  $ref: '#/definitions/models.AccountDetail'
20
  type: object
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
  dto.AnswerEventExamRequest:
22
  properties:
23
  answer:
@@ -364,6 +404,17 @@ definitions:
364
  status:
365
  type: string
366
  type: object
 
 
 
 
 
 
 
 
 
 
 
367
  dto.SuccessResponse-array_dto_UserResponse:
368
  properties:
369
  data:
@@ -386,6 +437,28 @@ definitions:
386
  status:
387
  type: string
388
  type: object
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
389
  dto.SuccessResponse-array_models_EventExamProctoringLogs:
390
  properties:
391
  data:
@@ -441,6 +514,17 @@ definitions:
441
  status:
442
  type: string
443
  type: object
 
 
 
 
 
 
 
 
 
 
 
444
  dto.SuccessResponse-dto_AcademyMiniDetailResponse:
445
  properties:
446
  data:
@@ -594,6 +678,15 @@ definitions:
594
  status:
595
  type: string
596
  type: object
 
 
 
 
 
 
 
 
 
597
  dto.SuccessResponse-models_EventExamAttempt:
598
  properties:
599
  data:
@@ -648,6 +741,15 @@ definitions:
648
  status:
649
  type: string
650
  type: object
 
 
 
 
 
 
 
 
 
651
  dto.SuccessResponse-string:
652
  properties:
653
  data:
@@ -700,6 +802,13 @@ definitions:
700
  title:
701
  type: string
702
  type: object
 
 
 
 
 
 
 
703
  dto.UpdateUserRequest:
704
  properties:
705
  email:
@@ -1032,6 +1141,21 @@ definitions:
1032
  token:
1033
  type: integer
1034
  type: object
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1035
  models.EventExamAnswer:
1036
  properties:
1037
  answer:
@@ -1053,6 +1177,19 @@ definitions:
1053
  updated_at:
1054
  type: string
1055
  type: object
 
 
 
 
 
 
 
 
 
 
 
 
 
1056
  models.EventExamAttempt:
1057
  properties:
1058
  account:
@@ -1307,6 +1444,17 @@ definitions:
1307
  name:
1308
  type: string
1309
  type: object
 
 
 
 
 
 
 
 
 
 
 
1310
  host: localhost:8000
1311
  info:
1312
  contact:
@@ -1777,10 +1925,67 @@ paths:
1777
  tags:
1778
  - Material
1779
  /api/v1/admin/events:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1780
  post:
1781
  consumes:
1782
  - application/json
1783
- description: Create a new event with the provided details
1784
  parameters:
1785
  - description: Create Event Request
1786
  in: body
@@ -1794,23 +1999,33 @@ paths:
1794
  "200":
1795
  description: OK
1796
  schema:
1797
- $ref: '#/definitions/dto.SuccessResponse-dto_EventDetailResponse'
1798
  "400":
1799
  description: Bad Request
1800
  schema:
1801
  $ref: '#/definitions/dto.ErrorResponse'
1802
- summary: Create Event
 
 
 
 
 
 
 
 
 
 
1803
  tags:
1804
- - Event
1805
- /api/v1/admin/events/{id}:
1806
  delete:
1807
  consumes:
1808
  - application/json
1809
- description: Delete an existing event by its ID
1810
  parameters:
1811
  - description: Event ID
1812
  in: path
1813
- name: id
1814
  required: true
1815
  type: string
1816
  produces:
@@ -1824,17 +2039,27 @@ paths:
1824
  description: Bad Request
1825
  schema:
1826
  $ref: '#/definitions/dto.ErrorResponse'
1827
- summary: Delete Event
 
 
 
 
 
 
 
 
 
 
1828
  tags:
1829
- - Event
1830
  put:
1831
  consumes:
1832
  - application/json
1833
- description: Update an existing event with the provided details
1834
  parameters:
1835
  - description: Event ID
1836
  in: path
1837
- name: id
1838
  required: true
1839
  type: string
1840
  - description: Update Event Request
@@ -1854,9 +2079,345 @@ paths:
1854
  description: Bad Request
1855
  schema:
1856
  $ref: '#/definitions/dto.ErrorResponse'
1857
- summary: Update Event
 
 
 
 
 
 
 
 
 
 
1858
  tags:
1859
- - Event
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1860
  /api/v1/admin/exam:
1861
  get:
1862
  consumes:
@@ -2913,7 +3474,22 @@ paths:
2913
  get:
2914
  consumes:
2915
  - application/json
2916
- description: Retrieve a list of all users in the system
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2917
  produces:
2918
  - application/json
2919
  responses:
@@ -2921,6 +3497,10 @@ paths:
2921
  description: OK
2922
  schema:
2923
  $ref: '#/definitions/dto.SuccessResponse-array_dto_UserResponse'
 
 
 
 
2924
  "401":
2925
  description: Unauthorized
2926
  schema:
@@ -2931,9 +3511,9 @@ paths:
2931
  $ref: '#/definitions/dto.ErrorResponse'
2932
  security:
2933
  - BearerAuth: []
2934
- summary: List All Users
2935
  tags:
2936
- - Admin Users
2937
  post:
2938
  consumes:
2939
  - application/json
@@ -2968,7 +3548,7 @@ paths:
2968
  - BearerAuth: []
2969
  summary: Create Single User
2970
  tags:
2971
- - Admin Users
2972
  /api/v1/super-admin/users/{id}:
2973
  delete:
2974
  consumes:
@@ -3007,7 +3587,7 @@ paths:
3007
  - BearerAuth: []
3008
  summary: Delete User
3009
  tags:
3010
- - Admin Users
3011
  put:
3012
  consumes:
3013
  - application/json
@@ -3051,7 +3631,7 @@ paths:
3051
  - BearerAuth: []
3052
  summary: Edit User
3053
  tags:
3054
- - Admin Users
3055
  /api/v1/super-admin/users/bulk:
3056
  post:
3057
  consumes:
@@ -3089,7 +3669,7 @@ paths:
3089
  - BearerAuth: []
3090
  summary: Bulk Create Users
3091
  tags:
3092
- - Admin Users
3093
  schemes:
3094
  - https
3095
  securityDefinitions:
 
18
  details:
19
  $ref: '#/definitions/models.AccountDetail'
20
  type: object
21
+ dto.AddParticipantRequest:
22
+ properties:
23
+ user_id:
24
+ type: string
25
+ required:
26
+ - user_id
27
+ type: object
28
+ dto.AdminEventResponse:
29
+ properties:
30
+ created_at:
31
+ type: string
32
+ deleted_at:
33
+ $ref: '#/definitions/gorm.DeletedAt'
34
+ end_event:
35
+ type: string
36
+ event_code:
37
+ type: string
38
+ exam_count:
39
+ type: integer
40
+ id_event:
41
+ type: string
42
+ img_banner:
43
+ type: string
44
+ is_public:
45
+ type: boolean
46
+ overview:
47
+ type: string
48
+ participant_count:
49
+ type: integer
50
+ price:
51
+ type: number
52
+ register_status:
53
+ type: integer
54
+ slug:
55
+ type: string
56
+ start_event:
57
+ type: string
58
+ title:
59
+ type: string
60
+ type: object
61
  dto.AnswerEventExamRequest:
62
  properties:
63
  answer:
 
404
  status:
405
  type: string
406
  type: object
407
+ dto.SuccessResponse-array_dto_AdminEventResponse:
408
+ properties:
409
+ data:
410
+ items:
411
+ $ref: '#/definitions/dto.AdminEventResponse'
412
+ type: array
413
+ message: {}
414
+ meta_data: {}
415
+ status:
416
+ type: string
417
+ type: object
418
  dto.SuccessResponse-array_dto_UserResponse:
419
  properties:
420
  data:
 
437
  status:
438
  type: string
439
  type: object
440
+ dto.SuccessResponse-array_models_EventAssign:
441
+ properties:
442
+ data:
443
+ items:
444
+ $ref: '#/definitions/models.EventAssign'
445
+ type: array
446
+ message: {}
447
+ meta_data: {}
448
+ status:
449
+ type: string
450
+ type: object
451
+ dto.SuccessResponse-array_models_EventExamAssign:
452
+ properties:
453
+ data:
454
+ items:
455
+ $ref: '#/definitions/models.EventExamAssign'
456
+ type: array
457
+ message: {}
458
+ meta_data: {}
459
+ status:
460
+ type: string
461
+ type: object
462
  dto.SuccessResponse-array_models_EventExamProctoringLogs:
463
  properties:
464
  data:
 
514
  status:
515
  type: string
516
  type: object
517
+ dto.SuccessResponse-array_models_Result:
518
+ properties:
519
+ data:
520
+ items:
521
+ $ref: '#/definitions/models.Result'
522
+ type: array
523
+ message: {}
524
+ meta_data: {}
525
+ status:
526
+ type: string
527
+ type: object
528
  dto.SuccessResponse-dto_AcademyMiniDetailResponse:
529
  properties:
530
  data:
 
678
  status:
679
  type: string
680
  type: object
681
+ dto.SuccessResponse-models_EventAssign:
682
+ properties:
683
+ data:
684
+ $ref: '#/definitions/models.EventAssign'
685
+ message: {}
686
+ meta_data: {}
687
+ status:
688
+ type: string
689
+ type: object
690
  dto.SuccessResponse-models_EventExamAttempt:
691
  properties:
692
  data:
 
741
  status:
742
  type: string
743
  type: object
744
+ dto.SuccessResponse-models_Result:
745
+ properties:
746
+ data:
747
+ $ref: '#/definitions/models.Result'
748
+ message: {}
749
+ meta_data: {}
750
+ status:
751
+ type: string
752
+ type: object
753
  dto.SuccessResponse-string:
754
  properties:
755
  data:
 
802
  title:
803
  type: string
804
  type: object
805
+ dto.UpdateResultRequest:
806
+ properties:
807
+ final_score:
808
+ type: number
809
+ required:
810
+ - final_score
811
+ type: object
812
  dto.UpdateUserRequest:
813
  properties:
814
  email:
 
1141
  token:
1142
  type: integer
1143
  type: object
1144
+ models.EventAssign:
1145
+ properties:
1146
+ account:
1147
+ $ref: '#/definitions/models.Account'
1148
+ assigned_at:
1149
+ type: string
1150
+ event:
1151
+ $ref: '#/definitions/models.Events'
1152
+ id_account:
1153
+ type: string
1154
+ id_assign:
1155
+ type: string
1156
+ id_event:
1157
+ type: string
1158
+ type: object
1159
  models.EventExamAnswer:
1160
  properties:
1161
  answer:
 
1177
  updated_at:
1178
  type: string
1179
  type: object
1180
+ models.EventExamAssign:
1181
+ properties:
1182
+ event:
1183
+ $ref: '#/definitions/models.Events'
1184
+ exam:
1185
+ $ref: '#/definitions/models.Exam'
1186
+ id_event:
1187
+ type: string
1188
+ id_exam:
1189
+ type: string
1190
+ id_exam_event_assign:
1191
+ type: string
1192
+ type: object
1193
  models.EventExamAttempt:
1194
  properties:
1195
  account:
 
1444
  name:
1445
  type: string
1446
  type: object
1447
+ models.Result:
1448
+ properties:
1449
+ exam_attempt:
1450
+ $ref: '#/definitions/models.EventExamAttempt'
1451
+ final_score:
1452
+ type: number
1453
+ id_attempt:
1454
+ type: string
1455
+ id_result:
1456
+ type: string
1457
+ type: object
1458
  host: localhost:8000
1459
  info:
1460
  contact:
 
1925
  tags:
1926
  - Material
1927
  /api/v1/admin/events:
1928
+ get:
1929
+ consumes:
1930
+ - application/json
1931
+ description: Admin view of all events with participant count, exam count and
1932
+ optional filters/pagination
1933
+ parameters:
1934
+ - default: 10
1935
+ description: Items per page
1936
+ in: query
1937
+ name: limit
1938
+ type: integer
1939
+ - default: 1
1940
+ description: Page number
1941
+ in: query
1942
+ name: page
1943
+ type: integer
1944
+ - description: Search by title / slug / event code
1945
+ in: query
1946
+ name: search
1947
+ type: string
1948
+ - description: Sort field (title, start_event, end_event, created_at, participant_count,
1949
+ exam_count)
1950
+ in: query
1951
+ name: sortBy
1952
+ type: string
1953
+ - description: Sort direction (asc / desc)
1954
+ in: query
1955
+ name: order
1956
+ type: string
1957
+ - description: Filter by status (UPCOMING, ONGOING, ENDED)
1958
+ in: query
1959
+ name: status
1960
+ type: string
1961
+ produces:
1962
+ - application/json
1963
+ responses:
1964
+ "200":
1965
+ description: OK
1966
+ schema:
1967
+ $ref: '#/definitions/dto.SuccessResponse-array_dto_AdminEventResponse'
1968
+ "400":
1969
+ description: Bad Request
1970
+ schema:
1971
+ $ref: '#/definitions/dto.ErrorResponse'
1972
+ "401":
1973
+ description: Unauthorized
1974
+ schema:
1975
+ $ref: '#/definitions/dto.ErrorResponse'
1976
+ "403":
1977
+ description: Forbidden
1978
+ schema:
1979
+ $ref: '#/definitions/dto.ErrorResponse'
1980
+ security:
1981
+ - BearerAuth: []
1982
+ summary: 'Admin: List Events'
1983
+ tags:
1984
+ - AdminEvent
1985
  post:
1986
  consumes:
1987
  - application/json
1988
+ description: Create a new event
1989
  parameters:
1990
  - description: Create Event Request
1991
  in: body
 
1999
  "200":
2000
  description: OK
2001
  schema:
2002
+ $ref: '#/definitions/dto.SuccessResponse-models_Events'
2003
  "400":
2004
  description: Bad Request
2005
  schema:
2006
  $ref: '#/definitions/dto.ErrorResponse'
2007
+ "401":
2008
+ description: Unauthorized
2009
+ schema:
2010
+ $ref: '#/definitions/dto.ErrorResponse'
2011
+ "403":
2012
+ description: Forbidden
2013
+ schema:
2014
+ $ref: '#/definitions/dto.ErrorResponse'
2015
+ security:
2016
+ - BearerAuth: []
2017
+ summary: 'Admin: Create Event'
2018
  tags:
2019
+ - AdminEvent
2020
+ /api/v1/admin/events/{event_id}:
2021
  delete:
2022
  consumes:
2023
  - application/json
2024
+ description: Delete an event by ID
2025
  parameters:
2026
  - description: Event ID
2027
  in: path
2028
+ name: event_id
2029
  required: true
2030
  type: string
2031
  produces:
 
2039
  description: Bad Request
2040
  schema:
2041
  $ref: '#/definitions/dto.ErrorResponse'
2042
+ "401":
2043
+ description: Unauthorized
2044
+ schema:
2045
+ $ref: '#/definitions/dto.ErrorResponse'
2046
+ "403":
2047
+ description: Forbidden
2048
+ schema:
2049
+ $ref: '#/definitions/dto.ErrorResponse'
2050
+ security:
2051
+ - BearerAuth: []
2052
+ summary: 'Admin: Delete Event'
2053
  tags:
2054
+ - AdminEvent
2055
  put:
2056
  consumes:
2057
  - application/json
2058
+ description: Update an existing event by ID
2059
  parameters:
2060
  - description: Event ID
2061
  in: path
2062
+ name: event_id
2063
  required: true
2064
  type: string
2065
  - description: Update Event Request
 
2079
  description: Bad Request
2080
  schema:
2081
  $ref: '#/definitions/dto.ErrorResponse'
2082
+ "401":
2083
+ description: Unauthorized
2084
+ schema:
2085
+ $ref: '#/definitions/dto.ErrorResponse'
2086
+ "403":
2087
+ description: Forbidden
2088
+ schema:
2089
+ $ref: '#/definitions/dto.ErrorResponse'
2090
+ security:
2091
+ - BearerAuth: []
2092
+ summary: 'Admin: Update Event'
2093
  tags:
2094
+ - AdminEvent
2095
+ /api/v1/admin/events/{event_id}/exam/{exam_id}/results:
2096
+ get:
2097
+ consumes:
2098
+ - application/json
2099
+ description: Retrieve all participant results for a specific exam within an
2100
+ event
2101
+ parameters:
2102
+ - description: Event ID
2103
+ in: path
2104
+ name: event_id
2105
+ required: true
2106
+ type: string
2107
+ - description: Exam ID
2108
+ in: path
2109
+ name: exam_id
2110
+ required: true
2111
+ type: string
2112
+ produces:
2113
+ - application/json
2114
+ responses:
2115
+ "200":
2116
+ description: OK
2117
+ schema:
2118
+ $ref: '#/definitions/dto.SuccessResponse-array_models_Result'
2119
+ "400":
2120
+ description: Bad Request
2121
+ schema:
2122
+ $ref: '#/definitions/dto.ErrorResponse'
2123
+ "401":
2124
+ description: Unauthorized
2125
+ schema:
2126
+ $ref: '#/definitions/dto.ErrorResponse'
2127
+ "403":
2128
+ description: Forbidden
2129
+ schema:
2130
+ $ref: '#/definitions/dto.ErrorResponse'
2131
+ security:
2132
+ - BearerAuth: []
2133
+ summary: 'Admin: List Exam Results for an Event'
2134
+ tags:
2135
+ - AdminEvent
2136
+ /api/v1/admin/events/{event_id}/exam/{exam_id}/results/{result_id}:
2137
+ delete:
2138
+ consumes:
2139
+ - application/json
2140
+ description: Delete a participant's exam result
2141
+ parameters:
2142
+ - description: Event ID
2143
+ in: path
2144
+ name: event_id
2145
+ required: true
2146
+ type: string
2147
+ - description: Exam ID
2148
+ in: path
2149
+ name: exam_id
2150
+ required: true
2151
+ type: string
2152
+ - description: Result ID
2153
+ in: path
2154
+ name: result_id
2155
+ required: true
2156
+ type: string
2157
+ produces:
2158
+ - application/json
2159
+ responses:
2160
+ "200":
2161
+ description: OK
2162
+ schema:
2163
+ $ref: '#/definitions/dto.SuccessResponse-map_string_bool'
2164
+ "400":
2165
+ description: Bad Request
2166
+ schema:
2167
+ $ref: '#/definitions/dto.ErrorResponse'
2168
+ "401":
2169
+ description: Unauthorized
2170
+ schema:
2171
+ $ref: '#/definitions/dto.ErrorResponse'
2172
+ "403":
2173
+ description: Forbidden
2174
+ schema:
2175
+ $ref: '#/definitions/dto.ErrorResponse'
2176
+ security:
2177
+ - BearerAuth: []
2178
+ summary: 'Admin: Delete Exam Result'
2179
+ tags:
2180
+ - AdminEvent
2181
+ put:
2182
+ consumes:
2183
+ - application/json
2184
+ description: Edit the final score of a participant's exam result
2185
+ parameters:
2186
+ - description: Event ID
2187
+ in: path
2188
+ name: event_id
2189
+ required: true
2190
+ type: string
2191
+ - description: Exam ID
2192
+ in: path
2193
+ name: exam_id
2194
+ required: true
2195
+ type: string
2196
+ - description: Result ID
2197
+ in: path
2198
+ name: result_id
2199
+ required: true
2200
+ type: string
2201
+ - description: Update Result Request
2202
+ in: body
2203
+ name: request
2204
+ required: true
2205
+ schema:
2206
+ $ref: '#/definitions/dto.UpdateResultRequest'
2207
+ produces:
2208
+ - application/json
2209
+ responses:
2210
+ "200":
2211
+ description: OK
2212
+ schema:
2213
+ $ref: '#/definitions/dto.SuccessResponse-models_Result'
2214
+ "400":
2215
+ description: Bad Request
2216
+ schema:
2217
+ $ref: '#/definitions/dto.ErrorResponse'
2218
+ "401":
2219
+ description: Unauthorized
2220
+ schema:
2221
+ $ref: '#/definitions/dto.ErrorResponse'
2222
+ "403":
2223
+ description: Forbidden
2224
+ schema:
2225
+ $ref: '#/definitions/dto.ErrorResponse'
2226
+ security:
2227
+ - BearerAuth: []
2228
+ summary: 'Admin: Update Exam Result'
2229
+ tags:
2230
+ - AdminEvent
2231
+ /api/v1/admin/events/{event_id}/exams:
2232
+ get:
2233
+ consumes:
2234
+ - application/json
2235
+ description: List all exams assigned to an event
2236
+ parameters:
2237
+ - description: Event ID
2238
+ in: path
2239
+ name: event_id
2240
+ required: true
2241
+ type: string
2242
+ produces:
2243
+ - application/json
2244
+ responses:
2245
+ "200":
2246
+ description: OK
2247
+ schema:
2248
+ $ref: '#/definitions/dto.SuccessResponse-array_models_EventExamAssign'
2249
+ "400":
2250
+ description: Bad Request
2251
+ schema:
2252
+ $ref: '#/definitions/dto.ErrorResponse'
2253
+ "401":
2254
+ description: Unauthorized
2255
+ schema:
2256
+ $ref: '#/definitions/dto.ErrorResponse'
2257
+ "403":
2258
+ description: Forbidden
2259
+ schema:
2260
+ $ref: '#/definitions/dto.ErrorResponse'
2261
+ security:
2262
+ - BearerAuth: []
2263
+ summary: 'Admin: List Event Exams'
2264
+ tags:
2265
+ - AdminEvent
2266
+ /api/v1/admin/events/{event_id}/exams/{exam_id}:
2267
+ delete:
2268
+ consumes:
2269
+ - application/json
2270
+ description: Unassign an exam from an event
2271
+ parameters:
2272
+ - description: Event ID
2273
+ in: path
2274
+ name: event_id
2275
+ required: true
2276
+ type: string
2277
+ - description: Exam ID
2278
+ in: path
2279
+ name: exam_id
2280
+ required: true
2281
+ type: string
2282
+ produces:
2283
+ - application/json
2284
+ responses:
2285
+ "200":
2286
+ description: OK
2287
+ schema:
2288
+ $ref: '#/definitions/dto.SuccessResponse-map_string_bool'
2289
+ "400":
2290
+ description: Bad Request
2291
+ schema:
2292
+ $ref: '#/definitions/dto.ErrorResponse'
2293
+ "401":
2294
+ description: Unauthorized
2295
+ schema:
2296
+ $ref: '#/definitions/dto.ErrorResponse'
2297
+ "403":
2298
+ description: Forbidden
2299
+ schema:
2300
+ $ref: '#/definitions/dto.ErrorResponse'
2301
+ security:
2302
+ - BearerAuth: []
2303
+ summary: 'Admin: Remove Exam from Event'
2304
+ tags:
2305
+ - AdminEvent
2306
+ /api/v1/admin/events/{event_id}/participants:
2307
+ get:
2308
+ consumes:
2309
+ - application/json
2310
+ description: List all participants assigned to an event
2311
+ parameters:
2312
+ - description: Event ID
2313
+ in: path
2314
+ name: event_id
2315
+ required: true
2316
+ type: string
2317
+ produces:
2318
+ - application/json
2319
+ responses:
2320
+ "200":
2321
+ description: OK
2322
+ schema:
2323
+ $ref: '#/definitions/dto.SuccessResponse-array_models_EventAssign'
2324
+ "400":
2325
+ description: Bad Request
2326
+ schema:
2327
+ $ref: '#/definitions/dto.ErrorResponse'
2328
+ "401":
2329
+ description: Unauthorized
2330
+ schema:
2331
+ $ref: '#/definitions/dto.ErrorResponse'
2332
+ "403":
2333
+ description: Forbidden
2334
+ schema:
2335
+ $ref: '#/definitions/dto.ErrorResponse'
2336
+ security:
2337
+ - BearerAuth: []
2338
+ summary: 'Admin: List Event Participants'
2339
+ tags:
2340
+ - AdminEvent
2341
+ post:
2342
+ consumes:
2343
+ - application/json
2344
+ description: Manually assign a user to an event (bypasses payment)
2345
+ parameters:
2346
+ - description: Event ID
2347
+ in: path
2348
+ name: event_id
2349
+ required: true
2350
+ type: string
2351
+ - description: Add Participant Request
2352
+ in: body
2353
+ name: request
2354
+ required: true
2355
+ schema:
2356
+ $ref: '#/definitions/dto.AddParticipantRequest'
2357
+ produces:
2358
+ - application/json
2359
+ responses:
2360
+ "200":
2361
+ description: OK
2362
+ schema:
2363
+ $ref: '#/definitions/dto.SuccessResponse-models_EventAssign'
2364
+ "400":
2365
+ description: Bad Request
2366
+ schema:
2367
+ $ref: '#/definitions/dto.ErrorResponse'
2368
+ "401":
2369
+ description: Unauthorized
2370
+ schema:
2371
+ $ref: '#/definitions/dto.ErrorResponse'
2372
+ "403":
2373
+ description: Forbidden
2374
+ schema:
2375
+ $ref: '#/definitions/dto.ErrorResponse'
2376
+ security:
2377
+ - BearerAuth: []
2378
+ summary: 'Admin: Add Participant to Event'
2379
+ tags:
2380
+ - AdminEvent
2381
+ /api/v1/admin/events/{event_id}/participants/{user_id}:
2382
+ delete:
2383
+ consumes:
2384
+ - application/json
2385
+ description: Unassign a user from an event
2386
+ parameters:
2387
+ - description: Event ID
2388
+ in: path
2389
+ name: event_id
2390
+ required: true
2391
+ type: string
2392
+ - description: User ID
2393
+ in: path
2394
+ name: user_id
2395
+ required: true
2396
+ type: string
2397
+ produces:
2398
+ - application/json
2399
+ responses:
2400
+ "200":
2401
+ description: OK
2402
+ schema:
2403
+ $ref: '#/definitions/dto.SuccessResponse-map_string_bool'
2404
+ "400":
2405
+ description: Bad Request
2406
+ schema:
2407
+ $ref: '#/definitions/dto.ErrorResponse'
2408
+ "401":
2409
+ description: Unauthorized
2410
+ schema:
2411
+ $ref: '#/definitions/dto.ErrorResponse'
2412
+ "403":
2413
+ description: Forbidden
2414
+ schema:
2415
+ $ref: '#/definitions/dto.ErrorResponse'
2416
+ security:
2417
+ - BearerAuth: []
2418
+ summary: 'Admin: Remove Participant from Event'
2419
+ tags:
2420
+ - AdminEvent
2421
  /api/v1/admin/exam:
2422
  get:
2423
  consumes:
 
3474
  get:
3475
  consumes:
3476
  - application/json
3477
+ description: Retrieve a paginated list of all users with optional role filter.
3478
+ Supports pagination parameters (page, limit) and can filter by user role.
3479
+ parameters:
3480
+ - description: Page number for pagination. Minimum value is 1. Default is 1.
3481
+ in: query
3482
+ name: page
3483
+ type: integer
3484
+ - description: Number of items per page. Minimum 1, Maximum 50. Default is 10.
3485
+ in: query
3486
+ name: limit
3487
+ type: integer
3488
+ - description: 'Filter users by role. Allowed values: user, admin, super_admin.
3489
+ Leave empty for no filter.'
3490
+ in: query
3491
+ name: role
3492
+ type: string
3493
  produces:
3494
  - application/json
3495
  responses:
 
3497
  description: OK
3498
  schema:
3499
  $ref: '#/definitions/dto.SuccessResponse-array_dto_UserResponse'
3500
+ "400":
3501
+ description: Bad Request
3502
+ schema:
3503
+ $ref: '#/definitions/dto.ErrorResponse'
3504
  "401":
3505
  description: Unauthorized
3506
  schema:
 
3511
  $ref: '#/definitions/dto.ErrorResponse'
3512
  security:
3513
  - BearerAuth: []
3514
+ summary: List All Users with Pagination
3515
  tags:
3516
+ - Super Admin Users Management
3517
  post:
3518
  consumes:
3519
  - application/json
 
3548
  - BearerAuth: []
3549
  summary: Create Single User
3550
  tags:
3551
+ - Super Admin Users Management
3552
  /api/v1/super-admin/users/{id}:
3553
  delete:
3554
  consumes:
 
3587
  - BearerAuth: []
3588
  summary: Delete User
3589
  tags:
3590
+ - Super Admin Users Management
3591
  put:
3592
  consumes:
3593
  - application/json
 
3631
  - BearerAuth: []
3632
  summary: Edit User
3633
  tags:
3634
+ - Super Admin Users Management
3635
  /api/v1/super-admin/users/bulk:
3636
  post:
3637
  consumes:
 
3669
  - BearerAuth: []
3670
  summary: Bulk Create Users
3671
  tags:
3672
+ - Super Admin Users Management
3673
  schemes:
3674
  - https
3675
  securityDefinitions:
utils/response_util.go CHANGED
@@ -58,6 +58,14 @@ func ResponseFAILED[TMetaData any](c *gin.Context, metaData TMetaData, err error
58
  MetaData: metaData,
59
  })
60
  return
 
 
 
 
 
 
 
 
61
  } else if errors.Is(err, http_error.TIMEOUT) {
62
  c.JSON(504, dto.ErrorResponse{
63
  Status: "error",
 
58
  MetaData: metaData,
59
  })
60
  return
61
+ } else if errors.Is(err, http_error.DUPLICATE_DATA) || errors.Is(err, gorm.ErrDuplicatedKey) {
62
+ c.JSON(409, dto.ErrorResponse{
63
+ Status: "error",
64
+ Error: err,
65
+ Message: "Data already exists, duplicated key not allowed!",
66
+ MetaData: metaData,
67
+ })
68
+ return
69
  } else if errors.Is(err, http_error.TIMEOUT) {
70
  c.JSON(504, dto.ErrorResponse{
71
  Status: "error",
utils/utils.go CHANGED
@@ -1,6 +1,7 @@
1
  package utils
2
 
3
  import (
 
4
  "os"
5
  "regexp"
6
  "strings"
@@ -8,6 +9,8 @@ import (
8
 
9
  http_error "abdanhafidz.com/go-boilerplate/models/error"
10
  "github.com/google/uuid"
 
 
11
  )
12
 
13
  func ToUUID(s any) (uuid.UUID, error) {
@@ -73,3 +76,11 @@ func GetEnv(key string) string {
73
 
74
  return ""
75
  }
 
 
 
 
 
 
 
 
 
1
  package utils
2
 
3
  import (
4
+ errors "errors"
5
  "os"
6
  "regexp"
7
  "strings"
 
9
 
10
  http_error "abdanhafidz.com/go-boilerplate/models/error"
11
  "github.com/google/uuid"
12
+ "github.com/jackc/pgx/v5/pgconn"
13
+ "gorm.io/gorm"
14
  )
15
 
16
  func ToUUID(s any) (uuid.UUID, error) {
 
76
 
77
  return ""
78
  }
79
+
80
+ func IsDuplicateKeyError(err error) bool {
81
+ if errors.Is(err, gorm.ErrDuplicatedKey) {
82
+ return true
83
+ }
84
+ var pgErr *pgconn.PgError
85
+ return errors.As(err, &pgErr) && pgErr.Code == "23505"
86
+ }