import { v4 as uuidv4 } from 'uuid'; import { query, run, get } from '../database'; import { User } from './user'; export interface UserGroup { id: string; name: string; description?: string; isSystem: boolean; createdAt: string; memberCount?: number; } export interface CreateUserGroupData { name: string; description?: string; } export class UserGroupModel { static async create(data: CreateUserGroupData): Promise { const id = uuidv4(); const sql = ` INSERT INTO user_groups (id, name, description, is_system) VALUES (?, ?, ?, 0) `; try { await run(sql, [id, data.name, data.description || '']); return this.findById(id) as Promise; } catch (error: any) { if (error.code === 'SQLITE_CONSTRAINT_UNIQUE') { throw new Error('用户组名称已存在'); } throw error; } } static async update(id: string, data: Partial): Promise { const group = await this.findById(id); if (!group) throw new Error('用户组不存在'); if (group.isSystem) throw new Error('系统内置用户组无法修改'); const fields: string[] = []; const values: any[] = []; if (data.name !== undefined) { fields.push('name = ?'); values.push(data.name); } if (data.description !== undefined) { fields.push('description = ?'); values.push(data.description); } if (fields.length === 0) { return group; } values.push(id); const sql = `UPDATE user_groups SET ${fields.join(', ')} WHERE id = ?`; try { await run(sql, values); return this.findById(id) as Promise; } catch (error: any) { if (error.code === 'SQLITE_CONSTRAINT_UNIQUE') { throw new Error('用户组名称已存在'); } throw error; } } static async delete(id: string): Promise { const group = await this.findById(id); if (!group) throw new Error('用户组不存在'); if (group.isSystem) throw new Error('系统内置用户组无法删除'); await run(`DELETE FROM user_groups WHERE id = ?`, [id]); } static async findById(id: string): Promise { const sql = ` SELECT id, name, description, is_system as isSystem, created_at as createdAt FROM user_groups WHERE id = ? `; const group = await get(sql, [id]); return group || null; } static async findAll(): Promise { const sql = ` SELECT g.id, g.name, g.description, g.is_system as isSystem, g.created_at as createdAt, (SELECT COUNT(*) FROM user_group_members m WHERE m.group_id = g.id) as memberCount FROM user_groups g ORDER BY g.is_system DESC, g.created_at DESC `; return await query(sql); } static async addMember(groupId: string, userId: string): Promise { const sql = `INSERT OR IGNORE INTO user_group_members (group_id, user_id) VALUES (?, ?)`; await run(sql, [groupId, userId]); } static async removeMember(groupId: string, userId: string): Promise { const group = await this.findById(groupId); if (group?.isSystem) { // Check if user is being deleted? No, this method is for removing member. // Requirement: "User cannot actively exit this group". // Implementation: Cannot remove member from system group via this API. // Only user deletion removes them (cascade). throw new Error('无法从系统内置组中移除成员'); } const sql = `DELETE FROM user_group_members WHERE group_id = ? AND user_id = ?`; await run(sql, [groupId, userId]); } static async getMembers(groupId: string): Promise { const sql = ` SELECT u.id, u.name, u.phone, u.created_at as createdAt FROM users u JOIN user_group_members m ON u.id = m.user_id WHERE m.group_id = ? ORDER BY m.created_at DESC `; return await query(sql, [groupId]); } static async getUserGroups(userId: string): Promise { const sql = ` SELECT g.id, g.name, g.description, g.is_system as isSystem, g.created_at as createdAt FROM user_groups g JOIN user_group_members m ON g.id = m.group_id WHERE m.user_id = ? ORDER BY g.is_system DESC, g.created_at DESC `; return await query(sql, [userId]); } static async getSystemGroup(): Promise { const sql = ` SELECT id, name, description, is_system as isSystem, created_at as createdAt FROM user_groups WHERE is_system = 1 `; return await get(sql); } static async updateMembers(groupId: string, userIds: string[]): Promise { const group = await this.findById(groupId); if (!group) throw new Error('用户组不存在'); if (group.isSystem) throw new Error('无法修改系统内置组成员'); // Transaction-like behavior needed but SQLite wrapper doesn't expose it easily. // We'll do delete then insert. await run(`DELETE FROM user_group_members WHERE group_id = ?`, [groupId]); if (userIds.length > 0) { // Batch insert // SQLite limit is usually high enough, but safer to loop or construct big query // For simplicity in this helper: for (const userId of userIds) { await run(`INSERT INTO user_group_members (group_id, user_id) VALUES (?, ?)`, [groupId, userId]); } } } static async updateUserGroups(userId: string, groupIds: string[]): Promise { // 1. Get current system group(s) the user belongs to const currentGroups = await this.getUserGroups(userId); const systemGroupIds = currentGroups.filter(g => g.isSystem).map(g => g.id); // 2. Ensure system groups are in the new list (force keep them) const newGroupSet = new Set(groupIds); for (const sysId of systemGroupIds) { newGroupSet.add(sysId); } // Also ensure the default "All Users" group is there if not already // (In case the user was created before groups existed and somehow not migrated, though migration handles it) // Safe to just ensure "All Users" is present. const allUsersGroup = await this.getSystemGroup(); if (allUsersGroup) { newGroupSet.add(allUsersGroup.id); } const finalGroupIds = Array.from(newGroupSet); // 3. Update await run(`DELETE FROM user_group_members WHERE user_id = ?`, [userId]); for (const gid of finalGroupIds) { await run(`INSERT INTO user_group_members (group_id, user_id) VALUES (?, ?)`, [gid, userId]); } } }