fix(reporting-service): use findFirst + update instead of upsert for realtimeStats

Replace upsert with findFirst + create/update pattern to avoid Prisma
unique constraint syntax issues. The @@unique constraint with a custom
name doesn't allow direct field-based queries in findUnique/upsert.

This approach maintains the same behavior without schema changes.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-01-06 21:36:26 -08:00
parent 2d9d6ceed7
commit 4e201d3a66
1 changed files with 104 additions and 58 deletions

View File

@ -15,8 +15,8 @@ export class RealtimeStatsRepository implements IRealtimeStatsRepository {
async getOrCreateByDate(date: Date): Promise<RealtimeStatsData> { async getOrCreateByDate(date: Date): Promise<RealtimeStatsData> {
const statsDate = this.normalizeDate(date); const statsDate = this.normalizeDate(date);
const existing = await this.prisma.realtimeStats.findUnique({ const existing = await this.prisma.realtimeStats.findFirst({
where: { uk_realtime_stats_date: { statsDate } }, where: { statsDate },
}); });
if (existing) { if (existing) {
@ -33,8 +33,8 @@ export class RealtimeStatsRepository implements IRealtimeStatsRepository {
async findByDate(date: Date): Promise<RealtimeStatsData | null> { async findByDate(date: Date): Promise<RealtimeStatsData | null> {
const statsDate = this.normalizeDate(date); const statsDate = this.normalizeDate(date);
const found = await this.prisma.realtimeStats.findUnique({ const found = await this.prisma.realtimeStats.findFirst({
where: { uk_realtime_stats_date: { statsDate } }, where: { statsDate },
}); });
return found ? this.toDomain(found) : null; return found ? this.toDomain(found) : null;
@ -54,57 +54,85 @@ export class RealtimeStatsRepository implements IRealtimeStatsRepository {
`Incrementing planting: date=${statsDate.toISOString()}, trees=${safeTreeCount}, amount=${safeAmount}`, `Incrementing planting: date=${statsDate.toISOString()}, trees=${safeTreeCount}, amount=${safeAmount}`,
); );
const result = await this.prisma.realtimeStats.upsert({ // 使用 findFirst + create/update 替代 upsert避免 Prisma 唯一约束语法问题
where: { uk_realtime_stats_date: { statsDate } }, const existing = await this.prisma.realtimeStats.findFirst({
create: { where: { statsDate },
statsDate,
dailyPlantingCount: safeTreeCount,
dailyOrderCount: 1,
dailyPlantingAmount: safeAmount,
},
update: {
dailyPlantingCount: { increment: safeTreeCount },
dailyOrderCount: { increment: 1 },
dailyPlantingAmount: { increment: safeAmount },
},
}); });
return this.toDomain(result); if (existing) {
const result = await this.prisma.realtimeStats.update({
where: { id: existing.id },
data: {
dailyPlantingCount: { increment: safeTreeCount },
dailyOrderCount: { increment: 1 },
dailyPlantingAmount: { increment: safeAmount },
},
});
return this.toDomain(result);
} else {
const result = await this.prisma.realtimeStats.create({
data: {
statsDate,
dailyPlantingCount: safeTreeCount,
dailyOrderCount: 1,
dailyPlantingAmount: safeAmount,
},
});
return this.toDomain(result);
}
} }
async incrementOrder(date: Date): Promise<RealtimeStatsData> { async incrementOrder(date: Date): Promise<RealtimeStatsData> {
const statsDate = this.normalizeDate(date); const statsDate = this.normalizeDate(date);
const result = await this.prisma.realtimeStats.upsert({ const existing = await this.prisma.realtimeStats.findFirst({
where: { uk_realtime_stats_date: { statsDate } }, where: { statsDate },
create: {
statsDate,
dailyOrderCount: 1,
},
update: {
dailyOrderCount: { increment: 1 },
},
}); });
return this.toDomain(result); if (existing) {
const result = await this.prisma.realtimeStats.update({
where: { id: existing.id },
data: {
dailyOrderCount: { increment: 1 },
},
});
return this.toDomain(result);
} else {
const result = await this.prisma.realtimeStats.create({
data: {
statsDate,
dailyOrderCount: 1,
},
});
return this.toDomain(result);
}
} }
async incrementNewUser(date: Date): Promise<RealtimeStatsData> { async incrementNewUser(date: Date): Promise<RealtimeStatsData> {
const statsDate = this.normalizeDate(date); const statsDate = this.normalizeDate(date);
this.logger.debug(`Incrementing new user: date=${statsDate.toISOString()}`); this.logger.debug(`Incrementing new user: date=${statsDate.toISOString()}`);
const result = await this.prisma.realtimeStats.upsert({ const existing = await this.prisma.realtimeStats.findFirst({
where: { uk_realtime_stats_date: { statsDate } }, where: { statsDate },
create: {
statsDate,
dailyNewUserCount: 1,
},
update: {
dailyNewUserCount: { increment: 1 },
},
}); });
return this.toDomain(result); if (existing) {
const result = await this.prisma.realtimeStats.update({
where: { id: existing.id },
data: {
dailyNewUserCount: { increment: 1 },
},
});
return this.toDomain(result);
} else {
const result = await this.prisma.realtimeStats.create({
data: {
statsDate,
dailyNewUserCount: 1,
},
});
return this.toDomain(result);
}
} }
async incrementProvinceAuth(date: Date): Promise<RealtimeStatsData> { async incrementProvinceAuth(date: Date): Promise<RealtimeStatsData> {
@ -113,36 +141,54 @@ export class RealtimeStatsRepository implements IRealtimeStatsRepository {
`Incrementing province auth: date=${statsDate.toISOString()}`, `Incrementing province auth: date=${statsDate.toISOString()}`,
); );
const result = await this.prisma.realtimeStats.upsert({ const existing = await this.prisma.realtimeStats.findFirst({
where: { uk_realtime_stats_date: { statsDate } }, where: { statsDate },
create: {
statsDate,
dailyProvinceAuthCount: 1,
},
update: {
dailyProvinceAuthCount: { increment: 1 },
},
}); });
return this.toDomain(result); if (existing) {
const result = await this.prisma.realtimeStats.update({
where: { id: existing.id },
data: {
dailyProvinceAuthCount: { increment: 1 },
},
});
return this.toDomain(result);
} else {
const result = await this.prisma.realtimeStats.create({
data: {
statsDate,
dailyProvinceAuthCount: 1,
},
});
return this.toDomain(result);
}
} }
async incrementCityAuth(date: Date): Promise<RealtimeStatsData> { async incrementCityAuth(date: Date): Promise<RealtimeStatsData> {
const statsDate = this.normalizeDate(date); const statsDate = this.normalizeDate(date);
this.logger.debug(`Incrementing city auth: date=${statsDate.toISOString()}`); this.logger.debug(`Incrementing city auth: date=${statsDate.toISOString()}`);
const result = await this.prisma.realtimeStats.upsert({ const existing = await this.prisma.realtimeStats.findFirst({
where: { uk_realtime_stats_date: { statsDate } }, where: { statsDate },
create: {
statsDate,
dailyCityAuthCount: 1,
},
update: {
dailyCityAuthCount: { increment: 1 },
},
}); });
return this.toDomain(result); if (existing) {
const result = await this.prisma.realtimeStats.update({
where: { id: existing.id },
data: {
dailyCityAuthCount: { increment: 1 },
},
});
return this.toDomain(result);
} else {
const result = await this.prisma.realtimeStats.create({
data: {
statsDate,
dailyCityAuthCount: 1,
},
});
return this.toDomain(result);
}
} }
async findRecentDays(days: number): Promise<RealtimeStatsData[]> { async findRecentDays(days: number): Promise<RealtimeStatsData[]> {