freelang-v4 4.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (261) hide show
  1. package/README.md +548 -0
  2. package/dist/ast.d.ts +367 -0
  3. package/dist/ast.js +4 -0
  4. package/dist/ast.js.map +1 -0
  5. package/dist/async-basic.test.d.ts +1 -0
  6. package/dist/async-basic.test.js +88 -0
  7. package/dist/async-basic.test.js.map +1 -0
  8. package/dist/async-jest.test.d.ts +1 -0
  9. package/dist/async-jest.test.js +99 -0
  10. package/dist/async-jest.test.js.map +1 -0
  11. package/dist/channel-jest.test.d.ts +1 -0
  12. package/dist/channel-jest.test.js +148 -0
  13. package/dist/channel-jest.test.js.map +1 -0
  14. package/dist/checker-jest.test.d.ts +1 -0
  15. package/dist/checker-jest.test.js +160 -0
  16. package/dist/checker-jest.test.js.map +1 -0
  17. package/dist/checker.d.ts +149 -0
  18. package/dist/checker.js +1565 -0
  19. package/dist/checker.js.map +1 -0
  20. package/dist/checker.test.d.ts +1 -0
  21. package/dist/checker.test.js +217 -0
  22. package/dist/checker.test.js.map +1 -0
  23. package/dist/compiler-jest.test.d.ts +1 -0
  24. package/dist/compiler-jest.test.js +233 -0
  25. package/dist/compiler-jest.test.js.map +1 -0
  26. package/dist/compiler.d.ts +127 -0
  27. package/dist/compiler.js +1588 -0
  28. package/dist/compiler.js.map +1 -0
  29. package/dist/compiler.test.d.ts +1 -0
  30. package/dist/compiler.test.js +313 -0
  31. package/dist/compiler.test.js.map +1 -0
  32. package/dist/db-100m-full.d.ts +5 -0
  33. package/dist/db-100m-full.js +78 -0
  34. package/dist/db-100m-full.js.map +1 -0
  35. package/dist/db-100m-no-index.d.ts +12 -0
  36. package/dist/db-100m-no-index.js +119 -0
  37. package/dist/db-100m-no-index.js.map +1 -0
  38. package/dist/db-100m-real.d.ts +5 -0
  39. package/dist/db-100m-real.js +131 -0
  40. package/dist/db-100m-real.js.map +1 -0
  41. package/dist/db-100m-streaming.d.ts +15 -0
  42. package/dist/db-100m-streaming.js +164 -0
  43. package/dist/db-100m-streaming.js.map +1 -0
  44. package/dist/db-100m-test.d.ts +5 -0
  45. package/dist/db-100m-test.js +111 -0
  46. package/dist/db-100m-test.js.map +1 -0
  47. package/dist/db-jest.test.d.ts +1 -0
  48. package/dist/db-jest.test.js +182 -0
  49. package/dist/db-jest.test.js.map +1 -0
  50. package/dist/db-runtime.d.ts +24 -0
  51. package/dist/db-runtime.js +204 -0
  52. package/dist/db-runtime.js.map +1 -0
  53. package/dist/db.d.ts +249 -0
  54. package/dist/db.js +593 -0
  55. package/dist/db.js.map +1 -0
  56. package/dist/file-io-jest.test.d.ts +1 -0
  57. package/dist/file-io-jest.test.js +225 -0
  58. package/dist/file-io-jest.test.js.map +1 -0
  59. package/dist/for-of-jest.test.d.ts +1 -0
  60. package/dist/for-of-jest.test.js +230 -0
  61. package/dist/for-of-jest.test.js.map +1 -0
  62. package/dist/for-of.test.d.ts +1 -0
  63. package/dist/for-of.test.js +305 -0
  64. package/dist/for-of.test.js.map +1 -0
  65. package/dist/function-literal-jest.test.d.ts +1 -0
  66. package/dist/function-literal-jest.test.js +180 -0
  67. package/dist/function-literal-jest.test.js.map +1 -0
  68. package/dist/function-literal.test.d.ts +1 -0
  69. package/dist/function-literal.test.js +245 -0
  70. package/dist/function-literal.test.js.map +1 -0
  71. package/dist/generics-jest.test.d.ts +1 -0
  72. package/dist/generics-jest.test.js +93 -0
  73. package/dist/generics-jest.test.js.map +1 -0
  74. package/dist/ir-gen.d.ts +15 -0
  75. package/dist/ir-gen.js +400 -0
  76. package/dist/ir-gen.js.map +1 -0
  77. package/dist/ir.d.ts +114 -0
  78. package/dist/ir.js +5 -0
  79. package/dist/ir.js.map +1 -0
  80. package/dist/lexer.d.ts +110 -0
  81. package/dist/lexer.js +467 -0
  82. package/dist/lexer.js.map +1 -0
  83. package/dist/lexer.test.d.ts +1 -0
  84. package/dist/lexer.test.js +426 -0
  85. package/dist/lexer.test.js.map +1 -0
  86. package/dist/main.d.ts +2 -0
  87. package/dist/main.js +241 -0
  88. package/dist/main.js.map +1 -0
  89. package/dist/module-jest.test.d.ts +1 -0
  90. package/dist/module-jest.test.js +123 -0
  91. package/dist/module-jest.test.js.map +1 -0
  92. package/dist/parser.d.ts +56 -0
  93. package/dist/parser.js +1060 -0
  94. package/dist/parser.js.map +1 -0
  95. package/dist/parser.test.d.ts +1 -0
  96. package/dist/parser.test.js +461 -0
  97. package/dist/parser.test.js.map +1 -0
  98. package/dist/pattern-matching-jest.test.d.ts +1 -0
  99. package/dist/pattern-matching-jest.test.js +158 -0
  100. package/dist/pattern-matching-jest.test.js.map +1 -0
  101. package/dist/pkg/init.d.ts +1 -0
  102. package/dist/pkg/init.js +118 -0
  103. package/dist/pkg/init.js.map +1 -0
  104. package/dist/pkg/install.d.ts +1 -0
  105. package/dist/pkg/install.js +77 -0
  106. package/dist/pkg/install.js.map +1 -0
  107. package/dist/pkg/registry.d.ts +23 -0
  108. package/dist/pkg/registry.js +106 -0
  109. package/dist/pkg/registry.js.map +1 -0
  110. package/dist/pkg/run.d.ts +1 -0
  111. package/dist/pkg/run.js +76 -0
  112. package/dist/pkg/run.js.map +1 -0
  113. package/dist/pkg/toml.d.ts +5 -0
  114. package/dist/pkg/toml.js +117 -0
  115. package/dist/pkg/toml.js.map +1 -0
  116. package/dist/repl.d.ts +15 -0
  117. package/dist/repl.js +197 -0
  118. package/dist/repl.js.map +1 -0
  119. package/dist/runtime/bytecode.d.ts +92 -0
  120. package/dist/runtime/bytecode.js +253 -0
  121. package/dist/runtime/bytecode.js.map +1 -0
  122. package/dist/runtime/value.d.ts +102 -0
  123. package/dist/runtime/value.js +302 -0
  124. package/dist/runtime/value.js.map +1 -0
  125. package/dist/runtime/vm.d.ts +65 -0
  126. package/dist/runtime/vm.js +293 -0
  127. package/dist/runtime/vm.js.map +1 -0
  128. package/dist/struct-instance-jest.test.d.ts +1 -0
  129. package/dist/struct-instance-jest.test.js +209 -0
  130. package/dist/struct-instance-jest.test.js.map +1 -0
  131. package/dist/struct-instance.test.d.ts +1 -0
  132. package/dist/struct-instance.test.js +291 -0
  133. package/dist/struct-instance.test.js.map +1 -0
  134. package/dist/struct-jest.test.d.ts +1 -0
  135. package/dist/struct-jest.test.js +176 -0
  136. package/dist/struct-jest.test.js.map +1 -0
  137. package/dist/struct.test.d.ts +1 -0
  138. package/dist/struct.test.js +231 -0
  139. package/dist/struct.test.js.map +1 -0
  140. package/dist/trait-jest.test.d.ts +1 -0
  141. package/dist/trait-jest.test.js +120 -0
  142. package/dist/trait-jest.test.js.map +1 -0
  143. package/dist/vm-jest.test.d.ts +1 -0
  144. package/dist/vm-jest.test.js +569 -0
  145. package/dist/vm-jest.test.js.map +1 -0
  146. package/dist/vm.d.ts +81 -0
  147. package/dist/vm.js +1956 -0
  148. package/dist/vm.js.map +1 -0
  149. package/dist/vm.test.d.ts +1 -0
  150. package/dist/vm.test.js +337 -0
  151. package/dist/vm.test.js.map +1 -0
  152. package/dist/web-repl/sandbox.d.ts +11 -0
  153. package/dist/web-repl/sandbox.js +76 -0
  154. package/dist/web-repl/sandbox.js.map +1 -0
  155. package/dist/web-repl/server.d.ts +1 -0
  156. package/dist/web-repl/server.js +111 -0
  157. package/dist/web-repl/server.js.map +1 -0
  158. package/dist/while-loop-jest.test.d.ts +1 -0
  159. package/dist/while-loop-jest.test.js +201 -0
  160. package/dist/while-loop-jest.test.js.map +1 -0
  161. package/dist/while-loop.test.d.ts +1 -0
  162. package/dist/while-loop.test.js +262 -0
  163. package/dist/while-loop.test.js.map +1 -0
  164. package/docs/EXPERIENCE.md +787 -0
  165. package/docs/README.md +175 -0
  166. package/docs/V1_V2_V3_ANALYSIS.md +107 -0
  167. package/docs/_config.yml +36 -0
  168. package/docs/api-reference.md +459 -0
  169. package/docs/architecture.md +470 -0
  170. package/docs/benchmarks.md +295 -0
  171. package/docs/comparison.md +454 -0
  172. package/docs/index.md +335 -0
  173. package/docs/language-completeness.md +228 -0
  174. package/docs/learning-guide.md +651 -0
  175. package/package.json +65 -0
  176. package/src/api/deploy_key.fl +294 -0
  177. package/src/api/issue.fl +302 -0
  178. package/src/api/org.fl +356 -0
  179. package/src/api/repo.fl +394 -0
  180. package/src/api/team.fl +299 -0
  181. package/src/api/user.fl +385 -0
  182. package/src/api/webhook.fl +273 -0
  183. package/src/ast.ts +158 -0
  184. package/src/async-basic.test.ts +94 -0
  185. package/src/async-jest.test.ts +107 -0
  186. package/src/channel-jest.test.ts +158 -0
  187. package/src/checker-jest.test.ts +189 -0
  188. package/src/checker.test.ts +279 -0
  189. package/src/checker.ts +1861 -0
  190. package/src/commands/analyze.fl +227 -0
  191. package/src/commands/auth.fl +315 -0
  192. package/src/commands/batch.fl +349 -0
  193. package/src/commands/config.fl +199 -0
  194. package/src/commands/deploy_key.fl +352 -0
  195. package/src/commands/issue.fl +275 -0
  196. package/src/commands/main.fl +492 -0
  197. package/src/commands/org.fl +425 -0
  198. package/src/commands/repo.fl +581 -0
  199. package/src/commands/team.fl +244 -0
  200. package/src/commands/user.fl +423 -0
  201. package/src/commands/webhook.fl +400 -0
  202. package/src/compiler-jest.test.ts +275 -0
  203. package/src/compiler.test.ts +375 -0
  204. package/src/compiler.ts +1770 -0
  205. package/src/config.fl +175 -0
  206. package/src/core/batch.fl +355 -0
  207. package/src/core/cache.fl +284 -0
  208. package/src/core/ensure.fl +324 -0
  209. package/src/db-100m-full.ts +96 -0
  210. package/src/db-100m-no-index.ts +133 -0
  211. package/src/db-100m-real.ts +152 -0
  212. package/src/db-100m-streaming.ts +154 -0
  213. package/src/db-100m-test.ts +136 -0
  214. package/src/db-jest.test.ts +161 -0
  215. package/src/db-runtime.ts +242 -0
  216. package/src/db.ts +676 -0
  217. package/src/errors.fl +134 -0
  218. package/src/for-of-jest.test.ts +246 -0
  219. package/src/for-of.test.ts +308 -0
  220. package/src/function-literal-jest.test.ts +193 -0
  221. package/src/function-literal.test.ts +248 -0
  222. package/src/generics-jest.test.ts +104 -0
  223. package/src/http/client.fl +327 -0
  224. package/src/ir-gen.ts +459 -0
  225. package/src/ir.ts +80 -0
  226. package/src/lexer.test.ts +499 -0
  227. package/src/lexer.ts +522 -0
  228. package/src/main.ts +223 -0
  229. package/src/models.fl +162 -0
  230. package/src/module-jest.test.ts +145 -0
  231. package/src/parser.test.ts +542 -0
  232. package/src/parser.ts +1211 -0
  233. package/src/pattern-matching-jest.test.ts +170 -0
  234. package/src/pkg/init.ts +91 -0
  235. package/src/pkg/install.ts +56 -0
  236. package/src/pkg/registry.ts +103 -0
  237. package/src/pkg/run.ts +49 -0
  238. package/src/pkg/toml.ts +129 -0
  239. package/src/repl.ts +190 -0
  240. package/src/runtime/bytecode.ts +291 -0
  241. package/src/runtime/value.ts +322 -0
  242. package/src/runtime/vm.ts +354 -0
  243. package/src/self-host/bootstrap.fl +68 -0
  244. package/src/self-host/interpreter.fl +361 -0
  245. package/src/self-host/lexer-simple.fl +22 -0
  246. package/src/self-host/lexer.fl +305 -0
  247. package/src/self-host/parser.fl +580 -0
  248. package/src/struct-instance-jest.test.ts +221 -0
  249. package/src/struct-instance.test.ts +293 -0
  250. package/src/struct-jest.test.ts +187 -0
  251. package/src/struct.test.ts +234 -0
  252. package/src/trait-jest.test.ts +136 -0
  253. package/src/vm-jest.test.ts +754 -0
  254. package/src/vm.ts +1976 -0
  255. package/src/web-repl/public/index.html +50 -0
  256. package/src/web-repl/public/main.js +105 -0
  257. package/src/web-repl/public/style.css +225 -0
  258. package/src/web-repl/sandbox.ts +88 -0
  259. package/src/web-repl/server.ts +97 -0
  260. package/src/while-loop-jest.test.ts +218 -0
  261. package/src/while-loop.test.ts +267 -0
@@ -0,0 +1,385 @@
1
+ // FreeLang v4.2 — Gogs CLI UserService
2
+ // 사용자 관리 API 서비스
3
+ // 300라인: list, get, create, update, delete, ensure, lock, unlock
4
+
5
+ /// 사용자 서비스
6
+ struct UserService {
7
+ client: HttpClient
8
+ cache: CacheManager
9
+ }
10
+
11
+ /// UserService 생성
12
+ fn new_user_service(client: HttpClient, cache: CacheManager) -> UserService {
13
+ UserService {
14
+ client: client,
15
+ cache: cache
16
+ }
17
+ }
18
+
19
+ // ==================== 사용자 CRUD ====================
20
+
21
+ /// 사용자 목록 조회 (관리자 전용)
22
+ async fn list_users(service: UserService) -> Result<[User]> {
23
+ var cache_key = "users:list"
24
+
25
+ // 캐시 확인
26
+ var cached = cache_get(service.cache, cache_key)
27
+ if cached != "" {
28
+ println("[cache] hit: users list")
29
+ var users_arr = json_parse(cached) as [User]
30
+ return Result::Ok(users_arr)
31
+ }
32
+
33
+ // API 호출 (관리자 권한 필요)
34
+ var resp = await http_get(service.client, "/api/v1/admin/users")
35
+
36
+ if is_error(resp.status) {
37
+ return Result::Err(response_to_error(resp))
38
+ }
39
+
40
+ // 캐시 저장
41
+ cache_set(service.cache, cache_key, resp.body)
42
+
43
+ // 파싱
44
+ var users_arr = json_parse(resp.body) as [User]
45
+ Result::Ok(users_arr)
46
+ }
47
+
48
+ /// 사용자 상세 조회
49
+ async fn get_user(service: UserService, username: str) -> Result<User> {
50
+ var cache_key = "user:" + username
51
+
52
+ // 캐시 확인
53
+ var cached = cache_get(service.cache, cache_key)
54
+ if cached != "" {
55
+ println("[cache] hit: " + cache_key)
56
+ var user = json_parse(cached) as User
57
+ return Result::Ok(user)
58
+ }
59
+
60
+ // API 호출
61
+ var path = "/api/v1/users/" + username
62
+ var resp = await http_get(service.client, path)
63
+
64
+ if is_error(resp.status) {
65
+ return Result::Err(response_to_error(resp))
66
+ }
67
+
68
+ // 캐시 저장
69
+ cache_set(service.cache, cache_key, resp.body)
70
+
71
+ // 파싱
72
+ var user = json_parse(resp.body) as User
73
+ Result::Ok(user)
74
+ }
75
+
76
+ /// 현재 사용자 정보 조회
77
+ async fn get_current_user(service: UserService) -> Result<User> {
78
+ var cache_key = "user:current"
79
+
80
+ // 캐시 확인
81
+ var cached = cache_get(service.cache, cache_key)
82
+ if cached != "" {
83
+ println("[cache] hit: current user")
84
+ var user = json_parse(cached) as User
85
+ return Result::Ok(user)
86
+ }
87
+
88
+ // API 호출
89
+ var resp = await http_get(service.client, "/api/v1/user")
90
+
91
+ if is_error(resp.status) {
92
+ return Result::Err(response_to_error(resp))
93
+ }
94
+
95
+ // 캐시 저장
96
+ cache_set(service.cache, cache_key, resp.body)
97
+
98
+ // 파싱
99
+ var user = json_parse(resp.body) as User
100
+ Result::Ok(user)
101
+ }
102
+
103
+ /// 사용자 생성 (관리자 전용)
104
+ async fn create_user(service: UserService, req: CreateUserRequest) -> Result<User> {
105
+ var json_data = json_stringify(req)
106
+ var resp = await http_post_json(service.client, "/api/v1/admin/users", json_data)
107
+
108
+ if is_error(resp.status) {
109
+ return Result::Err(response_to_error(resp))
110
+ }
111
+
112
+ // 캐시 무효화
113
+ cache_invalidate(service.cache, "users:list")
114
+
115
+ // 파싱
116
+ var user = json_parse(resp.body) as User
117
+ Result::Ok(user)
118
+ }
119
+
120
+ /// 사용자 업데이트 (관리자 전용)
121
+ async fn update_user(service: UserService, username: str, req: UpdateUserRequest) -> Result<User> {
122
+ var path = "/api/v1/admin/users/" + username
123
+ var json_data = json_stringify(req)
124
+ var resp = await http_patch(service.client, path, json_data)
125
+
126
+ if is_error(resp.status) {
127
+ return Result::Err(response_to_error(resp))
128
+ }
129
+
130
+ // 캐시 무효화
131
+ var cache_key = "user:" + username
132
+ cache_invalidate(service.cache, cache_key)
133
+ cache_invalidate(service.cache, "users:list")
134
+
135
+ // 파싱
136
+ var user = json_parse(resp.body) as User
137
+ Result::Ok(user)
138
+ }
139
+
140
+ /// 사용자 삭제 (관리자 전용)
141
+ async fn delete_user(service: UserService, username: str) -> Result<str> {
142
+ var path = "/api/v1/admin/users/" + username
143
+ var resp = await http_delete(service.client, path)
144
+
145
+ if is_error(resp.status) {
146
+ return Result::Err(response_to_error(resp))
147
+ }
148
+
149
+ // 캐시 무효화
150
+ var cache_key = "user:" + username
151
+ cache_invalidate(service.cache, cache_key)
152
+ cache_invalidate(service.cache, "users:list")
153
+
154
+ Result::Ok("User deleted")
155
+ }
156
+
157
+ // ==================== Ensure 패턴 ====================
158
+
159
+ /// 사용자 ensure (멱등성 보증)
160
+ async fn ensure_user(service: UserService, req: CreateUserRequest) -> Result<EnsureAction> {
161
+ var path = "/api/v1/users/" + req.username
162
+
163
+ // desired 상태 정의
164
+ var desired_json = json_stringify(req)
165
+
166
+ var ctx = EnsureContext {
167
+ name: req.username,
168
+
169
+ get_fn: async fn() -> Result<str> {
170
+ var resp = await http_get(service.client, path)
171
+ if is_error(resp.status) {
172
+ if resp.status == 404 {
173
+ Result::Ok("") // 없음
174
+ } else {
175
+ Result::Err(response_to_error(resp))
176
+ }
177
+ } else {
178
+ Result::Ok(resp.body)
179
+ }
180
+ },
181
+
182
+ create_fn: async fn() -> Result<str> {
183
+ var json_data = json_stringify(req)
184
+ var resp = await http_post_json(service.client, "/api/v1/admin/users", json_data)
185
+ if is_error(resp.status) {
186
+ Result::Err(response_to_error(resp))
187
+ } else {
188
+ // 캐시 무효화
189
+ cache_invalidate(service.cache, "users:list")
190
+ Result::Ok(resp.body)
191
+ }
192
+ },
193
+
194
+ update_fn: async fn(changes: [str]) -> Result<str> {
195
+ var update_req = UpdateUserRequest {
196
+ email: req.email,
197
+ is_admin: req.is_admin
198
+ }
199
+ var json_data = json_stringify(update_req)
200
+ var resp = await http_patch(service.client, path, json_data)
201
+ if is_error(resp.status) {
202
+ Result::Err(response_to_error(resp))
203
+ } else {
204
+ // 캐시 무효화
205
+ var cache_key = "user:" + req.username
206
+ cache_invalidate(service.cache, cache_key)
207
+ cache_invalidate(service.cache, "users:list")
208
+ Result::Ok(resp.body)
209
+ }
210
+ },
211
+
212
+ delete_fn: async fn() -> Result<str> {
213
+ var resp = await http_delete(service.client, path)
214
+ if is_error(resp.status) {
215
+ Result::Err(response_to_error(resp))
216
+ } else {
217
+ // 캐시 무효화
218
+ var cache_key = "user:" + req.username
219
+ cache_invalidate(service.cache, cache_key)
220
+ cache_invalidate(service.cache, "users:list")
221
+ Result::Ok("")
222
+ }
223
+ },
224
+
225
+ desired: desired_json
226
+ }
227
+
228
+ var action = await ensure(ctx)
229
+ Result::Ok(action)
230
+ }
231
+
232
+ // ==================== 사용자 계정 관리 ====================
233
+
234
+ /// 사용자 잠금 (관리자 전용)
235
+ async fn lock_user(service: UserService, username: str) -> Result<str> {
236
+ var path = "/api/v1/admin/users/" + username + "/lock"
237
+ var resp = await http_put(service.client, path, "{}")
238
+
239
+ if is_error(resp.status) {
240
+ return Result::Err(response_to_error(resp))
241
+ }
242
+
243
+ // 캐시 무효화
244
+ var cache_key = "user:" + username
245
+ cache_invalidate(service.cache, cache_key)
246
+ cache_invalidate(service.cache, "users:list")
247
+
248
+ Result::Ok("User locked")
249
+ }
250
+
251
+ /// 사용자 잠금 해제 (관리자 전용)
252
+ async fn unlock_user(service: UserService, username: str) -> Result<str> {
253
+ var path = "/api/v1/admin/users/" + username + "/lock"
254
+ var resp = await http_delete(service.client, path)
255
+
256
+ if is_error(resp.status) {
257
+ return Result::Err(response_to_error(resp))
258
+ }
259
+
260
+ // 캐시 무효화
261
+ var cache_key = "user:" + username
262
+ cache_invalidate(service.cache, cache_key)
263
+ cache_invalidate(service.cache, "users:list")
264
+
265
+ Result::Ok("User unlocked")
266
+ }
267
+
268
+ /// 사용자 권한 변경
269
+ async fn set_admin(service: UserService, username: str, is_admin: bool) -> Result<User> {
270
+ var req = UpdateUserRequest {
271
+ email: "",
272
+ is_admin: is_admin
273
+ }
274
+ await update_user(service, username, req)
275
+ }
276
+
277
+ // ==================== 사용자 저장소 ====================
278
+
279
+ /// 사용자 저장소 목록
280
+ async fn list_user_repos(service: UserService, username: str) -> Result<[Repo]> {
281
+ var cache_key = "user_repos:" + username
282
+
283
+ // 캐시 확인
284
+ var cached = cache_get(service.cache, cache_key)
285
+ if cached != "" {
286
+ println("[cache] hit: " + cache_key)
287
+ var repos_arr = json_parse(cached) as [Repo]
288
+ return Result::Ok(repos_arr)
289
+ }
290
+
291
+ // API 호출
292
+ var path = "/api/v1/users/" + username + "/repos"
293
+ var resp = await http_get(service.client, path)
294
+
295
+ if is_error(resp.status) {
296
+ return Result::Err(response_to_error(resp))
297
+ }
298
+
299
+ // 캐시 저장
300
+ cache_set(service.cache, cache_key, resp.body)
301
+
302
+ // 파싱
303
+ var repos_arr = json_parse(resp.body) as [Repo]
304
+ Result::Ok(repos_arr)
305
+ }
306
+
307
+ // ==================== 사용자 검색 ====================
308
+
309
+ /// 사용자 검색
310
+ async fn search_users(service: UserService, query: str) -> Result<[User]> {
311
+ var path = "/api/v1/users/search?q=" + query
312
+ var resp = await http_get(service.client, path)
313
+
314
+ if is_error(resp.status) {
315
+ return Result::Err(response_to_error(resp))
316
+ }
317
+
318
+ // 파싱
319
+ var users_arr = json_parse(resp.body) as [User]
320
+ Result::Ok(users_arr)
321
+ }
322
+
323
+ // ==================== 사용자 팔로우 ====================
324
+
325
+ /// 사용자 팔로우
326
+ async fn follow_user(service: UserService, username: str) -> Result<str> {
327
+ var path = "/api/v1/user/following/" + username
328
+ var resp = await http_put(service.client, path, "{}")
329
+
330
+ if is_error(resp.status) {
331
+ return Result::Err(response_to_error(resp))
332
+ }
333
+
334
+ Result::Ok("Following user")
335
+ }
336
+
337
+ /// 사용자 팔로우 해제
338
+ async fn unfollow_user(service: UserService, username: str) -> Result<str> {
339
+ var path = "/api/v1/user/following/" + username
340
+ var resp = await http_delete(service.client, path)
341
+
342
+ if is_error(resp.status) {
343
+ return Result::Err(response_to_error(resp))
344
+ }
345
+
346
+ Result::Ok("Unfollowing user")
347
+ }
348
+
349
+ // ==================== 별명 (Alias) ====================
350
+
351
+ /// 사용자 목록 조회 (별명)
352
+ async fn list_all_users(service: UserService) -> Result<[User]> {
353
+ await list_users(service)
354
+ }
355
+
356
+ /// 사용자 생성 (별명)
357
+ async fn new_user(service: UserService, username: str, email: str, password: str) -> Result<User> {
358
+ var req = CreateUserRequest {
359
+ username: username,
360
+ email: email,
361
+ password: password,
362
+ send_notify: true,
363
+ is_admin: false
364
+ }
365
+ await create_user(service, req)
366
+ }
367
+
368
+ /// 사용자 삭제 (별명)
369
+ async fn rm_user(service: UserService, username: str) -> Result<str> {
370
+ await delete_user(service, username)
371
+ }
372
+
373
+ // UpdateUserRequest 모델 정의
374
+ struct UpdateUserRequest {
375
+ email: str
376
+ is_admin: bool
377
+ }
378
+
379
+ // 필요한 함수들 (다른 모듈에서 임포트):
380
+ // - http_get, http_post_json, http_delete, http_patch, http_put
381
+ // - is_error, response_to_error
382
+ // - cache_get, cache_set, cache_invalidate
383
+ // - json_stringify, json_parse
384
+ // - ensure, EnsureContext, EnsureAction
385
+ // - println, length
@@ -0,0 +1,273 @@
1
+ // FreeLang v4.2 — Gogs CLI WebhookService
2
+ // 웹훅 관리 API 서비스
3
+ // 200라인: create, list, get, delete, update, test
4
+
5
+ /// 웹훅 서비스
6
+ struct WebhookService {
7
+ client: HttpClient
8
+ cache: CacheManager
9
+ }
10
+
11
+ /// WebhookService 생성
12
+ fn new_webhook_service(client: HttpClient, cache: CacheManager) -> WebhookService {
13
+ WebhookService {
14
+ client: client,
15
+ cache: cache
16
+ }
17
+ }
18
+
19
+ // ==================== 웹훅 CRUD ====================
20
+
21
+ /// 웹훅 목록 조회 (저장소 단위)
22
+ async fn list_webhooks(service: WebhookService, owner: str, repo: str) -> Result<[Webhook]> {
23
+ var cache_key = "webhooks:" + owner + "/" + repo
24
+
25
+ // 캐시 확인
26
+ var cached = cache_get(service.cache, cache_key)
27
+ if cached != "" {
28
+ println("[cache] hit: " + cache_key)
29
+ var webhooks_arr = json_parse(cached) as [Webhook]
30
+ return Result::Ok(webhooks_arr)
31
+ }
32
+
33
+ // API 호출
34
+ var path = "/api/v1/repos/" + owner + "/" + repo + "/hooks"
35
+ var resp = await http_get(service.client, path)
36
+
37
+ if is_error(resp.status) {
38
+ return Result::Err(response_to_error(resp))
39
+ }
40
+
41
+ // 캐시 저장
42
+ cache_set(service.cache, cache_key, resp.body)
43
+
44
+ // 파싱
45
+ var webhooks_arr = json_parse(resp.body) as [Webhook]
46
+ Result::Ok(webhooks_arr)
47
+ }
48
+
49
+ /// 웹훅 상세 조회
50
+ async fn get_webhook(service: WebhookService, owner: str, repo: str, hook_id: i32) -> Result<Webhook> {
51
+ var cache_key = "webhook:" + owner + "/" + repo + "/" + str(hook_id)
52
+
53
+ // 캐시 확인
54
+ var cached = cache_get(service.cache, cache_key)
55
+ if cached != "" {
56
+ println("[cache] hit: " + cache_key)
57
+ var webhook = json_parse(cached) as Webhook
58
+ return Result::Ok(webhook)
59
+ }
60
+
61
+ // API 호출
62
+ var path = "/api/v1/repos/" + owner + "/" + repo + "/hooks/" + str(hook_id)
63
+ var resp = await http_get(service.client, path)
64
+
65
+ if is_error(resp.status) {
66
+ return Result::Err(response_to_error(resp))
67
+ }
68
+
69
+ // 캐시 저장
70
+ cache_set(service.cache, cache_key, resp.body)
71
+
72
+ // 파싱
73
+ var webhook = json_parse(resp.body) as Webhook
74
+ Result::Ok(webhook)
75
+ }
76
+
77
+ /// 웹훅 생성
78
+ async fn create_webhook(service: WebhookService, owner: str, repo: str, req: CreateWebhookRequest) -> Result<Webhook> {
79
+ var path = "/api/v1/repos/" + owner + "/" + repo + "/hooks"
80
+ var json_data = json_stringify(req)
81
+ var resp = await http_post_json(service.client, path, json_data)
82
+
83
+ if is_error(resp.status) {
84
+ return Result::Err(response_to_error(resp))
85
+ }
86
+
87
+ // 캐시 무효화
88
+ cache_invalidate(service.cache, "webhooks:" + owner + "/" + repo)
89
+
90
+ // 파싱
91
+ var webhook = json_parse(resp.body) as Webhook
92
+ Result::Ok(webhook)
93
+ }
94
+
95
+ /// 웹훅 업데이트
96
+ async fn update_webhook(service: WebhookService, owner: str, repo: str, hook_id: i32, req: UpdateWebhookRequest) -> Result<Webhook> {
97
+ var path = "/api/v1/repos/" + owner + "/" + repo + "/hooks/" + str(hook_id)
98
+ var json_data = json_stringify(req)
99
+ var resp = await http_patch(service.client, path, json_data)
100
+
101
+ if is_error(resp.status) {
102
+ return Result::Err(response_to_error(resp))
103
+ }
104
+
105
+ // 캐시 무효화
106
+ var cache_key = "webhook:" + owner + "/" + repo + "/" + str(hook_id)
107
+ cache_invalidate(service.cache, cache_key)
108
+ cache_invalidate(service.cache, "webhooks:" + owner + "/" + repo)
109
+
110
+ // 파싱
111
+ var webhook = json_parse(resp.body) as Webhook
112
+ Result::Ok(webhook)
113
+ }
114
+
115
+ /// 웹훅 삭제
116
+ async fn delete_webhook(service: WebhookService, owner: str, repo: str, hook_id: i32) -> Result<str> {
117
+ var path = "/api/v1/repos/" + owner + "/" + repo + "/hooks/" + str(hook_id)
118
+ var resp = await http_delete(service.client, path)
119
+
120
+ if is_error(resp.status) {
121
+ return Result::Err(response_to_error(resp))
122
+ }
123
+
124
+ // 캐시 무효화
125
+ var cache_key = "webhook:" + owner + "/" + repo + "/" + str(hook_id)
126
+ cache_invalidate(service.cache, cache_key)
127
+ cache_invalidate(service.cache, "webhooks:" + owner + "/" + repo)
128
+
129
+ Result::Ok("Webhook deleted")
130
+ }
131
+
132
+ // ==================== 웹훅 테스트 ====================
133
+
134
+ /// 웹훅 테스트
135
+ async fn test_webhook(service: WebhookService, owner: str, repo: str, hook_id: i32) -> Result<str> {
136
+ var path = "/api/v1/repos/" + owner + "/" + repo + "/hooks/" + str(hook_id) + "/tests"
137
+ var resp = await http_post_json(service.client, path, "{}")
138
+
139
+ if is_error(resp.status) {
140
+ return Result::Err(response_to_error(resp))
141
+ }
142
+
143
+ Result::Ok("Webhook test triggered")
144
+ }
145
+
146
+ // ==================== 웹훅 활성화/비활성화 ====================
147
+
148
+ /// 웹훅 활성화
149
+ async fn enable_webhook(service: WebhookService, owner: str, repo: str, hook_id: i32) -> Result<Webhook> {
150
+ var req = UpdateWebhookRequest {
151
+ url: "",
152
+ content_type: "",
153
+ events: [],
154
+ active: true
155
+ }
156
+ await update_webhook(service, owner, repo, hook_id, req)
157
+ }
158
+
159
+ /// 웹훅 비활성화
160
+ async fn disable_webhook(service: WebhookService, owner: str, repo: str, hook_id: i32) -> Result<Webhook> {
161
+ var req = UpdateWebhookRequest {
162
+ url: "",
163
+ content_type: "",
164
+ events: [],
165
+ active: false
166
+ }
167
+ await update_webhook(service, owner, repo, hook_id, req)
168
+ }
169
+
170
+ // ==================== 웹훅 이벤트 ====================
171
+
172
+ /// 기본 웹훅 이벤트 목록
173
+ fn get_default_webhook_events() -> [str] {
174
+ var events: [str] = []
175
+ events.push("push")
176
+ events.push("pull_request")
177
+ events.push("issues")
178
+ events.push("commit_comment")
179
+ events.push("pull_request_review_comment")
180
+ events.push("create")
181
+ events.push("delete")
182
+ events.push("deployment")
183
+ events.push("fork")
184
+ events.push("gollum")
185
+ events.push("page_build")
186
+ events.push("public")
187
+ events.push("release")
188
+ events
189
+ }
190
+
191
+ /// 웹훅 이벤트 목록 조회
192
+ async fn get_webhook_events(service: WebhookService) -> Result<[str]> {
193
+ var cache_key = "webhook_events"
194
+
195
+ // 캐시 확인
196
+ var cached = cache_get(service.cache, cache_key)
197
+ if cached != "" {
198
+ println("[cache] hit: webhook_events")
199
+ var events_arr = json_parse(cached) as [str]
200
+ return Result::Ok(events_arr)
201
+ }
202
+
203
+ // API 호출
204
+ var resp = await http_get(service.client, "/api/v1/repos/events")
205
+
206
+ if is_error(resp.status) {
207
+ return Result::Err(response_to_error(resp))
208
+ }
209
+
210
+ // 캐시 저장
211
+ cache_set(service.cache, cache_key, resp.body)
212
+
213
+ // 파싱
214
+ var events_arr = json_parse(resp.body) as [str]
215
+ Result::Ok(events_arr)
216
+ }
217
+
218
+ // ==================== 웹훅 기본 설정 ====================
219
+
220
+ /// Gogs 호환 기본 웹훅 생성
221
+ async fn create_gogs_webhook(service: WebhookService, owner: str, repo: str, webhook_url: str) -> Result<Webhook> {
222
+ var req = CreateWebhookRequest {
223
+ url: webhook_url,
224
+ content_type: "json",
225
+ events: get_default_webhook_events(),
226
+ active: true
227
+ }
228
+ await create_webhook(service, owner, repo, req)
229
+ }
230
+
231
+ /// JSON 콘텐츠 타입으로 웹훅 생성
232
+ async fn create_json_webhook(service: WebhookService, owner: str, repo: str, webhook_url: str, events: [str]) -> Result<Webhook> {
233
+ var req = CreateWebhookRequest {
234
+ url: webhook_url,
235
+ content_type: "json",
236
+ events: events,
237
+ active: true
238
+ }
239
+ await create_webhook(service, owner, repo, req)
240
+ }
241
+
242
+ // ==================== 별명 (Alias) ====================
243
+
244
+ /// 웹훅 생성 (별명)
245
+ async fn new_webhook(service: WebhookService, owner: str, repo: str, url: str) -> Result<Webhook> {
246
+ var req = CreateWebhookRequest {
247
+ url: url,
248
+ content_type: "json",
249
+ events: get_default_webhook_events(),
250
+ active: true
251
+ }
252
+ await create_webhook(service, owner, repo, req)
253
+ }
254
+
255
+ /// 웹훅 삭제 (별명)
256
+ async fn rm_webhook(service: WebhookService, owner: str, repo: str, hook_id: i32) -> Result<str> {
257
+ await delete_webhook(service, owner, repo, hook_id)
258
+ }
259
+
260
+ // UpdateWebhookRequest 모델 정의
261
+ struct UpdateWebhookRequest {
262
+ url: str
263
+ content_type: str
264
+ events: [str]
265
+ active: bool
266
+ }
267
+
268
+ // 필요한 함수들 (다른 모듈에서 임포트):
269
+ // - http_get, http_post_json, http_delete, http_patch
270
+ // - is_error, response_to_error
271
+ // - cache_get, cache_set, cache_invalidate
272
+ // - json_stringify, json_parse
273
+ // - println, length, str