guilds.js 2.85 KB
import { randomUUID } from 'node:crypto'

const TABLE = 'vk_guilds'

/**
 * @typedef {{ id: string, name: string, slug: string, lead_user_id: string, require_lead_approval: boolean, created_at: Date }} Guild
 */

/**
 * @param {{ db: import('knex').Knex }} deps
 */
export function createGuilds({ db }) {
  if (typeof db !== 'function') throw new Error('createGuilds: db is required')

  /**
   * 建公会并把负责人登记为成员。slug 已被占用返回 CONFLICT(由 UNIQUE 约束裁决)。
   * @param {object} _ctx
   * @param {{ name: string, slug: string, lead_user_id: string, require_lead_approval: boolean, checkpoint_interval_sec?: number, piece_review_window_sec?: number, contest_review_window_sec?: number, transfer_ttl_sec?: number }} input 四个时间规则不传时用列默认值(6h / 24h / 72h / 24h)
   * @returns {Promise<{ ok: true, guild: Guild } | { ok: false, code: 'CONFLICT' }>}
   */
  async function create(_ctx, { name, slug, lead_user_id, require_lead_approval, ...windows }) {
    const guild = { id: `g_${randomUUID()}`, name, slug, lead_user_id, require_lead_approval, ...windows, created_at: new Date() }
    return db.transaction(async (trx) => {
      // slug 冲突由 UNIQUE 裁决:被忽略的插入读不回本次 id。
      await trx(TABLE).insert(guild).onConflict('slug').ignore()
      if ((await trx(TABLE).where({ id: guild.id }).first()) === undefined) return { ok: false, code: 'CONFLICT' }
      await trx('vk_guild_members').insert({ id: `gm_${randomUUID()}`, guild_id: guild.id, user_id: lead_user_id, role: 'lead', created_at: guild.created_at })
      return { ok: true, guild }
    })
  }

  /**
   * 全部公会(认领 Agent 时选择)。
   * @param {object} _ctx
   * @returns {Promise<Array<{ id: string, name: string, slug: string }>>}
   */
  async function list(_ctx) {
    return db(TABLE).orderBy('name', 'asc').select('id', 'name', 'slug')
  }

  /**
   * 运营视角的公会列表:负责人、公司 Agent 及其钱包、等级与周目标。
   * @param {object} _ctx
   */
  async function listForAdmin(_ctx) {
    return db(`${TABLE} as g`)
      .leftJoin('vk_agents as a', 'a.id', 'g.company_agent_id')
      .leftJoin('users as u', 'u.id', 'g.lead_user_id')
      .orderBy('g.name', 'asc')
      .select('g.id', 'g.name', 'g.slug', 'g.level', 'g.weekly_goal', 'g.require_lead_approval', 'u.email as lead_email', 'a.id as company_agent_id', 'a.name as company_agent_name', 'a.wallet_user_id as company_wallet_user_id')
  }

  /**
   * @param {object} _ctx
   * @param {{ guild_id: string, weekly_goal: number }} input 0 表示不设周目标
   * @returns {Promise<boolean>}
   */
  async function setWeeklyGoal(_ctx, { guild_id, weekly_goal }) {
    return (await db(TABLE).where({ id: guild_id }).update({ weekly_goal, updated_at: new Date() })) === 1
  }

  return { create, list, listForAdmin, setWeeklyGoal }
}