@dalmore/api-contracts 1.0.0 → 1.0.1
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/common/helpers/index.ts +88 -0
- package/common/types/account-manager.types.ts +10 -0
- package/common/types/account.types.ts +59 -2
- package/common/types/activity.types.ts +38 -0
- package/common/types/asset.types.ts +3 -0
- package/common/types/auth.types.ts +37 -10
- package/common/types/bonus-tier.types.ts +32 -0
- package/common/types/common.types.ts +19 -0
- package/common/types/contract-helpers.ts +88 -0
- package/common/types/investor-account.types.ts +137 -66
- package/common/types/phone.type.ts +1 -1
- package/common/types/secondary-trade.types.ts +8 -0
- package/common/types/trade.types.ts +81 -1
- package/common/types/user.types.ts +2 -0
- package/contracts/clients/accounts/index.ts +3 -0
- package/contracts/clients/auth/index.ts +18 -1
- package/contracts/clients/index.ts +2 -0
- package/contracts/clients/offerings/index.ts +4 -3
- package/contracts/clients/sites/index.ts +56 -0
- package/contracts/compliance/account-managers/index.ts +2 -0
- package/contracts/compliance/accounts/index.ts +4 -2
- package/contracts/compliance/bonus-tiers/index.ts +55 -0
- package/contracts/compliance/index.ts +2 -0
- package/contracts/compliance/investor-accounts/index.ts +4 -0
- package/contracts/issuers/accounts/index.ts +5 -0
- package/contracts/issuers/bonus-tiers/index.ts +18 -0
- package/contracts/issuers/investor-accounts/index.ts +22 -0
- package/package.json +12 -2
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Contract helpers - minimal subset for API contracts package
|
|
3
|
+
* This file is copied directly to @dalmore/api-contracts
|
|
4
|
+
*/
|
|
5
|
+
import { HttpStatus } from '@nestjs/common';
|
|
6
|
+
import parsePhoneNumberFromString, {
|
|
7
|
+
CountryCode,
|
|
8
|
+
PhoneNumber,
|
|
9
|
+
} from 'libphonenumber-js';
|
|
10
|
+
import { err, ok, Result } from 'neverthrow';
|
|
11
|
+
import { ErrorResult } from '../types/common.types';
|
|
12
|
+
|
|
13
|
+
type CurlOptions = {
|
|
14
|
+
query?: Record<string, string>;
|
|
15
|
+
headers?: Record<string, string>;
|
|
16
|
+
body?: Record<string, unknown>;
|
|
17
|
+
formData?: Record<string, string>;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const authHeader: CurlOptions['headers'] = {
|
|
21
|
+
Authorization: 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9',
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const generateCurlExample = (
|
|
25
|
+
method: string,
|
|
26
|
+
path: string,
|
|
27
|
+
options: CurlOptions,
|
|
28
|
+
) => {
|
|
29
|
+
const baseUrl = 'https://dalmore-client-portal-api-prod.onrender.com/api/v1';
|
|
30
|
+
let curl = `curl -X ${method.toUpperCase()} '${baseUrl}${path}`;
|
|
31
|
+
if (options.query) {
|
|
32
|
+
const queryString = new URLSearchParams(options.query).toString();
|
|
33
|
+
curl += `?${queryString}`;
|
|
34
|
+
}
|
|
35
|
+
curl += "'";
|
|
36
|
+
if (options.headers) {
|
|
37
|
+
Object.entries(options.headers).forEach(([key, value]) => {
|
|
38
|
+
curl += ` \\\n --header '${key}: ${value}'`;
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
if (options.body) {
|
|
42
|
+
curl += ` \\\n --header 'Content-Type: application/json'`;
|
|
43
|
+
curl += ` \\\n --data '${JSON.stringify(options.body)}'`;
|
|
44
|
+
}
|
|
45
|
+
if (options.formData) {
|
|
46
|
+
Object.entries(options.formData).forEach(([key, value]) => {
|
|
47
|
+
curl += ` \\\n --form '${key}=${value}'`;
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
return curl;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export const generateApiDescription = (
|
|
54
|
+
summary: string,
|
|
55
|
+
method: string,
|
|
56
|
+
path: string,
|
|
57
|
+
options: CurlOptions,
|
|
58
|
+
) => {
|
|
59
|
+
const curlExample = generateCurlExample(method, path, options);
|
|
60
|
+
return `${summary}
|
|
61
|
+
|
|
62
|
+
### Example curl request
|
|
63
|
+
|
|
64
|
+
\`\`\`bash
|
|
65
|
+
${curlExample}
|
|
66
|
+
\`\`\``;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Normalizes a phone number to E.164 format
|
|
71
|
+
*/
|
|
72
|
+
export function normalizePhoneNumber(
|
|
73
|
+
phoneNumber: string,
|
|
74
|
+
countryCode: CountryCode,
|
|
75
|
+
): Result<PhoneNumber, ErrorResult> {
|
|
76
|
+
const parsedPhoneNumber = parsePhoneNumberFromString(
|
|
77
|
+
phoneNumber,
|
|
78
|
+
countryCode,
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
if (!parsedPhoneNumber || !parsedPhoneNumber.isValid()) {
|
|
82
|
+
return err({
|
|
83
|
+
status: HttpStatus.BAD_REQUEST,
|
|
84
|
+
message: 'Invalid phone number. Only numbers',
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
return ok(parsedPhoneNumber);
|
|
88
|
+
}
|
|
@@ -112,3 +112,13 @@ export const SetDefaultAccountManagerForAllAccountsResponse = z.object({
|
|
|
112
112
|
export type SetDefaultAccountManagerForAllAccountsResponse = z.infer<
|
|
113
113
|
typeof SetDefaultAccountManagerForAllAccountsResponse
|
|
114
114
|
>;
|
|
115
|
+
|
|
116
|
+
export const AccountManagerDeleteQuery = z.object({
|
|
117
|
+
reassignTo: accountManagerIdSchema
|
|
118
|
+
.nullable()
|
|
119
|
+
.optional()
|
|
120
|
+
.openapi({ example: 'account_manager_01j5y5ghx5fg68d663j1fvy2x7' }),
|
|
121
|
+
});
|
|
122
|
+
export type AccountManagerDeleteQuery = z.infer<
|
|
123
|
+
typeof AccountManagerDeleteQuery
|
|
124
|
+
>;
|
|
@@ -47,7 +47,7 @@ export const UpdateAccountZod = z.object({
|
|
|
47
47
|
export type UpdateAccountZod = z.infer<typeof UpdateAccountZod>;
|
|
48
48
|
|
|
49
49
|
export const CreateAccountZod = z.object({
|
|
50
|
-
name: z.string().min(1).max(100),
|
|
50
|
+
name: z.string().min(1).max(100).openapi({ example: 'Name' }),
|
|
51
51
|
platform: z
|
|
52
52
|
.nativeEnum(Platform)
|
|
53
53
|
.optional()
|
|
@@ -56,7 +56,14 @@ export const CreateAccountZod = z.object({
|
|
|
56
56
|
.nativeEnum(ManagedByType)
|
|
57
57
|
.optional()
|
|
58
58
|
.openapi({ example: ManagedByType.DALMORE }),
|
|
59
|
-
approversCount: z.number().min(1).max(5).optional(),
|
|
59
|
+
approversCount: z.number().min(1).max(5).optional().openapi({ example: 2 }),
|
|
60
|
+
hubspotCompany: z
|
|
61
|
+
.string()
|
|
62
|
+
.url()
|
|
63
|
+
.max(2048)
|
|
64
|
+
.nullable()
|
|
65
|
+
.optional()
|
|
66
|
+
.openapi({ example: 'https://www.google.com' }),
|
|
60
67
|
});
|
|
61
68
|
export type CreateAccountZod = z.infer<typeof CreateAccountZod>;
|
|
62
69
|
|
|
@@ -135,6 +142,19 @@ export const PatchAccountZod = z.object({
|
|
|
135
142
|
});
|
|
136
143
|
export type PatchAccountZod = z.infer<typeof PatchAccountZod>;
|
|
137
144
|
|
|
145
|
+
export const CompliancePatchAccountZod = PatchAccountZod.extend({
|
|
146
|
+
hubspotCompany: z
|
|
147
|
+
.string()
|
|
148
|
+
.url()
|
|
149
|
+
.max(2048)
|
|
150
|
+
.nullable()
|
|
151
|
+
.optional()
|
|
152
|
+
.openapi({ example: 'https://www.google.com' }),
|
|
153
|
+
});
|
|
154
|
+
export type CompliancePatchAccountZod = z.infer<
|
|
155
|
+
typeof CompliancePatchAccountZod
|
|
156
|
+
>;
|
|
157
|
+
|
|
138
158
|
export const PostAccountResponse = z.object({
|
|
139
159
|
id: z.string(),
|
|
140
160
|
});
|
|
@@ -166,6 +186,41 @@ export const AccountsSummaryFilterZod = z.object({
|
|
|
166
186
|
status: z.nativeEnum(AccountStatus).optional(),
|
|
167
187
|
from: dateSchema.optional().openapi({ example: 'MM/DD/YYYY' }),
|
|
168
188
|
to: dateSchema.optional().openapi({ example: 'MM/DD/YYYY' }),
|
|
189
|
+
accountManagerIds: z
|
|
190
|
+
.string()
|
|
191
|
+
.optional()
|
|
192
|
+
.transform((val) => {
|
|
193
|
+
if (!val || typeof val !== 'string') return undefined;
|
|
194
|
+
|
|
195
|
+
const ids = val
|
|
196
|
+
.split(',')
|
|
197
|
+
.map((id: string) => id.trim())
|
|
198
|
+
.filter((id: string) => id.length > 0);
|
|
199
|
+
|
|
200
|
+
// Remove duplicates using Set
|
|
201
|
+
const uniqueIds = [...new Set(ids)];
|
|
202
|
+
|
|
203
|
+
return uniqueIds.length > 0 ? uniqueIds : undefined;
|
|
204
|
+
})
|
|
205
|
+
.pipe(
|
|
206
|
+
z
|
|
207
|
+
.array(
|
|
208
|
+
z.string().refine(
|
|
209
|
+
(value) => {
|
|
210
|
+
try {
|
|
211
|
+
const tid = TypeID.fromString(value);
|
|
212
|
+
return tid.getType() === 'account_manager';
|
|
213
|
+
} catch {
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
message: `Invalid account manager ID format. Must be a valid TypeID with "account_manager" prefix. Example: account_manager_01j5y5ghx5fg68d663j1fvy2x7`,
|
|
219
|
+
},
|
|
220
|
+
),
|
|
221
|
+
)
|
|
222
|
+
.optional(),
|
|
223
|
+
),
|
|
169
224
|
});
|
|
170
225
|
export type AccountsSummaryFilterZod = z.infer<typeof AccountsSummaryFilterZod>;
|
|
171
226
|
|
|
@@ -182,6 +237,8 @@ export const AccountsSummaryZod = z.object({
|
|
|
182
237
|
offerings: z.number().int(),
|
|
183
238
|
assets: z.number().int(),
|
|
184
239
|
users: z.number().int(),
|
|
240
|
+
trades: z.number().int(),
|
|
241
|
+
accountManagerName: z.string(),
|
|
185
242
|
createdOn: z.string(),
|
|
186
243
|
status: z.string(),
|
|
187
244
|
});
|
|
@@ -134,6 +134,8 @@ export enum ActivityTypeAction {
|
|
|
134
134
|
RE_PROCESS = BaseStatus.RE_PROCESS,
|
|
135
135
|
SAVE = BaseStatus.SAVE,
|
|
136
136
|
ACCEPTED = BaseStatus.ACCEPTED,
|
|
137
|
+
JOIN = BaseStatus.JOIN,
|
|
138
|
+
RESTORE = BaseStatus.RESTORE,
|
|
137
139
|
}
|
|
138
140
|
|
|
139
141
|
export const MethodMap: { [key: string]: ActivityTypeAction } = {
|
|
@@ -215,6 +217,9 @@ export const ActivitiesFilters = z.object({
|
|
|
215
217
|
targetId: z
|
|
216
218
|
.string()
|
|
217
219
|
.optional()
|
|
220
|
+
.openapi({
|
|
221
|
+
example: 'disbursement_01j6aqmtfyfwy9spjdcnh7yqk7',
|
|
222
|
+
})
|
|
218
223
|
.refine(
|
|
219
224
|
(value) => {
|
|
220
225
|
try {
|
|
@@ -232,5 +237,38 @@ export const ActivitiesFilters = z.object({
|
|
|
232
237
|
},
|
|
233
238
|
),
|
|
234
239
|
search: z.string().max(50).optional(),
|
|
240
|
+
targetIds: z
|
|
241
|
+
.string()
|
|
242
|
+
.optional()
|
|
243
|
+
.openapi({
|
|
244
|
+
example:
|
|
245
|
+
'disbursement_01j6aqmtfyfwy9spjdcnh7yqk7,disbursement_01j6aqmtfyfwy9spjdcnh7yqk8',
|
|
246
|
+
})
|
|
247
|
+
.transform((str) => {
|
|
248
|
+
if (!str) return [];
|
|
249
|
+
// Split by comma, trim whitespace, and remove duplicates using Set
|
|
250
|
+
const ids = str.split(',').map((id) => id.trim());
|
|
251
|
+
return Array.from(new Set(ids));
|
|
252
|
+
})
|
|
253
|
+
.refine(
|
|
254
|
+
(value) => {
|
|
255
|
+
if (!value || value.length === 0) return true;
|
|
256
|
+
|
|
257
|
+
// Validate each ID
|
|
258
|
+
return value.every((id) => {
|
|
259
|
+
try {
|
|
260
|
+
const tid = TypeID.fromString(id);
|
|
261
|
+
return Object.values(TargetTableConfig).some(
|
|
262
|
+
(config) => config.idPrefix === tid.getType(),
|
|
263
|
+
);
|
|
264
|
+
} catch {
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
});
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
message: `Invalid target IDs. All IDs must match the corresponding table's ID prefix. Valid prefix: ${TargetTableEnum.toString().toLowerCase()}. Example: disbursement_01j6aqmtfyfwy9spjdcnh7yqk7`,
|
|
271
|
+
},
|
|
272
|
+
),
|
|
235
273
|
});
|
|
236
274
|
export type ActivitiesFilters = z.infer<typeof ActivitiesFilters>;
|
|
@@ -53,6 +53,7 @@ export const IAsset = IBaseEntity.extend({
|
|
|
53
53
|
.nullable(), // Use z.lazy here
|
|
54
54
|
template: z.nativeEnum(TemplateType),
|
|
55
55
|
tiers: z.array(z.number().positive()).nullable(),
|
|
56
|
+
enableBonus: z.boolean(),
|
|
56
57
|
});
|
|
57
58
|
|
|
58
59
|
export type IAsset = z.infer<typeof IAsset>;
|
|
@@ -105,6 +106,7 @@ const PostAssetBase = z.object({
|
|
|
105
106
|
.default(TemplateType.STANDARD)
|
|
106
107
|
.openapi({ example: TemplateType.STANDARD }),
|
|
107
108
|
tiers: z.array(z.number().positive()).nullable().optional(),
|
|
109
|
+
enableBonus: z.boolean().default(false).openapi({ example: false }),
|
|
108
110
|
});
|
|
109
111
|
|
|
110
112
|
const postAssetRefinement = (data: any, ctx: any) => {
|
|
@@ -237,6 +239,7 @@ export const PutAsset = z.object({
|
|
|
237
239
|
.nullable()
|
|
238
240
|
.optional(),
|
|
239
241
|
tiers: z.array(z.number().positive()).nullable().optional(),
|
|
242
|
+
enableBonus: z.boolean().optional().openapi({ example: false }),
|
|
240
243
|
});
|
|
241
244
|
|
|
242
245
|
export type PutAsset = z.infer<typeof PutAsset>;
|
|
@@ -49,14 +49,14 @@ const BaseAuthBody = z.object({
|
|
|
49
49
|
firstName: z
|
|
50
50
|
.string()
|
|
51
51
|
.min(2, 'firstName is less than 2 characters')
|
|
52
|
-
.max(20, 'firstName is more than
|
|
52
|
+
.max(20, 'firstName is more than 20 characters')
|
|
53
53
|
.openapi({
|
|
54
54
|
example: 'Neil',
|
|
55
55
|
}),
|
|
56
56
|
lastName: z
|
|
57
57
|
.string()
|
|
58
58
|
.min(2, 'lastName is less than 2 characters')
|
|
59
|
-
.max(20, 'lastName is more than
|
|
59
|
+
.max(20, 'lastName is more than 20 characters')
|
|
60
60
|
.openapi({
|
|
61
61
|
example: 'Armstrong',
|
|
62
62
|
}),
|
|
@@ -201,7 +201,8 @@ export const AccessTokenResponse = z.object({
|
|
|
201
201
|
});
|
|
202
202
|
export type AccessTokenResponse = z.infer<typeof AccessTokenResponse>;
|
|
203
203
|
|
|
204
|
-
|
|
204
|
+
// Base schema for issuer registration (shared fields without refinements)
|
|
205
|
+
const BaseRegisterIssuerFields = BaseAuthBody.extend({
|
|
205
206
|
confirmPassword: PasswordSchema,
|
|
206
207
|
accountName: z
|
|
207
208
|
.string()
|
|
@@ -212,6 +213,17 @@ export const IRegisterIssuerBodyZod = BaseAuthBody.extend({
|
|
|
212
213
|
})
|
|
213
214
|
.nullable()
|
|
214
215
|
.optional(),
|
|
216
|
+
accountType: z
|
|
217
|
+
.nativeEnum(ManagedByType)
|
|
218
|
+
.optional()
|
|
219
|
+
.default(ManagedByType.DALMORE)
|
|
220
|
+
.openapi({
|
|
221
|
+
example: ManagedByType.DALMORE,
|
|
222
|
+
}),
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
// Full issuer registration schema with code (for invite-based registration)
|
|
226
|
+
export const IRegisterIssuerBodyZod = BaseRegisterIssuerFields.extend({
|
|
215
227
|
code: z
|
|
216
228
|
.string()
|
|
217
229
|
.refine(isBase64, {
|
|
@@ -221,13 +233,6 @@ export const IRegisterIssuerBodyZod = BaseAuthBody.extend({
|
|
|
221
233
|
.openapi({
|
|
222
234
|
example: 'eyJpdiI6InhhTFIyVG9ETnhnUzQ1QzUyNzRJenc9PSIsI',
|
|
223
235
|
}),
|
|
224
|
-
accountType: z
|
|
225
|
-
.nativeEnum(ManagedByType)
|
|
226
|
-
.optional()
|
|
227
|
-
.default(ManagedByType.DALMORE)
|
|
228
|
-
.openapi({
|
|
229
|
-
example: ManagedByType.DALMORE,
|
|
230
|
-
}),
|
|
231
236
|
})
|
|
232
237
|
.refine((data) => data.password === data.confirmPassword, {
|
|
233
238
|
message: 'Passwords do not match',
|
|
@@ -252,6 +257,28 @@ export const IRegisterIssuerBodyZod = BaseAuthBody.extend({
|
|
|
252
257
|
},
|
|
253
258
|
);
|
|
254
259
|
|
|
260
|
+
// Client issuer registration schema without code (for API key registration)
|
|
261
|
+
export const IRegisterClientIssuerBodyZod = BaseRegisterIssuerFields.refine(
|
|
262
|
+
(data) => data.password === data.confirmPassword,
|
|
263
|
+
{
|
|
264
|
+
message: 'Passwords do not match',
|
|
265
|
+
path: ['confirmPassword'],
|
|
266
|
+
},
|
|
267
|
+
).refine(
|
|
268
|
+
(data) => {
|
|
269
|
+
// For client registration, accountName is always required
|
|
270
|
+
return (
|
|
271
|
+
data.accountName !== null &&
|
|
272
|
+
data.accountName !== undefined &&
|
|
273
|
+
data.accountName !== ''
|
|
274
|
+
);
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
message: 'Account name is required',
|
|
278
|
+
path: ['accountName'],
|
|
279
|
+
},
|
|
280
|
+
);
|
|
281
|
+
|
|
255
282
|
// Investors are a special case in which they require phone number at the time of registration
|
|
256
283
|
// The phone number provided here is NOT validated and will still need to go thru the regular 2FA setup process
|
|
257
284
|
export const RegisterBodyInvestors = BaseAuthBody.extend({
|
|
@@ -66,6 +66,9 @@ export const IBonusTier = IBaseEntity.extend({
|
|
|
66
66
|
endAmount: z.number().nullable(),
|
|
67
67
|
});
|
|
68
68
|
|
|
69
|
+
export const IBonusTierList = z.array(IBonusTier);
|
|
70
|
+
export type IBonusTierList = z.infer<typeof IBonusTierList>;
|
|
71
|
+
|
|
69
72
|
export const EstimateBonusTierCalculationZod = z
|
|
70
73
|
.object({
|
|
71
74
|
assetId: assetIdSchema
|
|
@@ -113,3 +116,32 @@ export const EstimateBonusTierCalculationResponseZod = z.object({
|
|
|
113
116
|
export type EstimateBonusTierCalculationResponseZod = z.infer<
|
|
114
117
|
typeof EstimateBonusTierCalculationResponseZod
|
|
115
118
|
>;
|
|
119
|
+
export type IBonusTier = z.infer<typeof IBonusTier>;
|
|
120
|
+
export const BonusTierListZod = z
|
|
121
|
+
.array(
|
|
122
|
+
z.object({
|
|
123
|
+
type: z.nativeEnum(BonusType).openapi({ example: BonusType.PERCENTAGE }),
|
|
124
|
+
value: z.number().positive().int().max(1000000).openapi({ example: 50 }),
|
|
125
|
+
startAmount: z.number().max(999999999).openapi({ example: 100 }),
|
|
126
|
+
endAmount: z.number().max(999999999).openapi({ example: 200 }),
|
|
127
|
+
}),
|
|
128
|
+
)
|
|
129
|
+
.min(1, { message: 'At least one bonus tier is required' });
|
|
130
|
+
export type BonusTierListZod = z.infer<typeof BonusTierListZod>;
|
|
131
|
+
|
|
132
|
+
export const PostBonusTierZod = z.object({
|
|
133
|
+
assetId: assetIdSchema.openapi({
|
|
134
|
+
example: 'asset_00041061050r3gg28a1c60t3gf',
|
|
135
|
+
}),
|
|
136
|
+
bonusTiers: BonusTierListZod,
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
export type PostBonusTierZod = z.infer<typeof PostBonusTierZod>;
|
|
140
|
+
|
|
141
|
+
export const CompliancePostBonusTierZod = PostBonusTierZod.extend({
|
|
142
|
+
accountId: accountIdSchema,
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
export type CompliancePostBonusTierZod = z.infer<
|
|
146
|
+
typeof CompliancePostBonusTierZod
|
|
147
|
+
>;
|
|
@@ -67,6 +67,8 @@ export enum BaseStatus {
|
|
|
67
67
|
INACTIVE = 'INACTIVE',
|
|
68
68
|
PARTIALLY_REFUNDED = 'PARTIALLY_REFUNDED',
|
|
69
69
|
VOIDED = 'VOIDED',
|
|
70
|
+
JOIN = 'JOIN',
|
|
71
|
+
RESTORE = 'RESTORE',
|
|
70
72
|
}
|
|
71
73
|
|
|
72
74
|
export const UserWithoutPasswordAccountZod = IBaseEntity.extend({
|
|
@@ -809,6 +811,15 @@ export enum EventField {
|
|
|
809
811
|
TRANSACTION_STATUS = 'transactionStatus',
|
|
810
812
|
}
|
|
811
813
|
|
|
814
|
+
export enum EventName {
|
|
815
|
+
STATUS_CHANGED = 'status.changed',
|
|
816
|
+
UPDATE_FIELD = 'update.field',
|
|
817
|
+
ACTION_PERFORMED = 'action.performed',
|
|
818
|
+
PENDING_OVERDUE_TRADES_ALERT = 'pending_overdue.trades.alert',
|
|
819
|
+
IN_REVIEW_TRADES_ALERT = 'trade.inreview.alert',
|
|
820
|
+
INDIVIDUAL_AML_STATUS_CHANGED = 'individual.aml_status.changed',
|
|
821
|
+
}
|
|
822
|
+
|
|
812
823
|
// pending charged settled refunded cancelled
|
|
813
824
|
export enum TransactionStatus {
|
|
814
825
|
PENDING = BaseStatus.PENDING,
|
|
@@ -1499,3 +1510,11 @@ export enum JobState {
|
|
|
1499
1510
|
PRIORITIZED = 'prioritized',
|
|
1500
1511
|
WAITING_CHILDREN = 'waiting-children',
|
|
1501
1512
|
}
|
|
1513
|
+
|
|
1514
|
+
export const SUBJECT_TYPE_MAP: Record<BulkExportType, string> = {
|
|
1515
|
+
[BulkExportType.INVESTOR_ACCOUNTS]: 'Investor Accounts',
|
|
1516
|
+
[BulkExportType.TRADES]: 'Trades',
|
|
1517
|
+
[BulkExportType.COMPLIANCE_TRADES]: 'Compliance Trades',
|
|
1518
|
+
[BulkExportType.SECONDARY_CUSTOMERS]: 'Secondary Customers',
|
|
1519
|
+
[BulkExportType.SECONDARY_TRADES]: 'Secondary Trades',
|
|
1520
|
+
};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Contract helpers - minimal subset for API contracts package
|
|
3
|
+
* This file is copied directly to @dalmore/api-contracts
|
|
4
|
+
*/
|
|
5
|
+
import { HttpStatus } from '@nestjs/common';
|
|
6
|
+
import parsePhoneNumberFromString, {
|
|
7
|
+
CountryCode,
|
|
8
|
+
PhoneNumber,
|
|
9
|
+
} from 'libphonenumber-js';
|
|
10
|
+
import { err, ok, Result } from 'neverthrow';
|
|
11
|
+
import { ErrorResult } from './common.types';
|
|
12
|
+
|
|
13
|
+
type CurlOptions = {
|
|
14
|
+
query?: Record<string, string>;
|
|
15
|
+
headers?: Record<string, string>;
|
|
16
|
+
body?: Record<string, unknown>;
|
|
17
|
+
formData?: Record<string, string>;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const authHeader: CurlOptions['headers'] = {
|
|
21
|
+
Authorization: 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9',
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const generateCurlExample = (
|
|
25
|
+
method: string,
|
|
26
|
+
path: string,
|
|
27
|
+
options: CurlOptions,
|
|
28
|
+
) => {
|
|
29
|
+
const baseUrl = 'https://dalmore-client-portal-api-prod.onrender.com/api/v1';
|
|
30
|
+
let curl = `curl -X ${method.toUpperCase()} '${baseUrl}${path}`;
|
|
31
|
+
if (options.query) {
|
|
32
|
+
const queryString = new URLSearchParams(options.query).toString();
|
|
33
|
+
curl += `?${queryString}`;
|
|
34
|
+
}
|
|
35
|
+
curl += "'";
|
|
36
|
+
if (options.headers) {
|
|
37
|
+
Object.entries(options.headers).forEach(([key, value]) => {
|
|
38
|
+
curl += ` \\\n --header '${key}: ${value}'`;
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
if (options.body) {
|
|
42
|
+
curl += ` \\\n --header 'Content-Type: application/json'`;
|
|
43
|
+
curl += ` \\\n --data '${JSON.stringify(options.body)}'`;
|
|
44
|
+
}
|
|
45
|
+
if (options.formData) {
|
|
46
|
+
Object.entries(options.formData).forEach(([key, value]) => {
|
|
47
|
+
curl += ` \\\n --form '${key}=${value}'`;
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
return curl;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export const generateApiDescription = (
|
|
54
|
+
summary: string,
|
|
55
|
+
method: string,
|
|
56
|
+
path: string,
|
|
57
|
+
options: CurlOptions,
|
|
58
|
+
) => {
|
|
59
|
+
const curlExample = generateCurlExample(method, path, options);
|
|
60
|
+
return `${summary}
|
|
61
|
+
|
|
62
|
+
### Example curl request
|
|
63
|
+
|
|
64
|
+
\`\`\`bash
|
|
65
|
+
${curlExample}
|
|
66
|
+
\`\`\``;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Normalizes a phone number to E.164 format
|
|
71
|
+
*/
|
|
72
|
+
export function normalizePhoneNumber(
|
|
73
|
+
phoneNumber: string,
|
|
74
|
+
countryCode: CountryCode,
|
|
75
|
+
): Result<PhoneNumber, ErrorResult> {
|
|
76
|
+
const parsedPhoneNumber = parsePhoneNumberFromString(
|
|
77
|
+
phoneNumber,
|
|
78
|
+
countryCode,
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
if (!parsedPhoneNumber || !parsedPhoneNumber.isValid()) {
|
|
82
|
+
return err({
|
|
83
|
+
status: HttpStatus.BAD_REQUEST,
|
|
84
|
+
message: 'Invalid phone number. Only numbers',
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
return ok(parsedPhoneNumber);
|
|
88
|
+
}
|