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.
- package/README.md +548 -0
- package/dist/ast.d.ts +367 -0
- package/dist/ast.js +4 -0
- package/dist/ast.js.map +1 -0
- package/dist/async-basic.test.d.ts +1 -0
- package/dist/async-basic.test.js +88 -0
- package/dist/async-basic.test.js.map +1 -0
- package/dist/async-jest.test.d.ts +1 -0
- package/dist/async-jest.test.js +99 -0
- package/dist/async-jest.test.js.map +1 -0
- package/dist/channel-jest.test.d.ts +1 -0
- package/dist/channel-jest.test.js +148 -0
- package/dist/channel-jest.test.js.map +1 -0
- package/dist/checker-jest.test.d.ts +1 -0
- package/dist/checker-jest.test.js +160 -0
- package/dist/checker-jest.test.js.map +1 -0
- package/dist/checker.d.ts +149 -0
- package/dist/checker.js +1565 -0
- package/dist/checker.js.map +1 -0
- package/dist/checker.test.d.ts +1 -0
- package/dist/checker.test.js +217 -0
- package/dist/checker.test.js.map +1 -0
- package/dist/compiler-jest.test.d.ts +1 -0
- package/dist/compiler-jest.test.js +233 -0
- package/dist/compiler-jest.test.js.map +1 -0
- package/dist/compiler.d.ts +127 -0
- package/dist/compiler.js +1588 -0
- package/dist/compiler.js.map +1 -0
- package/dist/compiler.test.d.ts +1 -0
- package/dist/compiler.test.js +313 -0
- package/dist/compiler.test.js.map +1 -0
- package/dist/db-100m-full.d.ts +5 -0
- package/dist/db-100m-full.js +78 -0
- package/dist/db-100m-full.js.map +1 -0
- package/dist/db-100m-no-index.d.ts +12 -0
- package/dist/db-100m-no-index.js +119 -0
- package/dist/db-100m-no-index.js.map +1 -0
- package/dist/db-100m-real.d.ts +5 -0
- package/dist/db-100m-real.js +131 -0
- package/dist/db-100m-real.js.map +1 -0
- package/dist/db-100m-streaming.d.ts +15 -0
- package/dist/db-100m-streaming.js +164 -0
- package/dist/db-100m-streaming.js.map +1 -0
- package/dist/db-100m-test.d.ts +5 -0
- package/dist/db-100m-test.js +111 -0
- package/dist/db-100m-test.js.map +1 -0
- package/dist/db-jest.test.d.ts +1 -0
- package/dist/db-jest.test.js +182 -0
- package/dist/db-jest.test.js.map +1 -0
- package/dist/db-runtime.d.ts +24 -0
- package/dist/db-runtime.js +204 -0
- package/dist/db-runtime.js.map +1 -0
- package/dist/db.d.ts +249 -0
- package/dist/db.js +593 -0
- package/dist/db.js.map +1 -0
- package/dist/file-io-jest.test.d.ts +1 -0
- package/dist/file-io-jest.test.js +225 -0
- package/dist/file-io-jest.test.js.map +1 -0
- package/dist/for-of-jest.test.d.ts +1 -0
- package/dist/for-of-jest.test.js +230 -0
- package/dist/for-of-jest.test.js.map +1 -0
- package/dist/for-of.test.d.ts +1 -0
- package/dist/for-of.test.js +305 -0
- package/dist/for-of.test.js.map +1 -0
- package/dist/function-literal-jest.test.d.ts +1 -0
- package/dist/function-literal-jest.test.js +180 -0
- package/dist/function-literal-jest.test.js.map +1 -0
- package/dist/function-literal.test.d.ts +1 -0
- package/dist/function-literal.test.js +245 -0
- package/dist/function-literal.test.js.map +1 -0
- package/dist/generics-jest.test.d.ts +1 -0
- package/dist/generics-jest.test.js +93 -0
- package/dist/generics-jest.test.js.map +1 -0
- package/dist/ir-gen.d.ts +15 -0
- package/dist/ir-gen.js +400 -0
- package/dist/ir-gen.js.map +1 -0
- package/dist/ir.d.ts +114 -0
- package/dist/ir.js +5 -0
- package/dist/ir.js.map +1 -0
- package/dist/lexer.d.ts +110 -0
- package/dist/lexer.js +467 -0
- package/dist/lexer.js.map +1 -0
- package/dist/lexer.test.d.ts +1 -0
- package/dist/lexer.test.js +426 -0
- package/dist/lexer.test.js.map +1 -0
- package/dist/main.d.ts +2 -0
- package/dist/main.js +241 -0
- package/dist/main.js.map +1 -0
- package/dist/module-jest.test.d.ts +1 -0
- package/dist/module-jest.test.js +123 -0
- package/dist/module-jest.test.js.map +1 -0
- package/dist/parser.d.ts +56 -0
- package/dist/parser.js +1060 -0
- package/dist/parser.js.map +1 -0
- package/dist/parser.test.d.ts +1 -0
- package/dist/parser.test.js +461 -0
- package/dist/parser.test.js.map +1 -0
- package/dist/pattern-matching-jest.test.d.ts +1 -0
- package/dist/pattern-matching-jest.test.js +158 -0
- package/dist/pattern-matching-jest.test.js.map +1 -0
- package/dist/pkg/init.d.ts +1 -0
- package/dist/pkg/init.js +118 -0
- package/dist/pkg/init.js.map +1 -0
- package/dist/pkg/install.d.ts +1 -0
- package/dist/pkg/install.js +77 -0
- package/dist/pkg/install.js.map +1 -0
- package/dist/pkg/registry.d.ts +23 -0
- package/dist/pkg/registry.js +106 -0
- package/dist/pkg/registry.js.map +1 -0
- package/dist/pkg/run.d.ts +1 -0
- package/dist/pkg/run.js +76 -0
- package/dist/pkg/run.js.map +1 -0
- package/dist/pkg/toml.d.ts +5 -0
- package/dist/pkg/toml.js +117 -0
- package/dist/pkg/toml.js.map +1 -0
- package/dist/repl.d.ts +15 -0
- package/dist/repl.js +197 -0
- package/dist/repl.js.map +1 -0
- package/dist/runtime/bytecode.d.ts +92 -0
- package/dist/runtime/bytecode.js +253 -0
- package/dist/runtime/bytecode.js.map +1 -0
- package/dist/runtime/value.d.ts +102 -0
- package/dist/runtime/value.js +302 -0
- package/dist/runtime/value.js.map +1 -0
- package/dist/runtime/vm.d.ts +65 -0
- package/dist/runtime/vm.js +293 -0
- package/dist/runtime/vm.js.map +1 -0
- package/dist/struct-instance-jest.test.d.ts +1 -0
- package/dist/struct-instance-jest.test.js +209 -0
- package/dist/struct-instance-jest.test.js.map +1 -0
- package/dist/struct-instance.test.d.ts +1 -0
- package/dist/struct-instance.test.js +291 -0
- package/dist/struct-instance.test.js.map +1 -0
- package/dist/struct-jest.test.d.ts +1 -0
- package/dist/struct-jest.test.js +176 -0
- package/dist/struct-jest.test.js.map +1 -0
- package/dist/struct.test.d.ts +1 -0
- package/dist/struct.test.js +231 -0
- package/dist/struct.test.js.map +1 -0
- package/dist/trait-jest.test.d.ts +1 -0
- package/dist/trait-jest.test.js +120 -0
- package/dist/trait-jest.test.js.map +1 -0
- package/dist/vm-jest.test.d.ts +1 -0
- package/dist/vm-jest.test.js +569 -0
- package/dist/vm-jest.test.js.map +1 -0
- package/dist/vm.d.ts +81 -0
- package/dist/vm.js +1956 -0
- package/dist/vm.js.map +1 -0
- package/dist/vm.test.d.ts +1 -0
- package/dist/vm.test.js +337 -0
- package/dist/vm.test.js.map +1 -0
- package/dist/web-repl/sandbox.d.ts +11 -0
- package/dist/web-repl/sandbox.js +76 -0
- package/dist/web-repl/sandbox.js.map +1 -0
- package/dist/web-repl/server.d.ts +1 -0
- package/dist/web-repl/server.js +111 -0
- package/dist/web-repl/server.js.map +1 -0
- package/dist/while-loop-jest.test.d.ts +1 -0
- package/dist/while-loop-jest.test.js +201 -0
- package/dist/while-loop-jest.test.js.map +1 -0
- package/dist/while-loop.test.d.ts +1 -0
- package/dist/while-loop.test.js +262 -0
- package/dist/while-loop.test.js.map +1 -0
- package/docs/EXPERIENCE.md +787 -0
- package/docs/README.md +175 -0
- package/docs/V1_V2_V3_ANALYSIS.md +107 -0
- package/docs/_config.yml +36 -0
- package/docs/api-reference.md +459 -0
- package/docs/architecture.md +470 -0
- package/docs/benchmarks.md +295 -0
- package/docs/comparison.md +454 -0
- package/docs/index.md +335 -0
- package/docs/language-completeness.md +228 -0
- package/docs/learning-guide.md +651 -0
- package/package.json +65 -0
- package/src/api/deploy_key.fl +294 -0
- package/src/api/issue.fl +302 -0
- package/src/api/org.fl +356 -0
- package/src/api/repo.fl +394 -0
- package/src/api/team.fl +299 -0
- package/src/api/user.fl +385 -0
- package/src/api/webhook.fl +273 -0
- package/src/ast.ts +158 -0
- package/src/async-basic.test.ts +94 -0
- package/src/async-jest.test.ts +107 -0
- package/src/channel-jest.test.ts +158 -0
- package/src/checker-jest.test.ts +189 -0
- package/src/checker.test.ts +279 -0
- package/src/checker.ts +1861 -0
- package/src/commands/analyze.fl +227 -0
- package/src/commands/auth.fl +315 -0
- package/src/commands/batch.fl +349 -0
- package/src/commands/config.fl +199 -0
- package/src/commands/deploy_key.fl +352 -0
- package/src/commands/issue.fl +275 -0
- package/src/commands/main.fl +492 -0
- package/src/commands/org.fl +425 -0
- package/src/commands/repo.fl +581 -0
- package/src/commands/team.fl +244 -0
- package/src/commands/user.fl +423 -0
- package/src/commands/webhook.fl +400 -0
- package/src/compiler-jest.test.ts +275 -0
- package/src/compiler.test.ts +375 -0
- package/src/compiler.ts +1770 -0
- package/src/config.fl +175 -0
- package/src/core/batch.fl +355 -0
- package/src/core/cache.fl +284 -0
- package/src/core/ensure.fl +324 -0
- package/src/db-100m-full.ts +96 -0
- package/src/db-100m-no-index.ts +133 -0
- package/src/db-100m-real.ts +152 -0
- package/src/db-100m-streaming.ts +154 -0
- package/src/db-100m-test.ts +136 -0
- package/src/db-jest.test.ts +161 -0
- package/src/db-runtime.ts +242 -0
- package/src/db.ts +676 -0
- package/src/errors.fl +134 -0
- package/src/for-of-jest.test.ts +246 -0
- package/src/for-of.test.ts +308 -0
- package/src/function-literal-jest.test.ts +193 -0
- package/src/function-literal.test.ts +248 -0
- package/src/generics-jest.test.ts +104 -0
- package/src/http/client.fl +327 -0
- package/src/ir-gen.ts +459 -0
- package/src/ir.ts +80 -0
- package/src/lexer.test.ts +499 -0
- package/src/lexer.ts +522 -0
- package/src/main.ts +223 -0
- package/src/models.fl +162 -0
- package/src/module-jest.test.ts +145 -0
- package/src/parser.test.ts +542 -0
- package/src/parser.ts +1211 -0
- package/src/pattern-matching-jest.test.ts +170 -0
- package/src/pkg/init.ts +91 -0
- package/src/pkg/install.ts +56 -0
- package/src/pkg/registry.ts +103 -0
- package/src/pkg/run.ts +49 -0
- package/src/pkg/toml.ts +129 -0
- package/src/repl.ts +190 -0
- package/src/runtime/bytecode.ts +291 -0
- package/src/runtime/value.ts +322 -0
- package/src/runtime/vm.ts +354 -0
- package/src/self-host/bootstrap.fl +68 -0
- package/src/self-host/interpreter.fl +361 -0
- package/src/self-host/lexer-simple.fl +22 -0
- package/src/self-host/lexer.fl +305 -0
- package/src/self-host/parser.fl +580 -0
- package/src/struct-instance-jest.test.ts +221 -0
- package/src/struct-instance.test.ts +293 -0
- package/src/struct-jest.test.ts +187 -0
- package/src/struct.test.ts +234 -0
- package/src/trait-jest.test.ts +136 -0
- package/src/vm-jest.test.ts +754 -0
- package/src/vm.ts +1976 -0
- package/src/web-repl/public/index.html +50 -0
- package/src/web-repl/public/main.js +105 -0
- package/src/web-repl/public/style.css +225 -0
- package/src/web-repl/sandbox.ts +88 -0
- package/src/web-repl/server.ts +97 -0
- package/src/while-loop-jest.test.ts +218 -0
- package/src/while-loop.test.ts +267 -0
package/src/api/user.fl
ADDED
|
@@ -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
|