@kevisual/api 0.0.1
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/package.json +30 -0
- package/query/index.ts +7 -0
- package/query/kevisual.json +25 -0
- package/query/query-ai/defines/ai.ts +42 -0
- package/query/query-ai/query-ai.ts +101 -0
- package/query/query-app/defines/index.ts +3 -0
- package/query/query-app/defines/user-app-list.ts +62 -0
- package/query/query-app/defines/user-app.ts +33 -0
- package/query/query-app/query-app-define.ts +1 -0
- package/query/query-app/query-app.ts +18 -0
- package/query/query-login/login-cache.ts +204 -0
- package/query/query-login/login-node-cache.ts +132 -0
- package/query/query-login/query-login-browser.ts +12 -0
- package/query/query-login/query-login-node.ts +14 -0
- package/query/query-login/query-login.ts +434 -0
- package/query/query-mark/query-mark.ts +154 -0
- package/query/query-resources/index.ts +71 -0
- package/query/query-shop/defines/query-shop-define.ts +27 -0
- package/query/query-shop/query-shop.ts +17 -0
- package/query/query-upload/core/upload-chunk.ts +134 -0
- package/query/query-upload/core/upload-progress.ts +103 -0
- package/query/query-upload/core/upload.ts +113 -0
- package/query/query-upload/query-upload-browser.ts +51 -0
- package/query/query-upload/query-upload-node.ts +1 -0
- package/query/query-upload/query-upload.ts +11 -0
- package/query/query-upload/utils/filter-files.ts +23 -0
- package/query/query-upload/utils/index.ts +3 -0
- package/query/query-upload/utils/random-id.ts +3 -0
- package/query/query-upload/utils/to-file.ts +105 -0
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
import { Query, BaseQuery } from '@kevisual/query';
|
|
2
|
+
import type { Result, DataOpts } from '@kevisual/query/query';
|
|
3
|
+
import { setBaseResponse } from '@kevisual/query/query';
|
|
4
|
+
import { LoginCacheStore, CacheStore } from './login-cache.ts';
|
|
5
|
+
import { Cache } from './login-cache.ts';
|
|
6
|
+
|
|
7
|
+
export type QueryLoginOpts = {
|
|
8
|
+
query?: Query;
|
|
9
|
+
isBrowser?: boolean;
|
|
10
|
+
onLoad?: () => void;
|
|
11
|
+
storage?: Storage;
|
|
12
|
+
cache: Cache;
|
|
13
|
+
};
|
|
14
|
+
export type QueryLoginData = {
|
|
15
|
+
username?: string;
|
|
16
|
+
password: string;
|
|
17
|
+
email?: string;
|
|
18
|
+
};
|
|
19
|
+
export type QueryLoginResult = {
|
|
20
|
+
accessToken: string;
|
|
21
|
+
refreshToken: string;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export class QueryLogin extends BaseQuery {
|
|
25
|
+
/**
|
|
26
|
+
* query login cache, 非实际操作, 一个cache的包裹模块
|
|
27
|
+
*/
|
|
28
|
+
cacheStore: CacheStore;
|
|
29
|
+
isBrowser: boolean;
|
|
30
|
+
load?: boolean;
|
|
31
|
+
storage: Storage;
|
|
32
|
+
onLoad?: () => void;
|
|
33
|
+
|
|
34
|
+
constructor(opts?: QueryLoginOpts) {
|
|
35
|
+
super({
|
|
36
|
+
query: opts?.query || new Query(),
|
|
37
|
+
});
|
|
38
|
+
this.cacheStore = new LoginCacheStore({ name: 'login', cache: opts?.cache! });
|
|
39
|
+
this.isBrowser = opts?.isBrowser ?? true;
|
|
40
|
+
this.init();
|
|
41
|
+
this.onLoad = opts?.onLoad;
|
|
42
|
+
this.storage = opts?.storage || localStorage;
|
|
43
|
+
}
|
|
44
|
+
setQuery(query: Query) {
|
|
45
|
+
this.query = query;
|
|
46
|
+
}
|
|
47
|
+
private async init() {
|
|
48
|
+
await this.cacheStore.init();
|
|
49
|
+
this.load = true;
|
|
50
|
+
this.onLoad?.();
|
|
51
|
+
}
|
|
52
|
+
async post<T = any>(data: any, opts?: DataOpts) {
|
|
53
|
+
try {
|
|
54
|
+
return this.query.post<T>({ path: 'user', ...data }, opts);
|
|
55
|
+
} catch (error) {
|
|
56
|
+
console.log('error', error);
|
|
57
|
+
return {
|
|
58
|
+
code: 400,
|
|
59
|
+
} as any;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* 登录,
|
|
64
|
+
* @param data
|
|
65
|
+
* @returns
|
|
66
|
+
*/
|
|
67
|
+
async login(data: QueryLoginData) {
|
|
68
|
+
const res = await this.post<QueryLoginResult>({ key: 'login', ...data });
|
|
69
|
+
if (res.code === 200) {
|
|
70
|
+
const { accessToken, refreshToken } = res?.data || {};
|
|
71
|
+
this.storage.setItem('token', accessToken || '');
|
|
72
|
+
await this.beforeSetLoginUser({ accessToken, refreshToken });
|
|
73
|
+
}
|
|
74
|
+
return res;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* 手机号登录
|
|
78
|
+
* @param data
|
|
79
|
+
* @returns
|
|
80
|
+
*/
|
|
81
|
+
async loginByCode(data: { phone: string; code: string }) {
|
|
82
|
+
const res = await this.post<QueryLoginResult>({ path: 'sms', key: 'login', data });
|
|
83
|
+
if (res.code === 200) {
|
|
84
|
+
const { accessToken, refreshToken } = res?.data || {};
|
|
85
|
+
this.storage.setItem('token', accessToken || '');
|
|
86
|
+
await this.beforeSetLoginUser({ accessToken, refreshToken });
|
|
87
|
+
}
|
|
88
|
+
return res;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* 设置token
|
|
92
|
+
* @param token
|
|
93
|
+
*/
|
|
94
|
+
async setLoginToken(token: { accessToken: string; refreshToken: string }) {
|
|
95
|
+
const { accessToken, refreshToken } = token;
|
|
96
|
+
this.storage.setItem('token', accessToken || '');
|
|
97
|
+
await this.beforeSetLoginUser({ accessToken, refreshToken });
|
|
98
|
+
}
|
|
99
|
+
async loginByWechat(data: { code: string }) {
|
|
100
|
+
const res = await this.post<QueryLoginResult>({ path: 'wx', key: 'open-login', code: data.code });
|
|
101
|
+
if (res.code === 200) {
|
|
102
|
+
const { accessToken, refreshToken } = res?.data || {};
|
|
103
|
+
this.storage.setItem('token', accessToken || '');
|
|
104
|
+
await this.beforeSetLoginUser({ accessToken, refreshToken });
|
|
105
|
+
}
|
|
106
|
+
return res;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* 检测微信登录,登陆成功后,调用onSuccess,否则调用onError
|
|
110
|
+
* @param param0
|
|
111
|
+
*/
|
|
112
|
+
async checkWechat({ onSuccess, onError }: { onSuccess?: (res: QueryLoginResult) => void; onError?: (res: any) => void }) {
|
|
113
|
+
const url = new URL(window.location.href);
|
|
114
|
+
const code = url.searchParams.get('code');
|
|
115
|
+
const state = url.searchParams.get('state');
|
|
116
|
+
if (code && state) {
|
|
117
|
+
const res = await this.loginByWechat({ code });
|
|
118
|
+
if (res.code === 200) {
|
|
119
|
+
onSuccess?.(res.data);
|
|
120
|
+
} else {
|
|
121
|
+
onError?.(res);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* 登陆成功,需要获取用户信息进行缓存
|
|
127
|
+
* @param param0
|
|
128
|
+
*/
|
|
129
|
+
async beforeSetLoginUser({ accessToken, refreshToken, check401 }: { accessToken?: string; refreshToken?: string; check401?: boolean }) {
|
|
130
|
+
if (accessToken && refreshToken) {
|
|
131
|
+
const resUser = await this.getMe(accessToken, check401);
|
|
132
|
+
if (resUser.code === 200) {
|
|
133
|
+
const user = resUser.data;
|
|
134
|
+
if (user) {
|
|
135
|
+
this.cacheStore.setLoginUser({
|
|
136
|
+
user,
|
|
137
|
+
id: user.id,
|
|
138
|
+
accessToken,
|
|
139
|
+
refreshToken,
|
|
140
|
+
});
|
|
141
|
+
} else {
|
|
142
|
+
console.error('登录失败');
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* 刷新token
|
|
149
|
+
* @param refreshToken
|
|
150
|
+
* @returns
|
|
151
|
+
*/
|
|
152
|
+
async queryRefreshToken(refreshToken?: string) {
|
|
153
|
+
const _refreshToken = refreshToken || this.cacheStore.getRefreshToken();
|
|
154
|
+
let data = { refreshToken: _refreshToken };
|
|
155
|
+
if (!_refreshToken) {
|
|
156
|
+
await this.cacheStore.clearCurrentUser();
|
|
157
|
+
return {
|
|
158
|
+
code: 401,
|
|
159
|
+
message: '请先登录',
|
|
160
|
+
data: {} as any,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
return this.post(
|
|
164
|
+
{ key: 'refreshToken', data },
|
|
165
|
+
{
|
|
166
|
+
afterResponse: async (response, ctx) => {
|
|
167
|
+
setBaseResponse(response);
|
|
168
|
+
return response as any;
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* 检查401错误,并刷新token, 如果refreshToken存在,则刷新token, 否则返回401
|
|
175
|
+
* 拦截请求,请使用run401Action, 不要直接使用 afterCheck401ToRefreshToken
|
|
176
|
+
* @param response
|
|
177
|
+
* @param ctx
|
|
178
|
+
* @param refetch
|
|
179
|
+
* @returns
|
|
180
|
+
*/
|
|
181
|
+
async afterCheck401ToRefreshToken(response: Result, ctx?: { req?: any; res?: any; fetch?: any }, refetch?: boolean) {
|
|
182
|
+
const that = this;
|
|
183
|
+
if (response?.code === 401) {
|
|
184
|
+
const hasRefreshToken = await that.cacheStore.getRefreshToken();
|
|
185
|
+
if (hasRefreshToken) {
|
|
186
|
+
const res = await that.queryRefreshToken(hasRefreshToken);
|
|
187
|
+
if (res.code === 200) {
|
|
188
|
+
const { accessToken, refreshToken } = res?.data || {};
|
|
189
|
+
that.storage.setItem('token', accessToken || '');
|
|
190
|
+
await that.beforeSetLoginUser({ accessToken, refreshToken, check401: false });
|
|
191
|
+
if (refetch && ctx && ctx.req && ctx.req.url && ctx.fetch) {
|
|
192
|
+
await new Promise((resolve) => setTimeout(resolve, 1500));
|
|
193
|
+
const url = ctx.req?.url;
|
|
194
|
+
const body = ctx.req?.body;
|
|
195
|
+
const headers = ctx.req?.headers;
|
|
196
|
+
const res = await ctx.fetch(url, {
|
|
197
|
+
method: 'POST',
|
|
198
|
+
body: body,
|
|
199
|
+
headers: { ...headers, Authorization: `Bearer ${accessToken}` },
|
|
200
|
+
});
|
|
201
|
+
setBaseResponse(res);
|
|
202
|
+
return res;
|
|
203
|
+
}
|
|
204
|
+
} else {
|
|
205
|
+
that.storage.removeItem('token');
|
|
206
|
+
await that.cacheStore.clearCurrentUser();
|
|
207
|
+
}
|
|
208
|
+
return res;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return response as any;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* 一个简单的401处理, 如果401,则刷新token, 如果refreshToken不存在,则返回401
|
|
215
|
+
* refetch 是否重新请求, 会有bug,无限循环,按需要使用
|
|
216
|
+
* TODO:
|
|
217
|
+
* @param response
|
|
218
|
+
* @param ctx
|
|
219
|
+
* @param opts
|
|
220
|
+
* @returns
|
|
221
|
+
*/
|
|
222
|
+
async run401Action(
|
|
223
|
+
response: Result,
|
|
224
|
+
ctx?: { req?: any; res?: any; fetch?: any },
|
|
225
|
+
opts?: {
|
|
226
|
+
/**
|
|
227
|
+
* 是否重新请求, 会有bug,无限循环,按需要使用
|
|
228
|
+
*/
|
|
229
|
+
refetch?: boolean;
|
|
230
|
+
/**
|
|
231
|
+
* check之后的回调
|
|
232
|
+
*/
|
|
233
|
+
afterCheck?: (res: Result) => any;
|
|
234
|
+
/**
|
|
235
|
+
* 401处理后, 还是401, 则回调
|
|
236
|
+
*/
|
|
237
|
+
afterAlso401?: (res: Result) => any;
|
|
238
|
+
},
|
|
239
|
+
) {
|
|
240
|
+
const that = this;
|
|
241
|
+
const refetch = opts?.refetch ?? false;
|
|
242
|
+
if (response?.code === 401) {
|
|
243
|
+
if (that.query.stop === true) {
|
|
244
|
+
return { code: 500, success: false, message: 'refresh token loading...' };
|
|
245
|
+
}
|
|
246
|
+
that.query.stop = true;
|
|
247
|
+
const res = await that.afterCheck401ToRefreshToken(response, ctx, refetch);
|
|
248
|
+
that.query.stop = false;
|
|
249
|
+
opts?.afterCheck?.(res);
|
|
250
|
+
if (res.code === 401) {
|
|
251
|
+
opts?.afterAlso401?.(res);
|
|
252
|
+
}
|
|
253
|
+
return res;
|
|
254
|
+
} else {
|
|
255
|
+
return response as any;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* 获取用户信息
|
|
260
|
+
* @param token
|
|
261
|
+
* @returns
|
|
262
|
+
*/
|
|
263
|
+
async getMe(token?: string, check401: boolean = true) {
|
|
264
|
+
const _token = token || this.storage.getItem('token');
|
|
265
|
+
const that = this;
|
|
266
|
+
return that.post(
|
|
267
|
+
{ key: 'me' },
|
|
268
|
+
{
|
|
269
|
+
beforeRequest: async (config) => {
|
|
270
|
+
if (config.headers) {
|
|
271
|
+
config.headers['Authorization'] = `Bearer ${_token}`;
|
|
272
|
+
}
|
|
273
|
+
if (!_token) {
|
|
274
|
+
return false;
|
|
275
|
+
}
|
|
276
|
+
return config;
|
|
277
|
+
},
|
|
278
|
+
afterResponse: async (response, ctx) => {
|
|
279
|
+
if (response?.code === 401 && check401 && !token) {
|
|
280
|
+
return await that.afterCheck401ToRefreshToken(response, ctx);
|
|
281
|
+
}
|
|
282
|
+
return response as any;
|
|
283
|
+
},
|
|
284
|
+
},
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* 检查本地用户,如果本地用户存在,则返回本地用户,否则返回null
|
|
289
|
+
* @returns
|
|
290
|
+
*/
|
|
291
|
+
async checkLocalUser() {
|
|
292
|
+
const user = await this.cacheStore.getCurrentUser();
|
|
293
|
+
if (user) {
|
|
294
|
+
return user;
|
|
295
|
+
}
|
|
296
|
+
return null;
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* 检查本地token是否存在,简单的判断是否已经属于登陆状态
|
|
300
|
+
* @returns
|
|
301
|
+
*/
|
|
302
|
+
async checkLocalToken() {
|
|
303
|
+
const token = this.storage.getItem('token');
|
|
304
|
+
return !!token;
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* 检查本地用户列表
|
|
308
|
+
* @returns
|
|
309
|
+
*/
|
|
310
|
+
async getToken() {
|
|
311
|
+
const token = this.storage.getItem('token');
|
|
312
|
+
return token || '';
|
|
313
|
+
}
|
|
314
|
+
async beforeRequest(opts: any = {}) {
|
|
315
|
+
const token = this.storage.getItem('token');
|
|
316
|
+
if (token) {
|
|
317
|
+
opts.headers = { ...opts.headers, Authorization: `Bearer ${token}` };
|
|
318
|
+
}
|
|
319
|
+
return opts;
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* 请求更新,切换用户, 使用switchUser
|
|
323
|
+
* @param username
|
|
324
|
+
* @returns
|
|
325
|
+
*/
|
|
326
|
+
private async postSwitchUser(username: string) {
|
|
327
|
+
return this.post({ key: 'switchCheck', data: { username } });
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* 切换用户
|
|
331
|
+
* @param username
|
|
332
|
+
* @returns
|
|
333
|
+
*/
|
|
334
|
+
async switchUser(username: string) {
|
|
335
|
+
const localUserList = await this.cacheStore.getCurrentUserList();
|
|
336
|
+
const user = localUserList.find((userItem) => userItem.user!.username === username);
|
|
337
|
+
if (user) {
|
|
338
|
+
this.storage.setItem('token', user.accessToken || '');
|
|
339
|
+
await this.beforeSetLoginUser({ accessToken: user.accessToken, refreshToken: user.refreshToken });
|
|
340
|
+
return {
|
|
341
|
+
code: 200,
|
|
342
|
+
data: {
|
|
343
|
+
accessToken: user.accessToken,
|
|
344
|
+
refreshToken: user.refreshToken,
|
|
345
|
+
},
|
|
346
|
+
success: true,
|
|
347
|
+
message: '切换用户成功',
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
const res = await this.postSwitchUser(username);
|
|
351
|
+
|
|
352
|
+
if (res.code === 200) {
|
|
353
|
+
const { accessToken, refreshToken } = res?.data || {};
|
|
354
|
+
this.storage.setItem('token', accessToken || '');
|
|
355
|
+
await this.beforeSetLoginUser({ accessToken, refreshToken });
|
|
356
|
+
}
|
|
357
|
+
return res;
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* 退出登陆,去掉token, 并删除缓存
|
|
361
|
+
* @returns
|
|
362
|
+
*/
|
|
363
|
+
async logout() {
|
|
364
|
+
this.storage.removeItem('token');
|
|
365
|
+
const users = await this.cacheStore.getCurrentUserList();
|
|
366
|
+
const tokens = users
|
|
367
|
+
.map((user) => {
|
|
368
|
+
return user?.accessToken;
|
|
369
|
+
})
|
|
370
|
+
.filter(Boolean);
|
|
371
|
+
this.cacheStore.delValue();
|
|
372
|
+
return this.post<Result>({ key: 'logout', data: { tokens } });
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* 检查用户名的组,这个用户是否存在
|
|
376
|
+
* @param username
|
|
377
|
+
* @returns
|
|
378
|
+
*/
|
|
379
|
+
async hasUser(username: string) {
|
|
380
|
+
const that = this;
|
|
381
|
+
return this.post<Result>(
|
|
382
|
+
{
|
|
383
|
+
path: 'org',
|
|
384
|
+
key: 'hasUser',
|
|
385
|
+
data: {
|
|
386
|
+
username,
|
|
387
|
+
},
|
|
388
|
+
},
|
|
389
|
+
{
|
|
390
|
+
afterResponse: async (response, ctx) => {
|
|
391
|
+
if (response?.code === 401) {
|
|
392
|
+
const res = await that.afterCheck401ToRefreshToken(response, ctx, true);
|
|
393
|
+
return res;
|
|
394
|
+
}
|
|
395
|
+
return response as any;
|
|
396
|
+
},
|
|
397
|
+
},
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* 检查登录状态
|
|
402
|
+
* @param token
|
|
403
|
+
* @returns
|
|
404
|
+
*/
|
|
405
|
+
async checkLoginStatus(token: string) {
|
|
406
|
+
const res = await this.post({
|
|
407
|
+
path: 'user',
|
|
408
|
+
key: 'checkLoginStatus',
|
|
409
|
+
loginToken: token,
|
|
410
|
+
});
|
|
411
|
+
if (res.code === 200) {
|
|
412
|
+
const accessToken = res.data?.accessToken;
|
|
413
|
+
this.storage.setItem('token', accessToken || '');
|
|
414
|
+
await this.beforeSetLoginUser({ accessToken, refreshToken: res.data?.refreshToken });
|
|
415
|
+
return res;
|
|
416
|
+
}
|
|
417
|
+
return false;
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* 使用web登录,创建url地址, 需要MD5和jsonwebtoken
|
|
421
|
+
*/
|
|
422
|
+
loginWithWeb(baseURL: string, { MD5, jsonwebtoken }: { MD5: any; jsonwebtoken: any }) {
|
|
423
|
+
const randomId = Math.random().toString(36).substring(2, 15);
|
|
424
|
+
const timestamp = Date.now();
|
|
425
|
+
const tokenSecret = 'xiao' + randomId;
|
|
426
|
+
const sign = MD5(`${tokenSecret}${timestamp}`).toString();
|
|
427
|
+
const token = jsonwebtoken.sign({ randomId, timestamp, sign }, tokenSecret, {
|
|
428
|
+
// 10分钟过期
|
|
429
|
+
expiresIn: 60 * 10, // 10分钟
|
|
430
|
+
});
|
|
431
|
+
const url = `${baseURL}/api/router?path=user&key=webLogin&p&loginToken=${token}&sign=${sign}&randomId=${randomId}`;
|
|
432
|
+
return { url, token, tokenSecret };
|
|
433
|
+
}
|
|
434
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { Query } from '@kevisual/query';
|
|
2
|
+
import type { Result, DataOpts } from '@kevisual/query/query';
|
|
3
|
+
|
|
4
|
+
export type SimpleObject = Record<string, any>;
|
|
5
|
+
export const markType = ['simple', 'md', 'mdx', 'wallnote', 'excalidraw', 'chat'] as const;
|
|
6
|
+
export type MarkType = (typeof markType)[number];
|
|
7
|
+
export type MarkData = {
|
|
8
|
+
nodes?: any[];
|
|
9
|
+
edges?: any[];
|
|
10
|
+
elements?: any[];
|
|
11
|
+
permission?: any;
|
|
12
|
+
|
|
13
|
+
[key: string]: any;
|
|
14
|
+
};
|
|
15
|
+
export type Mark = {
|
|
16
|
+
id: string;
|
|
17
|
+
title: string;
|
|
18
|
+
description: string;
|
|
19
|
+
markType: MarkType;
|
|
20
|
+
link: string;
|
|
21
|
+
data?: MarkData;
|
|
22
|
+
uid: string;
|
|
23
|
+
puid: string;
|
|
24
|
+
summary: string;
|
|
25
|
+
thumbnail?: string;
|
|
26
|
+
tags: string[];
|
|
27
|
+
createdAt: string;
|
|
28
|
+
updatedAt: string;
|
|
29
|
+
version: number;
|
|
30
|
+
};
|
|
31
|
+
export type ShowMarkPick = Pick<Mark, 'id' | 'title' | 'description' | 'summary' | 'link' | 'tags' | 'thumbnail' | 'updatedAt'>;
|
|
32
|
+
|
|
33
|
+
export type SearchOpts = {
|
|
34
|
+
page?: number;
|
|
35
|
+
pageSize?: number;
|
|
36
|
+
search?: string;
|
|
37
|
+
sort?: string; // DESC, ASC
|
|
38
|
+
markType?: MarkType; // 类型
|
|
39
|
+
[key: string]: any;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export type QueryMarkOpts<T extends SimpleObject = SimpleObject> = {
|
|
43
|
+
query?: Query;
|
|
44
|
+
isBrowser?: boolean;
|
|
45
|
+
onLoad?: () => void;
|
|
46
|
+
} & T;
|
|
47
|
+
|
|
48
|
+
export type ResultMarkList = {
|
|
49
|
+
list: Mark[];
|
|
50
|
+
pagination: {
|
|
51
|
+
pageSize: number;
|
|
52
|
+
current: number;
|
|
53
|
+
total: number;
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
export type QueryMarkData = {
|
|
57
|
+
id?: string;
|
|
58
|
+
title?: string;
|
|
59
|
+
description?: string;
|
|
60
|
+
[key: string]: any;
|
|
61
|
+
};
|
|
62
|
+
export type QueryMarkResult = {
|
|
63
|
+
accessToken: string;
|
|
64
|
+
refreshToken: string;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export class QueryMarkBase<T extends SimpleObject = SimpleObject> {
|
|
68
|
+
query: Query;
|
|
69
|
+
isBrowser: boolean;
|
|
70
|
+
load?: boolean;
|
|
71
|
+
storage?: Storage;
|
|
72
|
+
onLoad?: () => void;
|
|
73
|
+
|
|
74
|
+
constructor(opts?: QueryMarkOpts<T>) {
|
|
75
|
+
this.query = opts?.query || new Query();
|
|
76
|
+
this.isBrowser = opts?.isBrowser ?? true;
|
|
77
|
+
this.init();
|
|
78
|
+
this.onLoad = opts?.onLoad;
|
|
79
|
+
}
|
|
80
|
+
setQuery(query: Query) {
|
|
81
|
+
this.query = query;
|
|
82
|
+
}
|
|
83
|
+
private async init() {
|
|
84
|
+
this.load = true;
|
|
85
|
+
this.onLoad?.();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async post<T = Result<any>>(data: any, opts?: DataOpts): Promise<T> {
|
|
89
|
+
try {
|
|
90
|
+
return this.query.post({ path: 'mark', ...data }, opts) as Promise<T>;
|
|
91
|
+
} catch (error) {
|
|
92
|
+
console.log('error', error);
|
|
93
|
+
return {
|
|
94
|
+
code: 400,
|
|
95
|
+
} as any;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async getMarkList(search: SearchOpts, opts?: DataOpts) {
|
|
100
|
+
return this.post<Result<ResultMarkList>>({ key: 'list', ...search }, opts);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async getMark(id: string, opts?: DataOpts) {
|
|
104
|
+
return this.post<Result<Mark>>({ key: 'get', id }, opts);
|
|
105
|
+
}
|
|
106
|
+
async getVersion(id: string, opts?: DataOpts) {
|
|
107
|
+
return this.post<Result<{ version: number; id: string }>>({ key: 'getVersion', id }, opts);
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* 检查版本
|
|
111
|
+
* 当需要更新时,返回true
|
|
112
|
+
* @param id
|
|
113
|
+
* @param version
|
|
114
|
+
* @param opts
|
|
115
|
+
* @returns
|
|
116
|
+
*/
|
|
117
|
+
async checkVersion(id: string, version?: number, opts?: DataOpts) {
|
|
118
|
+
if (!version) {
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
const res = await this.getVersion(id, opts);
|
|
122
|
+
if (res.code === 200) {
|
|
123
|
+
if (res.data!.version > version) {
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async updateMark(data: any, opts?: DataOpts) {
|
|
132
|
+
return this.post<Result<Mark>>({ key: 'update', data }, opts);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async deleteMark(id: string, opts?: DataOpts) {
|
|
136
|
+
return this.post<Result<Mark>>({ key: 'delete', id }, opts);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
export class QueryMark extends QueryMarkBase<SimpleObject> {
|
|
140
|
+
markType: string;
|
|
141
|
+
constructor(opts?: QueryMarkOpts & { markType?: MarkType }) {
|
|
142
|
+
super(opts);
|
|
143
|
+
this.markType = opts?.markType || 'simple';
|
|
144
|
+
}
|
|
145
|
+
async getMarkList(search?: SearchOpts, opts?: DataOpts) {
|
|
146
|
+
return this.post<Result<ResultMarkList>>({ key: 'list', ...search, markType: this.markType }, opts);
|
|
147
|
+
}
|
|
148
|
+
async updateMark(data: any, opts?: DataOpts) {
|
|
149
|
+
if (!data.id) {
|
|
150
|
+
data.markType = this.markType || 'simple';
|
|
151
|
+
}
|
|
152
|
+
return super.updateMark(data, opts);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { adapter, DataOpts, Result } from '@kevisual/query';
|
|
2
|
+
|
|
3
|
+
type QueryResourcesOptions = {
|
|
4
|
+
prefix?: string;
|
|
5
|
+
storage?: Storage;
|
|
6
|
+
username?: string;
|
|
7
|
+
[key: string]: any;
|
|
8
|
+
};
|
|
9
|
+
export class QueryResources {
|
|
10
|
+
prefix: string; // root/resources
|
|
11
|
+
storage: Storage;
|
|
12
|
+
constructor(opts: QueryResourcesOptions) {
|
|
13
|
+
if (opts.username) {
|
|
14
|
+
this.prefix = `/${opts.username}/resources/`;
|
|
15
|
+
} else {
|
|
16
|
+
this.prefix = opts.prefix || '';
|
|
17
|
+
}
|
|
18
|
+
this.storage = opts.storage || localStorage;
|
|
19
|
+
}
|
|
20
|
+
setUsername(username: string) {
|
|
21
|
+
this.prefix = `/${username}/resources/`;
|
|
22
|
+
}
|
|
23
|
+
header(headers?: Record<string, string>, json = true): Record<string, string> {
|
|
24
|
+
const token = this.storage.getItem('token');
|
|
25
|
+
const _headers: Record<string, string> = {
|
|
26
|
+
'Content-Type': 'application/json',
|
|
27
|
+
...headers,
|
|
28
|
+
};
|
|
29
|
+
if (!json) {
|
|
30
|
+
delete _headers['Content-Type'];
|
|
31
|
+
}
|
|
32
|
+
if (!token) {
|
|
33
|
+
return _headers;
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
..._headers,
|
|
37
|
+
Authorization: `Bearer ${token}`,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
async get(data: any, opts: DataOpts): Promise<any> {
|
|
41
|
+
return adapter({
|
|
42
|
+
url: opts.url!,
|
|
43
|
+
method: 'GET',
|
|
44
|
+
body: data,
|
|
45
|
+
...opts,
|
|
46
|
+
headers: this.header(opts?.headers),
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
async getList(prefix: string, data?: { recursive?: boolean }, opts?: DataOpts): Promise<Result<any[]>> {
|
|
50
|
+
return this.get(data, {
|
|
51
|
+
url: `${this.prefix}${prefix}`,
|
|
52
|
+
body: data,
|
|
53
|
+
...opts,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
async fetchFile(filepath: string, opts?: DataOpts): Promise<Result<any>> {
|
|
57
|
+
return fetch(`${this.prefix}${filepath}`, {
|
|
58
|
+
method: 'GET',
|
|
59
|
+
headers: this.header(opts?.headers, false),
|
|
60
|
+
}).then(async (res) => {
|
|
61
|
+
if (!res.ok) {
|
|
62
|
+
return {
|
|
63
|
+
code: 500,
|
|
64
|
+
success: false,
|
|
65
|
+
message: `Failed to fetch file: ${res.status} ${res.statusText}`,
|
|
66
|
+
} as Result<any>;
|
|
67
|
+
}
|
|
68
|
+
return { code: 200, data: await res.text(), success: true } as Result<any>;
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { QueryUtil } from '@/query/index.ts';
|
|
2
|
+
|
|
3
|
+
export const shopDefine = QueryUtil.create({
|
|
4
|
+
getRegistry: {
|
|
5
|
+
path: 'shop',
|
|
6
|
+
key: 'get-registry',
|
|
7
|
+
description: '获取应用商店注册表信息',
|
|
8
|
+
},
|
|
9
|
+
|
|
10
|
+
listInstalled: {
|
|
11
|
+
path: 'shop',
|
|
12
|
+
key: 'list-installed',
|
|
13
|
+
description: '列出当前已安装的所有应用',
|
|
14
|
+
},
|
|
15
|
+
|
|
16
|
+
install: {
|
|
17
|
+
path: 'shop',
|
|
18
|
+
key: 'install',
|
|
19
|
+
description: '安装指定的应用,可以指定 id、type、force 和 yes 参数',
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
uninstall: {
|
|
23
|
+
path: 'shop',
|
|
24
|
+
key: 'uninstall',
|
|
25
|
+
description: '卸载指定的应用,可以指定 id 和 type 参数',
|
|
26
|
+
},
|
|
27
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { shopDefine } from './defines/query-shop-define.ts';
|
|
2
|
+
|
|
3
|
+
import { BaseQuery, DataOpts, Query } from '@kevisual/query/query';
|
|
4
|
+
|
|
5
|
+
export { shopDefine };
|
|
6
|
+
|
|
7
|
+
export class QueryShop<T extends Query = Query> extends BaseQuery<T, typeof shopDefine> {
|
|
8
|
+
constructor(opts?: { query: T }) {
|
|
9
|
+
super({
|
|
10
|
+
query: opts?.query!,
|
|
11
|
+
queryDefine: shopDefine,
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
getInstall(data: any, opts?: DataOpts) {
|
|
15
|
+
return this.queryDefine.queryChain('install').post(data, opts);
|
|
16
|
+
}
|
|
17
|
+
}
|