refactor: 重构项目目录与代码结构,清理冗余文件
本次提交进行了大规模的项目结构调整: 1. 调整文件目录归属,将模块代码按功能域重新组织 2. 删除多处冗余的DTO、实体类和模块文件 3. 统一了常量、装饰器、工具类的存放位置 4. 修复了`.gitignore`的日志目录匹配规则 5. 新增了健康检查、认证、用户、剧集、订单等核心业务模块
This commit is contained in:
172
src/users/users.service.ts
Normal file
172
src/users/users.service.ts
Normal file
@@ -0,0 +1,172 @@
|
||||
import { Injectable, NotFoundException, Optional } from "@nestjs/common";
|
||||
import { InjectDataSource } from "@nestjs/typeorm";
|
||||
import { DataSource } from "typeorm";
|
||||
import { PaginatedResponse } from "../../common/dto/paginated-response.dto";
|
||||
import { User } from "./entities/user.entity";
|
||||
import { UserRecord } from "./interfaces/user-record.interface";
|
||||
|
||||
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,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user