Skip to content

Commit

Permalink
feat: sync user action
Browse files Browse the repository at this point in the history
  • Loading branch information
ourongxing committed Oct 15, 2024
1 parent 051c827 commit e8a2a7e
Show file tree
Hide file tree
Showing 23 changed files with 260 additions and 180 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@
"overlayscrollbars-react": "^0.5.6",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-use": "^17.5.1"
"react-use": "^17.5.1",
"zod": "^3.23.8"
},
"devDependencies": {
"@eslint-react/eslint-plugin": "^1.14.3",
Expand Down
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 0 additions & 6 deletions schema.sql

This file was deleted.

17 changes: 8 additions & 9 deletions server/database/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export class UserTable {
.run(id, email, "", type, now, now)
logger.success(`add user ${id}`)
} else if (u.email !== email && u.type !== type) {
await this.db.prepare(`REPLACE INTO user (id, email, updated) VALUES (?, ?, ?)`).run(id, email, now)
await this.db.prepare(`UPDATE user SET email = ?, updated = ? WHERE id = ?`).run(id, email, now)
logger.success(`update user ${id} email`)
} else {
logger.info(`user ${id} already exists`)
Expand All @@ -40,20 +40,19 @@ export class UserTable {
return (await this.db.prepare(`SELECT id, email, data, created, updated FROM user WHERE id = ?`).get(id)) as UserInfo
}

async setData(key: string, value: string) {
const now = Date.now()
async setData(key: string, value: string, updatedTime = Date.now()) {
const state = await this.db.prepare(
`REPLACE INTO user (id, data, updated) VALUES (?, ?, ?)`,
).run(key, JSON.stringify(value), now)
`UPDATE user SET data = ?, updated = ? WHERE id = ?`,
).run(value, updatedTime, key)
if (!state.success) throw new Error(`set user ${key} data failed`)
logger.success(`set ${key} cache`)
logger.success(`set ${key} data`)
}

async getData(id: string) {
const row: any = await this.db.prepare(`SELECT data, update FROM user WHERE id = ?`).get(id)
if (!row || !row.data) throw new Error(`user ${id} not found`)
const row: any = await this.db.prepare(`SELECT data, updated FROM user WHERE id = ?`).get(id)
if (!row) throw new Error(`user ${id} not found`)
logger.success(`get ${id} data`)
return row.data as {
return row as {
data: string
updated: number
}
Expand Down
6 changes: 4 additions & 2 deletions server/middleware/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ import process from "node:process"
import { jwtVerify } from "jose"

export default defineEventHandler(async (event) => {
const url = getRequestURL(event)
if (["JWT_SECRET", "G_CLIENT_ID", "G_CLIENT_SECRET"].find(k => !process.env[k])) {
event.context.disabledLogin = true
if (url.pathname.startsWith("/me")) throw createError({ statusCode: 506, message: "Server not configured" })
} else {
const url = getRequestURL(event)
if (/^\/(?:me|s)\//.test(url.pathname)) {
const token = getHeader(event, "Authorization")
if (token && process.env.JWT_SECRET) {
Expand All @@ -18,7 +19,8 @@ export default defineEventHandler(async (event) => {
}
}
} catch {
logger.error("JWT verification failed")
if (url.pathname.startsWith("/me")) throw createError({ statusCode: 401, message: "JWT verification failed" })
logger.warn("JWT verification failed")
}
}
}
Expand Down
5 changes: 5 additions & 0 deletions server/routes/me/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export default defineEventHandler(() => {
return {
hello: "world",
}
})
33 changes: 33 additions & 0 deletions server/routes/me/sync.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { verifyPrimitiveMetadata } from "@shared/verify"
import { UserTable } from "#/database/user"

export default defineEventHandler(async (event) => {
try {
const { id } = event.context.user
const db = useDatabase()
if (!db) throw new Error("Not found database")
const userTable = new UserTable(db)
if (event.method === "GET") {
const { data, updated } = await userTable.getData(id)
return {
data: data ? JSON.parse(data) : undefined,
updatedTime: updated,
}
} else if (event.method === "POST") {
const body = await readBody(event)
verifyPrimitiveMetadata(body)
const { updatedTime, data } = body
await userTable.setData(id, JSON.stringify(data), updatedTime)
return {
success: true,
updatedTime,
}
}
} catch (e) {
logger.error(e)
throw createError({
statusCode: 500,
message: e instanceof Error ? e.message : "Internal Server Error",
})
}
})
1 change: 1 addition & 0 deletions server/routes/oauth/github.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export default defineEventHandler(async (event) => {
},
})

console.log(userInfo)
const userID = String(userInfo.id)
await userTable.addUser(userID, userInfo.notification_email || userInfo.email, "github")

Expand Down
28 changes: 11 additions & 17 deletions server/routes/s/[id].ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export default defineEventHandler(async (event): Promise<SourceResponse> => {
const isValid = (id: SourceID) => !id || !sources[id] || !sourcesFn[id]

if (isValid(id)) {
const redirectID = sources[id].redirect
const redirectID = sources?.[id].redirect
if (redirectID) id = redirectID
if (isValid(id)) throw new Error("Invalid source id")
}
Expand All @@ -31,10 +31,8 @@ export default defineEventHandler(async (event): Promise<SourceResponse> => {
if (now - cache.updated < interval) {
return {
status: "success",
data: {
updatedTime: now,
items: cache.data,
},
updatedTime: now,
items: cache.data,
}
}

Expand All @@ -50,10 +48,8 @@ export default defineEventHandler(async (event): Promise<SourceResponse> => {
if (event.context.disabledLogin) {
return {
status: "cache",
data: {
updatedTime: cache.updated,
items: cache.data,
},
updatedTime: cache.updated,
items: cache.data,
}
}
}
Expand All @@ -66,16 +62,14 @@ export default defineEventHandler(async (event): Promise<SourceResponse> => {
if (cacheTable) event.waitUntil(cacheTable.set(id, data))
return {
status: "success",
data: {
updatedTime: now,
items: data,
},
updatedTime: now,
items: data,
}
} catch (e: any) {
logger.error(e)
return {
status: "error",
message: e.message ?? e,
}
throw createError({
statusCode: 500,
message: e instanceof Error ? e.message : "Internal Server Error",
})
}
})
18 changes: 8 additions & 10 deletions shared/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ export type SourceID = {
export type ColumnID = (typeof columnIds)[number]
export type Metadata = Record<ColumnID, Column>

export interface PrimitiveMetadata {
updatedTime: number
data: Record<ColumnID, SourceID[]>
action: "init" | "manual" | "sync"
}

export interface OriginSource {
name: string
title?: string
Expand Down Expand Up @@ -63,16 +69,8 @@ export interface NewsItem {
extra?: Record<string, any>
}

// 路由数据
export interface SourceInfo {
export interface SourceResponse {
status: "success" | "cache"
updatedTime: number | string
items: NewsItem[]
}

export type SourceResponse = {
status: "success" | "cache"
data: SourceInfo
} | {
status: "error"
message?: string
}
8 changes: 8 additions & 0 deletions shared/verify.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import z from "zod"

export function verifyPrimitiveMetadata(target: any) {
return z.object({
data: z.record(z.string(), z.array(z.string())),
updatedTime: z.number(),
}).parse(target)
}
67 changes: 0 additions & 67 deletions src/atoms/atomWithLocalStorage.ts

This file was deleted.

50 changes: 21 additions & 29 deletions src/atoms/index.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,22 @@
import { atom } from "jotai"
import type { ColumnID, SourceID } from "@shared/types"
import { metadata } from "@shared/metadata"
import { sources } from "@shared/sources"
import { typeSafeObjectEntries, typeSafeObjectFromEntries } from "@shared/type.util"
import { atomWithLocalStorage } from "./atomWithLocalStorage"
import { primitiveMetadataAtom } from "./primitiveMetadataAtom"
import type { Update } from "./types"

const initialSources = typeSafeObjectFromEntries(typeSafeObjectEntries(metadata).map(([id, val]) => [id, val.sources]))
export const localSourcesAtom = atomWithLocalStorage<Record<ColumnID, SourceID[]>>("localsources", () => {
return initialSources
}, (stored) => {
return typeSafeObjectFromEntries(typeSafeObjectEntries({
...initialSources,
...stored,
}).filter(([id]) => initialSources[id]).map(([id, val]) => {
if (id === "focus") return [id, val]
const oldS = val.filter(k => initialSources[id].includes(k))
const newS = initialSources[id].filter(k => !oldS.includes(k))
return [id, [...oldS, ...newS]]
}))
})
export { primitiveMetadataAtom, preprocessMetadata } from "./primitiveMetadataAtom"

export const focusSourcesAtom = atom((get) => {
return get(localSourcesAtom).focus
return get(primitiveMetadataAtom).data.focus
}, (get, set, update: Update<SourceID[]>) => {
const _ = update instanceof Function ? update(get(focusSourcesAtom)) : update
set(localSourcesAtom, {
...get(localSourcesAtom),
focus: _,
set(primitiveMetadataAtom, {
updatedTime: Date.now(),
action: "manual",
data: {
...get(primitiveMetadataAtom).data,
focus: _,
},
})
})

Expand All @@ -47,19 +37,21 @@ export const refetchSourcesAtom = atom(initRefetchSources())

export const currentColumnIDAtom = atom<ColumnID>("focus")

export const currentColumnAtom = atom((get) => {
export const currentSourcesAtom = atom((get) => {
const id = get(currentColumnIDAtom)
return get(localSourcesAtom)[id]
return get(primitiveMetadataAtom).data[id]
}, (get, set, update: Update<SourceID[]>) => {
const _ = update instanceof Function ? update(get(currentColumnAtom)) : update
set(localSourcesAtom, {
...get(localSourcesAtom),
[get(currentColumnIDAtom)]: _,
const _ = update instanceof Function ? update(get(currentSourcesAtom)) : update
set(primitiveMetadataAtom, {
updatedTime: Date.now(),
action: "manual",
data: {
...get(primitiveMetadataAtom).data,
[get(currentColumnIDAtom)]: _,
},
})
})

export type Update<T> = T | ((prev: T) => T)

export const goToTopAtom = atom({
ok: false,
fn: undefined as (() => void) | undefined,
Expand Down
Loading

0 comments on commit e8a2a7e

Please sign in to comment.