@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.
@@ -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 '../helpers';
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 '../helpers';
17
+ import { slugify } from './contract-helpers';
18
18
  import { fileIdSchema } from './file.types';
19
19
 
20
20
  extendZodWithOpenApi(z);
@@ -1,4 +1,3 @@
1
- import { JobsOptions } from 'bullmq';
2
1
  import { typeid } from 'typeid-js';
3
2
 
4
3
  export enum QueueFrequency {
@@ -53,8 +53,10 @@ import {
53
53
  SecondaryTrade,
54
54
  SecondaryTradeFiltersZod,
55
55
  } from './secondary-trade.types';
56
- import { InvestorAccount } from '../../investor-accounts/entities/investor-account.entity';
57
- import { Trade } from '../../trades/entities/trade.entity';
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({
@@ -1,5 +1,5 @@
1
1
  import { z } from 'zod';
2
- import { validateCanadaZipCode, validateUSZipCode } from '../helpers';
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,6 +1,6 @@
1
1
  {
2
2
  "name": "@dalmore/api-contracts",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "Type-safe API contracts for Dalmore Client Portal",
5
5
  "main": "./contracts/index.ts",
6
6
  "types": "./contracts/index.ts",
@@ -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
- }