@downcity/services 0.1.71 → 0.1.83
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/bin/accounts/index.d.ts +2 -2
- package/bin/accounts/index.js +9 -9
- package/bin/accounts/schema.d.ts +4 -4
- package/bin/accounts/schema.js +2 -2
- package/bin/balance/service.d.ts +1 -1
- package/bin/balance/service.js +1 -1
- package/bin/usage/service.d.ts +4 -4
- package/bin/usage/service.js +8 -8
- package/package.json +2 -2
package/bin/accounts/index.d.ts
CHANGED
|
@@ -194,8 +194,8 @@ export declare class AccountsService extends InstallableService {
|
|
|
194
194
|
}, {}, {
|
|
195
195
|
length: number | undefined;
|
|
196
196
|
}>;
|
|
197
|
-
|
|
198
|
-
name: "
|
|
197
|
+
city_id: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
198
|
+
name: "city_id";
|
|
199
199
|
tableName: "service_accounts_oauth_states";
|
|
200
200
|
dataType: "string";
|
|
201
201
|
columnType: "SQLiteText";
|
package/bin/accounts/index.js
CHANGED
|
@@ -41,7 +41,7 @@ export class AccountsService extends InstallableService {
|
|
|
41
41
|
this.options = options;
|
|
42
42
|
this.instruction = ({ actions }) => [
|
|
43
43
|
"提供 Downcity 的账号、邮箱验证、GitHub/Google/WeChat OAuth 登录能力。",
|
|
44
|
-
"注册或登录时传入
|
|
44
|
+
"注册或登录时传入 city_id 后,接口会返回绑定该 city 的 City user_token。",
|
|
45
45
|
"OAuth 回调地址固定为 /v1/accounts/oauth/callback,服务会根据 City 公网地址生成完整回调 URL。",
|
|
46
46
|
`当前暴露 ${actions.length} 个动作,常用流程是 register/login -> verify-email 或 oauth/start -> me。`,
|
|
47
47
|
].join("\n");
|
|
@@ -159,7 +159,7 @@ export class AccountsService extends InstallableService {
|
|
|
159
159
|
});
|
|
160
160
|
}
|
|
161
161
|
const userToken = await ctx.createUserToken({
|
|
162
|
-
|
|
162
|
+
city_id: String(body.city_id ?? ""),
|
|
163
163
|
user_id,
|
|
164
164
|
ttl: this.options.token_ttl,
|
|
165
165
|
});
|
|
@@ -201,7 +201,7 @@ export class AccountsService extends InstallableService {
|
|
|
201
201
|
});
|
|
202
202
|
}
|
|
203
203
|
const userToken = await ctx.createUserToken({
|
|
204
|
-
|
|
204
|
+
city_id: String(body.city_id ?? ""),
|
|
205
205
|
user_id,
|
|
206
206
|
ttl: this.options.token_ttl,
|
|
207
207
|
});
|
|
@@ -236,9 +236,9 @@ export class AccountsService extends InstallableService {
|
|
|
236
236
|
if (!config) {
|
|
237
237
|
return c.jsonResponse({ error: "provider not configured" }, 400);
|
|
238
238
|
}
|
|
239
|
-
const
|
|
239
|
+
const city_id = String(body.city_id ?? "").trim() || "city_downcity";
|
|
240
240
|
const state = randomToken(24);
|
|
241
|
-
await this.createOAuthState(
|
|
241
|
+
await this.createOAuthState(city_id, provider, state);
|
|
242
242
|
const url = buildOAuthAuthorizeURL(config, this.getOAuthCallbackURL(), state);
|
|
243
243
|
return c.jsonResponse({ url, state, provider });
|
|
244
244
|
},
|
|
@@ -337,7 +337,7 @@ export class AccountsService extends InstallableService {
|
|
|
337
337
|
const profile = await resolveOAuthProfile(provider, this.getOAuthProviderConfig(provider), code, this.getOAuthCallbackURL());
|
|
338
338
|
const authUserId = await this.ensureOAuthAuthUser(profile, request);
|
|
339
339
|
const result = await this._authenticator.createToken({
|
|
340
|
-
|
|
340
|
+
city_id: entry.city_id,
|
|
341
341
|
user_id: authUserId,
|
|
342
342
|
ttl: this.options.token_ttl,
|
|
343
343
|
});
|
|
@@ -468,14 +468,14 @@ export class AccountsService extends InstallableService {
|
|
|
468
468
|
/**
|
|
469
469
|
* 创建 OAuth state。
|
|
470
470
|
*/
|
|
471
|
-
async createOAuthState(
|
|
472
|
-
await runPrepared(this.rawPrepare(`INSERT INTO ${ACCOUNTS_OAUTH_STATE_TABLE} (state,
|
|
471
|
+
async createOAuthState(city_id, provider, state) {
|
|
472
|
+
await runPrepared(this.rawPrepare(`INSERT INTO ${ACCOUNTS_OAUTH_STATE_TABLE} (state, city_id, provider, user_token, created_at) VALUES (?, ?, ?, ?, ?)`), [state, city_id, provider, "", Date.now()]);
|
|
473
473
|
}
|
|
474
474
|
/**
|
|
475
475
|
* 读取 OAuth state。
|
|
476
476
|
*/
|
|
477
477
|
async readOAuthState(state) {
|
|
478
|
-
const row = await readPreparedFirst(this.rawPrepare(`SELECT state,
|
|
478
|
+
const row = await readPreparedFirst(this.rawPrepare(`SELECT state, city_id, provider, user_token, created_at FROM ${ACCOUNTS_OAUTH_STATE_TABLE} WHERE state = ?`), [state]);
|
|
479
479
|
if (!row)
|
|
480
480
|
return null;
|
|
481
481
|
if (Date.now() - Number(row.created_at) > OAUTH_STATE_TTL_MS) {
|
package/bin/accounts/schema.d.ts
CHANGED
|
@@ -73,9 +73,9 @@ export interface AccountsOAuthStateRow extends Record<string, unknown> {
|
|
|
73
73
|
*/
|
|
74
74
|
state: string;
|
|
75
75
|
/**
|
|
76
|
-
* 登录成功后要签发到哪个
|
|
76
|
+
* 登录成功后要签发到哪个 city。
|
|
77
77
|
*/
|
|
78
|
-
|
|
78
|
+
city_id: string;
|
|
79
79
|
/**
|
|
80
80
|
* 第三方 provider 标识。
|
|
81
81
|
*/
|
|
@@ -255,8 +255,8 @@ export declare const accountsOAuthStates: import("drizzle-orm/sqlite-core").SQLi
|
|
|
255
255
|
}, {}, {
|
|
256
256
|
length: number | undefined;
|
|
257
257
|
}>;
|
|
258
|
-
|
|
259
|
-
name: "
|
|
258
|
+
city_id: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
259
|
+
name: "city_id";
|
|
260
260
|
tableName: "service_accounts_oauth_states";
|
|
261
261
|
dataType: "string";
|
|
262
262
|
columnType: "SQLiteText";
|
package/bin/accounts/schema.js
CHANGED
|
@@ -69,9 +69,9 @@ export const accountsOAuthStates = sqliteTable(ACCOUNTS_OAUTH_STATE_TABLE, {
|
|
|
69
69
|
*/
|
|
70
70
|
state: text("state").primaryKey(),
|
|
71
71
|
/**
|
|
72
|
-
* 登录成功后要签发到哪个
|
|
72
|
+
* 登录成功后要签发到哪个 city。
|
|
73
73
|
*/
|
|
74
|
-
|
|
74
|
+
city_id: text("city_id").notNull(),
|
|
75
75
|
/**
|
|
76
76
|
* provider 标识。
|
|
77
77
|
*/
|
package/bin/balance/service.d.ts
CHANGED
package/bin/balance/service.js
CHANGED
package/bin/usage/service.d.ts
CHANGED
|
@@ -42,8 +42,8 @@ export declare const usageEvents: import("drizzle-orm/sqlite-core").SQLiteTableW
|
|
|
42
42
|
}, {}, {
|
|
43
43
|
length: number | undefined;
|
|
44
44
|
}>;
|
|
45
|
-
|
|
46
|
-
name: "
|
|
45
|
+
city_id: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
46
|
+
name: "city_id";
|
|
47
47
|
tableName: "service_usage_events";
|
|
48
48
|
dataType: "string";
|
|
49
49
|
columnType: "SQLiteText";
|
|
@@ -210,8 +210,8 @@ export declare class UsageService extends InstallableService {
|
|
|
210
210
|
}, {}, {
|
|
211
211
|
length: number | undefined;
|
|
212
212
|
}>;
|
|
213
|
-
|
|
214
|
-
name: "
|
|
213
|
+
city_id: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
214
|
+
name: "city_id";
|
|
215
215
|
tableName: "service_usage_events";
|
|
216
216
|
dataType: "string";
|
|
217
217
|
columnType: "SQLiteText";
|
package/bin/usage/service.js
CHANGED
|
@@ -11,7 +11,7 @@ import { InstallableService } from "@downcity/city";
|
|
|
11
11
|
*/
|
|
12
12
|
export const usageEvents = sqliteTable("service_usage_events", {
|
|
13
13
|
event_id: text("event_id").primaryKey(),
|
|
14
|
-
|
|
14
|
+
city_id: text("city_id").notNull(),
|
|
15
15
|
user_id: text("user_id").notNull(),
|
|
16
16
|
service: text("service").notNull(),
|
|
17
17
|
model_id: text("model_id").notNull(),
|
|
@@ -77,7 +77,7 @@ export class UsageService extends InstallableService {
|
|
|
77
77
|
return requestCtx.jsonResponse({
|
|
78
78
|
items: await events.select({
|
|
79
79
|
user_id: requestCtx.user?.user_id ?? "",
|
|
80
|
-
|
|
80
|
+
city_id: requestCtx.city?.city_id ?? "",
|
|
81
81
|
}),
|
|
82
82
|
});
|
|
83
83
|
},
|
|
@@ -87,11 +87,11 @@ export class UsageService extends InstallableService {
|
|
|
87
87
|
/**
|
|
88
88
|
* 只记录真实用户侧调用。
|
|
89
89
|
*
|
|
90
|
-
* 管理端操作没有 user/
|
|
90
|
+
* 管理端操作没有 user/city 上下文,usage 服务自己的查询也不应反过来
|
|
91
91
|
* 产生 usage 事件,否则统计接口会污染自身结果。
|
|
92
92
|
*/
|
|
93
93
|
function shouldRecordUsage(ctx) {
|
|
94
|
-
return Boolean(ctx.user?.user_id && ctx.
|
|
94
|
+
return Boolean(ctx.user?.user_id && ctx.city?.city_id && ctx.service?.id !== "usage");
|
|
95
95
|
}
|
|
96
96
|
/**
|
|
97
97
|
* 创建 usage 事件记录。
|
|
@@ -99,7 +99,7 @@ function shouldRecordUsage(ctx) {
|
|
|
99
99
|
function createUsageEvent(ctx, status) {
|
|
100
100
|
return {
|
|
101
101
|
event_id: `usage_${randomId()}`,
|
|
102
|
-
|
|
102
|
+
city_id: ctx.city?.city_id ?? "",
|
|
103
103
|
user_id: ctx.user?.user_id ?? "",
|
|
104
104
|
service: ctx.service?.id ?? "",
|
|
105
105
|
model_id: ctx.variant?.id ?? "",
|
|
@@ -119,12 +119,12 @@ function createUsageEvent(ctx, status) {
|
|
|
119
119
|
function summarizeUsage(rows) {
|
|
120
120
|
const byKey = new Map();
|
|
121
121
|
for (const row of rows) {
|
|
122
|
-
const key = `${row.
|
|
123
|
-
const current = byKey.get(key) ?? {
|
|
122
|
+
const key = `${row.city_id}\u0000${row.service}\u0000${row.status}`;
|
|
123
|
+
const current = byKey.get(key) ?? { city_id: row.city_id, service: row.service, status: row.status, count: 0 };
|
|
124
124
|
current.count += 1;
|
|
125
125
|
byKey.set(key, current);
|
|
126
126
|
}
|
|
127
|
-
return [...byKey.values()].sort((a, b) => `${a.
|
|
127
|
+
return [...byKey.values()].sort((a, b) => `${a.city_id}:${a.service}:${a.status}`.localeCompare(`${b.city_id}:${b.service}:${b.status}`));
|
|
128
128
|
}
|
|
129
129
|
/**
|
|
130
130
|
* 生成随机 ID(兼容 Node 和 Workers)。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@downcity/services",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.83",
|
|
4
4
|
"description": "Downcity public services package for accounts, balance, usage, Stripe, Creem, Dodo, and Waffo payment flows.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./bin/index.js",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
52
|
"@waffo/pancake-ts": "^0.11.0",
|
|
53
|
-
"@downcity/city": "0.2.
|
|
53
|
+
"@downcity/city": "0.2.92",
|
|
54
54
|
"better-auth": "^1.6.12",
|
|
55
55
|
"dodopayments": "^2.36.0",
|
|
56
56
|
"drizzle-orm": "^0.45.2"
|