Skip to content

Commit 51df103

Browse files
authored
Merge pull request #142 from sheepzh/data_sync
2 parents 0ee26b5 + ee89665 commit 51df103

File tree

38 files changed

+1583
-100
lines changed

38 files changed

+1583
-100
lines changed

global.d.ts

Lines changed: 79 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,27 @@ declare namespace timer {
100100
countLocalFiles: boolean
101101
}
102102

103-
type AllOption = PopupOption & AppearanceOption & StatisticsOption
103+
/**
104+
* The options of backup
105+
*
106+
* @since 1.2.0
107+
*/
108+
type BackupOption = {
109+
/**
110+
* The type 2 backup
111+
*/
112+
backupType: backup.Type
113+
/**
114+
* The auth of types, maybe ak/sk or static token
115+
*/
116+
backupAuths: { [type in backup.Type]?: string }
117+
/**
118+
* The name of this client
119+
*/
120+
clientName: string
121+
}
122+
123+
type AllOption = PopupOption & AppearanceOption & StatisticsOption & BackupOption
104124
/**
105125
* @since 0.8.0
106126
*/
@@ -114,6 +134,12 @@ declare namespace timer {
114134
popupCounter?: {
115135
_total?: number
116136
}
137+
/**
138+
* The id of this client
139+
*
140+
* @since 1.2.0
141+
*/
142+
cid?: string
117143
}
118144
}
119145

@@ -162,10 +188,12 @@ declare namespace timer {
162188
date?: string
163189
}
164190

191+
type RowBase = RowKey & Result
192+
165193
/**
166194
* Row of each statistics result
167195
*/
168-
type Row = RowKey & Result & {
196+
type Row = RowBase & {
169197
/**
170198
* The merged domains
171199
*
@@ -185,6 +213,15 @@ declare namespace timer {
185213
*/
186214
alias?: string
187215
}
216+
/**
217+
* @since 1.2.0
218+
*/
219+
type RemoteRow = RowBase & {
220+
/**
221+
* The name of client where the remote data is storaged
222+
*/
223+
clientName?: string
224+
}
188225
}
189226

190227
namespace limit {
@@ -241,6 +278,14 @@ declare namespace timer {
241278
num: number
242279
total: number
243280
}
281+
type PageQuery = {
282+
num?: number
283+
size?: number
284+
}
285+
type PageResult<T> = {
286+
list: T[]
287+
total: number
288+
}
244289
}
245290

246291
namespace popup {
@@ -323,6 +368,38 @@ declare namespace timer {
323368
timeFormat: TimeFormat
324369
}
325370
}
371+
}
372+
373+
/**
374+
* @since 1.2.0
375+
*/
376+
namespace backup {
377+
378+
type Type =
379+
| 'none'
380+
| 'gist'
381+
382+
/**
383+
* Snapshot of last backup
384+
*/
385+
type Snapshot = {
386+
/**
387+
* Timestamp
388+
*/
389+
ts: number
390+
/**
391+
* The date of the ts
392+
*/
393+
date: string
394+
}
395+
396+
/**
397+
* Snapshot cache
398+
*/
399+
type SnaptshotCache = Partial<{
400+
[type in Type]: Snapshot
401+
}>
326402

403+
type MetaCache = Partial<Record<Type, unknown>>
327404
}
328405
}

src/api/gist.ts

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
/**
2+
* Copyright (c) 2022-present Hengyang Zhang
3+
*
4+
* This software is released under the MIT License.
5+
* https://opensource.org/licenses/MIT
6+
*/
7+
8+
import type { AxiosAdapter, AxiosError, AxiosResponse } from "axios"
9+
10+
import FIFOCache from "@util/fifo-cache"
11+
12+
import axios from "axios"
13+
14+
type BaseFile = {
15+
filename: string
16+
}
17+
18+
export type FileForm = BaseFile & {
19+
content: string
20+
}
21+
22+
export type File = BaseFile & {
23+
type: string
24+
language: string
25+
raw_url: string
26+
content?: string
27+
}
28+
29+
type BaseGist<FileInfo> = {
30+
public: boolean
31+
description: string
32+
files: { [filename: string]: FileInfo }
33+
}
34+
35+
export type Gist = BaseGist<File> & {
36+
id: string
37+
}
38+
39+
export type GistForm = BaseGist<FileForm>
40+
41+
const BASE_URL = 'https://api.github.com/gists'
42+
43+
/**
44+
* Cache of get requests
45+
*/
46+
const GET_CACHE = new FIFOCache<AxiosResponse>(20)
47+
48+
function createCacheAdaptor(originAdaptor: AxiosAdapter): AxiosAdapter {
49+
return (config) => {
50+
const { url, method } = config;
51+
let useCache = method === 'get' && url.startsWith(BASE_URL)
52+
if (useCache) {
53+
// Use url and token as key
54+
const key = url + config?.headers?.['Authorization']
55+
return GET_CACHE.getOrSupply(key, () => originAdaptor(config))
56+
} else {
57+
return originAdaptor(config)
58+
}
59+
}
60+
}
61+
62+
async function get<T>(token: string, uri: string): Promise<T> {
63+
return new Promise(resolve => axios.get(BASE_URL + uri, {
64+
headers: {
65+
"Accept": "application/vnd.github+json",
66+
"Authorization": `token ${token}`
67+
},
68+
// Use cache
69+
adapter: createCacheAdaptor(axios.defaults.adapter)
70+
}).then(response => {
71+
if (response.status >= 200 && response.status < 300) {
72+
return resolve(response.data as T)
73+
} else {
74+
return resolve(null)
75+
}
76+
}).catch((error: AxiosError) => {
77+
console.log("AxisError", error)
78+
resolve(null)
79+
}))
80+
}
81+
82+
async function post<T, R>(token: string, uri: string, body?: R): Promise<T> {
83+
return new Promise(resolve => axios.post(BASE_URL + uri, body,
84+
{
85+
headers: {
86+
"Accept": "application/vnd.github+json",
87+
"Authorization": `token ${token}`
88+
}
89+
}
90+
).then(response => {
91+
// Clear cache if success to request
92+
GET_CACHE.clear()
93+
if (response.status >= 200 && response.status < 300) {
94+
return resolve(response.data as T)
95+
} else {
96+
return resolve(null)
97+
}
98+
}).catch((error: AxiosError) => {
99+
console.log("AxisError", error)
100+
resolve(null)
101+
}))
102+
}
103+
104+
/**
105+
* @param token token
106+
* @param id id
107+
* @returns detail of Gist
108+
*/
109+
export function getGist(token: string, id: string): Promise<Gist> {
110+
return get(token, `/${id}`)
111+
}
112+
113+
/**
114+
* Find the first target gist with predicate
115+
*
116+
* @param token gist token
117+
* @param predicate predicate
118+
* @returns
119+
*/
120+
export async function findTarget(token: string, predicate: (gist: Gist) => boolean): Promise<Gist> {
121+
let pageNum = 1
122+
while (true) {
123+
const uri = `?per_page=100&page=${pageNum}`
124+
const gists: Gist[] = await get(token, uri)
125+
if (!gists?.length) {
126+
break
127+
}
128+
const satisfied = gists.find(predicate)
129+
if (satisfied) {
130+
return satisfied
131+
}
132+
pageNum += 1
133+
}
134+
return undefined
135+
}
136+
137+
/**
138+
* Create one gist
139+
*
140+
* @param token token
141+
* @param gist gist info
142+
* @returns gist info with id
143+
*/
144+
export function createGist(token: string, gist: GistForm): Promise<Gist> {
145+
return post(token, "", gist)
146+
}
147+
148+
/**
149+
* Update gist
150+
*
151+
* @param token token
152+
* @param gist gist
153+
* @returns
154+
*/
155+
export async function updateGist(token: string, id: string, gist: GistForm): Promise<void> {
156+
await post(token, `/${id}`, gist)
157+
}
158+
159+
/**
160+
* Get content of file
161+
*/
162+
export async function getJsonFileContent<T>(file: File): Promise<T> {
163+
const content = file.content
164+
if (content) {
165+
return JSON.parse(content)
166+
}
167+
const rawUrl = file.raw_url
168+
if (!rawUrl) {
169+
return undefined
170+
}
171+
const response = await axios.get(rawUrl)
172+
return response.data
173+
}
174+
175+
/**
176+
* Test token to process gist
177+
*
178+
* @returns errorMsg or null/undefined
179+
*/
180+
export async function testToken(token: string): Promise<string> {
181+
return new Promise(resolve => {
182+
axios.get(BASE_URL + '?per_page=1&page=1', {
183+
headers: {
184+
"Accept": "application/vnd.github+json",
185+
"Authorization": `token ${token}`
186+
}
187+
}).then(response => {
188+
if (response.status >= 200 && response.status < 300) {
189+
resolve(undefined)
190+
} else {
191+
resolve(response.data?.message || 'Unknown error')
192+
}
193+
}).catch((error: AxiosError) => resolve((error.response?.data as any)?.message || 'Unknown error'))
194+
})
195+
}

src/api/index.d.ts

Lines changed: 0 additions & 28 deletions
This file was deleted.

src/api/version.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,33 @@
55
* https://opensource.org/licenses/MIT
66
*/
77

8-
import axios, { AxiosResponse } from "axios"
8+
import type { AxiosResponse } from 'axios'
9+
10+
import axios from "axios"
911
import { IS_CHROME, IS_EDGE, IS_FIREFOX } from "@util/constant/environment"
1012

13+
/**
14+
* @since 0.1.8
15+
*/
16+
type FirefoxDetail = {
17+
current_version: {
18+
// Like 0.1.5
19+
version: string
20+
}
21+
// Like '2021-06-11T08:45:32Z'
22+
last_updated: string
23+
}
24+
25+
/**
26+
* @since 0.1.8
27+
*/
28+
type EdgeDetail = {
29+
// Version like 0.1.5, without 'v' prefix
30+
version: string
31+
// Like '1619432502.5944779'
32+
lastUpdateDate: string
33+
}
34+
1135
async function getFirefoxVersion(): Promise<string | null> {
1236
const response: AxiosResponse<any> = await axios.get('https://addons.mozilla.org/api/v3/addons/addon/2690100')
1337
if (response.status !== 200) {

src/app/components/dashboard/components/top-k-visit.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ const _default = defineComponent({
116116
sortOrder: SortDirect.DESC,
117117
mergeDate: true,
118118
}
119-
const top: timer.stat.Row[] = (await timerService.selectByPage(query, { pageNum: 1, pageSize: TOP_NUM }, { alias: true })).list
119+
const top: timer.stat.Row[] = (await timerService.selectByPage(query, { num: 1, size: TOP_NUM }, { alias: true })).list
120120
const data: _Value[] = top.map(({ time, host, alias }) => ({ name: alias || host, host, alias, value: time }))
121121
for (let realSize = top.length; realSize < TOP_NUM; realSize++) {
122122
data.push({ name: '', host: '', value: 0 })

0 commit comments

Comments
 (0)