feat: complete TASK-WP4-04 color and dynamic stickers

This commit is contained in:
suyx
2026-08-03 14:11:40 +08:00
parent dd7a23a281
commit 457e1147e4
29 changed files with 2234 additions and 61 deletions
+15
View File
@@ -0,0 +1,15 @@
export interface AmapAdapter {
reverseGeocode(coordinates: { latitude: number; longitude: number }): Promise<{ formattedValue: string; serviceMode: "mock" }>;
}
export class MockAmapAdapter implements AmapAdapter {
readonly calls: Array<{ latitude: number; longitude: number }> = [];
async reverseGeocode(coordinates: { latitude: number; longitude: number }) {
this.calls.push({ ...coordinates });
return {
formattedValue: `模拟地点 ${coordinates.latitude.toFixed(4)}, ${coordinates.longitude.toFixed(4)}`,
serviceMode: "mock" as const,
};
}
}
+41
View File
@@ -93,6 +93,8 @@ import {
RecentAssetQuerySchema,
RecentAssetRecordRequestSchema,
RecentAssetRecordResponseSchema,
ReverseGeocodeRequestSchema,
ReverseGeocodeResponseSchema,
RegistrationCompleteHeadersSchema,
RegistrationCompleteRequestSchema,
RegistrationCompleteResponseSchema,
@@ -128,6 +130,7 @@ import {
type ProjectStateSaveHeaders,
type RecentAssetQuery,
type RecentAssetRecordRequest,
type ReverseGeocodeRequest,
type RegistrationCompleteRequest,
type RegistrationSendRequest,
} from "@dada/shared-contracts";
@@ -171,6 +174,7 @@ import {
} from "./registration-errors.js";
import type { RegistrationService } from "./registration.js";
import type { RecentAssetService } from "./recent-assets.js";
import type { AmapAdapter } from "./amap-adapter.js";
import { ModelConfigurationError } from "./model-configuration.js";
import type { ModelConfigurationService } from "./model-configuration.js";
@@ -187,6 +191,7 @@ const defaultBootstrap: BootstrapResponse = {
};
export interface CreateAppOptions {
amap?: AmapAdapter;
bootstrap?: () => BootstrapResponse | Promise<BootstrapResponse>;
browserGate?: boolean;
browserSupportRelease?: BrowserSupportRelease;
@@ -742,6 +747,8 @@ export async function createApp(options: CreateAppOptions = {}) {
RecentAssetListResponseSchema,
RecentAssetRecordRequestSchema,
RecentAssetRecordResponseSchema,
ReverseGeocodeRequestSchema,
ReverseGeocodeResponseSchema,
FailedEmptyTrashRequestSchema,
FailedEmptyTrashResponseSchema,
ModelIdSchema,
@@ -896,6 +903,40 @@ export async function createApp(options: CreateAppOptions = {}) {
},
);
app.post(
"/api/v1/location/reverse-geocode",
{
attachValidation: true,
schema: {
body: Type.Ref(ReverseGeocodeRequestSchema),
headers: Type.Ref(CsrfHeadersSchema),
operationId: "reverseGeocodeLocation",
response: {
200: Type.Ref(ReverseGeocodeResponseSchema),
400: Type.Null(),
401: Type.Ref(ErrorEnvelopeSchema),
503: Type.Null(),
},
tags: ["Location"],
},
},
async (request, reply) => {
if (request.validationError) return reply.code(400).send(null);
if (!options.registration || !options.amap) return reply.code(503).send(null);
const token = cookieValue(headerValue(request.headers.cookie), userSessionCookieName);
const csrfToken = headerValue(request.headers["x-csrf-token"]);
if (!token || !csrfToken) return reply.code(401).send(createErrorEnvelope({ code: "AUTH_SESSION_INVALID", correlationId: request.id }));
try {
options.registration.authorizeUserMutation({ csrfToken, sessionToken: token });
const result = await options.amap.reverseGeocode(request.body as ReverseGeocodeRequest);
return { formatted_value: result.formattedValue, service_mode: result.serviceMode, status: "resolved" as const };
} catch (error) {
if (error instanceof RegistrationError) return registrationFailure(reply, request.id, error);
return reply.code(503).send(null);
}
},
);
app.post(
"/api/v1/admin-auth/login/send",
{
+2
View File
@@ -17,6 +17,7 @@ import { readSecureConfigCandidate } from "./secure-config.js";
import { StructuredJsonlLogger } from "./structured-log.js";
import { attachApiSupervisorControl, initializeApiCredentialClients, receiveApiCredentials } from "./supervisor-channel.js";
import { ModelConfigurationService } from "./model-configuration.js";
import { MockAmapAdapter } from "./amap-adapter.js";
const credentialChannelEnabled = process.argv.includes("--dada-credential-stdin");
let registration: RegistrationService | undefined;
@@ -70,6 +71,7 @@ if (credentialChannelEnabled) {
const browserSupportRelease = readBrowserSupportRelease(resolve("RELEASE.json"));
const app = await createApp({
amap: new MockAmapAdapter(),
...(browserSupportRelease ? { browserSupportRelease } : {}),
...(credits ? { credits } : {}),
...(latestExports ? { latestExports } : {}),