batman
This commit is contained in:
@@ -0,0 +1,157 @@
|
||||
import {
|
||||
Injectable,
|
||||
NotFoundException,
|
||||
BadRequestException,
|
||||
} from '@nestjs/common';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { AuditService } from '../audit/audit.service';
|
||||
import { CreateCommandDto } from './dto/create-command.dto';
|
||||
import { COMMAND_EXPIRY_HOURS, DEVICE_OFFLINE_THRESHOLD_MS } from '@kooltab/shared';
|
||||
|
||||
@Injectable()
|
||||
export class CommandsService {
|
||||
constructor(
|
||||
private prisma: PrismaService,
|
||||
private auditService: AuditService,
|
||||
) {}
|
||||
|
||||
async create(venueId: string, dto: CreateCommandDto, user: any) {
|
||||
if (!dto.paymentMethod) {
|
||||
throw new BadRequestException({
|
||||
code: 'payment_method_required',
|
||||
message: 'Payment method must be explicitly selected',
|
||||
});
|
||||
}
|
||||
|
||||
const validMethods = ['cash', 'payconiq', 'payment_pending'];
|
||||
if (!validMethods.includes(dto.paymentMethod)) {
|
||||
throw new BadRequestException({
|
||||
code: 'invalid_payment_method',
|
||||
message: `Payment method must be one of: ${validMethods.join(', ')}`,
|
||||
});
|
||||
}
|
||||
|
||||
// Use transaction for consistency
|
||||
const result = await this.prisma.$transaction(async (tx) => {
|
||||
// Resolve tab ownership
|
||||
const ownership = await tx.tabOwnership.findUnique({
|
||||
where: { venueId_tabId: { venueId, tabId: dto.tabId } },
|
||||
});
|
||||
|
||||
if (!ownership) {
|
||||
throw new NotFoundException({
|
||||
code: 'tab_not_owned',
|
||||
message: 'Tab is not associated with any device',
|
||||
});
|
||||
}
|
||||
|
||||
const device = await tx.device.findUnique({ where: { id: ownership.deviceId } });
|
||||
if (!device) {
|
||||
throw new NotFoundException({
|
||||
code: 'device_not_found',
|
||||
message: 'Owning device not found',
|
||||
});
|
||||
}
|
||||
|
||||
const now = new Date();
|
||||
const expiresAt = new Date(now.getTime() + COMMAND_EXPIRY_HOURS * 60 * 60 * 1000);
|
||||
|
||||
const command = await tx.remoteCommand.create({
|
||||
data: {
|
||||
id: uuidv4(),
|
||||
venueId,
|
||||
deviceId: device.id,
|
||||
targetTabId: dto.tabId,
|
||||
type: dto.type || 'close_tab',
|
||||
expectedVersion: dto.expectedVersion,
|
||||
paymentMethod: dto.paymentMethod,
|
||||
requestedBy: user.name || user.email,
|
||||
status: 'pending',
|
||||
expiresAt,
|
||||
},
|
||||
});
|
||||
|
||||
await tx.auditLog.create({
|
||||
data: {
|
||||
id: uuidv4(),
|
||||
venueId,
|
||||
actorType: 'client_user',
|
||||
actorId: user.id,
|
||||
action: 'command_created',
|
||||
details: JSON.stringify({
|
||||
commandId: command.id,
|
||||
tabId: dto.tabId,
|
||||
type: dto.type,
|
||||
paymentMethod: dto.paymentMethod,
|
||||
deviceId: device.id,
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
// Check if device is stale
|
||||
const lastSeenAgo = now.getTime() - new Date(device.lastSeenAt).getTime();
|
||||
const warning = lastSeenAgo > DEVICE_OFFLINE_THRESHOLD_MS
|
||||
? { warning: 'Device has not been seen recently' }
|
||||
: {};
|
||||
|
||||
return {
|
||||
id: command.id,
|
||||
status: command.status,
|
||||
...warning,
|
||||
};
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async findAll(
|
||||
venueId: string,
|
||||
options: { status?: string; limit: number; offset: number },
|
||||
) {
|
||||
const where: any = { venueId };
|
||||
if (options.status) {
|
||||
where.status = options.status;
|
||||
}
|
||||
|
||||
const [commands, total] = await Promise.all([
|
||||
this.prisma.remoteCommand.findMany({
|
||||
where,
|
||||
select: {
|
||||
id: true,
|
||||
targetTabId: true,
|
||||
type: true,
|
||||
status: true,
|
||||
paymentMethod: true,
|
||||
requestedBy: true,
|
||||
expectedVersion: true,
|
||||
createdAt: true,
|
||||
acknowledgedAt: true,
|
||||
result: true,
|
||||
deviceId: true,
|
||||
},
|
||||
orderBy: { createdAt: 'desc' },
|
||||
take: options.limit,
|
||||
skip: options.offset,
|
||||
}),
|
||||
this.prisma.remoteCommand.count({ where }),
|
||||
]);
|
||||
|
||||
return {
|
||||
commands: commands.map((c) => ({
|
||||
id: c.id,
|
||||
tabId: c.targetTabId,
|
||||
type: c.type,
|
||||
status: c.status,
|
||||
paymentMethod: c.paymentMethod,
|
||||
requestedBy: c.requestedBy,
|
||||
expectedVersion: c.expectedVersion,
|
||||
createdAt: c.createdAt,
|
||||
processedAt: c.acknowledgedAt,
|
||||
result: c.result,
|
||||
deviceId: c.deviceId,
|
||||
})),
|
||||
total,
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user