@bantr-sports/sdk 12.10.0 → 12.11.0
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/api-type/src/modules/broadcast/broadcast.schema.d.ts +8 -0
- package/api-type/src/modules/broadcast/broadcast.service.d.ts +122 -1
- package/api-type/src/modules/broadcast/broadcast_content_hash.d.ts +17 -0
- package/api-type/src/modules/chat/chat.repository.d.ts +43 -1
- package/api-type/src/modules/chat/chat.schema.d.ts +2 -1
- package/api-type/src/modules/chat/chat.service.d.ts +23 -0
- package/api-type/src/modules/discount_code/discount_code.schema.d.ts +133 -2
- package/api-type/src/modules/marketplace/marketplace.schema.d.ts +1 -2
- package/api-type/src/modules/rules/rules.factory.d.ts +4 -0
- package/api-type/src/modules/rules/rules.schema.d.ts +67 -0
- package/api-type/src/modules/rules/rules.service.d.ts +38 -0
- package/api-type/src/modules/scout_feed/scout_feed.service.d.ts +6 -0
- package/api-type/src/modules/scout_feed/scout_feed_location.d.ts +27 -0
- package/api-type/src/modules/scout_profile/scout_soccer_profile.model.d.ts +2 -0
- package/api-type/src/modules/transaction/transaction.service.d.ts +1 -0
- package/api-type/src/trpcRouter/broadcast.router.d.ts +12 -0
- package/api-type/src/trpcRouter/chat.router.d.ts +32 -0
- package/api-type/src/trpcRouter/router.d.ts +452 -268
- package/api-type/src/trpcRouter/rules.router.d.ts +53 -0
- package/api-type/src/types/common.d.ts +1 -0
- package/api-type/src/types/game_stats.d.ts +0 -25
- package/api-type/src/types/league.d.ts +3 -1
- package/api-type/src/types/team.d.ts +2 -0
- package/dist/index.d.ts +732 -456
- package/dist/index.d.ts.map +1 -1
- package/dist/provider.d.ts +452 -268
- package/dist/provider.d.ts.map +1 -1
- package/dist/query.d.ts +194 -102
- package/dist/query.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -30,6 +30,14 @@ export declare const GetBroadcastByIdInputSchema: z.ZodObject<{
|
|
|
30
30
|
id: number;
|
|
31
31
|
}>;
|
|
32
32
|
export type GetBroadcastByIdInput = z.infer<typeof GetBroadcastByIdInputSchema>;
|
|
33
|
+
export declare const GetBroadcastDeliveryStatusesInputSchema: z.ZodObject<{
|
|
34
|
+
broadcastId: z.ZodNumber;
|
|
35
|
+
}, "strip", z.ZodTypeAny, {
|
|
36
|
+
broadcastId: number;
|
|
37
|
+
}, {
|
|
38
|
+
broadcastId: number;
|
|
39
|
+
}>;
|
|
40
|
+
export type GetBroadcastDeliveryStatusesInput = z.infer<typeof GetBroadcastDeliveryStatusesInputSchema>;
|
|
33
41
|
export declare const GetAudienceInputSchema: z.ZodObject<{
|
|
34
42
|
seasonId: z.ZodOptional<z.ZodNumber>;
|
|
35
43
|
leagueId: z.ZodOptional<z.ZodNumber>;
|
|
@@ -1,9 +1,30 @@
|
|
|
1
|
+
import type { Queue } from 'bullmq';
|
|
1
2
|
import { Member } from '../../models/member';
|
|
2
3
|
import { Broadcast } from '../../models/broadcast';
|
|
3
4
|
import { BroadcastAudienceGroup } from '../../models/broadcast_audience_group';
|
|
4
5
|
import { BroadcastAudienceMember } from '../../models/broadcast_audience_member';
|
|
5
|
-
import { ArchiveBroadcastInput, DeleteBroadcastInput, DownloadBroadcastAudienceCsvInput, GetAudienceInput, GetBroadcastByIdInput, GetBroadcastsInput, SaveBroadcastInput, SendBroadcastInput } from './broadcast.schema';
|
|
6
|
+
import { ArchiveBroadcastInput, DeleteBroadcastInput, DownloadBroadcastAudienceCsvInput, GetAudienceInput, GetBroadcastByIdInput, GetBroadcastDeliveryStatusesInput, GetBroadcastsInput, SaveBroadcastInput, SendBroadcastInput } from './broadcast.schema';
|
|
6
7
|
import { Organizer } from '../../models/organizer';
|
|
8
|
+
import type { BroadcastDeliveryStatus } from '../../models/broadcast_delivery';
|
|
9
|
+
/**
|
|
10
|
+
* `earlier_version` means a delivery exists, but of a message that has since been
|
|
11
|
+
* edited — so a resend correctly targets them again. `sending` distinguishes "no
|
|
12
|
+
* ledger row yet" on a broadcast still in flight from one that has finished.
|
|
13
|
+
*/
|
|
14
|
+
export type BroadcastDeliverySummary = 'sent' | 'partial' | 'unknown' | 'failed' | 'earlier_version' | 'sending' | 'not_sent';
|
|
15
|
+
export type BroadcastMemberDeliveryStatus = {
|
|
16
|
+
memberId: number;
|
|
17
|
+
/** Outcome per channel for the content currently saved on the broadcast. */
|
|
18
|
+
email: BroadcastDeliveryStatus | null;
|
|
19
|
+
push: BroadcastDeliveryStatus | null;
|
|
20
|
+
notification: BroadcastDeliveryStatus | null;
|
|
21
|
+
summary: BroadcastDeliverySummary;
|
|
22
|
+
lastError: string | null;
|
|
23
|
+
deliveredAt: Date | null;
|
|
24
|
+
/** Highest attempt count across this member's channels — i.e. was this retried. */
|
|
25
|
+
attemptCount: number;
|
|
26
|
+
receivedEarlierVersion: boolean;
|
|
27
|
+
};
|
|
7
28
|
export declare class BroadcastService {
|
|
8
29
|
private readonly MemberModel;
|
|
9
30
|
private readonly BroadcastModel;
|
|
@@ -14,10 +35,75 @@ export declare class BroadcastService {
|
|
|
14
35
|
private assertOrganizationAccess;
|
|
15
36
|
private getBroadcastForOrganization;
|
|
16
37
|
private markBroadcastAsSending;
|
|
38
|
+
private normalizeFailureReason;
|
|
39
|
+
/**
|
|
40
|
+
* `note` is appended AFTER `normalizeFailureReason` truncates, so a long provider
|
|
41
|
+
* error cannot push it out of the message. The column is TEXT, so the combined
|
|
42
|
+
* length is not a concern. Used to warn that a resend may duplicate.
|
|
43
|
+
*/
|
|
17
44
|
private markBroadcastAsFailed;
|
|
45
|
+
/**
|
|
46
|
+
* Public safety net for the queue worker's `failed` handler. If a job dies or
|
|
47
|
+
* stalls before `processQueuedBroadcast` runs its own catch block (crash, OOM,
|
|
48
|
+
* lost lock), the broadcast would otherwise be stranded in SENDING forever.
|
|
49
|
+
* This resets it to FAILED so it can be resent. Idempotent: the underlying
|
|
50
|
+
* patch only matches rows still in SENDING, so a reason set by
|
|
51
|
+
* `processQueuedBroadcast`'s own catch is preserved.
|
|
52
|
+
*/
|
|
53
|
+
failQueuedBroadcast(organizationId: number, broadcastId: number, reason?: string | null): Promise<void>;
|
|
18
54
|
private assertOrganizationExists;
|
|
19
55
|
private insertInChunks;
|
|
56
|
+
/**
|
|
57
|
+
* Retries a ledger write a few times before giving up.
|
|
58
|
+
*
|
|
59
|
+
* A dropped ledger row is not a cosmetic problem: `getDeliveredMemberIdsByChannel`
|
|
60
|
+
* treats "no row" as "not delivered", so a lost row means a resend mails that
|
|
61
|
+
* person again. A transient connection blip is worth a second attempt.
|
|
62
|
+
*/
|
|
63
|
+
private withLedgerWriteRetry;
|
|
64
|
+
/**
|
|
65
|
+
* Members who already received `contentHash` on each channel.
|
|
66
|
+
*
|
|
67
|
+
* Scoped to `status = 'sent'`, so `unknown` (timed-out, possibly delivered) and
|
|
68
|
+
* `failed` recipients are re-targeted by construction: for game-day comms a
|
|
69
|
+
* missed notice is worse than a duplicate, and the ambiguous count is reported
|
|
70
|
+
* back to the organizer instead of being silently resolved either way.
|
|
71
|
+
*/
|
|
72
|
+
private getDeliveredMemberIdsByChannel;
|
|
73
|
+
/**
|
|
74
|
+
* Records what was handed to a delivery channel, one autocommit statement per
|
|
75
|
+
* chunk, immediately after that batch is acknowledged.
|
|
76
|
+
*
|
|
77
|
+
* DELIBERATELY NOT IN A TRANSACTION (the `notification` channel excepted, which
|
|
78
|
+
* passes its `trx`). This is the entire point of the ledger: if checkpoints were
|
|
79
|
+
* written inside a transaction spanning the send, a crash mid-delivery would roll
|
|
80
|
+
* them all back, and the resend would duplicate everything — which is the bug
|
|
81
|
+
* this exists to fix. Order is send-then-record, so a crash in that gap re-sends
|
|
82
|
+
* one batch (at-least-once). Recording intent first would be at-most-once, i.e.
|
|
83
|
+
* silent drops.
|
|
84
|
+
*/
|
|
85
|
+
private recordDeliveries;
|
|
86
|
+
/**
|
|
87
|
+
* Recipients whose delivery outcome is genuinely unknown — a provider request
|
|
88
|
+
* timed out without being aborted, so it may or may not have been delivered.
|
|
89
|
+
* Surfaced in `failure_reason` so the organizer knows a resend can duplicate.
|
|
90
|
+
*/
|
|
91
|
+
private countAmbiguousDeliveries;
|
|
20
92
|
private enqueueBroadcastSend;
|
|
93
|
+
/**
|
|
94
|
+
* Boot-time recovery for broadcasts stranded in SENDING. The worker's `failed`
|
|
95
|
+
* handler only fires for jobs that actually fail, so a row whose job never ran
|
|
96
|
+
* (Redis flushed, job ID collision, add rejected) or whose process died before
|
|
97
|
+
* that handler executed stays SENDING forever — it cannot be edited, resent, or
|
|
98
|
+
* completed. Rows younger than `staleAfterMs` are left alone so a legitimately
|
|
99
|
+
* slow send is never interrupted, and every patch is scoped to `status =
|
|
100
|
+
* 'SENDING'`, so concurrent API replicas running this cannot clobber a send that
|
|
101
|
+
* completed in the meantime.
|
|
102
|
+
*/
|
|
103
|
+
reconcileStuckBroadcasts(queue: Pick<Queue, 'getJob'>, staleAfterMs?: number): Promise<{
|
|
104
|
+
checked: number;
|
|
105
|
+
reset: number;
|
|
106
|
+
}>;
|
|
21
107
|
private assertWorkspaceAccess;
|
|
22
108
|
private normalizeAudienceGroups;
|
|
23
109
|
private applyBroadcastFilters;
|
|
@@ -27,6 +113,28 @@ export declare class BroadcastService {
|
|
|
27
113
|
private stripHtmlForPushNotification;
|
|
28
114
|
downloadBroadcastAudienceCsv(organizationId: number, { id }: DownloadBroadcastAudienceCsvInput): Promise<string | null>;
|
|
29
115
|
getBroadcastById(organizationId: number, { id }: GetBroadcastByIdInput): Promise<Broadcast | null>;
|
|
116
|
+
/**
|
|
117
|
+
* Per-recipient delivery state for the Recipients table, read straight off the
|
|
118
|
+
* ledger. Answers the question a `FAILED` broadcast otherwise leaves open: who
|
|
119
|
+
* actually received this, and who would a resend re-target?
|
|
120
|
+
*
|
|
121
|
+
* Rows are grouped per member rather than per channel because that is how the
|
|
122
|
+
* table displays them; the per-channel statuses stay on the payload so the UI can
|
|
123
|
+
* show the breakdown without a second request.
|
|
124
|
+
*/
|
|
125
|
+
getBroadcastDeliveryStatuses(organizationId: number, { broadcastId }: GetBroadcastDeliveryStatusesInput): Promise<{
|
|
126
|
+
contentHash: string;
|
|
127
|
+
members: BroadcastMemberDeliveryStatus[];
|
|
128
|
+
} | null>;
|
|
129
|
+
/**
|
|
130
|
+
* Collapses a member's per-channel statuses into the single label the table shows.
|
|
131
|
+
*
|
|
132
|
+
* Derived from the rows that exist, deliberately not from the broadcast's channel
|
|
133
|
+
* toggles: a member with push enabled but no registered device, or who muted
|
|
134
|
+
* broadcasts, is never handed to FCM and has no `push` row. Treating that as an
|
|
135
|
+
* incomplete delivery would label ordinary opt-outs as a problem.
|
|
136
|
+
*/
|
|
137
|
+
private summarizeMemberDelivery;
|
|
30
138
|
deleteBroadcast(organizationId: number, { id }: DeleteBroadcastInput): Promise<{
|
|
31
139
|
success: boolean;
|
|
32
140
|
id: number;
|
|
@@ -69,6 +177,7 @@ export declare class BroadcastService {
|
|
|
69
177
|
pushNotification: boolean;
|
|
70
178
|
senderEmail: string;
|
|
71
179
|
status: string;
|
|
180
|
+
failureReason: string | null;
|
|
72
181
|
isArchived: boolean;
|
|
73
182
|
sentAt: Date | null;
|
|
74
183
|
createdAt: Date;
|
|
@@ -91,8 +200,20 @@ export declare class BroadcastService {
|
|
|
91
200
|
} | null>;
|
|
92
201
|
processQueuedBroadcast(creatorId: number, organizationId: number, broadcastId: number): Promise<{
|
|
93
202
|
broadcast: Broadcast;
|
|
203
|
+
/** Addresses handed to Mailgun — see `recordedEmailDeliveries` for delivered. */
|
|
94
204
|
sentEmails: number;
|
|
205
|
+
/** Tokens handed to FCM — see `recordedPushDeliveries` for delivered. */
|
|
95
206
|
sentPushNotifications: number;
|
|
96
207
|
createdNotifications: number;
|
|
208
|
+
recordedEmailDeliveries: number;
|
|
209
|
+
ambiguousEmailDeliveries: number;
|
|
210
|
+
recordedPushDeliveries: number;
|
|
211
|
+
ambiguousPushDeliveries: number;
|
|
212
|
+
skippedAlreadyDelivered: number;
|
|
213
|
+
/**
|
|
214
|
+
* Reached, but missing from the ledger, so a resend cannot skip them.
|
|
215
|
+
* Non-zero means the ledger under-reports what was delivered.
|
|
216
|
+
*/
|
|
217
|
+
unrecordedDeliveries: number;
|
|
97
218
|
} | null>;
|
|
98
219
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fingerprints the content a recipient actually received, so the delivery ledger
|
|
3
|
+
* can tell "already got this message" apart from "already got an earlier draft".
|
|
4
|
+
*
|
|
5
|
+
* A FAILED broadcast is fully editable: the organizer can reword the subject or
|
|
6
|
+
* body before hitting Resend. Skipping every previously-delivered recipient would
|
|
7
|
+
* mean the reworded version silently never reaches them. Keying the ledger on
|
|
8
|
+
* this hash instead makes an edited resend re-target the whole audience, while an
|
|
9
|
+
* unchanged resend targets only the recipients who were never reached.
|
|
10
|
+
*
|
|
11
|
+
* The tuple is JSON-encoded before hashing so that a subject/message split cannot
|
|
12
|
+
* be shifted without changing the digest — ('ab', 'c') and ('a', 'bc') hash
|
|
13
|
+
* differently. Channel toggles, sender, and audience are deliberately excluded:
|
|
14
|
+
* enabling push after an email-only send then finds no `push` rows and correctly
|
|
15
|
+
* targets everyone.
|
|
16
|
+
*/
|
|
17
|
+
export declare const buildBroadcastContentHash: (subject?: string | null, message?: string | null) => string;
|
|
@@ -4,7 +4,7 @@ type PrismaClientLike = typeof prisma;
|
|
|
4
4
|
type DbClient = any;
|
|
5
5
|
type CreateContextChatInput = {
|
|
6
6
|
actorId: number;
|
|
7
|
-
chatType: typeof CHAT_TYPES.TEAM | typeof CHAT_TYPES.MATCH | typeof CHAT_TYPES.PICKUP;
|
|
7
|
+
chatType: typeof CHAT_TYPES.TEAM | typeof CHAT_TYPES.MATCH | typeof CHAT_TYPES.PICKUP_GROUP | typeof CHAT_TYPES.PICKUP;
|
|
8
8
|
contextId: number;
|
|
9
9
|
ownerId?: number | null;
|
|
10
10
|
name?: string | null;
|
|
@@ -107,12 +107,54 @@ export declare class ChatRepository {
|
|
|
107
107
|
getSharedTeamMemberIds(memberId: number, targetMemberIds: number[]): Promise<unknown[]>;
|
|
108
108
|
getTeamContext(teamId: number): Promise<any>;
|
|
109
109
|
getMatchContext(eventId: number): Promise<any>;
|
|
110
|
+
getNextTeamRsvpEvent(teamId: number, memberId: number): Promise<{
|
|
111
|
+
nextEvent: null;
|
|
112
|
+
hasRsvp: boolean;
|
|
113
|
+
rsvpStatus: string;
|
|
114
|
+
} | {
|
|
115
|
+
nextEvent: {
|
|
116
|
+
id: any;
|
|
117
|
+
title: any;
|
|
118
|
+
dateAndTime: any;
|
|
119
|
+
addressName: any;
|
|
120
|
+
location: any;
|
|
121
|
+
teamId: any;
|
|
122
|
+
timezone: any;
|
|
123
|
+
};
|
|
124
|
+
hasRsvp: boolean;
|
|
125
|
+
rsvpStatus: any;
|
|
126
|
+
}>;
|
|
127
|
+
getRsvpEvent(eventId: number, memberId: number): Promise<{
|
|
128
|
+
nextEvent: null;
|
|
129
|
+
hasRsvp: boolean;
|
|
130
|
+
rsvpStatus: string;
|
|
131
|
+
} | {
|
|
132
|
+
nextEvent: {
|
|
133
|
+
id: any;
|
|
134
|
+
title: any;
|
|
135
|
+
dateAndTime: any;
|
|
136
|
+
addressName: any;
|
|
137
|
+
location: any;
|
|
138
|
+
teamId: any;
|
|
139
|
+
timezone: any;
|
|
140
|
+
};
|
|
141
|
+
hasRsvp: boolean;
|
|
142
|
+
rsvpStatus: any;
|
|
143
|
+
}>;
|
|
110
144
|
getPickupContext(pickupId: number): Promise<any>;
|
|
145
|
+
getPickupGroupContext(pickupGroupId: number): Promise<any>;
|
|
111
146
|
getPickupMemberIds(pickupId: number): Promise<any>;
|
|
147
|
+
getPickupGroupMemberIds(pickupGroupId: number): Promise<any>;
|
|
112
148
|
getActivePickupsForMember(memberId: number): Promise<any>;
|
|
149
|
+
getActivePickupGroupsForMember(memberId: number): Promise<any>;
|
|
150
|
+
private addContextMetadata;
|
|
113
151
|
private chatMemberInclude;
|
|
114
152
|
private messageInclude;
|
|
115
153
|
private legacyChatType;
|
|
154
|
+
private emptyRsvpEventResponse;
|
|
155
|
+
private rsvpEventResponse;
|
|
156
|
+
private mapRsvpEvent;
|
|
157
|
+
private rsvpEventSelect;
|
|
116
158
|
private sortMemberPair;
|
|
117
159
|
private uniqueMemberIds;
|
|
118
160
|
}
|
|
@@ -4,6 +4,7 @@ export declare const CHAT_TYPES: {
|
|
|
4
4
|
readonly PRIVATE_GROUP: "private_group";
|
|
5
5
|
readonly TEAM: "team";
|
|
6
6
|
readonly MATCH: "match";
|
|
7
|
+
readonly PICKUP_GROUP: "pickup_group";
|
|
7
8
|
readonly PICKUP: "pickup";
|
|
8
9
|
};
|
|
9
10
|
export declare const CHAT_MEMBER_ROLES: {
|
|
@@ -21,7 +22,7 @@ export declare const CHAT_MESSAGE_TYPES: {
|
|
|
21
22
|
readonly POLL: "poll";
|
|
22
23
|
readonly INVITE: "invite";
|
|
23
24
|
};
|
|
24
|
-
export declare const chatTypeSchema: z.ZodEnum<["direct", "private_group", "team", "match", "pickup"]>;
|
|
25
|
+
export declare const chatTypeSchema: z.ZodEnum<["direct", "private_group", "team", "match", "pickup_group", "pickup"]>;
|
|
25
26
|
export declare const chatMemberRoleSchema: z.ZodEnum<["owner", "admin", "member"]>;
|
|
26
27
|
export declare const listChatsInputSchema: z.ZodObject<{
|
|
27
28
|
limit: z.ZodDefault<z.ZodNumber>;
|
|
@@ -27,6 +27,25 @@ export declare class ChatService {
|
|
|
27
27
|
items: any;
|
|
28
28
|
nextCursor: any;
|
|
29
29
|
}>;
|
|
30
|
+
getNextRsvpEvent(memberId: number, input: {
|
|
31
|
+
chatId: number;
|
|
32
|
+
}): Promise<{
|
|
33
|
+
nextEvent: null;
|
|
34
|
+
hasRsvp: boolean;
|
|
35
|
+
rsvpStatus: string;
|
|
36
|
+
} | {
|
|
37
|
+
nextEvent: {
|
|
38
|
+
id: any;
|
|
39
|
+
title: any;
|
|
40
|
+
dateAndTime: any;
|
|
41
|
+
addressName: any;
|
|
42
|
+
location: any;
|
|
43
|
+
teamId: any;
|
|
44
|
+
timezone: any;
|
|
45
|
+
};
|
|
46
|
+
hasRsvp: boolean;
|
|
47
|
+
rsvpStatus: any;
|
|
48
|
+
}>;
|
|
30
49
|
createDirectChat(actorId: number, input: CreateDirectChatInput): Promise<any>;
|
|
31
50
|
createPrivateGroup(actorId: number, input: CreatePrivateGroupInput): Promise<any>;
|
|
32
51
|
listGroupMemberCandidates(memberId: number, input: ListGroupMemberCandidatesInput): Promise<any[]>;
|
|
@@ -34,6 +53,8 @@ export declare class ChatService {
|
|
|
34
53
|
syncTeamChat(teamId: number, actorId?: number): Promise<any>;
|
|
35
54
|
getOrCreateMatchChat(actorId: number, input: ContextChatInput): Promise<any>;
|
|
36
55
|
getOrCreatePickupChat(actorId: number, input: ContextChatInput): Promise<any>;
|
|
56
|
+
getOrCreatePickupGroupChat(actorId: number, input: ContextChatInput): Promise<any>;
|
|
57
|
+
syncPickupGroupChat(pickupGroupId: number, actorId?: number): Promise<any>;
|
|
37
58
|
syncPickupChat(pickupId: number, actorId?: number): Promise<any>;
|
|
38
59
|
sendMessage(actorId: number, input: SendMessageInput): Promise<any>;
|
|
39
60
|
sendScoutInviteMessage(actorId: number, input: SendScoutInviteMessageInput): Promise<any>;
|
|
@@ -82,7 +103,9 @@ export declare class ChatService {
|
|
|
82
103
|
private requireSharedTeamMembers;
|
|
83
104
|
private requireMessageInChat;
|
|
84
105
|
private ensureTeamChatsForMember;
|
|
106
|
+
private ensurePickupGroupChatsForMember;
|
|
85
107
|
private ensurePickupChatsForMember;
|
|
86
108
|
private uniqueMemberIds;
|
|
109
|
+
private formatPickupChatTitle;
|
|
87
110
|
}
|
|
88
111
|
export {};
|
|
@@ -118,7 +118,7 @@ export declare const GetDiscountCodeSchema: z.ZodObject<Pick<{
|
|
|
118
118
|
id: number;
|
|
119
119
|
}>;
|
|
120
120
|
export type GetDiscountCodeSchema = z.infer<typeof GetDiscountCodeSchema>;
|
|
121
|
-
export declare const
|
|
121
|
+
export declare const CreateDiscountCodeFieldsSchema: z.ZodObject<Pick<{
|
|
122
122
|
id: z.ZodNumber;
|
|
123
123
|
code: z.ZodString;
|
|
124
124
|
description: z.ZodString;
|
|
@@ -159,8 +159,115 @@ export declare const CreateDiscountCodeSchema: z.ZodObject<Pick<{
|
|
|
159
159
|
validUntil: Date | null;
|
|
160
160
|
orgWorkspaceId?: number | null | undefined;
|
|
161
161
|
}>;
|
|
162
|
+
export declare const validateDiscountCodeAmount: (value: Pick<DiscountCodeSchema, "amountType" | "amountValue">, ctx: z.RefinementCtx) => void;
|
|
163
|
+
export declare const CreateDiscountCodeSchema: z.ZodEffects<z.ZodObject<Pick<{
|
|
164
|
+
id: z.ZodNumber;
|
|
165
|
+
code: z.ZodString;
|
|
166
|
+
description: z.ZodString;
|
|
167
|
+
appliesWhen: z.ZodEnum<["purchase"]>;
|
|
168
|
+
applicableOn: z.ZodNullable<z.ZodEnum<["league-registration", "pickup-registration", "free-agent-registration"]>>;
|
|
169
|
+
amountType: z.ZodEnum<["flat-rate", "percentage"]>;
|
|
170
|
+
amountValue: z.ZodNumber;
|
|
171
|
+
validFrom: z.ZodNullable<z.ZodDate>;
|
|
172
|
+
validUntil: z.ZodNullable<z.ZodEffects<z.ZodDate, Date, Date>>;
|
|
173
|
+
usageLimit: z.ZodNullable<z.ZodNumber>;
|
|
174
|
+
usageCount: z.ZodDefault<z.ZodNumber>;
|
|
175
|
+
stripePromotionCodeId: z.ZodNullable<z.ZodString>;
|
|
176
|
+
updatedAt: z.ZodDate;
|
|
177
|
+
createdAt: z.ZodDate;
|
|
178
|
+
deletedAt: z.ZodNullable<z.ZodDate>;
|
|
179
|
+
organizationId: z.ZodNumber;
|
|
180
|
+
orgWorkspaceId: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
181
|
+
leagueId: z.ZodNullable<z.ZodNumber>;
|
|
182
|
+
seasonId: z.ZodNullable<z.ZodNumber>;
|
|
183
|
+
}, "description" | "orgWorkspaceId" | "code" | "appliesWhen" | "applicableOn" | "amountType" | "amountValue" | "validFrom" | "validUntil">, "strip", z.ZodTypeAny, {
|
|
184
|
+
description: string;
|
|
185
|
+
code: string;
|
|
186
|
+
appliesWhen: "purchase";
|
|
187
|
+
applicableOn: "league-registration" | "pickup-registration" | "free-agent-registration" | null;
|
|
188
|
+
amountType: "flat-rate" | "percentage";
|
|
189
|
+
amountValue: number;
|
|
190
|
+
validFrom: Date | null;
|
|
191
|
+
validUntil: Date | null;
|
|
192
|
+
orgWorkspaceId?: number | null | undefined;
|
|
193
|
+
}, {
|
|
194
|
+
description: string;
|
|
195
|
+
code: string;
|
|
196
|
+
appliesWhen: "purchase";
|
|
197
|
+
applicableOn: "league-registration" | "pickup-registration" | "free-agent-registration" | null;
|
|
198
|
+
amountType: "flat-rate" | "percentage";
|
|
199
|
+
amountValue: number;
|
|
200
|
+
validFrom: Date | null;
|
|
201
|
+
validUntil: Date | null;
|
|
202
|
+
orgWorkspaceId?: number | null | undefined;
|
|
203
|
+
}>, {
|
|
204
|
+
description: string;
|
|
205
|
+
code: string;
|
|
206
|
+
appliesWhen: "purchase";
|
|
207
|
+
applicableOn: "league-registration" | "pickup-registration" | "free-agent-registration" | null;
|
|
208
|
+
amountType: "flat-rate" | "percentage";
|
|
209
|
+
amountValue: number;
|
|
210
|
+
validFrom: Date | null;
|
|
211
|
+
validUntil: Date | null;
|
|
212
|
+
orgWorkspaceId?: number | null | undefined;
|
|
213
|
+
}, {
|
|
214
|
+
description: string;
|
|
215
|
+
code: string;
|
|
216
|
+
appliesWhen: "purchase";
|
|
217
|
+
applicableOn: "league-registration" | "pickup-registration" | "free-agent-registration" | null;
|
|
218
|
+
amountType: "flat-rate" | "percentage";
|
|
219
|
+
amountValue: number;
|
|
220
|
+
validFrom: Date | null;
|
|
221
|
+
validUntil: Date | null;
|
|
222
|
+
orgWorkspaceId?: number | null | undefined;
|
|
223
|
+
}>;
|
|
162
224
|
export type CreateDiscountCodeSchema = z.infer<typeof CreateDiscountCodeSchema>;
|
|
163
|
-
export declare const
|
|
225
|
+
export declare const UpdateDiscountCodeFieldsSchema: z.ZodObject<Pick<{
|
|
226
|
+
id: z.ZodNumber;
|
|
227
|
+
code: z.ZodString;
|
|
228
|
+
description: z.ZodString;
|
|
229
|
+
appliesWhen: z.ZodEnum<["purchase"]>;
|
|
230
|
+
applicableOn: z.ZodNullable<z.ZodEnum<["league-registration", "pickup-registration", "free-agent-registration"]>>;
|
|
231
|
+
amountType: z.ZodEnum<["flat-rate", "percentage"]>;
|
|
232
|
+
amountValue: z.ZodNumber;
|
|
233
|
+
validFrom: z.ZodNullable<z.ZodDate>;
|
|
234
|
+
validUntil: z.ZodNullable<z.ZodEffects<z.ZodDate, Date, Date>>;
|
|
235
|
+
usageLimit: z.ZodNullable<z.ZodNumber>;
|
|
236
|
+
usageCount: z.ZodDefault<z.ZodNumber>;
|
|
237
|
+
stripePromotionCodeId: z.ZodNullable<z.ZodString>;
|
|
238
|
+
updatedAt: z.ZodDate;
|
|
239
|
+
createdAt: z.ZodDate;
|
|
240
|
+
deletedAt: z.ZodNullable<z.ZodDate>;
|
|
241
|
+
organizationId: z.ZodNumber;
|
|
242
|
+
orgWorkspaceId: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
243
|
+
leagueId: z.ZodNullable<z.ZodNumber>;
|
|
244
|
+
seasonId: z.ZodNullable<z.ZodNumber>;
|
|
245
|
+
}, "id" | "description" | "orgWorkspaceId" | "code" | "appliesWhen" | "applicableOn" | "amountType" | "amountValue" | "validFrom" | "validUntil" | "usageLimit">, "strip", z.ZodTypeAny, {
|
|
246
|
+
id: number;
|
|
247
|
+
description: string;
|
|
248
|
+
code: string;
|
|
249
|
+
appliesWhen: "purchase";
|
|
250
|
+
applicableOn: "league-registration" | "pickup-registration" | "free-agent-registration" | null;
|
|
251
|
+
amountType: "flat-rate" | "percentage";
|
|
252
|
+
amountValue: number;
|
|
253
|
+
validFrom: Date | null;
|
|
254
|
+
validUntil: Date | null;
|
|
255
|
+
usageLimit: number | null;
|
|
256
|
+
orgWorkspaceId?: number | null | undefined;
|
|
257
|
+
}, {
|
|
258
|
+
id: number;
|
|
259
|
+
description: string;
|
|
260
|
+
code: string;
|
|
261
|
+
appliesWhen: "purchase";
|
|
262
|
+
applicableOn: "league-registration" | "pickup-registration" | "free-agent-registration" | null;
|
|
263
|
+
amountType: "flat-rate" | "percentage";
|
|
264
|
+
amountValue: number;
|
|
265
|
+
validFrom: Date | null;
|
|
266
|
+
validUntil: Date | null;
|
|
267
|
+
usageLimit: number | null;
|
|
268
|
+
orgWorkspaceId?: number | null | undefined;
|
|
269
|
+
}>;
|
|
270
|
+
export declare const UpdateDiscountCodeSchema: z.ZodEffects<z.ZodObject<Pick<{
|
|
164
271
|
id: z.ZodNumber;
|
|
165
272
|
code: z.ZodString;
|
|
166
273
|
description: z.ZodString;
|
|
@@ -204,6 +311,30 @@ export declare const UpdateDiscountCodeSchema: z.ZodObject<Pick<{
|
|
|
204
311
|
validUntil: Date | null;
|
|
205
312
|
usageLimit: number | null;
|
|
206
313
|
orgWorkspaceId?: number | null | undefined;
|
|
314
|
+
}>, {
|
|
315
|
+
id: number;
|
|
316
|
+
description: string;
|
|
317
|
+
code: string;
|
|
318
|
+
appliesWhen: "purchase";
|
|
319
|
+
applicableOn: "league-registration" | "pickup-registration" | "free-agent-registration" | null;
|
|
320
|
+
amountType: "flat-rate" | "percentage";
|
|
321
|
+
amountValue: number;
|
|
322
|
+
validFrom: Date | null;
|
|
323
|
+
validUntil: Date | null;
|
|
324
|
+
usageLimit: number | null;
|
|
325
|
+
orgWorkspaceId?: number | null | undefined;
|
|
326
|
+
}, {
|
|
327
|
+
id: number;
|
|
328
|
+
description: string;
|
|
329
|
+
code: string;
|
|
330
|
+
appliesWhen: "purchase";
|
|
331
|
+
applicableOn: "league-registration" | "pickup-registration" | "free-agent-registration" | null;
|
|
332
|
+
amountType: "flat-rate" | "percentage";
|
|
333
|
+
amountValue: number;
|
|
334
|
+
validFrom: Date | null;
|
|
335
|
+
validUntil: Date | null;
|
|
336
|
+
usageLimit: number | null;
|
|
337
|
+
orgWorkspaceId?: number | null | undefined;
|
|
207
338
|
}>;
|
|
208
339
|
export type UpdateDiscountCodeSchema = z.infer<typeof UpdateDiscountCodeSchema>;
|
|
209
340
|
export declare const DeleteDiscountCodeSchema: z.ZodObject<Pick<{
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const GetWorkspaceRulesInputSchema: z.ZodObject<{
|
|
3
|
+
organizationId: z.ZodNumber;
|
|
4
|
+
workspaceId: z.ZodNumber;
|
|
5
|
+
}, "strip", z.ZodTypeAny, {
|
|
6
|
+
organizationId: number;
|
|
7
|
+
workspaceId: number;
|
|
8
|
+
}, {
|
|
9
|
+
organizationId: number;
|
|
10
|
+
workspaceId: number;
|
|
11
|
+
}>;
|
|
12
|
+
export declare const PublishWorkspaceRulesInputSchema: z.ZodObject<{
|
|
13
|
+
organizationId: z.ZodNumber;
|
|
14
|
+
workspaceId: z.ZodNumber;
|
|
15
|
+
content: z.ZodString;
|
|
16
|
+
}, "strip", z.ZodTypeAny, {
|
|
17
|
+
content: string;
|
|
18
|
+
organizationId: number;
|
|
19
|
+
workspaceId: number;
|
|
20
|
+
}, {
|
|
21
|
+
content: string;
|
|
22
|
+
organizationId: number;
|
|
23
|
+
workspaceId: number;
|
|
24
|
+
}>;
|
|
25
|
+
export declare const DeleteWorkspaceRulesInputSchema: z.ZodObject<{
|
|
26
|
+
organizationId: z.ZodNumber;
|
|
27
|
+
workspaceId: z.ZodNumber;
|
|
28
|
+
}, "strip", z.ZodTypeAny, {
|
|
29
|
+
organizationId: number;
|
|
30
|
+
workspaceId: number;
|
|
31
|
+
}, {
|
|
32
|
+
organizationId: number;
|
|
33
|
+
workspaceId: number;
|
|
34
|
+
}>;
|
|
35
|
+
/** Same shape as the organizer read - the marketplace just reaches it without a session. */
|
|
36
|
+
export declare const GetPublicWorkspaceRulesInputSchema: z.ZodObject<{
|
|
37
|
+
organizationId: z.ZodNumber;
|
|
38
|
+
workspaceId: z.ZodNumber;
|
|
39
|
+
}, "strip", z.ZodTypeAny, {
|
|
40
|
+
organizationId: number;
|
|
41
|
+
workspaceId: number;
|
|
42
|
+
}, {
|
|
43
|
+
organizationId: number;
|
|
44
|
+
workspaceId: number;
|
|
45
|
+
}>;
|
|
46
|
+
export type GetWorkspaceRulesInput = z.infer<typeof GetWorkspaceRulesInputSchema>;
|
|
47
|
+
export type PublishWorkspaceRulesInput = z.infer<typeof PublishWorkspaceRulesInputSchema>;
|
|
48
|
+
export type DeleteWorkspaceRulesInput = z.infer<typeof DeleteWorkspaceRulesInputSchema>;
|
|
49
|
+
export type GetPublicWorkspaceRulesInput = z.infer<typeof GetPublicWorkspaceRulesInputSchema>;
|
|
50
|
+
export type WorkspaceRules = {
|
|
51
|
+
organizationId: number;
|
|
52
|
+
workspaceId: number;
|
|
53
|
+
content: string;
|
|
54
|
+
version: number;
|
|
55
|
+
publishedAt: Date;
|
|
56
|
+
updatedAt: Date;
|
|
57
|
+
publishedBy: {
|
|
58
|
+
id: number;
|
|
59
|
+
firstName: string;
|
|
60
|
+
lastName: string;
|
|
61
|
+
} | null;
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* The player-facing read. Deliberately drops `publishedBy` - the marketplace product page
|
|
65
|
+
* is reachable without a session, and which organizer pressed publish is not public info.
|
|
66
|
+
*/
|
|
67
|
+
export type PublicWorkspaceRules = Omit<WorkspaceRules, 'publishedBy'>;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { type DeleteWorkspaceRulesInput, type GetPublicWorkspaceRulesInput, type GetWorkspaceRulesInput, type PublicWorkspaceRules, type PublishWorkspaceRulesInput, type WorkspaceRules } from './rules.schema';
|
|
2
|
+
export declare class RulesService {
|
|
3
|
+
/**
|
|
4
|
+
* Reads carry `organizationId` from the caller, so it has to be checked against the
|
|
5
|
+
* workspace - otherwise passing someone else's `workspaceId` alongside your own
|
|
6
|
+
* `organizationId` would read their rules.
|
|
7
|
+
*/
|
|
8
|
+
private assertWorkspaceInOrganization;
|
|
9
|
+
private assertOrganizerInOrganization;
|
|
10
|
+
/** Only `publishedBy` needs mapping - the rest of the row is already camelCase. */
|
|
11
|
+
private toWorkspaceRules;
|
|
12
|
+
/**
|
|
13
|
+
* Returns `null` rather than throwing when a workspace has no rules yet - that is the
|
|
14
|
+
* empty state the settings page renders, not an error.
|
|
15
|
+
*
|
|
16
|
+
* This read exposes `publishedBy`, which the public read deliberately withholds, so the
|
|
17
|
+
* caller has to be an organizer of the organization and not merely signed in.
|
|
18
|
+
*/
|
|
19
|
+
getWorkspaceRules(userId: number, input: GetWorkspaceRulesInput): Promise<WorkspaceRules | null>;
|
|
20
|
+
/**
|
|
21
|
+
* Unauthenticated read for the marketplace product page. Published rules are already
|
|
22
|
+
* public by intent, so the only guard is the same workspace/organization pairing check;
|
|
23
|
+
* `publishedBy` is not selected so no organizer identity leaks to anonymous visitors.
|
|
24
|
+
*
|
|
25
|
+
* `null` means "this workspace has not published rules", which the modal renders as its
|
|
26
|
+
* empty state rather than as a failure.
|
|
27
|
+
*/
|
|
28
|
+
getPublicWorkspaceRules(input: GetPublicWorkspaceRulesInput): Promise<PublicWorkspaceRules | null>;
|
|
29
|
+
/**
|
|
30
|
+
* Create-or-replace: the workspace keeps a single live document and every publish
|
|
31
|
+
* bumps `version`, so "add rules" and "edit rules" are the same write.
|
|
32
|
+
*/
|
|
33
|
+
publishWorkspaceRules(userId: number, input: PublishWorkspaceRulesInput): Promise<WorkspaceRules>;
|
|
34
|
+
/** Takes the workspace back to the empty state. */
|
|
35
|
+
deleteWorkspaceRules(userId: number, input: DeleteWorkspaceRulesInput): Promise<{
|
|
36
|
+
deleted: boolean;
|
|
37
|
+
}>;
|
|
38
|
+
}
|
|
@@ -57,6 +57,8 @@ export declare class ScoutFeedService {
|
|
|
57
57
|
distanceFromEventInMiles?: number;
|
|
58
58
|
age?: number;
|
|
59
59
|
currentCity?: string;
|
|
60
|
+
longitude?: number | null;
|
|
61
|
+
latitude?: number | null;
|
|
60
62
|
QueryBuilderType: import("objection-js-soft-delete").SoftDeleteQueryBuilder<ScoutSoccerProfile, ScoutSoccerProfile[]> & import("objection").QueryBuilder<ScoutSoccerProfile, ScoutSoccerProfile[]>;
|
|
61
63
|
$modelClass: import("objection").ModelClass<ScoutSoccerProfile>;
|
|
62
64
|
})[];
|
|
@@ -96,6 +98,8 @@ export declare class ScoutFeedService {
|
|
|
96
98
|
distanceFromEventInMiles?: number;
|
|
97
99
|
age?: number;
|
|
98
100
|
currentCity?: string;
|
|
101
|
+
longitude?: number | null;
|
|
102
|
+
latitude?: number | null;
|
|
99
103
|
QueryBuilderType: import("objection-js-soft-delete").SoftDeleteQueryBuilder<ScoutSoccerProfile, ScoutSoccerProfile[]> & import("objection").QueryBuilder<ScoutSoccerProfile, ScoutSoccerProfile[]>;
|
|
100
104
|
$modelClass: import("objection").ModelClass<ScoutSoccerProfile>;
|
|
101
105
|
})[];
|
|
@@ -143,6 +147,8 @@ export declare class ScoutFeedService {
|
|
|
143
147
|
distanceFromEventInMiles?: number;
|
|
144
148
|
age?: number;
|
|
145
149
|
currentCity?: string;
|
|
150
|
+
longitude?: number | null;
|
|
151
|
+
latitude?: number | null;
|
|
146
152
|
QueryBuilderType: import("objection-js-soft-delete").SoftDeleteQueryBuilder<ScoutSoccerProfile, ScoutSoccerProfile[]> & import("objection").QueryBuilder<ScoutSoccerProfile, ScoutSoccerProfile[]>;
|
|
147
153
|
$modelClass: import("objection").ModelClass<ScoutSoccerProfile>;
|
|
148
154
|
}[];
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
type TCoordinatesRow = {
|
|
2
|
+
longitude?: number | null;
|
|
3
|
+
latitude?: number | null;
|
|
4
|
+
} | undefined;
|
|
5
|
+
/**
|
|
6
|
+
* BAN-1924: the member's own scout profile location (set when they created/edited their
|
|
7
|
+
* scout profile) is a more reliable "where do I want to be scouted" signal than their
|
|
8
|
+
* general account address, so it takes priority when no venue has been explicitly chosen.
|
|
9
|
+
*/
|
|
10
|
+
export declare function resolveScoutFeedDefaultCoordinates(scoutProfileCoordinates: TCoordinatesRow, memberCoordinates: TCoordinatesRow): {
|
|
11
|
+
lng?: number;
|
|
12
|
+
lat?: number;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Throws when the requesting member has no usable location anywhere (no explicit
|
|
16
|
+
* venue, no scout profile location, no account address). PRECONDITION_FAILED lets
|
|
17
|
+
* clients distinguish this expected "no location set" state from a malformed request
|
|
18
|
+
* so they can degrade gracefully (eg. prompt for a location) instead of erroring out.
|
|
19
|
+
*/
|
|
20
|
+
export declare function assertHasScoutFeedLocation(coordinates?: {
|
|
21
|
+
lat?: number | null;
|
|
22
|
+
lng?: number | null;
|
|
23
|
+
} | null): asserts coordinates is {
|
|
24
|
+
lat: number;
|
|
25
|
+
lng: number;
|
|
26
|
+
};
|
|
27
|
+
export {};
|
|
@@ -35,6 +35,8 @@ export declare class ScoutSoccerProfile extends ScoutSoccerProfile_base {
|
|
|
35
35
|
distanceFromEventInMiles?: number;
|
|
36
36
|
age?: number;
|
|
37
37
|
currentCity?: string;
|
|
38
|
+
longitude?: number | null;
|
|
39
|
+
latitude?: number | null;
|
|
38
40
|
static get tableName(): string;
|
|
39
41
|
static get idColumn(): string;
|
|
40
42
|
static get relationMappings(): RelationMappings;
|