feat(M08-A): 接入顾客端选店下单入口
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import type { FastifyInstance } from 'fastify';
|
||||
import { z } from 'zod';
|
||||
import type { AuthRepository } from '../auth/auth-repository.js';
|
||||
import type { AuthRepository, LoginContext } from '../auth/auth-repository.js';
|
||||
import type { StoreDiscoveryRepository } from '../stores/store-discovery-repository.js';
|
||||
|
||||
const headersSchema = z.object({
|
||||
@@ -21,7 +21,7 @@ const querySchema = z.object({
|
||||
});
|
||||
|
||||
export interface StoreDiscoveryRouteOptions {
|
||||
repository: Pick<StoreDiscoveryRepository, 'findStores'>;
|
||||
repository: Pick<StoreDiscoveryRepository, 'findStores' | 'getStore' | 'listRooms'>;
|
||||
tenancy: Pick<AuthRepository, 'resolveLoginContext'>;
|
||||
}
|
||||
|
||||
@@ -64,4 +64,89 @@ export async function registerStoreDiscoveryRoutes(
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/app-api/stores/:storeId', async (request, reply) => {
|
||||
const context = await resolveContext(request, reply, options);
|
||||
const params = storeIdSchema.safeParse(request.params);
|
||||
if (!context) return;
|
||||
if (!params.success) return invalid(reply, request.traceId);
|
||||
const store = await options.repository.getStore({
|
||||
tenantId: context.tenantId,
|
||||
storeId: params.data.storeId
|
||||
});
|
||||
if (!store) {
|
||||
return reply.status(404).send({
|
||||
code: 'STORE_NOT_FOUND',
|
||||
message: 'Store was not found.',
|
||||
traceId: request.traceId
|
||||
});
|
||||
}
|
||||
return { code: 0, data: store, traceId: request.traceId };
|
||||
});
|
||||
|
||||
app.get('/app-api/stores/:storeId/rooms', async (request, reply) => {
|
||||
const context = await resolveContext(request, reply, options);
|
||||
const params = storeIdSchema.safeParse(request.params);
|
||||
if (!context) return;
|
||||
if (!params.success) return invalid(reply, request.traceId);
|
||||
return {
|
||||
code: 0,
|
||||
data: await options.repository.listRooms({
|
||||
tenantId: context.tenantId,
|
||||
storeId: params.data.storeId
|
||||
}),
|
||||
traceId: request.traceId
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
const storeIdSchema = z.object({
|
||||
storeId: z.string().regex(/^[1-9]\d{0,19}$/)
|
||||
});
|
||||
|
||||
async function resolveContext(
|
||||
request: { headers: unknown; traceId: string },
|
||||
reply: { status(code: number): { send(payload: unknown): unknown } },
|
||||
options: StoreDiscoveryRouteOptions
|
||||
): Promise<LoginContext | null> {
|
||||
const headers = headersSchema.safeParse(request.headers);
|
||||
if (!headers.success) {
|
||||
invalid(reply, request.traceId);
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
const context = await options.tenancy.resolveLoginContext(
|
||||
headers.data['x-wechat-appid'], headers.data['tenant-id']
|
||||
);
|
||||
if (!context) {
|
||||
reply.status(404).send({
|
||||
code: 'APP_TENANT_NOT_FOUND',
|
||||
message: 'Application tenant binding was not found.',
|
||||
traceId: request.traceId
|
||||
});
|
||||
return null;
|
||||
}
|
||||
return context;
|
||||
} catch (error) {
|
||||
if (error instanceof Error && error.message === 'TENANT_SELECTION_REQUIRED') {
|
||||
reply.status(409).send({
|
||||
code: 'TENANT_SELECTION_REQUIRED',
|
||||
message: 'tenant-id is required for an application bound to multiple tenants.',
|
||||
traceId: request.traceId
|
||||
});
|
||||
return null;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
function invalid(
|
||||
reply: { status(code: number): { send(payload: unknown): unknown } },
|
||||
traceId: string
|
||||
) {
|
||||
return reply.status(400).send({
|
||||
code: 'INVALID_STORE_DISCOVERY_REQUEST',
|
||||
message: 'AppID and valid store filters are required.',
|
||||
traceId
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user