@openora/core 0.4.1-canary.67 → 0.4.1-canary.68
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.tsbuildinfo +1 -1
- package/dist/audit/__tests__/audit.service.int.test.js +45 -0
- package/dist/audit/__tests__/audit.service.int.test.js.map +1 -1
- package/dist/audit/plugin.d.ts.map +1 -1
- package/dist/audit/plugin.js +26 -0
- package/dist/audit/plugin.js.map +1 -1
- package/dist/contracts/schemas/events.d.ts +17 -0
- package/dist/contracts/schemas/events.d.ts.map +1 -1
- package/dist/contracts/schemas/events.js +19 -0
- package/dist/contracts/schemas/events.js.map +1 -1
- package/dist/engagement/contracts.d.ts +1 -0
- package/dist/engagement/contracts.d.ts.map +1 -1
- package/dist/engagement/contracts.js +1 -0
- package/dist/engagement/contracts.js.map +1 -1
- package/dist/engagement/index.d.ts +1 -0
- package/dist/engagement/index.d.ts.map +1 -1
- package/dist/engagement/index.js +1 -0
- package/dist/engagement/index.js.map +1 -1
- package/dist/engagement/notifications/__tests__/notifications.service.int.test.js +34 -0
- package/dist/engagement/notifications/__tests__/notifications.service.int.test.js.map +1 -1
- package/dist/engagement/notifications/contract/index.d.ts +9 -1
- package/dist/engagement/notifications/contract/index.d.ts.map +1 -1
- package/dist/engagement/notifications/contract/index.js +2 -0
- package/dist/engagement/notifications/contract/index.js.map +1 -1
- package/dist/engagement/notifications/plugin.d.ts.map +1 -1
- package/dist/engagement/notifications/plugin.js +24 -0
- package/dist/engagement/notifications/plugin.js.map +1 -1
- package/dist/engagement/notifications/router/index.d.ts +2 -0
- package/dist/engagement/notifications/router/index.d.ts.map +1 -1
- package/dist/engagement/server.d.ts +1 -0
- package/dist/engagement/server.d.ts.map +1 -1
- package/dist/engagement/server.js +1 -0
- package/dist/engagement/server.js.map +1 -1
- package/dist/engagement/social/__tests__/social.router.int.test.d.ts +2 -0
- package/dist/engagement/social/__tests__/social.router.int.test.d.ts.map +1 -0
- package/dist/engagement/social/__tests__/social.router.int.test.js +97 -0
- package/dist/engagement/social/__tests__/social.router.int.test.js.map +1 -0
- package/dist/engagement/social/__tests__/social.service.int.test.d.ts +2 -0
- package/dist/engagement/social/__tests__/social.service.int.test.d.ts.map +1 -0
- package/dist/engagement/social/__tests__/social.service.int.test.js +233 -0
- package/dist/engagement/social/__tests__/social.service.int.test.js.map +1 -0
- package/dist/engagement/social/contract/index.d.ts +73 -0
- package/dist/engagement/social/contract/index.d.ts.map +1 -0
- package/dist/engagement/social/contract/index.js +54 -0
- package/dist/engagement/social/contract/index.js.map +1 -0
- package/dist/engagement/social/drizzle/migrations/0000_fine_jamie_braddock.sql +24 -0
- package/dist/engagement/social/drizzle/migrations/0001_lame_eddie_brock.sql +10 -0
- package/dist/engagement/social/drizzle/migrations/0002_foamy_saracen.sql +1 -0
- package/dist/engagement/social/drizzle/migrations/meta/0000_snapshot.json +232 -0
- package/dist/engagement/social/drizzle/migrations/meta/0001_snapshot.json +129 -0
- package/dist/engagement/social/drizzle/migrations/meta/0002_snapshot.json +134 -0
- package/dist/engagement/social/drizzle/migrations/meta/_journal.json +27 -0
- package/dist/engagement/social/drizzle.config.d.ts +4 -0
- package/dist/engagement/social/drizzle.config.d.ts.map +1 -0
- package/dist/engagement/social/drizzle.config.js +21 -0
- package/dist/engagement/social/drizzle.config.js.map +1 -0
- package/dist/engagement/social/index.d.ts +3 -0
- package/dist/engagement/social/index.d.ts.map +1 -0
- package/dist/engagement/social/index.js +6 -0
- package/dist/engagement/social/index.js.map +1 -0
- package/dist/engagement/social/migrate.d.ts +5 -0
- package/dist/engagement/social/migrate.d.ts.map +1 -0
- package/dist/engagement/social/migrate.js +17 -0
- package/dist/engagement/social/migrate.js.map +1 -0
- package/dist/engagement/social/plugin.d.ts +111 -0
- package/dist/engagement/social/plugin.d.ts.map +1 -0
- package/dist/engagement/social/plugin.js +16 -0
- package/dist/engagement/social/plugin.js.map +1 -0
- package/dist/engagement/social/router/index.d.ts +31 -0
- package/dist/engagement/social/router/index.d.ts.map +1 -0
- package/dist/engagement/social/router/index.js +31 -0
- package/dist/engagement/social/router/index.js.map +1 -0
- package/dist/engagement/social/schema/index.d.ts +128 -0
- package/dist/engagement/social/schema/index.d.ts.map +1 -0
- package/dist/engagement/social/schema/index.js +36 -0
- package/dist/engagement/social/schema/index.js.map +1 -0
- package/dist/engagement/social/service/social.service.d.ts +39 -0
- package/dist/engagement/social/service/social.service.d.ts.map +1 -0
- package/dist/engagement/social/service/social.service.js +251 -0
- package/dist/engagement/social/service/social.service.js.map +1 -0
- package/package.json +21 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/engagement/social/index.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,2EAA2E;AAC3E,+FAA+F;AAC/F,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAC5D,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migrate.d.ts","sourceRoot":"","sources":["../../../src/engagement/social/migrate.ts"],"names":[],"mappings":"AAMA;;GAEG;AACH,wBAAgB,OAAO,CAAC,WAAW,CAAC,EAAE,MAAM,iBAO3C"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Applies this module's own migration set against its own tracking table, so it
|
|
2
|
+
// never collides with sibling modules that share the database. SQL ships in the
|
|
3
|
+
// tarball ('files') and loads via an import.meta.url-relative path. See ADR-0020/0027.
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { runMigrations } from '@openora/core/server/migrate';
|
|
6
|
+
/**
|
|
7
|
+
* Apply the social module migrations (idempotent: drizzle skips already-recorded ones).
|
|
8
|
+
*/
|
|
9
|
+
export function migrate(databaseUrl) {
|
|
10
|
+
return runMigrations({
|
|
11
|
+
migrationsFolder: fileURLToPath(new URL('./drizzle/migrations', import.meta.url)),
|
|
12
|
+
migrationsTable: '__drizzle_migrations_social',
|
|
13
|
+
migrationsSchema: 'drizzle',
|
|
14
|
+
...(databaseUrl ? { databaseUrl } : {}),
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=migrate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migrate.js","sourceRoot":"","sources":["../../../src/engagement/social/migrate.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,gFAAgF;AAChF,uFAAuF;AACvF,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAE7D;;GAEG;AACH,MAAM,UAAU,OAAO,CAAC,WAAoB;IAC1C,OAAO,aAAa,CAAC;QACnB,gBAAgB,EAAE,aAAa,CAAC,IAAI,GAAG,CAAC,sBAAsB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACjF,eAAe,EAAE,6BAA6B;QAC9C,gBAAgB,EAAE,SAAS;QAC3B,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACxC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
declare const _default: {
|
|
2
|
+
readonly id: "social";
|
|
3
|
+
readonly dependsOn: readonly ["chat"];
|
|
4
|
+
readonly register: (ctx: import("@openora/core/server").PluginContext<{
|
|
5
|
+
ADMIN_GAME_REPORTING: import("../../contracts/index.js").Token<import("../../contracts/index.js").AdminGameReporting>;
|
|
6
|
+
ADMIN_GUARD: import("../../contracts/index.js").Token<import("@openora/core/server").AdminGuard>;
|
|
7
|
+
ADMIN_PERMISSION_RESOLVER: import("../../contracts/index.js").Token<import("../../contracts/index.js").AdminPermissionResolver>;
|
|
8
|
+
ADMIN_PLAYER_ACTIVITY: import("../../contracts/index.js").Token<import("../../contracts/index.js").AdminPlayerActivity>;
|
|
9
|
+
ADMIN_USER_DIRECTORY: import("../../contracts/index.js").Token<import("../../contracts/index.js").AdminUserDirectory>;
|
|
10
|
+
ADMIN_WALLET_REPORTING: import("../../contracts/index.js").Token<import("../../contracts/index.js").AdminWalletReporting>;
|
|
11
|
+
AUDIT_WRITER: import("../../contracts/index.js").SealedToken<import("../../contracts/index.js").AuditWritePort>;
|
|
12
|
+
AUTH_SESSION: import("../../contracts/index.js").Token<import("@openora/core/server").SessionResolver>;
|
|
13
|
+
CACHE: import("../../contracts/index.js").Token<import("../../contracts/index.js").CacheAdapter>;
|
|
14
|
+
CHAT_BLOCK_WRITER: import("../../contracts/index.js").Token<import("../../contracts/index.js").ChatBlockWriter>;
|
|
15
|
+
CHAT_REALTIME_CLIENT_AUTHORIZER: import("../../contracts/index.js").Token<import("../../contracts/index.js").RealtimeClientAuthorizer>;
|
|
16
|
+
CHAT_REALTIME_TRANSPORT: import("../../contracts/index.js").Token<import("../../contracts/index.js").RealtimeTransport>;
|
|
17
|
+
CHAT_ROOM_ACCESS: import("../../contracts/index.js").Token<import("../../contracts/index.js").ChatRoomAccess>;
|
|
18
|
+
CHAT_SYSTEM_WRITER: import("../../contracts/index.js").Token<import("../../contracts/index.js").ChatSystemWriter, string>;
|
|
19
|
+
DRIZZLE: import("../../contracts/index.js").Token<import("@openora/core/server").DrizzleService>;
|
|
20
|
+
EMAIL_TEMPLATE_RENDERER: import("../../contracts/index.js").Token<import("../../contracts/index.js").EmailTemplateRenderer>;
|
|
21
|
+
ERROR_TRACKING: import("../../contracts/index.js").Token<import("../../contracts/index.js").ErrorTrackingAdapter>;
|
|
22
|
+
EVENT_BUS: import("../../contracts/index.js").Token<import("@openora/core/server").EventBus>;
|
|
23
|
+
GAME_ADAPTER: import("../../contracts/index.js").Token<import("../../contracts/index.js").GameAdapter>;
|
|
24
|
+
GEO_IP_ADAPTER: import("../../contracts/index.js").Token<import("../../contracts/index.js").GeoIpAdapter>;
|
|
25
|
+
GIFT_COMMANDS: import("../../contracts/index.js").Token<import("../../contracts/index.js").GiftCommands>;
|
|
26
|
+
IDENTITY_OPTIONS: import("../../contracts/index.js").Token<import("../../contracts/index.js").IdentityServiceOptions>;
|
|
27
|
+
IDENTITY_READER: import("../../contracts/index.js").Token<import("../../contracts/index.js").IdentityReader, string>;
|
|
28
|
+
IGAMING_CONFIG: import("../../contracts/index.js").Token<{
|
|
29
|
+
branding: {
|
|
30
|
+
name: string;
|
|
31
|
+
themePreset?: string | undefined;
|
|
32
|
+
supportEmail?: string | undefined;
|
|
33
|
+
};
|
|
34
|
+
currencies: string[];
|
|
35
|
+
jurisdictions: string[];
|
|
36
|
+
blockedCountries: string[];
|
|
37
|
+
limits: {
|
|
38
|
+
maxDepositPerDay?: number | undefined;
|
|
39
|
+
maxDepositPerMonth?: number | undefined;
|
|
40
|
+
maxLossPerDay?: number | undefined;
|
|
41
|
+
maxStakePerBet?: number | undefined;
|
|
42
|
+
sessionReminderMinutes?: number | undefined;
|
|
43
|
+
};
|
|
44
|
+
providers: {
|
|
45
|
+
payment?: string | undefined;
|
|
46
|
+
kyc?: string | undefined;
|
|
47
|
+
geoIp?: string | undefined;
|
|
48
|
+
aggregator?: string | undefined;
|
|
49
|
+
notification?: string | undefined;
|
|
50
|
+
};
|
|
51
|
+
enabledModules?: string[] | undefined;
|
|
52
|
+
}, string>;
|
|
53
|
+
JOB_QUEUE: import("../../contracts/index.js").Token<import("../../contracts/index.js").JobQueueAdapter>;
|
|
54
|
+
KYC_ADAPTER: import("../../contracts/index.js").Token<import("../../contracts/index.js").KycAdapter>;
|
|
55
|
+
KYC_STATUS_WRITER: import("../../contracts/index.js").Token<import("../../contracts/index.js").KycStatusWriter>;
|
|
56
|
+
KYC_WEBHOOK_VERIFIER: import("../../contracts/index.js").Token<import("../../contracts/index.js").KycWebhookVerifier>;
|
|
57
|
+
LOGIN_ENFORCEMENT: import("../../contracts/index.js").Token<import("../../contracts/index.js").LoginEnforcementPort>;
|
|
58
|
+
MESSAGE_BROKER: import("../../contracts/index.js").Token<import("../../contracts/index.js").MessageBrokerAdapter>;
|
|
59
|
+
NOTIFICATION_DELIVERY_ADAPTER: import("../../contracts/index.js").Token<import("../../contracts/index.js").NotificationDeliveryAdapter>;
|
|
60
|
+
OUTBOX: import("../../contracts/index.js").Token<import("../../contracts/index.js").OutboxWriter>;
|
|
61
|
+
PAYMENT_ADAPTER: import("../../contracts/index.js").Token<import("../../contracts/index.js").PaymentAdapter>;
|
|
62
|
+
PAYMENT_WEBHOOK_VERIFIER: import("../../contracts/index.js").Token<import("../../contracts/index.js").PaymentWebhookVerifier>;
|
|
63
|
+
PLATFORM_CONFIG: import("../../contracts/index.js").Token<{
|
|
64
|
+
features: Record<string, boolean>;
|
|
65
|
+
brands: {
|
|
66
|
+
id: string;
|
|
67
|
+
name: string;
|
|
68
|
+
themePreset?: string | undefined;
|
|
69
|
+
themeOverrides?: Record<string, string> | undefined;
|
|
70
|
+
}[];
|
|
71
|
+
rgLimits: Record<string, {
|
|
72
|
+
maxDepositPerDay?: number | undefined;
|
|
73
|
+
maxDepositPerMonth?: number | undefined;
|
|
74
|
+
maxLossPerDay?: number | undefined;
|
|
75
|
+
maxStakePerBet?: number | undefined;
|
|
76
|
+
sessionReminderMinutes?: number | undefined;
|
|
77
|
+
}>;
|
|
78
|
+
activeBrand?: string | undefined;
|
|
79
|
+
kyc?: {
|
|
80
|
+
gateWithdrawals: boolean;
|
|
81
|
+
provider?: string | undefined;
|
|
82
|
+
webhookSecretEnv?: string | undefined;
|
|
83
|
+
reverifyThresholds?: Record<string, string> | undefined;
|
|
84
|
+
} | undefined;
|
|
85
|
+
autoWithdrawal?: {
|
|
86
|
+
enabled: boolean;
|
|
87
|
+
dailyCapAmount?: string | undefined;
|
|
88
|
+
dailyCapCount?: number | undefined;
|
|
89
|
+
} | undefined;
|
|
90
|
+
wallet?: {
|
|
91
|
+
cryptoCurrencies?: string[] | undefined;
|
|
92
|
+
} | undefined;
|
|
93
|
+
supportedLanguages?: string[] | undefined;
|
|
94
|
+
}, string>;
|
|
95
|
+
PLAYER_TAGS: import("../../contracts/index.js").Token<import("../../contracts/index.js").PlayerTags>;
|
|
96
|
+
PLAY_ELIGIBILITY: import("../../contracts/index.js").Token<import("../../contracts/index.js").PlayEligibilityPort>;
|
|
97
|
+
RAIN_COMMANDS: import("../../contracts/index.js").Token<import("../../contracts/index.js").RainCommands>;
|
|
98
|
+
RATE_LIMITER: import("../../contracts/index.js").Token<import("../../contracts/index.js").RateLimiterAdapter<`register:${string}` | `login:${string}` | `enable2fa:${string}` | `verify2fa:${string}` | `disable2fa:${string}` | `pwreset-req:${string}` | `pwreset-verify:${string}` | `pwreset:${string}` | `change-password:${string}` | `email-verify:${string}` | `verify-email:${string}` | `wallet-mutation:${string}` | `chat-room-join:${string}` | `chat-send:${string}` | `report-access-denied:${string}`>>;
|
|
99
|
+
REALTIME_CLIENT_AUTHORIZER: import("../../contracts/index.js").Token<import("../../contracts/index.js").RealtimeClientAuthorizer>;
|
|
100
|
+
REALTIME_TRANSPORT: import("../../contracts/index.js").Token<import("../../contracts/index.js").RealtimeTransport>;
|
|
101
|
+
RNG_ADAPTER: import("../../contracts/index.js").Token<import("../../contracts/index.js").RngAdapter>;
|
|
102
|
+
SEND_EMAIL: import("../../contracts/index.js").Token<import("../../contracts/index.js").SendEmailPort>;
|
|
103
|
+
SESSION_COMMANDS: import("../../contracts/index.js").Token<import("../../contracts/index.js").SessionCommands>;
|
|
104
|
+
SMS_ADAPTER: import("../../contracts/index.js").Token<import("../../contracts/index.js").SmsAdapter>;
|
|
105
|
+
TAG_EVALUATION_COMMANDS: import("../../contracts/index.js").Token<import("../../contracts/index.js").TagEvaluationCommands>;
|
|
106
|
+
WALLET_COMMANDS: import("../../contracts/index.js").Token<import("../../contracts/index.js").WalletCommands>;
|
|
107
|
+
WALLET_READER: import("../../contracts/index.js").Token<import("../../contracts/index.js").WalletReader, string>;
|
|
108
|
+
}>) => void;
|
|
109
|
+
};
|
|
110
|
+
export default _default;
|
|
111
|
+
//# sourceMappingURL=plugin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../../src/engagement/social/plugin.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAUA,wBAQ8C"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { EVENT_BUS, DRIZZLE } from '@openora/core/server';
|
|
2
|
+
import { SocialService } from './service/social.service.js';
|
|
3
|
+
import { createSocialRouter } from './router/index.js';
|
|
4
|
+
// DI wiring only - no business logic here. sendFriendRequest/getRelationships read
|
|
5
|
+
// the `player` table via the read-only @openora/core/pam/schema/profile subpath
|
|
6
|
+
// (ADR-0020), not a DI token/command port, so no dependsOn is needed to pin load
|
|
7
|
+
// order - the imported pgTable is a plain object available regardless of plugin
|
|
8
|
+
// registration order.
|
|
9
|
+
export default {
|
|
10
|
+
id: 'social',
|
|
11
|
+
dependsOn: ['chat'],
|
|
12
|
+
register(ctx) {
|
|
13
|
+
ctx.routers.add('social', (c) => createSocialRouter(new SocialService(c.get(DRIZZLE), c.get(EVENT_BUS))));
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
//# sourceMappingURL=plugin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.js","sourceRoot":"","sources":["../../../src/engagement/social/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAE1D,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAC5D,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAEvD,mFAAmF;AACnF,gFAAgF;AAChF,iFAAiF;AACjF,gFAAgF;AAChF,sBAAsB;AACtB,eAAe;IACb,EAAE,EAAE,QAAQ;IACZ,SAAS,EAAE,CAAC,MAAM,CAAC;IACnB,QAAQ,CAAC,GAAG;QACV,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAC9B,kBAAkB,CAAC,IAAI,aAAa,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,CACxE,CAAC;IACJ,CAAC;CAC0C,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type OssContext } from '@openora/core/server';
|
|
2
|
+
import { SocialService } from '../service/social.service.js';
|
|
3
|
+
export declare function createSocialRouter(social: SocialService): {
|
|
4
|
+
sendFriendRequest: import("@orpc/server").Procedure<import("@orpc/server").MergedInitialContext<OssContext & Record<never, never>, OssContext & Record<never, never>, OssContext>, OssContext, import("zod").ZodObject<{
|
|
5
|
+
targetUserId: import("zod").ZodUUID;
|
|
6
|
+
}, import("zod/v4/core").$strip>, import("zod").ZodObject<{
|
|
7
|
+
id: import("zod").ZodUUID;
|
|
8
|
+
requesterId: import("zod").ZodUUID;
|
|
9
|
+
addresseeId: import("zod").ZodUUID;
|
|
10
|
+
createdAt: import("zod").ZodISODateTime;
|
|
11
|
+
acceptedAt: import("zod").ZodNullable<import("zod").ZodISODateTime>;
|
|
12
|
+
refusedAt: import("zod").ZodNullable<import("zod").ZodISODateTime>;
|
|
13
|
+
}, import("zod/v4/core").$strip>, import("@orpc/server").MergedErrorMap<Record<never, never>, Record<never, never>>, Record<never, never>>;
|
|
14
|
+
getRelationships: import("@orpc/server").Procedure<import("@orpc/server").MergedInitialContext<OssContext & Record<never, never>, OssContext & Record<never, never>, OssContext>, OssContext, import("zod").ZodObject<{
|
|
15
|
+
userIds: import("zod").ZodArray<import("zod").ZodUUID>;
|
|
16
|
+
}, import("zod/v4/core").$strip>, import("zod").ZodArray<import("zod").ZodObject<{
|
|
17
|
+
userId: import("zod").ZodUUID;
|
|
18
|
+
status: import("zod").ZodEnum<{
|
|
19
|
+
none: "none";
|
|
20
|
+
pending_outgoing: "pending_outgoing";
|
|
21
|
+
pending_incoming: "pending_incoming";
|
|
22
|
+
friends: "friends";
|
|
23
|
+
refused: "refused";
|
|
24
|
+
blocked_by_me: "blocked_by_me";
|
|
25
|
+
unavailable: "unavailable";
|
|
26
|
+
}>;
|
|
27
|
+
friendshipId: import("zod").ZodNullable<import("zod").ZodUUID>;
|
|
28
|
+
canSendRequest: import("zod").ZodBoolean;
|
|
29
|
+
}, import("zod/v4/core").$strip>>, import("@orpc/server").MergedErrorMap<Record<never, never>, Record<never, never>>, Record<never, never>>;
|
|
30
|
+
};
|
|
31
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/engagement/social/router/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAwB,KAAK,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAE7E,OAAO,EACL,aAAa,EAQd,MAAM,8BAA8B,CAAC;AAKtC,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;EA2BvD"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { implement } from '@orpc/server';
|
|
2
|
+
import { getUserId, mapErrors } from '@openora/core/server';
|
|
3
|
+
import { socialContract } from '../contract/index.js';
|
|
4
|
+
import { SelfFriendRequestError, FriendRequestTargetNotFoundError, FriendRequestUnavailableError, AlreadyFriendsError, RequestAlreadyPendingError, FriendRequestRefusedError, BlockedBySelfError, } from '../service/social.service.js';
|
|
5
|
+
// oRPC router factory for Social. plugin.ts builds the service from the
|
|
6
|
+
// container and passes it here; each procedure delegates to the service. Keep this
|
|
7
|
+
// thin: resolve the caller, call the service, map domain errors - no business rules.
|
|
8
|
+
export function createSocialRouter(social) {
|
|
9
|
+
const os = implement(socialContract).$context();
|
|
10
|
+
return os.router({
|
|
11
|
+
sendFriendRequest: os.sendFriendRequest.handler(({ input, context }) => {
|
|
12
|
+
const callerId = getUserId(context);
|
|
13
|
+
return mapErrors({
|
|
14
|
+
BAD_REQUEST: [SelfFriendRequestError],
|
|
15
|
+
NOT_FOUND: [FriendRequestTargetNotFoundError],
|
|
16
|
+
CONFLICT: [
|
|
17
|
+
AlreadyFriendsError,
|
|
18
|
+
RequestAlreadyPendingError,
|
|
19
|
+
FriendRequestRefusedError,
|
|
20
|
+
FriendRequestUnavailableError,
|
|
21
|
+
BlockedBySelfError,
|
|
22
|
+
],
|
|
23
|
+
}, () => social.sendFriendRequest(callerId, input.targetUserId));
|
|
24
|
+
}),
|
|
25
|
+
getRelationships: os.getRelationships.handler(({ input, context }) => {
|
|
26
|
+
const callerId = getUserId(context);
|
|
27
|
+
return social.getRelationships(callerId, input.userIds);
|
|
28
|
+
}),
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/engagement/social/router/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,SAAS,EAAmB,MAAM,sBAAsB,CAAC;AAC7E,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAEL,sBAAsB,EACtB,gCAAgC,EAChC,6BAA6B,EAC7B,mBAAmB,EACnB,0BAA0B,EAC1B,yBAAyB,EACzB,kBAAkB,GACnB,MAAM,8BAA8B,CAAC;AAEtC,wEAAwE;AACxE,mFAAmF;AACnF,qFAAqF;AACrF,MAAM,UAAU,kBAAkB,CAAC,MAAqB;IACtD,MAAM,EAAE,GAAG,SAAS,CAAC,cAAc,CAAC,CAAC,QAAQ,EAAc,CAAC;IAE5D,OAAO,EAAE,CAAC,MAAM,CAAC;QACf,iBAAiB,EAAE,EAAE,CAAC,iBAAiB,CAAC,OAAO,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE;YACrE,MAAM,QAAQ,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC;YACpC,OAAO,SAAS,CACd;gBACE,WAAW,EAAE,CAAC,sBAAsB,CAAC;gBACrC,SAAS,EAAE,CAAC,gCAAgC,CAAC;gBAC7C,QAAQ,EAAE;oBACR,mBAAmB;oBACnB,0BAA0B;oBAC1B,yBAAyB;oBACzB,6BAA6B;oBAC7B,kBAAkB;iBACnB;aACF,EACD,GAAG,EAAE,CAAC,MAAM,CAAC,iBAAiB,CAAC,QAAQ,EAAE,KAAK,CAAC,YAAY,CAAC,CAC7D,CAAC;QACJ,CAAC,CAAC;QAEF,gBAAgB,EAAE,EAAE,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE;YACnE,MAAM,QAAQ,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC;YACpC,OAAO,MAAM,CAAC,gBAAgB,CAAC,QAAQ,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;QAC1D,CAAC,CAAC;KACH,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
export declare const friendship: import("drizzle-orm/pg-core").PgTableWithColumns<{
|
|
2
|
+
name: "friendship";
|
|
3
|
+
schema: undefined;
|
|
4
|
+
columns: {
|
|
5
|
+
id: import("drizzle-orm/pg-core").PgColumn<{
|
|
6
|
+
name: "id";
|
|
7
|
+
tableName: "friendship";
|
|
8
|
+
dataType: "string";
|
|
9
|
+
columnType: "PgUUID";
|
|
10
|
+
data: string;
|
|
11
|
+
driverParam: string;
|
|
12
|
+
notNull: true;
|
|
13
|
+
hasDefault: true;
|
|
14
|
+
isPrimaryKey: true;
|
|
15
|
+
isAutoincrement: false;
|
|
16
|
+
hasRuntimeDefault: false;
|
|
17
|
+
enumValues: undefined;
|
|
18
|
+
baseColumn: never;
|
|
19
|
+
identity: undefined;
|
|
20
|
+
generated: undefined;
|
|
21
|
+
}, {}, {}>;
|
|
22
|
+
requesterId: import("drizzle-orm/pg-core").PgColumn<{
|
|
23
|
+
name: "requesterId";
|
|
24
|
+
tableName: "friendship";
|
|
25
|
+
dataType: "string";
|
|
26
|
+
columnType: "PgUUID";
|
|
27
|
+
data: string;
|
|
28
|
+
driverParam: string;
|
|
29
|
+
notNull: true;
|
|
30
|
+
hasDefault: false;
|
|
31
|
+
isPrimaryKey: false;
|
|
32
|
+
isAutoincrement: false;
|
|
33
|
+
hasRuntimeDefault: false;
|
|
34
|
+
enumValues: undefined;
|
|
35
|
+
baseColumn: never;
|
|
36
|
+
identity: undefined;
|
|
37
|
+
generated: undefined;
|
|
38
|
+
}, {}, {}>;
|
|
39
|
+
addresseeId: import("drizzle-orm/pg-core").PgColumn<{
|
|
40
|
+
name: "addresseeId";
|
|
41
|
+
tableName: "friendship";
|
|
42
|
+
dataType: "string";
|
|
43
|
+
columnType: "PgUUID";
|
|
44
|
+
data: string;
|
|
45
|
+
driverParam: string;
|
|
46
|
+
notNull: true;
|
|
47
|
+
hasDefault: false;
|
|
48
|
+
isPrimaryKey: false;
|
|
49
|
+
isAutoincrement: false;
|
|
50
|
+
hasRuntimeDefault: false;
|
|
51
|
+
enumValues: undefined;
|
|
52
|
+
baseColumn: never;
|
|
53
|
+
identity: undefined;
|
|
54
|
+
generated: undefined;
|
|
55
|
+
}, {}, {}>;
|
|
56
|
+
createdAt: import("drizzle-orm/pg-core").PgColumn<{
|
|
57
|
+
name: "createdAt";
|
|
58
|
+
tableName: "friendship";
|
|
59
|
+
dataType: "date";
|
|
60
|
+
columnType: "PgTimestamp";
|
|
61
|
+
data: Date;
|
|
62
|
+
driverParam: string;
|
|
63
|
+
notNull: true;
|
|
64
|
+
hasDefault: true;
|
|
65
|
+
isPrimaryKey: false;
|
|
66
|
+
isAutoincrement: false;
|
|
67
|
+
hasRuntimeDefault: false;
|
|
68
|
+
enumValues: undefined;
|
|
69
|
+
baseColumn: never;
|
|
70
|
+
identity: undefined;
|
|
71
|
+
generated: undefined;
|
|
72
|
+
}, {}, {}>;
|
|
73
|
+
updatedAt: import("drizzle-orm/pg-core").PgColumn<{
|
|
74
|
+
name: "updatedAt";
|
|
75
|
+
tableName: "friendship";
|
|
76
|
+
dataType: "date";
|
|
77
|
+
columnType: "PgTimestamp";
|
|
78
|
+
data: Date;
|
|
79
|
+
driverParam: string;
|
|
80
|
+
notNull: true;
|
|
81
|
+
hasDefault: true;
|
|
82
|
+
isPrimaryKey: false;
|
|
83
|
+
isAutoincrement: false;
|
|
84
|
+
hasRuntimeDefault: false;
|
|
85
|
+
enumValues: undefined;
|
|
86
|
+
baseColumn: never;
|
|
87
|
+
identity: undefined;
|
|
88
|
+
generated: undefined;
|
|
89
|
+
}, {}, {}>;
|
|
90
|
+
acceptedAt: import("drizzle-orm/pg-core").PgColumn<{
|
|
91
|
+
name: "acceptedAt";
|
|
92
|
+
tableName: "friendship";
|
|
93
|
+
dataType: "date";
|
|
94
|
+
columnType: "PgTimestamp";
|
|
95
|
+
data: Date;
|
|
96
|
+
driverParam: string;
|
|
97
|
+
notNull: false;
|
|
98
|
+
hasDefault: false;
|
|
99
|
+
isPrimaryKey: false;
|
|
100
|
+
isAutoincrement: false;
|
|
101
|
+
hasRuntimeDefault: false;
|
|
102
|
+
enumValues: undefined;
|
|
103
|
+
baseColumn: never;
|
|
104
|
+
identity: undefined;
|
|
105
|
+
generated: undefined;
|
|
106
|
+
}, {}, {}>;
|
|
107
|
+
refusedAt: import("drizzle-orm/pg-core").PgColumn<{
|
|
108
|
+
name: "refusedAt";
|
|
109
|
+
tableName: "friendship";
|
|
110
|
+
dataType: "date";
|
|
111
|
+
columnType: "PgTimestamp";
|
|
112
|
+
data: Date;
|
|
113
|
+
driverParam: string;
|
|
114
|
+
notNull: false;
|
|
115
|
+
hasDefault: false;
|
|
116
|
+
isPrimaryKey: false;
|
|
117
|
+
isAutoincrement: false;
|
|
118
|
+
hasRuntimeDefault: false;
|
|
119
|
+
enumValues: undefined;
|
|
120
|
+
baseColumn: never;
|
|
121
|
+
identity: undefined;
|
|
122
|
+
generated: undefined;
|
|
123
|
+
}, {}, {}>;
|
|
124
|
+
};
|
|
125
|
+
dialect: "pg";
|
|
126
|
+
}>;
|
|
127
|
+
export type Friendship = typeof friendship.$inferSelect;
|
|
128
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/engagement/social/schema/index.ts"],"names":[],"mappings":"AAgBA,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA6BtB,CAAC;AACF,MAAM,MAAM,UAAU,GAAG,OAAO,UAAU,CAAC,YAAY,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { sql } from 'drizzle-orm';
|
|
2
|
+
import { pgTable, uuid, timestamp, uniqueIndex, index, check } from 'drizzle-orm/pg-core';
|
|
3
|
+
// Drizzle tables owned by the Social module.
|
|
4
|
+
// Rules:
|
|
5
|
+
// - Column names come from the key via the snake_case casing config - never pass
|
|
6
|
+
// an explicit string (lint: drizzle-snake-case).
|
|
7
|
+
// - Timestamps are always `withTimezone` (lint: no-naive-timestamp).
|
|
8
|
+
// - Do NOT add FK references to tables owned by another domain - reference by id.
|
|
9
|
+
// One row per friendship, directional at creation (requester -> addressee) but the
|
|
10
|
+
// PAIR is what must stay unique regardless of direction - the canonical-pair unique
|
|
11
|
+
// index below is what makes duplicate-request and the mutual/simultaneous-request
|
|
12
|
+
// race condition (see social.service.ts sendFriendRequest) safe at the DB level: an
|
|
13
|
+
// INSERT is attempted directly (no pre-SELECT), and a unique-violation on the pair
|
|
14
|
+
// key is caught and resolved. A request is pending while both decision timestamps
|
|
15
|
+
// are null, accepted when acceptedAt is set, and refused when refusedAt is set.
|
|
16
|
+
export const friendship = pgTable('friendship', {
|
|
17
|
+
id: uuid().primaryKey().defaultRandom(),
|
|
18
|
+
requesterId: uuid().notNull(), // bare id - cross-module (user from pam), no FK
|
|
19
|
+
addresseeId: uuid().notNull(), // bare id - cross-module (user from pam), no FK
|
|
20
|
+
createdAt: timestamp({ withTimezone: true }).notNull().defaultNow(),
|
|
21
|
+
updatedAt: timestamp({ withTimezone: true })
|
|
22
|
+
.notNull()
|
|
23
|
+
.defaultNow()
|
|
24
|
+
.$onUpdateFn(() => new Date()),
|
|
25
|
+
acceptedAt: timestamp({ withTimezone: true }),
|
|
26
|
+
refusedAt: timestamp({ withTimezone: true }),
|
|
27
|
+
}, (t) => [
|
|
28
|
+
// Canonical-pair unique index: the same two users can never have two rows
|
|
29
|
+
// regardless of who sent the request. LEAST/GREATEST normalize the pair so
|
|
30
|
+
// (A, B) and (B, A) collide on the same index entry.
|
|
31
|
+
uniqueIndex('friendship_pair_key').on(sql `LEAST(${t.requesterId}, ${t.addresseeId})`, sql `GREATEST(${t.requesterId}, ${t.addresseeId})`),
|
|
32
|
+
index('friendship_addressee_idx').on(t.addresseeId),
|
|
33
|
+
index('friendship_requester_idx').on(t.requesterId),
|
|
34
|
+
check('friendship_one_decision_key', sql `NOT (${t.acceptedAt} IS NOT NULL AND ${t.refusedAt} IS NOT NULL)`),
|
|
35
|
+
]);
|
|
36
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/engagement/social/schema/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,WAAW,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAE1F,6CAA6C;AAC7C,SAAS;AACT,mFAAmF;AACnF,qDAAqD;AACrD,uEAAuE;AACvE,oFAAoF;AACpF,mFAAmF;AACnF,oFAAoF;AACpF,kFAAkF;AAClF,oFAAoF;AACpF,mFAAmF;AACnF,kFAAkF;AAClF,gFAAgF;AAChF,MAAM,CAAC,MAAM,UAAU,GAAG,OAAO,CAC/B,YAAY,EACZ;IACE,EAAE,EAAE,IAAI,EAAE,CAAC,UAAU,EAAE,CAAC,aAAa,EAAE;IACvC,WAAW,EAAE,IAAI,EAAE,CAAC,OAAO,EAAE,EAAE,gDAAgD;IAC/E,WAAW,EAAE,IAAI,EAAE,CAAC,OAAO,EAAE,EAAE,gDAAgD;IAC/E,SAAS,EAAE,SAAS,CAAC,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,UAAU,EAAE;IACnE,SAAS,EAAE,SAAS,CAAC,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC;SACzC,OAAO,EAAE;SACT,UAAU,EAAE;SACZ,WAAW,CAAC,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE,CAAC;IAChC,UAAU,EAAE,SAAS,CAAC,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC;IAC7C,SAAS,EAAE,SAAS,CAAC,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC;CAC7C,EACD,CAAC,CAAC,EAAE,EAAE,CAAC;IACL,0EAA0E;IAC1E,2EAA2E;IAC3E,qDAAqD;IACrD,WAAW,CAAC,qBAAqB,CAAC,CAAC,EAAE,CACnC,GAAG,CAAA,SAAS,CAAC,CAAC,WAAW,KAAK,CAAC,CAAC,WAAW,GAAG,EAC9C,GAAG,CAAA,YAAY,CAAC,CAAC,WAAW,KAAK,CAAC,CAAC,WAAW,GAAG,CAClD;IACD,KAAK,CAAC,0BAA0B,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,WAAW,CAAC;IACnD,KAAK,CAAC,0BAA0B,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,WAAW,CAAC;IACnD,KAAK,CACH,6BAA6B,EAC7B,GAAG,CAAA,QAAQ,CAAC,CAAC,UAAU,oBAAoB,CAAC,CAAC,SAAS,eAAe,CACtE;CACF,CACF,CAAC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { type DrizzleService, type EventBus } from '@openora/core/server';
|
|
2
|
+
import { type Uuid } from '@openora/core/contracts';
|
|
3
|
+
import { type Friendship, type Relationship } from '../contract/index.js';
|
|
4
|
+
export declare const SelfFriendRequestError: new () => Error;
|
|
5
|
+
export declare const FriendRequestTargetNotFoundError: new (id: string) => Error;
|
|
6
|
+
export declare const FriendRequestUnavailableError: new () => Error;
|
|
7
|
+
export declare const BlockedBySelfError: new () => Error;
|
|
8
|
+
export declare const AlreadyFriendsError: new () => Error;
|
|
9
|
+
export declare const RequestAlreadyPendingError: new () => Error;
|
|
10
|
+
export declare const FriendRequestRefusedError: new () => Error;
|
|
11
|
+
export declare class SocialService {
|
|
12
|
+
private readonly drizzle;
|
|
13
|
+
private readonly events;
|
|
14
|
+
constructor(drizzle: DrizzleService, events: EventBus);
|
|
15
|
+
/**
|
|
16
|
+
* Sends a friend request from `callerId` to `targetUserId`. The insert is
|
|
17
|
+
* attempted directly against the canonical-pair unique index (no pre-SELECT,
|
|
18
|
+
* avoiding a TOCTOU race); a unique-violation means a row for this pair already
|
|
19
|
+
* exists, which is resolved by re-reading it - including the mutual/simultaneous
|
|
20
|
+
* case where the existing row was already addressed TO the caller, which
|
|
21
|
+
* auto-accepts instead of conflicting. Events are emitted strictly after the
|
|
22
|
+
* mutating statement/transaction has committed.
|
|
23
|
+
*/
|
|
24
|
+
sendFriendRequest(callerId: Uuid, targetUserId: Uuid): Promise<Friendship>;
|
|
25
|
+
/**
|
|
26
|
+
* Re-reads the row a unique-violation on `friendship_pair_key` proved already
|
|
27
|
+
* exists and resolves it: already accepted -> conflict, same-direction duplicate
|
|
28
|
+
* -> conflict, opposite-direction (mutual/simultaneous request) -> auto-accept.
|
|
29
|
+
* `FOR UPDATE` serializes this against a concurrent resolution of the same pair.
|
|
30
|
+
*/
|
|
31
|
+
private resolveExistingPair;
|
|
32
|
+
/**
|
|
33
|
+
* Batch relationship lookup for a set of target users, from the caller's point
|
|
34
|
+
* of view. Reads are batched (one query per table, `IN (...)` over the target
|
|
35
|
+
* ids) rather than N+1 per target.
|
|
36
|
+
*/
|
|
37
|
+
getRelationships(callerId: Uuid, targetUserIds: readonly Uuid[]): Promise<Relationship[]>;
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=social.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"social.service.d.ts","sourceRoot":"","sources":["../../../../src/engagement/social/service/social.service.ts"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,QAAQ,EAKd,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,KAAK,IAAI,EAAE,MAAM,yBAAyB,CAAC;AAIpD,OAAO,EAAE,KAAK,UAAU,EAAE,KAAK,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAK1E,eAAO,MAAM,sBAAsB,iBAGlC,CAAC;AAKF,eAAO,MAAM,gCAAgC,2BAA2C,CAAC;AAOzF,eAAO,MAAM,6BAA6B,iBAGzC,CAAC;AAGF,eAAO,MAAM,kBAAkB,iBAG9B,CAAC;AACF,eAAO,MAAM,mBAAmB,iBAG/B,CAAC;AACF,eAAO,MAAM,0BAA0B,iBAGtC,CAAC;AACF,eAAO,MAAM,yBAAyB,iBAGrC,CAAC;AAwCF,qBAAa,aAAa;IAEtB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,MAAM;gBADN,OAAO,EAAE,cAAc,EACvB,MAAM,EAAE,QAAQ;IAGnC;;;;;;;;OAQG;IACG,iBAAiB,CAAC,QAAQ,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,GAAG,OAAO,CAAC,UAAU,CAAC;IAwEhF;;;;;OAKG;YACW,mBAAmB;IAuCjC;;;;OAIG;IACG,gBAAgB,CAAC,QAAQ,EAAE,IAAI,EAAE,aAAa,EAAE,SAAS,IAAI,EAAE,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;CAuGhG"}
|