@mohasinac/appkit 4.41.0 → 4.41.2
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/features/seller/schemas/coupon-form.d.ts +37 -37
- package/dist/features/seller/schemas/coupon-form.js +36 -8
- package/dist/features/shell/SectionForm.js +44 -5
- package/dist/schemas/registry.d.ts +10 -10
- package/package.json +2 -2
- package/dist/_internal/server/features/classified/actions.d.ts +0 -12
- package/dist/_internal/server/features/classified/actions.js +0 -33
- package/dist/_internal/server/features/digital-code/actions.d.ts +0 -12
- package/dist/_internal/server/features/digital-code/actions.js +0 -33
- package/dist/_internal/server/features/homepage/data.d.ts +0 -18
- package/dist/_internal/server/features/homepage/data.js +0 -70
- package/dist/_internal/server/features/live/actions.d.ts +0 -6
- package/dist/_internal/server/features/live/actions.js +0 -27
- package/dist/_internal/server/features/messages/actions.d.ts +0 -6
- package/dist/_internal/server/features/messages/actions.js +0 -6
- package/dist/_internal/server/features/messages/data.d.ts +0 -10
- package/dist/_internal/server/features/messages/data.js +0 -24
- package/dist/_internal/server/features/messages/index.d.ts +0 -2
- package/dist/_internal/server/features/messages/index.js +0 -2
- package/dist/_internal/server/jobs/core/testerSandboxRefresh.d.ts +0 -12
- package/dist/_internal/server/jobs/core/testerSandboxRefresh.js +0 -112
- package/dist/_internal/server/jobs/handlers/testerSandboxRefresh.d.ts +0 -2
- package/dist/_internal/server/jobs/handlers/testerSandboxRefresh.js +0 -2
- package/dist/_internal/shared/features/homepage/section-gate.d.ts +0 -14
- package/dist/_internal/shared/features/homepage/section-gate.js +0 -51
- package/dist/_internal/shared/features/lottery/config-form.d.ts +0 -58
- package/dist/_internal/shared/features/lottery/config-form.js +0 -71
- package/dist/client/api/useFeatureFlags.d.ts +0 -22
- package/dist/client/api/useFeatureFlags.js +0 -27
- package/dist/features/account/address-validation.d.ts +0 -20
- package/dist/features/account/address-validation.js +0 -35
- package/dist/features/account/components/ChatList.d.ts +0 -14
- package/dist/features/account/components/ChatList.js +0 -9
- package/dist/features/account/components/ChatWindow.d.ts +0 -17
- package/dist/features/account/components/ChatWindow.js +0 -6
- package/dist/features/account/components/MessagesView.d.ts +0 -16
- package/dist/features/account/components/MessagesView.js +0 -5
- package/dist/features/account/components/UserOffersView.d.ts +0 -10
- package/dist/features/account/components/UserOffersView.js +0 -5
- package/dist/features/account/constants/addresses.d.ts +0 -13
- package/dist/features/account/constants/addresses.js +0 -43
- package/dist/features/account/hooks/useAddressForm.d.ts +0 -24
- package/dist/features/account/hooks/useAddressForm.js +0 -58
- package/dist/features/admin/actions/chat-actions.d.ts +0 -29
- package/dist/features/admin/actions/chat-actions.js +0 -101
- package/dist/features/admin/components/AdminFeatureFlagsView.d.ts +0 -9
- package/dist/features/admin/components/AdminFeatureFlagsView.js +0 -109
- package/dist/features/admin/components/AdminSiteView.d.ts +0 -10
- package/dist/features/admin/components/AdminSiteView.js +0 -52
- package/dist/features/admin/hooks/useChat.d.ts +0 -24
- package/dist/features/admin/hooks/useChat.js +0 -100
- package/dist/features/admin/permission-map.d.ts +0 -3
- package/dist/features/admin/permission-map.js +0 -26
- package/dist/features/admin/repository/chat.repository.d.ts +0 -50
- package/dist/features/admin/repository/chat.repository.js +0 -223
- package/dist/features/auth/actions/password-change-otp-actions.d.ts +0 -18
- package/dist/features/auth/actions/password-change-otp-actions.js +0 -88
- package/dist/features/auth/actions/realtime-token-actions.d.ts +0 -11
- package/dist/features/auth/actions/realtime-token-actions.js +0 -38
- package/dist/features/auth/password-change-otp.d.ts +0 -34
- package/dist/features/auth/password-change-otp.js +0 -56
- package/dist/features/classified/components/ClassifiedContactSellerPanel.d.ts +0 -12
- package/dist/features/classified/components/ClassifiedContactSellerPanel.js +0 -49
- package/dist/features/events/api/route.d.ts +0 -12
- package/dist/features/events/api/route.js +0 -90
- package/dist/features/faq/api/route.d.ts +0 -11
- package/dist/features/faq/api/route.js +0 -52
- package/dist/features/faq/hooks/useFAQs.d.ts +0 -22
- package/dist/features/faq/hooks/useFAQs.js +0 -41
- package/dist/features/media/upload/video-poster.test.d.ts +0 -1
- package/dist/features/media/upload/video-poster.test.js +0 -42
- package/dist/features/messages/actions/index.d.ts +0 -2
- package/dist/features/messages/actions/index.js +0 -2
- package/dist/features/messages/actions/messages-actions.d.ts +0 -15
- package/dist/features/messages/actions/messages-actions.js +0 -33
- package/dist/features/messages/actions/ping-rtdb.d.ts +0 -2
- package/dist/features/messages/actions/ping-rtdb.js +0 -26
- package/dist/features/messages/hooks/useConversation.d.ts +0 -22
- package/dist/features/messages/hooks/useConversation.js +0 -155
- package/dist/features/messages/hooks/useConversations.d.ts +0 -15
- package/dist/features/messages/hooks/useConversations.js +0 -104
- package/dist/features/messages/index.d.ts +0 -6
- package/dist/features/messages/index.js +0 -4
- package/dist/features/messages/realtime.d.ts +0 -32
- package/dist/features/messages/realtime.js +0 -39
- package/dist/features/messages/repository/conversations.repository.d.ts +0 -71
- package/dist/features/messages/repository/conversations.repository.js +0 -284
- package/dist/features/messages/schemas/firestore.d.ts +0 -34
- package/dist/features/messages/schemas/firestore.js +0 -16
- package/dist/features/messages/schemas/index.d.ts +0 -177
- package/dist/features/messages/schemas/index.js +0 -35
- package/dist/features/messages/server.d.ts +0 -6
- package/dist/features/messages/server.js +0 -6
- package/dist/features/products/components/MakeOfferForm.d.ts +0 -20
- package/dist/features/products/components/MakeOfferForm.js +0 -9
- package/dist/features/reviews/api/route.d.ts +0 -21
- package/dist/features/reviews/api/route.js +0 -174
- package/dist/features/seller/api/offers/route.d.ts +0 -17
- package/dist/features/seller/api/offers/route.js +0 -63
- package/dist/features/seller/components/SellerAuctionsView.d.ts +0 -9
- package/dist/features/seller/components/SellerAuctionsView.js +0 -113
- package/dist/features/seller/components/SellerOffersPanel.d.ts +0 -21
- package/dist/features/seller/components/SellerOffersPanel.js +0 -160
- package/dist/features/seller/components/SellerPreOrdersView.d.ts +0 -6
- package/dist/features/seller/components/SellerPreOrdersView.js +0 -148
- package/dist/features/seller/components/SellerPrizeDrawsView.d.ts +0 -6
- package/dist/features/seller/components/SellerPrizeDrawsView.js +0 -152
- package/dist/features/seller/permission-map.d.ts +0 -5
- package/dist/features/seller/permission-map.js +0 -17
- package/dist/features/shell/StepForm.d.ts +0 -80
- package/dist/features/shell/StepForm.js +0 -136
- package/dist/features/stores/api/route.d.ts +0 -12
- package/dist/features/stores/api/route.js +0 -110
- package/dist/features/tester/seed-data/authored/account-auth__testing-program--admin.d.ts +0 -2
- package/dist/features/tester/seed-data/authored/account-auth__testing-program--admin.js +0 -33
- package/dist/features/tester/seed-data/authored/account-auth__testing-program.d.ts +0 -2
- package/dist/features/tester/seed-data/authored/account-auth__testing-program.js +0 -138
- package/dist/features/tester/seed-data/bids-tester-seed-data.d.ts +0 -2
- package/dist/features/tester/seed-data/bids-tester-seed-data.js +0 -37
- package/dist/features/tester/seed-data/blog-tester-seed-data.d.ts +0 -2
- package/dist/features/tester/seed-data/blog-tester-seed-data.js +0 -40
- package/dist/features/tester/seed-data/carts-tester-seed-data.d.ts +0 -3
- package/dist/features/tester/seed-data/carts-tester-seed-data.js +0 -70
- package/dist/features/tester/seed-data/categories-tester-seed-data.d.ts +0 -2
- package/dist/features/tester/seed-data/categories-tester-seed-data.js +0 -218
- package/dist/features/tester/seed-data/coupons-tester-seed-data.d.ts +0 -2
- package/dist/features/tester/seed-data/coupons-tester-seed-data.js +0 -76
- package/dist/features/tester/seed-data/events-tester-seed-data.d.ts +0 -2
- package/dist/features/tester/seed-data/events-tester-seed-data.js +0 -193
- package/dist/features/tester/seed-data/offers-tester-seed-data.d.ts +0 -2
- package/dist/features/tester/seed-data/offers-tester-seed-data.js +0 -191
- package/dist/features/tester/seed-data/orders-tester-seed-data.d.ts +0 -2
- package/dist/features/tester/seed-data/orders-tester-seed-data.js +0 -329
- package/dist/features/tester/seed-data/products-tester-seed-data.d.ts +0 -2
- package/dist/features/tester/seed-data/products-tester-seed-data.js +0 -815
- package/dist/features/tester/seed-data/stores-tester-seed-data.d.ts +0 -2
- package/dist/features/tester/seed-data/stores-tester-seed-data.js +0 -79
- package/dist/features/wishlist/components/WishlistToggleButton.d.ts +0 -12
- package/dist/features/wishlist/components/WishlistToggleButton.js +0 -36
- package/dist/monitoring/analytics.d.ts +0 -80
- package/dist/monitoring/analytics.js +0 -176
- package/dist/next/middleware/pii-redactor.d.ts +0 -32
- package/dist/next/middleware/pii-redactor.js +0 -84
- package/dist/providers/db-firebase/realtime.d.ts +0 -42
- package/dist/providers/db-firebase/realtime.js +0 -138
- package/dist/security/rbac/Can.d.ts +0 -31
- package/dist/security/rbac/Can.js +0 -22
- package/dist/security/rbac/default-roles.d.ts +0 -2
- package/dist/security/rbac/default-roles.js +0 -83
- package/dist/security/rbac/hook.d.ts +0 -20
- package/dist/security/rbac/hook.js +0 -30
- package/dist/security/rbac/index.d.ts +0 -8
- package/dist/security/rbac/index.js +0 -6
- package/dist/security/rbac/middleware.d.ts +0 -51
- package/dist/security/rbac/middleware.js +0 -51
- package/dist/security/rbac/resolver.d.ts +0 -5
- package/dist/security/rbac/resolver.js +0 -38
- package/dist/security/rbac/server.d.ts +0 -24
- package/dist/security/rbac/server.js +0 -42
- package/dist/security/rbac/types.d.ts +0 -22
- package/dist/security/rbac/types.js +0 -2
- package/dist/seed/conversations-seed-data.d.ts +0 -2
- package/dist/seed/conversations-seed-data.js +0 -292
- package/dist/utils/filter.helper.d.ts +0 -18
- package/dist/utils/filter.helper.js +0 -21
- package/dist/utils/pagination.helper.d.ts +0 -23
- package/dist/utils/pagination.helper.js +0 -24
- package/dist/utils/search-tokens.d.ts +0 -2
- package/dist/utils/search-tokens.js +0 -21
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
export * from "./hooks/useConversations";
|
|
2
|
-
export * from "./hooks/useConversation";
|
|
3
|
-
export { CONVERSATIONS_COLLECTION, CONVERSATIONS_INDEXED_FIELDS, } from "./schemas/firestore";
|
|
4
|
-
export { conversationPingPath, userConversationsPingPath, buildConversationPingPaths, } from "./realtime";
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Conversations realtime channel — shared between client hooks and server
|
|
3
|
-
* routes. Lives in a non-`"use client"` module so server route handlers can
|
|
4
|
-
* import the path builders without dragging the React hooks into a server
|
|
5
|
-
* bundle.
|
|
6
|
-
*
|
|
7
|
-
* Strategy (D5 + VC7):
|
|
8
|
-
* - Firestore = canonical store; messages embedded in `conversations/{id}`.
|
|
9
|
-
* - RTDB = "ping" channel only — server writes Date.now() to two paths on
|
|
10
|
-
* every mutation, clients subscribe and refetch.
|
|
11
|
-
*
|
|
12
|
-
* chats/{id}/lastUpdate — single-conversation ping
|
|
13
|
-
* chats/user/{userId}/lastUpdate — per-user fan-out ping
|
|
14
|
-
*
|
|
15
|
-
* Both ends import these constants instead of inlining the path strings.
|
|
16
|
-
*/
|
|
17
|
-
/** RTDB path bumped on every write to a single conversation. */
|
|
18
|
-
export declare function conversationPingPath(conversationId: string): string;
|
|
19
|
-
/**
|
|
20
|
-
* RTDB path bumped on every write across all of a user's conversations.
|
|
21
|
-
* Subscribing to this is how the chat list / nav bell refresh without
|
|
22
|
-
* subscribing to every individual conversation.
|
|
23
|
-
*/
|
|
24
|
-
export declare function userConversationsPingPath(userId: string): string;
|
|
25
|
-
/** All ping paths to bump on a single conversation write. */
|
|
26
|
-
export interface ConversationPingTargets {
|
|
27
|
-
conversationId: string;
|
|
28
|
-
buyerId: string;
|
|
29
|
-
/** Resolved store-owner UID for the seller side; null when not known. */
|
|
30
|
-
sellerOwnerId: string | null;
|
|
31
|
-
}
|
|
32
|
-
export declare function buildConversationPingPaths(targets: ConversationPingTargets): string[];
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Conversations realtime channel — shared between client hooks and server
|
|
3
|
-
* routes. Lives in a non-`"use client"` module so server route handlers can
|
|
4
|
-
* import the path builders without dragging the React hooks into a server
|
|
5
|
-
* bundle.
|
|
6
|
-
*
|
|
7
|
-
* Strategy (D5 + VC7):
|
|
8
|
-
* - Firestore = canonical store; messages embedded in `conversations/{id}`.
|
|
9
|
-
* - RTDB = "ping" channel only — server writes Date.now() to two paths on
|
|
10
|
-
* every mutation, clients subscribe and refetch.
|
|
11
|
-
*
|
|
12
|
-
* chats/{id}/lastUpdate — single-conversation ping
|
|
13
|
-
* chats/user/{userId}/lastUpdate — per-user fan-out ping
|
|
14
|
-
*
|
|
15
|
-
* Both ends import these constants instead of inlining the path strings.
|
|
16
|
-
*/
|
|
17
|
-
const PING_KEY = "lastUpdate";
|
|
18
|
-
/** RTDB path bumped on every write to a single conversation. */
|
|
19
|
-
export function conversationPingPath(conversationId) {
|
|
20
|
-
return `chats/${conversationId}/${PING_KEY}`;
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* RTDB path bumped on every write across all of a user's conversations.
|
|
24
|
-
* Subscribing to this is how the chat list / nav bell refresh without
|
|
25
|
-
* subscribing to every individual conversation.
|
|
26
|
-
*/
|
|
27
|
-
export function userConversationsPingPath(userId) {
|
|
28
|
-
return `chats/user/${userId}/${PING_KEY}`;
|
|
29
|
-
}
|
|
30
|
-
export function buildConversationPingPaths(targets) {
|
|
31
|
-
const paths = [
|
|
32
|
-
conversationPingPath(targets.conversationId),
|
|
33
|
-
userConversationsPingPath(targets.buyerId),
|
|
34
|
-
];
|
|
35
|
-
if (targets.sellerOwnerId) {
|
|
36
|
-
paths.push(userConversationsPingPath(targets.sellerOwnerId));
|
|
37
|
-
}
|
|
38
|
-
return paths;
|
|
39
|
-
}
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ConversationsRepository — buyer↔seller messaging.
|
|
3
|
-
*
|
|
4
|
-
* One document per buyer×storeId×productId tuple. Messages are embedded in
|
|
5
|
-
* the document as an array (good up to ~200 entries; migrate to subcollection
|
|
6
|
-
* later if threads explode).
|
|
7
|
-
*
|
|
8
|
-
* All mutations are transactional so concurrent writes don't drop messages
|
|
9
|
-
* or unread counters.
|
|
10
|
-
*/
|
|
11
|
-
import { type ConversationDocument, type ConversationMessage } from "../schemas/firestore";
|
|
12
|
-
export declare class ConversationFullError extends Error {
|
|
13
|
-
readonly code: "CONVERSATION_FULL";
|
|
14
|
-
}
|
|
15
|
-
export declare class ConversationsRepository {
|
|
16
|
-
private collection;
|
|
17
|
-
/**
|
|
18
|
-
* Find an existing buyer×store×product conversation or create a new one.
|
|
19
|
-
* Idempotent — safe to call on every "Contact Seller" click.
|
|
20
|
-
*/
|
|
21
|
-
findOrCreateByContext(params: {
|
|
22
|
-
buyerId: string;
|
|
23
|
-
buyerDisplayName: string;
|
|
24
|
-
storeId: string;
|
|
25
|
-
storeName: string;
|
|
26
|
-
sellerDisplayName: string;
|
|
27
|
-
productId?: string;
|
|
28
|
-
productTitle?: string;
|
|
29
|
-
}): Promise<ConversationDocument>;
|
|
30
|
-
findById(id: string): Promise<ConversationDocument | null>;
|
|
31
|
-
listByBuyer(buyerId: string): Promise<ConversationDocument[]>;
|
|
32
|
-
listByStore(storeId: string): Promise<ConversationDocument[]>;
|
|
33
|
-
/**
|
|
34
|
-
* Conversation ids this user may subscribe to, for the RTDB `conversationIds`
|
|
35
|
-
* custom-token claim.
|
|
36
|
-
*
|
|
37
|
-
* 🛑 This claim previously had NO ISSUER ANYWHERE. The `chats/$conversationId`
|
|
38
|
-
* rule requires `auth.token.conversationIds[$conversationId] == true`, but
|
|
39
|
-
* `/api/realtime/token` only ever issued `chatIds`, built by
|
|
40
|
-
* `chatRepository.getChatIdsForUser()` over the **`chatRooms`** collection —
|
|
41
|
-
* a different collection with different document ids. So the buyer↔seller
|
|
42
|
-
* live-message channel was denied for every user, and both subscribing hooks
|
|
43
|
-
* swallowed the permission error (one into `setIsConnected(false)`, one into
|
|
44
|
-
* a literal `// ignore`). Messages only ever appeared for the sender, via an
|
|
45
|
-
* unrelated `refetch()` after send.
|
|
46
|
-
*
|
|
47
|
-
* `.select()` with no fields fetches ids only — the document bodies are not
|
|
48
|
-
* needed and this runs on every token issue.
|
|
49
|
-
*
|
|
50
|
-
* @param limit hard cap. Firebase custom claims are limited to 1000 BYTES
|
|
51
|
-
* total; each entry costs roughly `"<20-char id>":true,` ≈ 28 bytes, so a
|
|
52
|
-
* user in hundreds of conversations would make `createCustomToken` throw
|
|
53
|
-
* and take the whole route down (see the route's own comment).
|
|
54
|
-
*/
|
|
55
|
-
getConversationIdsForUser(userId: string, storeIds?: readonly string[], limit?: number): Promise<string[]>;
|
|
56
|
-
/**
|
|
57
|
-
* Append a message to a conversation in a transaction. Bumps the counterparty
|
|
58
|
-
* unread counter; updates lastMessage / lastMessageAt / updatedAt.
|
|
59
|
-
*/
|
|
60
|
-
appendMessage(conversationId: string, msg: Omit<ConversationMessage, "id" | "sentAt" | "isRead"> & {
|
|
61
|
-
id?: string;
|
|
62
|
-
sentAt?: Date;
|
|
63
|
-
}): Promise<ConversationDocument>;
|
|
64
|
-
/**
|
|
65
|
-
* Zero out the unread counter for one party. Also flips `isRead` on the
|
|
66
|
-
* embedded messages sent by the other party so the seller side can see what
|
|
67
|
-
* the buyer has already read.
|
|
68
|
-
*/
|
|
69
|
-
markRead(conversationId: string, role: "buyer" | "seller"): Promise<void>;
|
|
70
|
-
}
|
|
71
|
-
export declare const conversationsRepository: ConversationsRepository;
|
|
@@ -1,284 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ConversationsRepository — buyer↔seller messaging.
|
|
3
|
-
*
|
|
4
|
-
* One document per buyer×storeId×productId tuple. Messages are embedded in
|
|
5
|
-
* the document as an array (good up to ~200 entries; migrate to subcollection
|
|
6
|
-
* later if threads explode).
|
|
7
|
-
*
|
|
8
|
-
* All mutations are transactional so concurrent writes don't drop messages
|
|
9
|
-
* or unread counters.
|
|
10
|
-
*/
|
|
11
|
-
import { getAdminDb } from "../../../providers/db-firebase";
|
|
12
|
-
import { encryptPiiFields, decryptPiiFields } from "../../../security/pii-encrypt";
|
|
13
|
-
import { CONVERSATION_PII_FIELDS } from "../../../security/pii-schemas";
|
|
14
|
-
import { serverLogger } from "../../../monitoring";
|
|
15
|
-
import { CONVERSATION_FIELDS } from "../../../constants/field-names";
|
|
16
|
-
import { CONVERSATIONS_COLLECTION, } from "../schemas/firestore";
|
|
17
|
-
function toDate(raw) {
|
|
18
|
-
if (raw instanceof Date)
|
|
19
|
-
return raw;
|
|
20
|
-
if (raw && typeof raw.toDate === "function") {
|
|
21
|
-
return raw.toDate();
|
|
22
|
-
}
|
|
23
|
-
if (typeof raw === "string" || typeof raw === "number")
|
|
24
|
-
return new Date(raw);
|
|
25
|
-
return new Date();
|
|
26
|
-
}
|
|
27
|
-
function normaliseMessage(raw) {
|
|
28
|
-
const r = (raw ?? {});
|
|
29
|
-
return {
|
|
30
|
-
id: String(r.id ?? ""),
|
|
31
|
-
senderId: String(r.senderId ?? ""),
|
|
32
|
-
senderRole: r.senderRole ?? "buyer",
|
|
33
|
-
body: String(r.body ?? ""),
|
|
34
|
-
isRead: r.isRead === true,
|
|
35
|
-
sentAt: toDate(r.sentAt),
|
|
36
|
-
attachments: Array.isArray(r.attachments)
|
|
37
|
-
? r.attachments
|
|
38
|
-
: undefined,
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
function normaliseDoc(id, raw) {
|
|
42
|
-
// Decrypt here rather than at the six call sites: this is the one function
|
|
43
|
-
// every read path already goes through, so a new reader cannot forget it.
|
|
44
|
-
// `markRead` deliberately does NOT decrypt — it reads raw messages, flips
|
|
45
|
-
// isRead and writes them straight back, so ciphertext round-trips untouched.
|
|
46
|
-
const r = decryptPiiFields(raw, [...CONVERSATION_PII_FIELDS]);
|
|
47
|
-
return {
|
|
48
|
-
id,
|
|
49
|
-
buyerId: String(r.buyerId ?? ""),
|
|
50
|
-
buyerDisplayName: String(r.buyerDisplayName ?? ""),
|
|
51
|
-
storeId: String(r.storeId ?? ""),
|
|
52
|
-
storeName: String(r.storeName ?? ""),
|
|
53
|
-
sellerDisplayName: String(r.sellerDisplayName ?? ""),
|
|
54
|
-
productId: typeof r.productId === "string" ? r.productId : undefined,
|
|
55
|
-
productTitle: typeof r.productTitle === "string" ? r.productTitle : undefined,
|
|
56
|
-
messages: Array.isArray(r.messages) ? r.messages.map(normaliseMessage) : [],
|
|
57
|
-
lastMessage: String(r.lastMessage ?? ""),
|
|
58
|
-
lastMessageAt: toDate(r.lastMessageAt),
|
|
59
|
-
unreadBuyer: typeof r.unreadBuyer === "number" ? r.unreadBuyer : 0,
|
|
60
|
-
unreadSeller: typeof r.unreadSeller === "number" ? r.unreadSeller : 0,
|
|
61
|
-
status: r.status ?? "active",
|
|
62
|
-
createdAt: toDate(r.createdAt),
|
|
63
|
-
updatedAt: toDate(r.updatedAt),
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
export class ConversationFullError extends Error {
|
|
67
|
-
constructor() {
|
|
68
|
-
super(...arguments);
|
|
69
|
-
this.code = "CONVERSATION_FULL";
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
export class ConversationsRepository {
|
|
73
|
-
collection() {
|
|
74
|
-
return getAdminDb().collection(CONVERSATIONS_COLLECTION);
|
|
75
|
-
}
|
|
76
|
-
/**
|
|
77
|
-
* Find an existing buyer×store×product conversation or create a new one.
|
|
78
|
-
* Idempotent — safe to call on every "Contact Seller" click.
|
|
79
|
-
*/
|
|
80
|
-
async findOrCreateByContext(params) {
|
|
81
|
-
const db = getAdminDb();
|
|
82
|
-
const coll = this.collection();
|
|
83
|
-
const { buyerId, storeId, productId } = params;
|
|
84
|
-
// Stable composite key: buyerId + storeId + (productId | "general")
|
|
85
|
-
const productKey = productId ?? "general";
|
|
86
|
-
const stableId = `conv-${buyerId}-${storeId}-${productKey}`;
|
|
87
|
-
const ref = coll.doc(stableId);
|
|
88
|
-
return db.runTransaction(async (tx) => {
|
|
89
|
-
const snap = await tx.get(ref);
|
|
90
|
-
if (snap.exists) {
|
|
91
|
-
return normaliseDoc(snap.id, snap.data() ?? {});
|
|
92
|
-
}
|
|
93
|
-
const now = new Date();
|
|
94
|
-
const newDoc = {
|
|
95
|
-
buyerId: params.buyerId,
|
|
96
|
-
buyerDisplayName: params.buyerDisplayName,
|
|
97
|
-
storeId: params.storeId,
|
|
98
|
-
storeName: params.storeName,
|
|
99
|
-
sellerDisplayName: params.sellerDisplayName,
|
|
100
|
-
productId: params.productId,
|
|
101
|
-
productTitle: params.productTitle,
|
|
102
|
-
messages: [],
|
|
103
|
-
lastMessage: "",
|
|
104
|
-
lastMessageAt: now,
|
|
105
|
-
unreadBuyer: 0,
|
|
106
|
-
unreadSeller: 0,
|
|
107
|
-
status: "active",
|
|
108
|
-
createdAt: now,
|
|
109
|
-
updatedAt: now,
|
|
110
|
-
};
|
|
111
|
-
const encryptedDoc = encryptPiiFields(newDoc, [...CONVERSATION_PII_FIELDS]);
|
|
112
|
-
tx.set(ref, encryptedDoc);
|
|
113
|
-
return normaliseDoc(stableId, encryptedDoc);
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
|
-
async findById(id) {
|
|
117
|
-
const snap = await this.collection().doc(id).get();
|
|
118
|
-
if (!snap.exists)
|
|
119
|
-
return null;
|
|
120
|
-
return normaliseDoc(snap.id, snap.data() ?? {});
|
|
121
|
-
}
|
|
122
|
-
async listByBuyer(buyerId) {
|
|
123
|
-
const snap = await this.collection()
|
|
124
|
-
.where(CONVERSATION_FIELDS.BUYER_ID, "==", buyerId)
|
|
125
|
-
.orderBy(CONVERSATION_FIELDS.LAST_MESSAGE_AT, "desc")
|
|
126
|
-
.get();
|
|
127
|
-
return snap.docs.map((d) => normaliseDoc(d.id, d.data()));
|
|
128
|
-
}
|
|
129
|
-
async listByStore(storeId) {
|
|
130
|
-
const snap = await this.collection()
|
|
131
|
-
.where(CONVERSATION_FIELDS.STORE_ID, "==", storeId)
|
|
132
|
-
.orderBy(CONVERSATION_FIELDS.LAST_MESSAGE_AT, "desc")
|
|
133
|
-
.get();
|
|
134
|
-
return snap.docs.map((d) => normaliseDoc(d.id, d.data()));
|
|
135
|
-
}
|
|
136
|
-
/**
|
|
137
|
-
* Conversation ids this user may subscribe to, for the RTDB `conversationIds`
|
|
138
|
-
* custom-token claim.
|
|
139
|
-
*
|
|
140
|
-
* 🛑 This claim previously had NO ISSUER ANYWHERE. The `chats/$conversationId`
|
|
141
|
-
* rule requires `auth.token.conversationIds[$conversationId] == true`, but
|
|
142
|
-
* `/api/realtime/token` only ever issued `chatIds`, built by
|
|
143
|
-
* `chatRepository.getChatIdsForUser()` over the **`chatRooms`** collection —
|
|
144
|
-
* a different collection with different document ids. So the buyer↔seller
|
|
145
|
-
* live-message channel was denied for every user, and both subscribing hooks
|
|
146
|
-
* swallowed the permission error (one into `setIsConnected(false)`, one into
|
|
147
|
-
* a literal `// ignore`). Messages only ever appeared for the sender, via an
|
|
148
|
-
* unrelated `refetch()` after send.
|
|
149
|
-
*
|
|
150
|
-
* `.select()` with no fields fetches ids only — the document bodies are not
|
|
151
|
-
* needed and this runs on every token issue.
|
|
152
|
-
*
|
|
153
|
-
* @param limit hard cap. Firebase custom claims are limited to 1000 BYTES
|
|
154
|
-
* total; each entry costs roughly `"<20-char id>":true,` ≈ 28 bytes, so a
|
|
155
|
-
* user in hundreds of conversations would make `createCustomToken` throw
|
|
156
|
-
* and take the whole route down (see the route's own comment).
|
|
157
|
-
*/
|
|
158
|
-
async getConversationIdsForUser(userId, storeIds = [], limit = 25) {
|
|
159
|
-
const queries = [
|
|
160
|
-
this.collection()
|
|
161
|
-
.where(CONVERSATION_FIELDS.BUYER_ID, "==", userId)
|
|
162
|
-
.orderBy(CONVERSATION_FIELDS.LAST_MESSAGE_AT, "desc")
|
|
163
|
-
.limit(limit)
|
|
164
|
-
.select()
|
|
165
|
-
.get(),
|
|
166
|
-
// Firestore `in` caps at 30 values; a seller with more stores than that
|
|
167
|
-
// is not a shape this app produces.
|
|
168
|
-
...(storeIds.length > 0
|
|
169
|
-
? [
|
|
170
|
-
this.collection()
|
|
171
|
-
.where(CONVERSATION_FIELDS.STORE_ID, "in", storeIds.slice(0, 30))
|
|
172
|
-
.orderBy(CONVERSATION_FIELDS.LAST_MESSAGE_AT, "desc")
|
|
173
|
-
.limit(limit)
|
|
174
|
-
.select()
|
|
175
|
-
.get(),
|
|
176
|
-
]
|
|
177
|
-
: []),
|
|
178
|
-
];
|
|
179
|
-
const snaps = await Promise.all(queries);
|
|
180
|
-
const ids = new Set();
|
|
181
|
-
for (const snap of snaps) {
|
|
182
|
-
for (const doc of snap.docs)
|
|
183
|
-
ids.add(doc.id);
|
|
184
|
-
}
|
|
185
|
-
// Most-recent-first is already the sort order; truncating here keeps the
|
|
186
|
-
// claim inside the byte budget when a user is both buyer and seller.
|
|
187
|
-
return [...ids].slice(0, limit);
|
|
188
|
-
}
|
|
189
|
-
/**
|
|
190
|
-
* Append a message to a conversation in a transaction. Bumps the counterparty
|
|
191
|
-
* unread counter; updates lastMessage / lastMessageAt / updatedAt.
|
|
192
|
-
*/
|
|
193
|
-
async appendMessage(conversationId, msg) {
|
|
194
|
-
const db = getAdminDb();
|
|
195
|
-
const ref = this.collection().doc(conversationId);
|
|
196
|
-
try {
|
|
197
|
-
return await db.runTransaction(async (tx) => {
|
|
198
|
-
const snap = await tx.get(ref);
|
|
199
|
-
if (!snap.exists) {
|
|
200
|
-
throw new Error("Conversation not found");
|
|
201
|
-
}
|
|
202
|
-
const data = snap.data() ?? {};
|
|
203
|
-
const existing = Array.isArray(data.messages)
|
|
204
|
-
? data.messages.map(normaliseMessage)
|
|
205
|
-
: [];
|
|
206
|
-
const now = msg.sentAt ?? new Date();
|
|
207
|
-
const newMessage = {
|
|
208
|
-
id: msg.id ?? `msg-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
209
|
-
senderId: msg.senderId,
|
|
210
|
-
senderRole: msg.senderRole,
|
|
211
|
-
body: msg.body,
|
|
212
|
-
isRead: false,
|
|
213
|
-
sentAt: now,
|
|
214
|
-
attachments: msg.attachments,
|
|
215
|
-
};
|
|
216
|
-
const nextMessages = [...existing, newMessage];
|
|
217
|
-
const unreadBuyer = msg.senderRole === "seller"
|
|
218
|
-
? (typeof data.unreadBuyer === "number" ? data.unreadBuyer : 0) + 1
|
|
219
|
-
: (typeof data.unreadBuyer === "number" ? data.unreadBuyer : 0);
|
|
220
|
-
const unreadSeller = msg.senderRole === "buyer"
|
|
221
|
-
? (typeof data.unreadSeller === "number" ? data.unreadSeller : 0) + 1
|
|
222
|
-
: (typeof data.unreadSeller === "number" ? data.unreadSeller : 0);
|
|
223
|
-
const patch = {
|
|
224
|
-
messages: nextMessages,
|
|
225
|
-
lastMessage: msg.body,
|
|
226
|
-
lastMessageAt: now,
|
|
227
|
-
unreadBuyer,
|
|
228
|
-
unreadSeller,
|
|
229
|
-
updatedAt: now,
|
|
230
|
-
};
|
|
231
|
-
// Encrypt before the write; normaliseDoc decrypts on the way back out,
|
|
232
|
-
// so the caller still receives plaintext.
|
|
233
|
-
const encryptedPatch = encryptPiiFields(patch, [...CONVERSATION_PII_FIELDS]);
|
|
234
|
-
tx.update(ref, encryptedPatch);
|
|
235
|
-
return normaliseDoc(conversationId, { ...data, ...encryptedPatch });
|
|
236
|
-
});
|
|
237
|
-
}
|
|
238
|
-
catch (error) {
|
|
239
|
-
serverLogger.error("ConversationsRepository.appendMessage error", {
|
|
240
|
-
conversationId,
|
|
241
|
-
error,
|
|
242
|
-
});
|
|
243
|
-
throw error;
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
/**
|
|
247
|
-
* Zero out the unread counter for one party. Also flips `isRead` on the
|
|
248
|
-
* embedded messages sent by the other party so the seller side can see what
|
|
249
|
-
* the buyer has already read.
|
|
250
|
-
*/
|
|
251
|
-
async markRead(conversationId, role) {
|
|
252
|
-
const db = getAdminDb();
|
|
253
|
-
const ref = this.collection().doc(conversationId);
|
|
254
|
-
try {
|
|
255
|
-
await db.runTransaction(async (tx) => {
|
|
256
|
-
const snap = await tx.get(ref);
|
|
257
|
-
if (!snap.exists)
|
|
258
|
-
return;
|
|
259
|
-
const data = snap.data() ?? {};
|
|
260
|
-
const messages = Array.isArray(data.messages)
|
|
261
|
-
? data.messages.map(normaliseMessage)
|
|
262
|
-
: [];
|
|
263
|
-
// role="buyer" means the buyer just opened the chat → mark all
|
|
264
|
-
// seller-sent messages read.
|
|
265
|
-
const otherRole = role === "buyer" ? "seller" : "buyer";
|
|
266
|
-
const nextMessages = messages.map((m) => m.senderRole === otherRole && !m.isRead ? { ...m, isRead: true } : m);
|
|
267
|
-
tx.update(ref, {
|
|
268
|
-
messages: nextMessages,
|
|
269
|
-
[role === "buyer" ? "unreadBuyer" : "unreadSeller"]: 0,
|
|
270
|
-
updatedAt: new Date(),
|
|
271
|
-
});
|
|
272
|
-
});
|
|
273
|
-
}
|
|
274
|
-
catch (error) {
|
|
275
|
-
serverLogger.error("ConversationsRepository.markRead error", {
|
|
276
|
-
conversationId,
|
|
277
|
-
role,
|
|
278
|
-
error,
|
|
279
|
-
});
|
|
280
|
-
throw error;
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
export const conversationsRepository = new ConversationsRepository();
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Messages / Conversations Firestore Document Types & Constants
|
|
3
|
-
*
|
|
4
|
-
* Architecture: one `conversations` document per buyer↔seller pair on a product.
|
|
5
|
-
* Messages are embedded as an array for seed purposes; production can migrate to
|
|
6
|
-
* a subcollection when the thread exceeds ~200 messages.
|
|
7
|
-
*/
|
|
8
|
-
import type { BaseDocument } from "../../../_internal/shared/types/base-document";
|
|
9
|
-
export type ConversationMessage = {
|
|
10
|
-
id: string;
|
|
11
|
-
senderId: string;
|
|
12
|
-
senderRole: "buyer" | "seller";
|
|
13
|
-
body: string;
|
|
14
|
-
isRead: boolean;
|
|
15
|
-
sentAt: Date;
|
|
16
|
-
attachments?: string[];
|
|
17
|
-
};
|
|
18
|
-
export interface ConversationDocument extends BaseDocument {
|
|
19
|
-
buyerId: string;
|
|
20
|
-
buyerDisplayName: string;
|
|
21
|
-
storeId: string;
|
|
22
|
-
storeName: string;
|
|
23
|
-
sellerDisplayName: string;
|
|
24
|
-
productId?: string;
|
|
25
|
-
productTitle?: string;
|
|
26
|
-
messages: ConversationMessage[];
|
|
27
|
-
lastMessage: string;
|
|
28
|
-
lastMessageAt: Date;
|
|
29
|
-
unreadBuyer: number;
|
|
30
|
-
unreadSeller: number;
|
|
31
|
-
status: "active" | "archived" | "blocked";
|
|
32
|
-
}
|
|
33
|
-
export declare const CONVERSATIONS_COLLECTION: "conversations";
|
|
34
|
-
export declare const CONVERSATIONS_INDEXED_FIELDS: readonly ["buyerId", "storeId", "productId", "status", "lastMessageAt", "createdAt"];
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Messages / Conversations Firestore Document Types & Constants
|
|
3
|
-
*
|
|
4
|
-
* Architecture: one `conversations` document per buyer↔seller pair on a product.
|
|
5
|
-
* Messages are embedded as an array for seed purposes; production can migrate to
|
|
6
|
-
* a subcollection when the thread exceeds ~200 messages.
|
|
7
|
-
*/
|
|
8
|
-
export const CONVERSATIONS_COLLECTION = "conversations";
|
|
9
|
-
export const CONVERSATIONS_INDEXED_FIELDS = [
|
|
10
|
-
"buyerId",
|
|
11
|
-
"storeId",
|
|
12
|
-
"productId",
|
|
13
|
-
"status",
|
|
14
|
-
"lastMessageAt",
|
|
15
|
-
"createdAt",
|
|
16
|
-
];
|
|
@@ -1,177 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
export * from "./firestore";
|
|
3
|
-
export declare const conversationMessageSenderRoleSchema: z.ZodEnum<["buyer", "seller"]>;
|
|
4
|
-
export declare const conversationStatusSchema: z.ZodEnum<["active", "archived", "blocked"]>;
|
|
5
|
-
export declare const conversationMessageSchema: z.ZodObject<{
|
|
6
|
-
id: z.ZodString;
|
|
7
|
-
senderId: z.ZodString;
|
|
8
|
-
senderRole: z.ZodEnum<["buyer", "seller"]>;
|
|
9
|
-
body: z.ZodString;
|
|
10
|
-
isRead: z.ZodBoolean;
|
|
11
|
-
sentAt: z.ZodUnion<[z.ZodDate, z.ZodString, z.ZodObject<{
|
|
12
|
-
toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
|
|
13
|
-
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
14
|
-
toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
|
|
15
|
-
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
16
|
-
toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
|
|
17
|
-
}, z.ZodTypeAny, "passthrough">>]>;
|
|
18
|
-
attachments: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
19
|
-
}, "strip", z.ZodTypeAny, {
|
|
20
|
-
id: string;
|
|
21
|
-
body: string;
|
|
22
|
-
isRead: boolean;
|
|
23
|
-
senderId: string;
|
|
24
|
-
senderRole: "buyer" | "seller";
|
|
25
|
-
sentAt: string | Date | z.objectOutputType<{
|
|
26
|
-
toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
|
|
27
|
-
}, z.ZodTypeAny, "passthrough">;
|
|
28
|
-
attachments?: string[] | undefined;
|
|
29
|
-
}, {
|
|
30
|
-
id: string;
|
|
31
|
-
body: string;
|
|
32
|
-
isRead: boolean;
|
|
33
|
-
senderId: string;
|
|
34
|
-
senderRole: "buyer" | "seller";
|
|
35
|
-
sentAt: string | Date | z.objectInputType<{
|
|
36
|
-
toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
|
|
37
|
-
}, z.ZodTypeAny, "passthrough">;
|
|
38
|
-
attachments?: string[] | undefined;
|
|
39
|
-
}>;
|
|
40
|
-
export declare const conversationFirestoreSchema: z.ZodObject<{
|
|
41
|
-
createdAt: z.ZodUnion<[z.ZodDate, z.ZodString, z.ZodObject<{
|
|
42
|
-
toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
|
|
43
|
-
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
44
|
-
toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
|
|
45
|
-
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
46
|
-
toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
|
|
47
|
-
}, z.ZodTypeAny, "passthrough">>]>;
|
|
48
|
-
updatedAt: z.ZodUnion<[z.ZodDate, z.ZodString, z.ZodObject<{
|
|
49
|
-
toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
|
|
50
|
-
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
51
|
-
toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
|
|
52
|
-
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
53
|
-
toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
|
|
54
|
-
}, z.ZodTypeAny, "passthrough">>]>;
|
|
55
|
-
id: z.ZodString;
|
|
56
|
-
buyerId: z.ZodString;
|
|
57
|
-
buyerDisplayName: z.ZodString;
|
|
58
|
-
storeId: z.ZodString;
|
|
59
|
-
storeName: z.ZodString;
|
|
60
|
-
sellerDisplayName: z.ZodString;
|
|
61
|
-
productId: z.ZodOptional<z.ZodString>;
|
|
62
|
-
productTitle: z.ZodOptional<z.ZodString>;
|
|
63
|
-
messages: z.ZodArray<z.ZodObject<{
|
|
64
|
-
id: z.ZodString;
|
|
65
|
-
senderId: z.ZodString;
|
|
66
|
-
senderRole: z.ZodEnum<["buyer", "seller"]>;
|
|
67
|
-
body: z.ZodString;
|
|
68
|
-
isRead: z.ZodBoolean;
|
|
69
|
-
sentAt: z.ZodUnion<[z.ZodDate, z.ZodString, z.ZodObject<{
|
|
70
|
-
toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
|
|
71
|
-
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
72
|
-
toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
|
|
73
|
-
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
74
|
-
toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
|
|
75
|
-
}, z.ZodTypeAny, "passthrough">>]>;
|
|
76
|
-
attachments: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
77
|
-
}, "strip", z.ZodTypeAny, {
|
|
78
|
-
id: string;
|
|
79
|
-
body: string;
|
|
80
|
-
isRead: boolean;
|
|
81
|
-
senderId: string;
|
|
82
|
-
senderRole: "buyer" | "seller";
|
|
83
|
-
sentAt: string | Date | z.objectOutputType<{
|
|
84
|
-
toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
|
|
85
|
-
}, z.ZodTypeAny, "passthrough">;
|
|
86
|
-
attachments?: string[] | undefined;
|
|
87
|
-
}, {
|
|
88
|
-
id: string;
|
|
89
|
-
body: string;
|
|
90
|
-
isRead: boolean;
|
|
91
|
-
senderId: string;
|
|
92
|
-
senderRole: "buyer" | "seller";
|
|
93
|
-
sentAt: string | Date | z.objectInputType<{
|
|
94
|
-
toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
|
|
95
|
-
}, z.ZodTypeAny, "passthrough">;
|
|
96
|
-
attachments?: string[] | undefined;
|
|
97
|
-
}>, "many">;
|
|
98
|
-
lastMessage: z.ZodString;
|
|
99
|
-
lastMessageAt: z.ZodUnion<[z.ZodDate, z.ZodString, z.ZodObject<{
|
|
100
|
-
toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
|
|
101
|
-
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
102
|
-
toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
|
|
103
|
-
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
104
|
-
toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
|
|
105
|
-
}, z.ZodTypeAny, "passthrough">>]>;
|
|
106
|
-
unreadBuyer: z.ZodNumber;
|
|
107
|
-
unreadSeller: z.ZodNumber;
|
|
108
|
-
status: z.ZodEnum<["active", "archived", "blocked"]>;
|
|
109
|
-
}, "strip", z.ZodTypeAny, {
|
|
110
|
-
status: "archived" | "active" | "blocked";
|
|
111
|
-
id: string;
|
|
112
|
-
createdAt: string | Date | z.objectOutputType<{
|
|
113
|
-
toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
|
|
114
|
-
}, z.ZodTypeAny, "passthrough">;
|
|
115
|
-
updatedAt: string | Date | z.objectOutputType<{
|
|
116
|
-
toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
|
|
117
|
-
}, z.ZodTypeAny, "passthrough">;
|
|
118
|
-
storeId: string;
|
|
119
|
-
storeName: string;
|
|
120
|
-
buyerId: string;
|
|
121
|
-
lastMessageAt: string | Date | z.objectOutputType<{
|
|
122
|
-
toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
|
|
123
|
-
}, z.ZodTypeAny, "passthrough">;
|
|
124
|
-
lastMessage: string;
|
|
125
|
-
messages: {
|
|
126
|
-
id: string;
|
|
127
|
-
body: string;
|
|
128
|
-
isRead: boolean;
|
|
129
|
-
senderId: string;
|
|
130
|
-
senderRole: "buyer" | "seller";
|
|
131
|
-
sentAt: string | Date | z.objectOutputType<{
|
|
132
|
-
toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
|
|
133
|
-
}, z.ZodTypeAny, "passthrough">;
|
|
134
|
-
attachments?: string[] | undefined;
|
|
135
|
-
}[];
|
|
136
|
-
buyerDisplayName: string;
|
|
137
|
-
sellerDisplayName: string;
|
|
138
|
-
unreadBuyer: number;
|
|
139
|
-
unreadSeller: number;
|
|
140
|
-
productId?: string | undefined;
|
|
141
|
-
productTitle?: string | undefined;
|
|
142
|
-
}, {
|
|
143
|
-
status: "archived" | "active" | "blocked";
|
|
144
|
-
id: string;
|
|
145
|
-
createdAt: string | Date | z.objectInputType<{
|
|
146
|
-
toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
|
|
147
|
-
}, z.ZodTypeAny, "passthrough">;
|
|
148
|
-
updatedAt: string | Date | z.objectInputType<{
|
|
149
|
-
toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
|
|
150
|
-
}, z.ZodTypeAny, "passthrough">;
|
|
151
|
-
storeId: string;
|
|
152
|
-
storeName: string;
|
|
153
|
-
buyerId: string;
|
|
154
|
-
lastMessageAt: string | Date | z.objectInputType<{
|
|
155
|
-
toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
|
|
156
|
-
}, z.ZodTypeAny, "passthrough">;
|
|
157
|
-
lastMessage: string;
|
|
158
|
-
messages: {
|
|
159
|
-
id: string;
|
|
160
|
-
body: string;
|
|
161
|
-
isRead: boolean;
|
|
162
|
-
senderId: string;
|
|
163
|
-
senderRole: "buyer" | "seller";
|
|
164
|
-
sentAt: string | Date | z.objectInputType<{
|
|
165
|
-
toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
|
|
166
|
-
}, z.ZodTypeAny, "passthrough">;
|
|
167
|
-
attachments?: string[] | undefined;
|
|
168
|
-
}[];
|
|
169
|
-
buyerDisplayName: string;
|
|
170
|
-
sellerDisplayName: string;
|
|
171
|
-
unreadBuyer: number;
|
|
172
|
-
unreadSeller: number;
|
|
173
|
-
productId?: string | undefined;
|
|
174
|
-
productTitle?: string | undefined;
|
|
175
|
-
}>;
|
|
176
|
-
export type ConversationFromSchema = z.infer<typeof conversationFirestoreSchema>;
|
|
177
|
-
export type ConversationMessageFromSchema = z.infer<typeof conversationMessageSchema>;
|