UNPKG

@waline/vercel

Version:

vercel server for waline comment system

440 lines (352 loc) 10.2 kB
const AV = require('leancloud-storage'); const Base = require('./base.js'); const { LEAN_ID, LEAN_KEY, LEAN_MASTER_KEY, LEAN_SERVER } = process.env; if (LEAN_ID && LEAN_KEY && LEAN_MASTER_KEY) { AV.Cloud.useMasterKey(true); AV.init({ appId: LEAN_ID, appKey: LEAN_KEY, masterKey: LEAN_MASTER_KEY, // required for leancloud china serverURL: LEAN_SERVER, }); } module.exports = class extends Base { parseWhere(className, where) { const instance = new AV.Query(className); if (think.isEmpty(where)) { return instance; } for (const k in where) { if (k === '_complex') { continue; } if (think.isString(where[k])) { instance.equalTo(k, where[k]); continue; } if (where[k] === undefined) { instance.doesNotExist(k); } if (Array.isArray(where[k])) { if (where[k][0]) { const handler = where[k][0].toUpperCase(); switch (handler) { case 'IN': instance.containedIn(k, where[k][1]); break; case 'NOT IN': instance.notContainedIn(k, where[k][1]); break; case 'LIKE': { const first = where[k][1][0]; const last = where[k][1].slice(-1); if (first === '%' && last === '%') { instance.contains(k, where[k][1].slice(1, -1)); } else if (first === '%') { instance.endsWith(k, where[k][1].slice(1)); } else if (last === '%') { instance.startsWith(k, where[k][1].slice(0, -1)); } break; } case '!=': instance.notEqualTo(k, where[k][1]); break; case '>': instance.greaterThan(k, where[k][1]); break; } } } } return instance; } where(className, where) { if (!where._complex) { return this.parseWhere(className, where); } const filters = []; for (const k in where._complex) { if (k === '_logic') { continue; } const filter = this.parseWhere(className, { ...where, [k]: where._complex[k], }); filters.push(filter); } return AV.Query[where._complex._logic](...filters); } async _select(where, { desc, limit, offset, field } = {}) { const instance = this.where(this.tableName, where); if (desc) { instance.descending(desc); } if (limit) { instance.limit(limit); } if (offset) { instance.skip(offset); } if (field) { instance.select(field); } const data = await instance.find().catch((e) => { if (e.code === 101) { return []; } throw e; }); return data.map((item) => item.toJSON()); } async select(where, options = {}) { let data = []; let ret = []; let offset = options.offset || 0; do { options.offset = offset + data.length; ret = await this._select(where, options); data = data.concat(ret); } while (ret.length === 100); return data; } async _getCmtGroupByMailUserIdCache(key, where) { if ( this.tableName !== 'Comment' || key !== 'user_id_mail' || !think.isArray(think.config('levels')) ) { return []; } const cacheTableName = `cache_group_count_${key}`; const currentTableName = this.tableName; this.tableName = cacheTableName; const cacheData = await this.select({ _complex: where._complex }); this.tableName = currentTableName; return cacheData; } async _setCmtGroupByMailUserIdCache(key, data) { if ( this.tableName !== 'Comment' || key !== 'user_id_mail' || !think.isArray(think.config('levels')) ) { return; } const cacheTableName = `cache_group_count_${key}`; const currentTableName = this.tableName; this.tableName = cacheTableName; await think.promiseAllQueue( data.map((item) => { if (item.user_id && !think.isString(item.user_id)) { item.user_id = item.user_id.toString(); } return this.add(item); }), 1, ); this.tableName = currentTableName; } async _updateCmtGroupByMailUserIdCache(data, method) { if ( this.tableName !== 'Comment' || !think.isArray(think.config('levels')) ) { return; } if (!data.user_id && !data.mail) { return; } const cacheTableName = `cache_group_count_user_id_mail`; const cacheData = await this.select({ _complex: { _logic: 'or', user_id: think.isObject(data.user_id) ? data.user_id.toString() : data.user_id, mail: data.mail, }, }); if (think.isEmpty(data)) { return; } let count = cacheData[0].count; switch (method) { case 'add': if (data.status === 'approved') { count += 1; } break; case 'udpate_status': if (data.status === 'approved') { count += 1; } else { count -= 1; } break; case 'delete': count -= 1; break; } const currentTableName = this.tableName; this.tableName = cacheTableName; await this.update({ count }, { objectId: cacheData[0].objectId }).catch( (e) => { if (e.code === 101) { return; } throw e; }, ); this.tableName = currentTableName; } async count(where = {}, options = {}) { const instance = this.where(this.tableName, where); if (!options.group) { return instance.count(options).catch((e) => { if (e.code === 101) { return 0; } throw e; }); } // get group count cache by group field where data const cacheData = await this._getCmtGroupByMailUserIdCache( options.group.join('_'), where, ); if (!where._complex) { if (cacheData.length) { return cacheData; } const counts = await this.select(where, { field: options.group }); const countsMap = {}; for (const count of counts) { const key = options.group .map((item) => count[item] || undefined) .join('_'); if (!countsMap[key]) { countsMap[key] = {}; for (const field of options.group) { countsMap[key][field] = count[field]; } countsMap[key].count = 0; } countsMap[key].count += 1; } const ret = Object.values(countsMap); // cache data await this._setCmtGroupByMailUserIdCache(options.group.join('_'), ret); return ret; } const cacheDataMap = {}; for (const item of cacheData) { const key = options.group .map((item) => item[item] || undefined) .join('_'); cacheDataMap[key] = item; } const counts = []; const countsPromise = []; for (let i = 0; i < options.group.length; i++) { const groupName = options.group[i]; if (!where._complex || !Array.isArray(where._complex[groupName])) { continue; } const groupFlatValue = {}; options.group.slice(0, i).forEach((group) => { groupFlatValue[group] = undefined; }); for (const item of where._complex[groupName][1]) { const cacheKey = options.group .map( (item) => ({ ...groupFlatValue, [groupName]: item, })[item] || undefined, ) .join('_'); if (cacheDataMap[cacheKey]) { continue; } const groupWhere = { ...where, ...groupFlatValue, _complex: undefined, [groupName]: item, }; const countPromise = this.count(groupWhere, { ...options, group: undefined, }).then((num) => { counts.push({ ...groupFlatValue, [groupName]: item, count: num, }); }); countsPromise.push(countPromise); } } await think.promiseAllQueue(countsPromise, 1); // cache data await this._setCmtGroupByMailUserIdCache(options.group.join('_'), counts); return [...cacheData, ...counts]; } async add( data, { access: { read = true, write = true } = { read: true, write: true }, } = {}, ) { const Table = AV.Object.extend(this.tableName); const instance = new Table(); const REVERSED_KEYS = ['objectId', 'createdAt', 'updatedAt']; for (const k in data) { if (REVERSED_KEYS.includes(k)) { continue; } instance.set(k, data[k]); } const acl = new AV.ACL(); acl.setPublicReadAccess(read); acl.setPublicWriteAccess(write); instance.setACL(acl); const resp = await instance.save(); await this._updateCmtGroupByMailUserIdCache(data, 'add'); return resp.toJSON(); } async update(data, where) { const instance = this.where(this.tableName, where); const ret = await instance.find(); return Promise.all( ret.map(async (item) => { const _oldStatus = item.get('status'); const updateData = typeof data === 'function' ? data(item.toJSON()) : data; const REVERSED_KEYS = ['createdAt', 'updatedAt']; for (const k in updateData) { if (REVERSED_KEYS.includes(k)) { continue; } item.set(k, updateData[k]); } const _newStatus = item.get('status'); if (_newStatus && _oldStatus !== _newStatus) { await this._updateCmtGroupByMailUserIdCache(data, 'update_status'); } const resp = await item.save(); return resp.toJSON(); }), ); } async delete(where) { const instance = this.where(this.tableName, where); const data = await instance.find(); await this._updateCmtGroupByMailUserIdCache(data, 'delete'); return AV.Object.destroyAll(data); } };