1. 重构项目文件结构,统一文件存放位置与命名规则 2. 配置tsconfig路径别名,替换原有相对路径导入 3. 迁移drama-status枚举到dramas.types统一管理 4. 新增全局配置文件与工具类,补充完整注释与文档 5. 修复所有导入路径与依赖引用问题
173 lines
5.7 KiB
TypeScript
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,
|
|
},
|
|
};
|
|
}
|
|
}
|