refactor: 个人自用,移除登录与云同步全链路
站点只给本人用,云乐坊 SSO 授权、收藏/历史云同步、获客归因和账号切换记账都没有收益,却被登录状态检查和同步失败提示持续打扰。 - 删除 useYunlefunAuth / yunlefunAuth / yunlefunSsoConfig / yunlefunAuth 适配器、useUserDataSync / userDataSync / userData 适配器、三个 client 插件与 /auth/callback 回调页 - 抽出 adapters/cloudbase.ts,让「反馈一下」和匿名筛选信号不再依赖登录态;cook-feedback 云函数保留 - 删除 cook-user-data 云函数与 cook_user_data 权限规则,移除 @yunlefun/sso 依赖 - 「我的」页删掉登录按钮、账号卡、四个统计格和退出登录;「关于」页删掉项目作者、致谢名单和上游宣传链接 - 本地收藏/历史与导出导入保留,删除同步专用的 lastUid 记账 取舍见 docs/decisions/0005-drop-login-and-sync.md
This commit is contained in:
@@ -1,129 +0,0 @@
|
||||
/**
|
||||
* Cook favorites / history sync (YunYouJun/cook#98).
|
||||
*
|
||||
* The `cook_user_data` collection is ADMINONLY: browsers never read or write
|
||||
* it directly. This function takes the caller from the verified CloudBase
|
||||
* session, never from the request, and always uses it as the document id, so
|
||||
* nobody can read, overwrite or pre-create another account's document.
|
||||
*/
|
||||
|
||||
const { Buffer } = require('node:buffer')
|
||||
|
||||
const COLLECTION = 'cook_user_data'
|
||||
const USER_DATA_VERSION = 1
|
||||
const MAX_DOCUMENT_BYTES = 256 * 1024
|
||||
const LIST_LIMITS = {
|
||||
favorites: 5000,
|
||||
favoriteRemovals: 1000,
|
||||
histories: 500,
|
||||
historyRemovals: 1000,
|
||||
}
|
||||
const ANON_UIDS = new Set(['', 'anon'])
|
||||
|
||||
let app
|
||||
|
||||
function getApp() {
|
||||
if (!app) {
|
||||
// Loaded lazily so tests can inject a fake app without the SDK installed.
|
||||
const tcb = require('@cloudbase/node-sdk')
|
||||
app = tcb.init({ env: tcb.SYMBOL_CURRENT_ENV })
|
||||
}
|
||||
return app
|
||||
}
|
||||
|
||||
/** The signed-in, non-anonymous caller, or '' when there is none. */
|
||||
function callerUid() {
|
||||
try {
|
||||
const info = getApp().auth().getUserInfo()
|
||||
const uid = info && info.uid ? String(info.uid) : ''
|
||||
return ANON_UIDS.has(uid) || (info && info.isAnonymous === true) ? '' : uid
|
||||
}
|
||||
catch {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
function fail(code) {
|
||||
return { ok: false, code }
|
||||
}
|
||||
|
||||
function isRecord(value) {
|
||||
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the shape and size of an uploaded document. Entry contents are
|
||||
* normalised again by every client that reads them.
|
||||
*/
|
||||
function readDocument(value) {
|
||||
if (!isRecord(value) || value.version !== USER_DATA_VERSION)
|
||||
return null
|
||||
if (Buffer.byteLength(JSON.stringify(value), 'utf8') > MAX_DOCUMENT_BYTES)
|
||||
return null
|
||||
|
||||
const document = { version: USER_DATA_VERSION }
|
||||
for (const [key, limit] of Object.entries(LIST_LIMITS)) {
|
||||
const list = value[key] ?? []
|
||||
if (!Array.isArray(list) || list.length > limit || !list.every(isRecord))
|
||||
return null
|
||||
document[key] = list
|
||||
}
|
||||
const clearedAt = value.historyClearedAt ?? 0
|
||||
if (typeof clearedAt !== 'number' || !Number.isFinite(clearedAt) || clearedAt < 0)
|
||||
return null
|
||||
document.historyClearedAt = clearedAt
|
||||
return document
|
||||
}
|
||||
|
||||
function stripMeta(doc) {
|
||||
if (!isRecord(doc))
|
||||
return null
|
||||
const { _id, _openid, ...rest } = doc
|
||||
return rest
|
||||
}
|
||||
|
||||
async function getDocument(uid) {
|
||||
const result = await getApp().database().collection(COLLECTION).doc(uid).get()
|
||||
const doc = result && Array.isArray(result.data) ? result.data[0] : null
|
||||
return { ok: true, document: stripMeta(doc) }
|
||||
}
|
||||
|
||||
async function setDocument(uid, input, now) {
|
||||
const document = readDocument(input)
|
||||
if (!document)
|
||||
return fail('INVALID_DOCUMENT')
|
||||
await getApp().database().collection(COLLECTION).doc(uid).set({ ...document, updatedAt: now })
|
||||
return { ok: true, updatedAt: now }
|
||||
}
|
||||
|
||||
async function main(event = {}) {
|
||||
const uid = callerUid()
|
||||
if (!uid)
|
||||
return fail('LOGIN_REQUIRED')
|
||||
|
||||
try {
|
||||
switch (event.action) {
|
||||
case 'get':
|
||||
return await getDocument(uid)
|
||||
case 'set':
|
||||
return await setDocument(uid, event.document, Date.now())
|
||||
default:
|
||||
return fail('UNKNOWN_ACTION')
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error('[cook-user-data]', event.action, error && error.message)
|
||||
return fail('INTERNAL_ERROR')
|
||||
}
|
||||
}
|
||||
|
||||
exports.main = main
|
||||
exports._internal = {
|
||||
COLLECTION,
|
||||
LIST_LIMITS,
|
||||
MAX_DOCUMENT_BYTES,
|
||||
readDocument,
|
||||
setAppForTest(nextApp) {
|
||||
app = nextApp
|
||||
},
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"name": "cook-user-data",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"@cloudbase/node-sdk": "^3.18.1"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user