Skip to content

Commit 2524055

Browse files
committed
Semi
1 parent d5a24ce commit 2524055

File tree

3 files changed

+98
-36
lines changed

3 files changed

+98
-36
lines changed

src/api/quantified-resume.ts

Lines changed: 51 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { fetchGet, fetchPost, fetchPut } from "./http"
1+
import { fetchDelete, fetchGet, fetchPost, fetchPut } from "./http"
22

33
const QR_BUILTIN_TYPE = "BrowserTime"
44
export const DEFAULT_ENDPOINT = "http://localhost:12233"
@@ -18,6 +18,18 @@ export type Bucket = {
1818
payload?: BucketPayload
1919
}
2020

21+
export type Item = {
22+
refId: string
23+
timestamp: number
24+
name?: string
25+
action: string
26+
metrics: {
27+
visit: number
28+
focus: number
29+
}
30+
payload?: Record<string, any>
31+
}
32+
2133
export type QrRequestContext = {
2234
endpoint?: string
2335
}
@@ -30,40 +42,57 @@ const headers = (): Headers => {
3042

3143
export const listBuckets = async (ctx: QrRequestContext, clientId?: string): Promise<Bucket[]> => {
3244
const response = await fetchGet(`${ctx?.endpoint || DEFAULT_ENDPOINT}/api/0/bucket?bt=${QR_BUILTIN_TYPE}&bt_rid=${clientId || ''}`)
33-
const status = response?.status
34-
if (status === 200) {
35-
return await response.json()
36-
} else if (status === 422) {
37-
throw new Error("Failed to connect Quantified Resume, please contact the developer")
38-
} else {
39-
console.error(response)
40-
throw new Error("Unexpected status code: " + status)
41-
}
45+
return handleResponseJson(response)
4246
}
4347

4448
export const createBucket = async (ctx: QrRequestContext, bucket: Bucket): Promise<number> => {
4549
const response = await fetchPost(`${ctx?.endpoint || DEFAULT_ENDPOINT}/api/0/bucket`, bucket, { headers: headers() })
46-
const status = response?.status
47-
if (status === 200) {
48-
return await response.json()
49-
} else if (status === 422) {
50-
throw new Error("Failed to connect Quantified Resume, please contact the developer")
51-
} else {
52-
console.error(response)
53-
throw new Error("Unexpected status code: " + status)
54-
}
50+
return handleResponseJson(response)
5551
}
5652

57-
export const updateBucket = async (ctx: QrRequestContext, bucket: Bucket): Promise<void> => {
58-
const response = await fetchPut(`${ctx?.endpoint || DEFAULT_ENDPOINT}/api/0/bucket/${bucket?.id}`, bucket, { headers: headers() })
53+
async function handleResponseJson<T>(response: Response): Promise<T> {
54+
await handleResponse(response)
55+
return response.json()
56+
}
57+
58+
async function handleResponse(response: Response): Promise<Response> {
5959
const status = response?.status
6060
if (status === 200) {
6161
return
6262
}
63+
let resMsg = null
64+
try {
65+
resMsg = (await response.json()).message
66+
} catch { }
67+
if (resMsg) {
68+
throw new Error(resMsg)
69+
}
6370
if (status === 422) {
6471
throw new Error("Failed to connect Quantified Resume, please contact the developer")
72+
} else if (status === 500) {
73+
throw new Error("Internal server error")
6574
} else {
6675
console.error(response)
67-
throw new Error("Unexpected status code: " + status)
76+
throw new Error(`Unexpected status code: ${status}, url=${response.url}`)
6877
}
78+
}
79+
80+
export const updateBucket = async (ctx: QrRequestContext, bucket: Bucket): Promise<void> => {
81+
const response = await fetchPut(`${ctx?.endpoint || DEFAULT_ENDPOINT}/api/0/bucket/${bucket?.id}`, bucket, { headers: headers() })
82+
await handleResponse(response)
83+
}
84+
85+
export const batchCreateItems = async (ctx: QrRequestContext, bucketId: number, items: Item[]): Promise<void> => {
86+
const response = await fetchPost(`${ctx?.endpoint || DEFAULT_ENDPOINT}/api/0/bucket/${bucketId}/item`, items, { headers: headers() })
87+
await handleResponse(response)
88+
}
89+
90+
export const listAllItems = async (ctx: QrRequestContext, bucketId: number): Promise<Item[]> => {
91+
const response = await fetchGet(`${ctx?.endpoint || DEFAULT_ENDPOINT}/api/0/bucket/${bucketId}/item`, { headers: headers() })
92+
return await handleResponseJson(response)
93+
}
94+
95+
export const removeBucket = async (ctx: QrRequestContext, bucketId: number): Promise<void> => {
96+
const response = await fetchDelete(`${ctx?.endpoint || DEFAULT_ENDPOINT}/api/0/bucket/${bucketId}?force=true`, { headers: headers() })
97+
await handleResponse(response)
6998
}

src/common/backup/quantified-resume/coordinator.ts

Lines changed: 44 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
import { Bucket, createBucket, listBuckets, updateBucket } from "@api/quantified-resume"
1+
import { batchCreateItems, Bucket, createBucket, Item, listAllItems, listBuckets, removeBucket, updateBucket } from "@api/quantified-resume"
22
import metaMessages, { } from "@i18n/message/common/meta"
33
import { t } from "@i18n"
44
import { groupBy } from "@util/array"
5+
import { formatTimeYMD, parseTime } from "@util/time"
56

67
export type QuantifiedResumeCache = {
78
bucketIds: {
@@ -23,15 +24,16 @@ async function createNewBucket(context: timer.backup.CoordinatorContext<Quantifi
2324
return createBucket({ endpoint }, bucket)
2425
}
2526

26-
async function getBucketId(context: timer.backup.CoordinatorContext<QuantifiedResumeCache>): Promise<number> {
27-
const { cid, cache } = context || {}
27+
async function getBucketId(context: timer.backup.CoordinatorContext<QuantifiedResumeCache>, specificCid?: string): Promise<number> {
28+
const cid = specificCid || context?.cid
29+
const { cache } = context || {}
2830
// 1. query from cache
2931
let bucketId = cache?.bucketIds?.[cid]
30-
if (!bucketId) return bucketId
32+
if (bucketId) return bucketId
3133

3234
const { endpoint } = context?.ext || {}
3335
// 2. query again
34-
bucketId = (await listBuckets({ endpoint }, cid))?.[0]?.id
36+
bucketId = (await listBuckets({ endpoint }, cid))?.filter(b => b.builtinRefId === cid)?.[0]?.id
3537
if (!bucketId) {
3638
// 3. create one
3739
bucketId = await createNewBucket(context)
@@ -82,15 +84,44 @@ export default class QuantifiedResumeCoordinator implements timer.backup.Coordin
8284
return result
8385
}
8486

85-
download(context: timer.backup.CoordinatorContext<QuantifiedResumeCache>, dateStart: Date, dateEnd: Date, targetCid?: string): Promise<timer.stat.RowBase[]> {
86-
throw new Error("Method not implemented.");
87+
async download(context: timer.backup.CoordinatorContext<QuantifiedResumeCache>, dateStart: Date, dateEnd: Date, targetCid?: string): Promise<timer.stat.RowBase[]> {
88+
let bucketId = await getBucketId(context, targetCid)
89+
if (!bucketId) return []
90+
const items = await listAllItems({ endpoint: context?.ext?.endpoint }, bucketId)
91+
return items?.map(({ name, timestamp, metrics }) => ({
92+
host: name,
93+
date: formatTimeYMD(timestamp),
94+
focus: metrics?.focus,
95+
time: metrics?.visit,
96+
} satisfies timer.stat.RowBase)) || []
8797
}
8898

8999
async upload(context: timer.backup.CoordinatorContext<QuantifiedResumeCache>, rows: timer.stat.RowBase[]): Promise<void> {
90-
const bucketId = await getBucketId(context)
91-
rows.forEach(row => {
100+
if (!rows?.length) return
92101

102+
const bucketId = await getBucketId(context)
103+
let items = rows.map(({ host, date, focus, time: visit }) => {
104+
const time = parseTime(date)
105+
time.setHours(0)
106+
time.setMinutes(0)
107+
time.setSeconds(0)
108+
time.setMilliseconds(0)
109+
const item: Item = {
110+
refId: `${date}${host}`,
111+
timestamp: time.getTime(),
112+
metrics: { visit, focus },
113+
action: "web_time",
114+
name: host,
115+
payload: { date, host, cid: context.cid },
116+
}
117+
return item
93118
})
119+
const groups = groupBy(items, (_, idx) => Math.floor(idx / 2000), l => l)
120+
121+
const { endpoint } = context?.ext || {}
122+
for (const group of Object.values(groups)) {
123+
await batchCreateItems({ endpoint }, bucketId, group)
124+
}
94125
}
95126

96127
async testAuth(_auth: timer.backup.Auth, ext: timer.backup.TypeExt): Promise<string> {
@@ -102,7 +133,9 @@ export default class QuantifiedResumeCoordinator implements timer.backup.Coordin
102133
}
103134
}
104135

105-
clear(context: timer.backup.CoordinatorContext<QuantifiedResumeCache>, client: timer.backup.Client): Promise<void> {
106-
throw new Error("Method not implemented.");
136+
async clear(context: timer.backup.CoordinatorContext<QuantifiedResumeCache>, client: timer.backup.Client): Promise<void> {
137+
const bucketId = await getBucketId(context, client.id)
138+
if (!bucketId) return
139+
await removeBucket({ endpoint: context?.ext?.endpoint }, bucketId)
107140
}
108141
}

src/util/array.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,12 @@
1515
*/
1616
export function groupBy<T, R>(
1717
arr: T[],
18-
keyFunc: (e: T) => string | number,
18+
keyFunc: (e: T, idx: number) => string | number,
1919
downstream: (grouped: T[], key: string) => R
2020
): { [key: string]: R } {
2121
const groupedMap: { [key: string]: T[] } = {}
22-
arr.forEach(e => {
23-
const key = keyFunc(e)
22+
arr.forEach((e, idx) => {
23+
const key = keyFunc(e, idx)
2424
if (key === undefined || key === null) {
2525
return
2626
}

0 commit comments

Comments
 (0)