Files
cyh-tk-backend/src/users/users.service.ts
zhxiao1124 4eff4d877e refactor: 完成项目整体目录重构与路径别名配置
1.  重构项目文件结构,统一文件存放位置与命名规则
2.  配置tsconfig路径别名,替换原有相对路径导入
3.  迁移drama-status枚举到dramas.types统一管理
4.  新增全局配置文件与工具类,补充完整注释与文档
5.  修复所有导入路径与依赖引用问题
2026-07-02 19:17:56 +08:00

173 lines
5.7 KiB
TypeScript

import { Injectable, NotFoundException, Optional } from "@nestjs/common";
import { InjectDataSource } from "@nestjs/typeorm";
import { DataSource } from "typeorm";
import { PaginatedResponse } from "@app/common/paginated-response.dto";
import { User } from "./user.entity";
import { UserRecord } from "./users.types";
interface UpsertTikTokUserInput {
tiktokOpenId: string;
tiktokUnionId?: string;
nickname?: string;
avatarUrl?: string;
}
interface PaginationInput {
page: number;
pageSize: number;
}
@Injectable()
export class UsersService {
private userSequence = 1;
private readonly users: UserRecord[] = [];
constructor(
@Optional()
@InjectDataSource()
readonly dataSource?: DataSource,
) {}
async upsertTikTokUser(input: UpsertTikTokUserInput) {
if (this.hasDatabase()) {
const repository = this.dataSource.getRepository(User);
const existing = await repository.findOne({
where: { tiktokOpenId: input.tiktokOpenId },
});
if (existing) {
existing.tiktokUnionId = input.tiktokUnionId ?? existing.tiktokUnionId;
existing.nickname = input.nickname ?? existing.nickname;
existing.avatarUrl = input.avatarUrl ?? existing.avatarUrl;
const saved = await repository.save(existing);
return { user: this.toRecord(saved), isNewUser: false };
}
const user = repository.create({
tiktokOpenId: input.tiktokOpenId,
tiktokUnionId: input.tiktokUnionId,
nickname: input.nickname,
avatarUrl: input.avatarUrl,
status: "active",
});
const saved = await repository.save(user);
return { user: this.toRecord(saved), isNewUser: true };
}
const existing = this.users.find((user) => user.tiktokOpenId === input.tiktokOpenId);
const now = new Date().toISOString();
if (existing) {
existing.tiktokUnionId = input.tiktokUnionId ?? existing.tiktokUnionId;
existing.nickname = input.nickname ?? existing.nickname;
existing.avatarUrl = input.avatarUrl ?? existing.avatarUrl;
existing.updatedAt = now;
return { user: existing, isNewUser: false };
}
const user: UserRecord = {
id: this.userSequence++,
tiktokOpenId: input.tiktokOpenId,
tiktokUnionId: input.tiktokUnionId,
nickname: input.nickname,
avatarUrl: input.avatarUrl,
status: "active",
createdAt: now,
updatedAt: now,
};
this.users.push(user);
return { user, isNewUser: true };
}
async findById(id: number) {
if (this.hasDatabase()) {
const user = await this.dataSource.getRepository(User).findOne({
where: { id },
});
return user ? this.toRecord(user) : undefined;
}
return this.users.find((user) => user.id === id);
}
async listUsers(pagination: PaginationInput): Promise<PaginatedResponse<UserRecord>> {
if (this.hasDatabase()) {
const page = Math.max(pagination.page, 1);
const pageSize = Math.min(Math.max(pagination.pageSize, 1), 100);
const repository = this.dataSource.getRepository(User);
const [records, total] = await repository.findAndCount({
order: { id: "DESC" },
skip: (page - 1) * pageSize,
take: pageSize,
});
return {
list: records.map((user) => this.toRecord(user)),
pagination: { page, pageSize, total },
};
}
return this.paginate([...this.users].reverse(), pagination);
}
async getUserOrThrow(id: number) {
const user = await this.findById(id);
if (!user) {
throw new NotFoundException("User not found");
}
return user;
}
async updateStatus(id: number, status: "active" | "disabled") {
if (this.hasDatabase()) {
const repository = this.dataSource.getRepository(User);
const user = await repository.findOne({ where: { id } });
if (!user) {
throw new NotFoundException("User not found");
}
user.status = status;
return this.toRecord(await repository.save(user));
}
const user = await this.getUserOrThrow(id);
user.status = status;
user.updatedAt = new Date().toISOString();
return user;
}
private hasDatabase(): this is this & { dataSource: DataSource } {
return Boolean(this.dataSource?.isInitialized);
}
private toRecord(user: User): UserRecord {
return {
id: Number(user.id),
tiktokOpenId: user.tiktokOpenId,
tiktokUnionId: user.tiktokUnionId,
nickname: user.nickname,
avatarUrl: user.avatarUrl,
status: user.status as "active" | "disabled",
createdAt: user.createdAt?.toISOString?.() ?? new Date().toISOString(),
updatedAt: user.updatedAt?.toISOString?.() ?? new Date().toISOString(),
};
}
private paginate<T>(records: T[], input: PaginationInput): PaginatedResponse<T> {
const page = Math.max(input.page, 1);
const pageSize = Math.min(Math.max(input.pageSize, 1), 100);
const start = (page - 1) * pageSize;
return {
list: records.slice(start, start + pageSize),
pagination: {
page,
pageSize,
total: records.length,
},
};
}
}