tychat-contracts 1.6.69 → 1.6.73
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/connections/connections.dto.d.ts +53 -0
- package/dist/connections/connections.dto.d.ts.map +1 -0
- package/dist/connections/connections.dto.js +376 -0
- package/dist/fiscal/enqueue-nfe.dto.d.ts +3 -0
- package/dist/fiscal/enqueue-nfe.dto.d.ts.map +1 -1
- package/dist/fiscal/enqueue-nfe.dto.js +14 -0
- package/dist/tenants/index.d.ts +1 -0
- package/dist/tenants/index.d.ts.map +1 -1
- package/dist/tenants/index.js +1 -0
- package/dist/tenants/tenant-fiscal-profile.dto.d.ts +27 -0
- package/dist/tenants/tenant-fiscal-profile.dto.d.ts.map +1 -0
- package/dist/tenants/tenant-fiscal-profile.dto.js +128 -0
- package/package.json +1 -1
- package/src/campaigns/campaign-template-header-media.dto.ts +25 -25
- package/src/campaigns/campaign-template.dto.ts +45 -45
- package/src/campaigns/create-campaign.dto.ts +80 -80
- package/src/fiscal/enqueue-nfe.dto.ts +22 -1
- package/src/patients/index.ts +10 -10
- package/src/tenants/anamnesis-public-frontend-url.spec.ts +45 -45
- package/src/tenants/anamnesis-public-frontend-url.ts +61 -61
- package/src/tenants/index.ts +1 -0
- package/src/tenants/tenant-fiscal-profile.dto.ts +85 -0
- package/dist/anamneses/normalize-anamnesis-access-token.d.ts +0 -9
- package/dist/anamneses/normalize-anamnesis-access-token.d.ts.map +0 -1
- package/dist/anamneses/normalize-anamnesis-access-token.js +0 -21
- package/dist/anamneses/normalize-anamnesis-access-token.spec.d.ts +0 -2
- package/dist/anamneses/normalize-anamnesis-access-token.spec.d.ts.map +0 -1
- package/dist/anamneses/normalize-anamnesis-access-token.spec.js +0 -18
|
@@ -1,61 +1,61 @@
|
|
|
1
|
-
import { slugToDomainSegment } from './tenant-slug.util';
|
|
2
|
-
|
|
3
|
-
const TYCHAT_APP_SUFFIX = '.tychat.app';
|
|
4
|
-
|
|
5
|
-
function normalizeFrontendBaseUrl(raw: string): string {
|
|
6
|
-
const trimmed = raw.trim().replace(/\/+$/, '');
|
|
7
|
-
if (!trimmed) return '';
|
|
8
|
-
if (/^https?:\/\//i.test(trimmed)) {
|
|
9
|
-
return trimmed;
|
|
10
|
-
}
|
|
11
|
-
return `https://${trimmed}`;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function firstHostnameLabel(hostname: string): string {
|
|
15
|
-
return hostname.toLowerCase().split('.').filter(Boolean)[0] ?? '';
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/** True when the hostname's first label matches the tenant slug segment. */
|
|
19
|
-
export function hostnameMatchesTenantSlug(hostname: string, tenantSlug: string): boolean {
|
|
20
|
-
const segment = slugToDomainSegment(tenantSlug);
|
|
21
|
-
if (!segment || segment === 'default') return false;
|
|
22
|
-
return firstHostnameLabel(hostname) === segment;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Builds a frontend base URL that resolves to a single tenant on public routes.
|
|
27
|
-
*
|
|
28
|
-
* Shared hosts (e.g. `qa.tychat.app`) are scoped as `{slug}.qa.tychat.app`.
|
|
29
|
-
* Custom domains and tenant-specific `*.tychat.app` hosts are kept as configured.
|
|
30
|
-
*/
|
|
31
|
-
export function resolveTenantScopedFrontendBaseUrl(
|
|
32
|
-
tenantSlug: string,
|
|
33
|
-
configuredHostRaw: string | null | undefined,
|
|
34
|
-
): string {
|
|
35
|
-
const slug = tenantSlug?.trim();
|
|
36
|
-
if (!slug) return '';
|
|
37
|
-
|
|
38
|
-
const segment = slugToDomainSegment(slug);
|
|
39
|
-
const fallback = `${segment}${TYCHAT_APP_SUFFIX}`;
|
|
40
|
-
const configured = configuredHostRaw?.trim();
|
|
41
|
-
const base = normalizeFrontendBaseUrl(configured || fallback);
|
|
42
|
-
|
|
43
|
-
let url: URL;
|
|
44
|
-
try {
|
|
45
|
-
url = new URL(base);
|
|
46
|
-
} catch {
|
|
47
|
-
return normalizeFrontendBaseUrl(fallback);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const hostname = url.hostname.toLowerCase();
|
|
51
|
-
if (hostnameMatchesTenantSlug(hostname, slug)) {
|
|
52
|
-
return url.origin;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
if (hostname.endsWith(TYCHAT_APP_SUFFIX)) {
|
|
56
|
-
url.hostname = `${segment}.${hostname}`;
|
|
57
|
-
return url.origin;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
return url.origin;
|
|
61
|
-
}
|
|
1
|
+
import { slugToDomainSegment } from './tenant-slug.util';
|
|
2
|
+
|
|
3
|
+
const TYCHAT_APP_SUFFIX = '.tychat.app';
|
|
4
|
+
|
|
5
|
+
function normalizeFrontendBaseUrl(raw: string): string {
|
|
6
|
+
const trimmed = raw.trim().replace(/\/+$/, '');
|
|
7
|
+
if (!trimmed) return '';
|
|
8
|
+
if (/^https?:\/\//i.test(trimmed)) {
|
|
9
|
+
return trimmed;
|
|
10
|
+
}
|
|
11
|
+
return `https://${trimmed}`;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function firstHostnameLabel(hostname: string): string {
|
|
15
|
+
return hostname.toLowerCase().split('.').filter(Boolean)[0] ?? '';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** True when the hostname's first label matches the tenant slug segment. */
|
|
19
|
+
export function hostnameMatchesTenantSlug(hostname: string, tenantSlug: string): boolean {
|
|
20
|
+
const segment = slugToDomainSegment(tenantSlug);
|
|
21
|
+
if (!segment || segment === 'default') return false;
|
|
22
|
+
return firstHostnameLabel(hostname) === segment;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Builds a frontend base URL that resolves to a single tenant on public routes.
|
|
27
|
+
*
|
|
28
|
+
* Shared hosts (e.g. `qa.tychat.app`) are scoped as `{slug}.qa.tychat.app`.
|
|
29
|
+
* Custom domains and tenant-specific `*.tychat.app` hosts are kept as configured.
|
|
30
|
+
*/
|
|
31
|
+
export function resolveTenantScopedFrontendBaseUrl(
|
|
32
|
+
tenantSlug: string,
|
|
33
|
+
configuredHostRaw: string | null | undefined,
|
|
34
|
+
): string {
|
|
35
|
+
const slug = tenantSlug?.trim();
|
|
36
|
+
if (!slug) return '';
|
|
37
|
+
|
|
38
|
+
const segment = slugToDomainSegment(slug);
|
|
39
|
+
const fallback = `${segment}${TYCHAT_APP_SUFFIX}`;
|
|
40
|
+
const configured = configuredHostRaw?.trim();
|
|
41
|
+
const base = normalizeFrontendBaseUrl(configured || fallback);
|
|
42
|
+
|
|
43
|
+
let url: URL;
|
|
44
|
+
try {
|
|
45
|
+
url = new URL(base);
|
|
46
|
+
} catch {
|
|
47
|
+
return normalizeFrontendBaseUrl(fallback);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const hostname = url.hostname.toLowerCase();
|
|
51
|
+
if (hostnameMatchesTenantSlug(hostname, slug)) {
|
|
52
|
+
return url.origin;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (hostname.endsWith(TYCHAT_APP_SUFFIX)) {
|
|
56
|
+
url.hostname = `${segment}.${hostname}`;
|
|
57
|
+
return url.origin;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return url.origin;
|
|
61
|
+
}
|
package/src/tenants/index.ts
CHANGED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { ApiPropertyOptional } from '@nestjs/swagger';
|
|
2
|
+
import { IsObject, IsOptional, IsString, IsUUID, ValidateNested } from 'class-validator';
|
|
3
|
+
import { Type } from 'class-transformer';
|
|
4
|
+
|
|
5
|
+
export const TENANT_FISCAL_PROFILE_GET = 'tenant.fiscalProfile.get';
|
|
6
|
+
export const TENANT_FISCAL_PROFILE_UPSERT = 'tenant.fiscalProfile.upsert';
|
|
7
|
+
|
|
8
|
+
export class TenantFiscalProfileCompanyDto {
|
|
9
|
+
@ApiPropertyOptional({ description: 'CPF/CNPJ (somente números)' })
|
|
10
|
+
@IsOptional()
|
|
11
|
+
@IsString()
|
|
12
|
+
cpfCnpj?: string;
|
|
13
|
+
|
|
14
|
+
@ApiPropertyOptional({ description: 'Razão social' })
|
|
15
|
+
@IsOptional()
|
|
16
|
+
@IsString()
|
|
17
|
+
razaoSocial?: string;
|
|
18
|
+
|
|
19
|
+
@ApiPropertyOptional({ description: 'Nome fantasia' })
|
|
20
|
+
@IsOptional()
|
|
21
|
+
@IsString()
|
|
22
|
+
nomeFantasia?: string;
|
|
23
|
+
|
|
24
|
+
@ApiPropertyOptional({ description: 'E-mail' })
|
|
25
|
+
@IsOptional()
|
|
26
|
+
@IsString()
|
|
27
|
+
email?: string;
|
|
28
|
+
|
|
29
|
+
@ApiPropertyOptional({ description: 'Telefone' })
|
|
30
|
+
@IsOptional()
|
|
31
|
+
@IsString()
|
|
32
|
+
telefone?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export class TenantFiscalProfileDto {
|
|
36
|
+
@ApiPropertyOptional({ description: 'Dados principais da empresa do tenant (SaaS: 1 tenant = 1 empresa fiscal)' })
|
|
37
|
+
@IsOptional()
|
|
38
|
+
@ValidateNested()
|
|
39
|
+
@Type(() => TenantFiscalProfileCompanyDto)
|
|
40
|
+
company?: TenantFiscalProfileCompanyDto;
|
|
41
|
+
|
|
42
|
+
@ApiPropertyOptional({ description: 'Payload bruto compatível com endpoints /empresas (ACBr API)' })
|
|
43
|
+
@IsOptional()
|
|
44
|
+
@IsObject()
|
|
45
|
+
acbrCompanyPayload?: Record<string, unknown>;
|
|
46
|
+
|
|
47
|
+
@ApiPropertyOptional({ description: 'Defaults e preferências do módulo fiscal (livre)' })
|
|
48
|
+
@IsOptional()
|
|
49
|
+
@IsObject()
|
|
50
|
+
fiscalDefaults?: Record<string, unknown>;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export class GetTenantFiscalProfileDto {
|
|
54
|
+
@IsString()
|
|
55
|
+
tenantSlug: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export class UpsertTenantFiscalProfileDto {
|
|
59
|
+
@IsString()
|
|
60
|
+
tenantSlug: string;
|
|
61
|
+
|
|
62
|
+
@IsOptional()
|
|
63
|
+
@ValidateNested()
|
|
64
|
+
@Type(() => TenantFiscalProfileDto)
|
|
65
|
+
profile?: TenantFiscalProfileDto;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export class TenantFiscalProfileResponseDto {
|
|
69
|
+
@ApiPropertyOptional()
|
|
70
|
+
@IsOptional()
|
|
71
|
+
@IsUUID()
|
|
72
|
+
tenantId?: string;
|
|
73
|
+
|
|
74
|
+
@ApiPropertyOptional()
|
|
75
|
+
@IsOptional()
|
|
76
|
+
@IsString()
|
|
77
|
+
tenantSlug?: string;
|
|
78
|
+
|
|
79
|
+
@ApiPropertyOptional()
|
|
80
|
+
@IsOptional()
|
|
81
|
+
@ValidateNested()
|
|
82
|
+
@Type(() => TenantFiscalProfileDto)
|
|
83
|
+
profile?: TenantFiscalProfileDto;
|
|
84
|
+
}
|
|
85
|
+
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
/** Hex length of `randomBytes(32).toString('hex')` anamnesis tokens. */
|
|
2
|
-
export declare const ANAMNESIS_ACCESS_TOKEN_HEX_LENGTH = 64;
|
|
3
|
-
/**
|
|
4
|
-
* Normalizes a raw token from URL/query/WhatsApp linkification.
|
|
5
|
-
* Meta templates often place punctuation right after {{1}} (e.g. "{{1}}."),
|
|
6
|
-
* which mobile clients include in the tapped URL and breaks SHA-256 lookup.
|
|
7
|
-
*/
|
|
8
|
-
export declare function normalizeAnamnesisAccessToken(raw: string | null | undefined): string;
|
|
9
|
-
//# sourceMappingURL=normalize-anamnesis-access-token.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"normalize-anamnesis-access-token.d.ts","sourceRoot":"","sources":["../../src/anamneses/normalize-anamnesis-access-token.ts"],"names":[],"mappings":"AAAA,wEAAwE;AACxE,eAAO,MAAM,iCAAiC,KAAK,CAAC;AAEpD;;;;GAIG;AACH,wBAAgB,6BAA6B,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAYpF"}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ANAMNESIS_ACCESS_TOKEN_HEX_LENGTH = void 0;
|
|
4
|
-
exports.normalizeAnamnesisAccessToken = normalizeAnamnesisAccessToken;
|
|
5
|
-
/** Hex length of `randomBytes(32).toString('hex')` anamnesis tokens. */
|
|
6
|
-
exports.ANAMNESIS_ACCESS_TOKEN_HEX_LENGTH = 64;
|
|
7
|
-
/**
|
|
8
|
-
* Normalizes a raw token from URL/query/WhatsApp linkification.
|
|
9
|
-
* Meta templates often place punctuation right after {{1}} (e.g. "{{1}}."),
|
|
10
|
-
* which mobile clients include in the tapped URL and breaks SHA-256 lookup.
|
|
11
|
-
*/
|
|
12
|
-
function normalizeAnamnesisAccessToken(raw) {
|
|
13
|
-
const trimmed = (raw ?? '').trim();
|
|
14
|
-
if (!trimmed)
|
|
15
|
-
return '';
|
|
16
|
-
const exact = trimmed.match(new RegExp(`^([a-fA-F0-9]{${exports.ANAMNESIS_ACCESS_TOKEN_HEX_LENGTH}})`));
|
|
17
|
-
if (exact?.[1]) {
|
|
18
|
-
return exact[1].toLowerCase();
|
|
19
|
-
}
|
|
20
|
-
return trimmed.replace(/[.,;:!?)>\]"']+$/g, '').trim();
|
|
21
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"normalize-anamnesis-access-token.spec.d.ts","sourceRoot":"","sources":["../../src/anamneses/normalize-anamnesis-access-token.spec.ts"],"names":[],"mappings":""}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const normalize_anamnesis_access_token_1 = require("./normalize-anamnesis-access-token");
|
|
4
|
-
const TOKEN = '22d58c2cd36a147d464d1525cfd45657bd42491276bdc9ae5bef580d70b57168';
|
|
5
|
-
describe('normalizeAnamnesisAccessToken', () => {
|
|
6
|
-
it('returns valid 64-char hex token unchanged (lowercased)', () => {
|
|
7
|
-
expect((0, normalize_anamnesis_access_token_1.normalizeAnamnesisAccessToken)(TOKEN)).toBe(TOKEN);
|
|
8
|
-
});
|
|
9
|
-
it('strips trailing period from WhatsApp linkification', () => {
|
|
10
|
-
expect((0, normalize_anamnesis_access_token_1.normalizeAnamnesisAccessToken)(`${TOKEN}.`)).toBe(TOKEN);
|
|
11
|
-
});
|
|
12
|
-
it('strips trailing punctuation cluster', () => {
|
|
13
|
-
expect((0, normalize_anamnesis_access_token_1.normalizeAnamnesisAccessToken)(`${TOKEN}).`)).toBe(TOKEN);
|
|
14
|
-
});
|
|
15
|
-
it('returns empty for blank input', () => {
|
|
16
|
-
expect((0, normalize_anamnesis_access_token_1.normalizeAnamnesisAccessToken)(' ')).toBe('');
|
|
17
|
-
});
|
|
18
|
-
});
|