@dalmore/api-contracts 1.0.1 → 1.0.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/common/helpers/index.ts +117 -0
- package/common/types/batch-jobs.types.ts +0 -1
- package/common/types/common.types.ts +1 -1
- package/common/types/contract-helpers.ts +117 -0
- package/common/types/page.types.ts +1 -1
- package/common/types/queue.types.ts +0 -1
- package/common/types/trade.types.ts +4 -2
- package/common/types/zip.type.ts +1 -1
- package/package.json +1 -1
- package/common/types/reminder-config.types.ts +0 -40
package/common/helpers/index.ts
CHANGED
|
@@ -10,6 +10,123 @@ import parsePhoneNumberFromString, {
|
|
|
10
10
|
import { err, ok, Result } from 'neverthrow';
|
|
11
11
|
import { ErrorResult } from '../types/common.types';
|
|
12
12
|
|
|
13
|
+
/**
|
|
14
|
+
* Validates a US zip code format (12345 or 12345-6789)
|
|
15
|
+
*/
|
|
16
|
+
export const validateUSZipCode = (zipCode: string): boolean => {
|
|
17
|
+
const regex = /^[0-9]{5}(?:-[0-9]{4})?$/;
|
|
18
|
+
return regex.test(zipCode);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Validates a Canadian postal code format (A1A 1A1)
|
|
23
|
+
*/
|
|
24
|
+
export const validateCanadaZipCode = (zipCode: string): boolean => {
|
|
25
|
+
const regex = /^[A-Za-z]\d[A-Za-z] \d[A-Za-z]\d$/;
|
|
26
|
+
return regex.test(zipCode);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Normalizes a short date string (M/D/YY or M/D/YYYY) to MM/DD/YYYY format
|
|
31
|
+
*/
|
|
32
|
+
export function normalizeShortDate(input: string): string {
|
|
33
|
+
const [month, day, year] = input.split('/');
|
|
34
|
+
|
|
35
|
+
if (!month || !day || !year) {
|
|
36
|
+
throw new Error('Invalid date format');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const paddedMonth = month.padStart(2, '0');
|
|
40
|
+
const paddedDay = day.padStart(2, '0');
|
|
41
|
+
const fullYear = year.length === 2 ? `20${year}` : year;
|
|
42
|
+
|
|
43
|
+
return `${paddedMonth}/${paddedDay}/${fullYear}`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Converts a string into a URL-safe slug
|
|
48
|
+
*/
|
|
49
|
+
export function slugify(input: string, exclude: boolean = false): string {
|
|
50
|
+
if (exclude) {
|
|
51
|
+
const excludeWords = ['llc', 'inc', 'corp', 'co'];
|
|
52
|
+
const negativeWordsList = [
|
|
53
|
+
'www',
|
|
54
|
+
'api',
|
|
55
|
+
'admin',
|
|
56
|
+
'dashboard',
|
|
57
|
+
'auth',
|
|
58
|
+
'cdn',
|
|
59
|
+
'static',
|
|
60
|
+
'blog',
|
|
61
|
+
'support',
|
|
62
|
+
'status',
|
|
63
|
+
'mail',
|
|
64
|
+
'smtp',
|
|
65
|
+
'ftp',
|
|
66
|
+
'ssh',
|
|
67
|
+
'login',
|
|
68
|
+
'register',
|
|
69
|
+
'signup',
|
|
70
|
+
'account',
|
|
71
|
+
'accounts',
|
|
72
|
+
'user',
|
|
73
|
+
'users',
|
|
74
|
+
'profile',
|
|
75
|
+
'settings',
|
|
76
|
+
'help',
|
|
77
|
+
'docs',
|
|
78
|
+
'documentation',
|
|
79
|
+
'developer',
|
|
80
|
+
'developers',
|
|
81
|
+
'app',
|
|
82
|
+
'apps',
|
|
83
|
+
'test',
|
|
84
|
+
'demo',
|
|
85
|
+
'staging',
|
|
86
|
+
'dev',
|
|
87
|
+
'prod',
|
|
88
|
+
'production',
|
|
89
|
+
'beta',
|
|
90
|
+
'alpha',
|
|
91
|
+
'portal',
|
|
92
|
+
'portals',
|
|
93
|
+
'client',
|
|
94
|
+
'clients',
|
|
95
|
+
'investor',
|
|
96
|
+
'investors',
|
|
97
|
+
'issuer',
|
|
98
|
+
'issuers',
|
|
99
|
+
'compliance',
|
|
100
|
+
'offering',
|
|
101
|
+
'offerings',
|
|
102
|
+
'trade',
|
|
103
|
+
'trades',
|
|
104
|
+
];
|
|
105
|
+
|
|
106
|
+
let slug = input
|
|
107
|
+
.toLowerCase()
|
|
108
|
+
.replace(/[^\w\s-]/g, '')
|
|
109
|
+
.replace(/[\s_-]+/g, '-')
|
|
110
|
+
.replace(/^-+|-+$/g, '');
|
|
111
|
+
|
|
112
|
+
excludeWords.forEach((word) => {
|
|
113
|
+
slug = slug.replace(new RegExp(`-${word}$`, 'i'), '');
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
if (negativeWordsList.includes(slug)) {
|
|
117
|
+
return 'new-account';
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return slug;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return input
|
|
124
|
+
.toLowerCase()
|
|
125
|
+
.replace(/[^\w\s-]/g, '')
|
|
126
|
+
.replace(/[\s_-]+/g, '-')
|
|
127
|
+
.replace(/^-+|-+$/g, '');
|
|
128
|
+
}
|
|
129
|
+
|
|
13
130
|
type CurlOptions = {
|
|
14
131
|
query?: Record<string, string>;
|
|
15
132
|
headers?: Record<string, string>;
|
|
@@ -5,7 +5,6 @@ import { dateSchema, IPaginationMeta, UserRole } from './common.types';
|
|
|
5
5
|
import { userIdSchema } from './user.types';
|
|
6
6
|
import { accountIdSchema } from './account.types';
|
|
7
7
|
import { IBaseEntity } from './entity.types';
|
|
8
|
-
import { JobsOptions } from 'bullmq';
|
|
9
8
|
|
|
10
9
|
extendZodWithOpenApi(z);
|
|
11
10
|
|
|
@@ -4,7 +4,7 @@ import { z } from 'zod';
|
|
|
4
4
|
import { TypeID } from 'typeid-js';
|
|
5
5
|
import { ErrorHttpStatusCode } from '@ts-rest/core';
|
|
6
6
|
import { TwoFactorMethod } from './sms.types';
|
|
7
|
-
import { normalizeShortDate } from '
|
|
7
|
+
import { normalizeShortDate } from './contract-helpers';
|
|
8
8
|
import { HttpStatus } from '@nestjs/common';
|
|
9
9
|
|
|
10
10
|
extendZodWithOpenApi(z);
|
|
@@ -10,6 +10,123 @@ import parsePhoneNumberFromString, {
|
|
|
10
10
|
import { err, ok, Result } from 'neverthrow';
|
|
11
11
|
import { ErrorResult } from './common.types';
|
|
12
12
|
|
|
13
|
+
/**
|
|
14
|
+
* Validates a US zip code format (12345 or 12345-6789)
|
|
15
|
+
*/
|
|
16
|
+
export const validateUSZipCode = (zipCode: string): boolean => {
|
|
17
|
+
const regex = /^[0-9]{5}(?:-[0-9]{4})?$/;
|
|
18
|
+
return regex.test(zipCode);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Validates a Canadian postal code format (A1A 1A1)
|
|
23
|
+
*/
|
|
24
|
+
export const validateCanadaZipCode = (zipCode: string): boolean => {
|
|
25
|
+
const regex = /^[A-Za-z]\d[A-Za-z] \d[A-Za-z]\d$/;
|
|
26
|
+
return regex.test(zipCode);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Normalizes a short date string (M/D/YY or M/D/YYYY) to MM/DD/YYYY format
|
|
31
|
+
*/
|
|
32
|
+
export function normalizeShortDate(input: string): string {
|
|
33
|
+
const [month, day, year] = input.split('/');
|
|
34
|
+
|
|
35
|
+
if (!month || !day || !year) {
|
|
36
|
+
throw new Error('Invalid date format');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const paddedMonth = month.padStart(2, '0');
|
|
40
|
+
const paddedDay = day.padStart(2, '0');
|
|
41
|
+
const fullYear = year.length === 2 ? `20${year}` : year;
|
|
42
|
+
|
|
43
|
+
return `${paddedMonth}/${paddedDay}/${fullYear}`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Converts a string into a URL-safe slug
|
|
48
|
+
*/
|
|
49
|
+
export function slugify(input: string, exclude: boolean = false): string {
|
|
50
|
+
if (exclude) {
|
|
51
|
+
const excludeWords = ['llc', 'inc', 'corp', 'co'];
|
|
52
|
+
const negativeWordsList = [
|
|
53
|
+
'www',
|
|
54
|
+
'api',
|
|
55
|
+
'admin',
|
|
56
|
+
'dashboard',
|
|
57
|
+
'auth',
|
|
58
|
+
'cdn',
|
|
59
|
+
'static',
|
|
60
|
+
'blog',
|
|
61
|
+
'support',
|
|
62
|
+
'status',
|
|
63
|
+
'mail',
|
|
64
|
+
'smtp',
|
|
65
|
+
'ftp',
|
|
66
|
+
'ssh',
|
|
67
|
+
'login',
|
|
68
|
+
'register',
|
|
69
|
+
'signup',
|
|
70
|
+
'account',
|
|
71
|
+
'accounts',
|
|
72
|
+
'user',
|
|
73
|
+
'users',
|
|
74
|
+
'profile',
|
|
75
|
+
'settings',
|
|
76
|
+
'help',
|
|
77
|
+
'docs',
|
|
78
|
+
'documentation',
|
|
79
|
+
'developer',
|
|
80
|
+
'developers',
|
|
81
|
+
'app',
|
|
82
|
+
'apps',
|
|
83
|
+
'test',
|
|
84
|
+
'demo',
|
|
85
|
+
'staging',
|
|
86
|
+
'dev',
|
|
87
|
+
'prod',
|
|
88
|
+
'production',
|
|
89
|
+
'beta',
|
|
90
|
+
'alpha',
|
|
91
|
+
'portal',
|
|
92
|
+
'portals',
|
|
93
|
+
'client',
|
|
94
|
+
'clients',
|
|
95
|
+
'investor',
|
|
96
|
+
'investors',
|
|
97
|
+
'issuer',
|
|
98
|
+
'issuers',
|
|
99
|
+
'compliance',
|
|
100
|
+
'offering',
|
|
101
|
+
'offerings',
|
|
102
|
+
'trade',
|
|
103
|
+
'trades',
|
|
104
|
+
];
|
|
105
|
+
|
|
106
|
+
let slug = input
|
|
107
|
+
.toLowerCase()
|
|
108
|
+
.replace(/[^\w\s-]/g, '')
|
|
109
|
+
.replace(/[\s_-]+/g, '-')
|
|
110
|
+
.replace(/^-+|-+$/g, '');
|
|
111
|
+
|
|
112
|
+
excludeWords.forEach((word) => {
|
|
113
|
+
slug = slug.replace(new RegExp(`-${word}$`, 'i'), '');
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
if (negativeWordsList.includes(slug)) {
|
|
117
|
+
return 'new-account';
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return slug;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return input
|
|
124
|
+
.toLowerCase()
|
|
125
|
+
.replace(/[^\w\s-]/g, '')
|
|
126
|
+
.replace(/[\s_-]+/g, '-')
|
|
127
|
+
.replace(/^-+|-+$/g, '');
|
|
128
|
+
}
|
|
129
|
+
|
|
13
130
|
type CurlOptions = {
|
|
14
131
|
query?: Record<string, string>;
|
|
15
132
|
headers?: Record<string, string>;
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
UrlSchema,
|
|
15
15
|
} from './common.types';
|
|
16
16
|
import { accountIdSchema } from './account.types';
|
|
17
|
-
import { slugify } from '
|
|
17
|
+
import { slugify } from './contract-helpers';
|
|
18
18
|
import { fileIdSchema } from './file.types';
|
|
19
19
|
|
|
20
20
|
extendZodWithOpenApi(z);
|
|
@@ -53,8 +53,10 @@ import {
|
|
|
53
53
|
SecondaryTrade,
|
|
54
54
|
SecondaryTradeFiltersZod,
|
|
55
55
|
} from './secondary-trade.types';
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
|
|
57
|
+
// Stub types for backend entities (not included in contracts package)
|
|
58
|
+
type InvestorAccount = Record<string, unknown>;
|
|
59
|
+
type Trade = Record<string, unknown>;
|
|
58
60
|
|
|
59
61
|
extendZodWithOpenApi(z);
|
|
60
62
|
export const CheckResultsSchema = z.object({
|
package/common/types/zip.type.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { validateCanadaZipCode, validateUSZipCode } from '
|
|
2
|
+
import { validateCanadaZipCode, validateUSZipCode } from './contract-helpers';
|
|
3
3
|
import { CountryCode, CountryEnumSchema } from './countries.types';
|
|
4
4
|
|
|
5
5
|
export function refineAddressZip(
|
package/package.json
CHANGED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import { Job } from 'bullmq';
|
|
2
|
-
import { QueryRunner } from 'typeorm';
|
|
3
|
-
import { Site } from '../../sites/entities/site.entity';
|
|
4
|
-
import { ProcessTradeCancellationReminderDto } from '../../workers/dto.worker';
|
|
5
|
-
|
|
6
|
-
export enum ReminderType {
|
|
7
|
-
DAILY = 'daily',
|
|
8
|
-
CUMULATIVE = 'cumulative',
|
|
9
|
-
WARNING = 'warning',
|
|
10
|
-
CANCEL = 'cancel',
|
|
11
|
-
SKIP = 'skip', // for days with no action
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export interface ReminderConfig {
|
|
15
|
-
day: number;
|
|
16
|
-
type: ReminderType;
|
|
17
|
-
daysLeft?: number; // for warning type
|
|
18
|
-
createTask?: boolean; // for daily type
|
|
19
|
-
priority?: 'low' | 'medium' | 'high' | 'urgent';
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface ReminderTemplate {
|
|
23
|
-
subject: (params: ReminderTemplateParams) => string;
|
|
24
|
-
description: (params: ReminderTemplateParams) => string;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export interface ReminderTemplateParams {
|
|
28
|
-
day: number;
|
|
29
|
-
offeringName: string;
|
|
30
|
-
daysLeft?: number;
|
|
31
|
-
userName: string;
|
|
32
|
-
tradeStatus: string;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export interface ReminderContext {
|
|
36
|
-
queryRunner: QueryRunner;
|
|
37
|
-
job: Job<ProcessTradeCancellationReminderDto>;
|
|
38
|
-
site: Site;
|
|
39
|
-
logContext: any;
|
|
40
|
-
}
|