2026-09-09 06:46:27 +03:30
|
|
|
import { Body, Controller, Get, Param, Patch, Post } from '@nestjs/common';
|
|
|
|
|
import { OrdersService } from './orders.service';
|
|
|
|
|
import { CreateOrderDto } from './dto/create-order.dto';
|
|
|
|
|
import { UpdateOrderStatusDto } from './dto/update-order-status.dto';
|
2026-09-22 07:09:33 +03:30
|
|
|
import { RequirePermission } from '../auth/guards';
|
|
|
|
|
import { CurrentCustomer, CustomerAuth } from '../customer-auth/customer.guards';
|
|
|
|
|
import type { AuthenticatedCustomer } from '../customer-auth/customer-jwt.strategy';
|
2026-09-09 06:46:27 +03:30
|
|
|
|
2026-09-22 00:43:11 +03:30
|
|
|
/**
|
2026-09-22 07:09:33 +03:30
|
|
|
* Order routes.
|
2026-09-22 00:43:11 +03:30
|
|
|
*
|
2026-09-22 07:09:33 +03:30
|
|
|
* All three were `@Public()` until the security hotfix. The reference's
|
|
|
|
|
* customer-facing order endpoints sat behind its `api-customer` guard and read
|
|
|
|
|
* the customer from the token (`auth()->id()`,
|
|
|
|
|
* henza_server ApiMobileController.php:533); the target had no customer session
|
|
|
|
|
* at the time, so the id travelled in the body and the server trusted it.
|
|
|
|
|
* That meant an anonymous caller could place an order on anyone's account —
|
|
|
|
|
* and an order lands on that customer's gold debt and ledger. Now that the
|
|
|
|
|
* customer token exists, the reference's arrangement is restored.
|
2026-09-22 00:43:11 +03:30
|
|
|
*/
|
2026-09-09 06:46:27 +03:30
|
|
|
@Controller('orders')
|
|
|
|
|
export class OrdersController {
|
|
|
|
|
constructor(private readonly orders: OrdersService) {}
|
|
|
|
|
|
2026-09-22 07:09:33 +03:30
|
|
|
/** The customer comes from the token; the body's `customerId` is ignored. */
|
|
|
|
|
@CustomerAuth()
|
2026-09-09 06:46:27 +03:30
|
|
|
@Post()
|
2026-09-22 07:09:33 +03:30
|
|
|
create(@Body() dto: CreateOrderDto, @CurrentCustomer() me: AuthenticatedCustomer) {
|
|
|
|
|
return this.orders.createOrder(dto, me.id);
|
2026-09-09 06:46:27 +03:30
|
|
|
}
|
|
|
|
|
|
2026-09-22 07:09:33 +03:30
|
|
|
/**
|
|
|
|
|
* Unscoped read for operators.
|
|
|
|
|
*
|
|
|
|
|
* Declared BEFORE the `:id` route below: Nest matches in declaration order,
|
|
|
|
|
* so a literal segment that could also be read as a parameter has to come
|
|
|
|
|
* first or it is never reached.
|
|
|
|
|
*/
|
|
|
|
|
@Get('admin/:id')
|
|
|
|
|
@RequirePermission('order.list')
|
|
|
|
|
getAsAdmin(@Param('id') id: string) {
|
2026-09-09 06:46:27 +03:30
|
|
|
return this.orders.getById(id);
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-22 07:09:33 +03:30
|
|
|
/**
|
|
|
|
|
* A customer reading one of their own orders. Scoped by token; someone
|
|
|
|
|
* else's order is a 404 rather than a 403, so ids stay unenumerable.
|
|
|
|
|
*
|
|
|
|
|
* Mounted under /orders/:id (not /customer/me/orders/:id) so the existing
|
|
|
|
|
* client paths keep working; it is the scoping that matters, not the shape.
|
|
|
|
|
*/
|
|
|
|
|
@CustomerAuth()
|
|
|
|
|
@Get(':id')
|
|
|
|
|
get(@Param('id') id: string, @CurrentCustomer() me: AuthenticatedCustomer) {
|
|
|
|
|
return this.orders.getOwnedById(id, me.id);
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-09 06:46:27 +03:30
|
|
|
@Patch(':id/status')
|
2026-09-22 00:43:11 +03:30
|
|
|
@RequirePermission('order.list.sts.submit')
|
2026-09-09 06:46:27 +03:30
|
|
|
updateStatus(@Param('id') id: string, @Body() dto: UpdateOrderStatusDto) {
|
|
|
|
|
return this.orders.updateStatus(id, dto.status, dto.actorAdminUserId, dto.note);
|
|
|
|
|
}
|
|
|
|
|
}
|