mirror of
https://github.com/documenso/documenso.git
synced 2026-07-24 08:54:20 +10:00
@@ -76,6 +76,7 @@ export const mapEnvelopeToDocumentLite = (envelope: Envelope): TDocumentLite =>
|
||||
return {
|
||||
id: documentId, // Use legacy ID.
|
||||
envelopeId: envelope.id,
|
||||
internalVersion: envelope.internalVersion,
|
||||
visibility: envelope.visibility,
|
||||
status: envelope.status,
|
||||
source: envelope.source,
|
||||
@@ -115,6 +116,7 @@ export const mapEnvelopesToDocumentMany = (
|
||||
return {
|
||||
id: legacyDocumentId, // Use legacy ID.
|
||||
envelopeId: envelope.id,
|
||||
internalVersion: envelope.internalVersion,
|
||||
visibility: envelope.visibility,
|
||||
status: envelope.status,
|
||||
source: envelope.source,
|
||||
|
||||
@@ -0,0 +1,263 @@
|
||||
import type { Field } from '@prisma/client';
|
||||
import { FieldType } from '@prisma/client';
|
||||
import { DateTime } from 'luxon';
|
||||
import { P, match } from 'ts-pattern';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { validateCheckboxLength } from '@documenso/lib/advanced-fields-validation/validate-checkbox';
|
||||
import { validateDropdownField } from '@documenso/lib/advanced-fields-validation/validate-dropdown';
|
||||
import { validateNumberField } from '@documenso/lib/advanced-fields-validation/validate-number';
|
||||
import { validateTextField } from '@documenso/lib/advanced-fields-validation/validate-text';
|
||||
import { DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-formats';
|
||||
import { isBase64Image } from '@documenso/lib/constants/signatures';
|
||||
import { DEFAULT_DOCUMENT_TIME_ZONE } from '@documenso/lib/constants/time-zones';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import type { TDocumentMeta } from '@documenso/lib/types/document-meta';
|
||||
import {
|
||||
ZCheckboxFieldMeta,
|
||||
ZDropdownFieldMeta,
|
||||
ZNumberFieldMeta,
|
||||
ZRadioFieldMeta,
|
||||
ZTextFieldMeta,
|
||||
} from '@documenso/lib/types/field-meta';
|
||||
import { toCheckboxCustomText, toRadioCustomText } from '@documenso/lib/utils/fields';
|
||||
import type { TSignEnvelopeFieldValue } from '@documenso/trpc/server/envelope-router/sign-envelope-field.types';
|
||||
import { checkboxValidationSigns } from '@documenso/ui/primitives/document-flow/field-items-advanced-settings/constants';
|
||||
|
||||
export type ExtractFieldInsertionValuesOptions = {
|
||||
fieldValue: TSignEnvelopeFieldValue;
|
||||
field: Field;
|
||||
documentMeta: Pick<TDocumentMeta, 'timezone' | 'dateFormat' | 'typedSignatureEnabled'>;
|
||||
};
|
||||
|
||||
export const extractFieldInsertionValues = ({
|
||||
fieldValue,
|
||||
field,
|
||||
documentMeta,
|
||||
}: ExtractFieldInsertionValuesOptions): { customText: string; inserted: boolean } => {
|
||||
return match(fieldValue)
|
||||
.with({ type: FieldType.EMAIL }, (fieldValue) => {
|
||||
const parsedEmailValue = z.string().email().nullable().safeParse(fieldValue.value);
|
||||
|
||||
if (!parsedEmailValue.success) {
|
||||
throw new AppError(AppErrorCode.INVALID_BODY, {
|
||||
message: 'Invalid email',
|
||||
});
|
||||
}
|
||||
|
||||
if (parsedEmailValue.data === null) {
|
||||
return {
|
||||
customText: '',
|
||||
inserted: false,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
customText: parsedEmailValue.data,
|
||||
inserted: true,
|
||||
};
|
||||
})
|
||||
.with({ type: P.union(FieldType.NAME, FieldType.INITIALS) }, (fieldValue) => {
|
||||
const parsedGenericStringValue = z.string().min(1).nullable().safeParse(fieldValue.value);
|
||||
|
||||
if (!parsedGenericStringValue.success) {
|
||||
throw new AppError(AppErrorCode.INVALID_BODY, {
|
||||
message: 'Value is required',
|
||||
});
|
||||
}
|
||||
|
||||
if (parsedGenericStringValue.data === null) {
|
||||
return {
|
||||
customText: '',
|
||||
inserted: false,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
customText: parsedGenericStringValue.data,
|
||||
inserted: true,
|
||||
};
|
||||
})
|
||||
.with({ type: FieldType.DATE }, (fieldValue) => {
|
||||
if (!fieldValue.value) {
|
||||
return {
|
||||
customText: '',
|
||||
inserted: false,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
customText: DateTime.now()
|
||||
.setZone(documentMeta.timezone ?? DEFAULT_DOCUMENT_TIME_ZONE)
|
||||
.toFormat(documentMeta.dateFormat ?? DEFAULT_DOCUMENT_DATE_FORMAT),
|
||||
inserted: true,
|
||||
};
|
||||
})
|
||||
.with({ type: FieldType.NUMBER }, (fieldValue) => {
|
||||
if (!fieldValue.value) {
|
||||
return {
|
||||
customText: '',
|
||||
inserted: false,
|
||||
};
|
||||
}
|
||||
|
||||
const numberFieldParsedMeta = ZNumberFieldMeta.parse(field.fieldMeta);
|
||||
const errors = validateNumberField(fieldValue.value.toString(), numberFieldParsedMeta, true);
|
||||
|
||||
// Todo
|
||||
if (errors.length > 0) {
|
||||
throw new AppError(AppErrorCode.INVALID_BODY, {
|
||||
message: 'Invalid number',
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
customText: fieldValue.value.toString(),
|
||||
inserted: true,
|
||||
};
|
||||
})
|
||||
.with({ type: FieldType.TEXT }, (fieldValue) => {
|
||||
if (fieldValue.value === null) {
|
||||
return {
|
||||
customText: '',
|
||||
inserted: false,
|
||||
};
|
||||
}
|
||||
|
||||
const parsedTextFieldMeta = ZTextFieldMeta.parse(field.fieldMeta);
|
||||
const errors = validateTextField(fieldValue.value, parsedTextFieldMeta, true);
|
||||
|
||||
// Todo
|
||||
if (errors.length > 0) {
|
||||
throw new AppError(AppErrorCode.INVALID_BODY, {
|
||||
message: 'Invalid email',
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
customText: fieldValue.value,
|
||||
inserted: true,
|
||||
};
|
||||
})
|
||||
.with({ type: FieldType.RADIO }, (fieldValue) => {
|
||||
if (fieldValue.value === null) {
|
||||
return {
|
||||
customText: '',
|
||||
inserted: false,
|
||||
};
|
||||
}
|
||||
|
||||
const parsedRadioFieldParsedMeta = ZRadioFieldMeta.parse(field.fieldMeta);
|
||||
const radioFieldValues = parsedRadioFieldParsedMeta.values || [];
|
||||
|
||||
if (!radioFieldValues[fieldValue.value]) {
|
||||
throw new AppError(AppErrorCode.INVALID_BODY, {
|
||||
message: 'Invalid radio value',
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
customText: toRadioCustomText(fieldValue.value),
|
||||
inserted: true,
|
||||
};
|
||||
})
|
||||
.with({ type: FieldType.CHECKBOX }, (fieldValue) => {
|
||||
if (fieldValue.value === null || fieldValue.value.length === 0) {
|
||||
return {
|
||||
customText: '',
|
||||
inserted: false,
|
||||
};
|
||||
}
|
||||
|
||||
const parsedCheckboxFieldParsedMeta = ZCheckboxFieldMeta.parse(field.fieldMeta);
|
||||
const checkboxFieldValues = parsedCheckboxFieldParsedMeta.values || [];
|
||||
|
||||
const { value } = fieldValue;
|
||||
|
||||
const selectedValues = value.map((valueIndex) => checkboxFieldValues[valueIndex]);
|
||||
|
||||
if (selectedValues.some((value) => !value)) {
|
||||
throw new AppError(AppErrorCode.INVALID_BODY, {
|
||||
message: 'Invalid checkbox values',
|
||||
});
|
||||
}
|
||||
|
||||
const { validationRule, validationLength } = parsedCheckboxFieldParsedMeta;
|
||||
|
||||
if (validationRule && validationLength) {
|
||||
const checkboxValidationRule = checkboxValidationSigns.find(
|
||||
(sign) => sign.label === validationRule,
|
||||
);
|
||||
|
||||
// Todo: Envelopes - Test this.
|
||||
if (checkboxValidationRule) {
|
||||
const isValid = validateCheckboxLength(
|
||||
selectedValues.length,
|
||||
checkboxValidationRule.value,
|
||||
validationLength,
|
||||
);
|
||||
|
||||
if (!isValid) {
|
||||
throw new AppError(AppErrorCode.INVALID_BODY, {
|
||||
message: 'Checkbox values failed length validation',
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// Should throw an error, but we don't want to throw configuration errors during signing.
|
||||
// Todo: Logging.
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
customText: toCheckboxCustomText(fieldValue.value),
|
||||
inserted: true,
|
||||
};
|
||||
})
|
||||
.with({ type: FieldType.DROPDOWN }, (fieldValue) => {
|
||||
if (fieldValue.value === null) {
|
||||
return {
|
||||
customText: '',
|
||||
inserted: false,
|
||||
};
|
||||
}
|
||||
|
||||
const parsedDropdownFieldMeta = ZDropdownFieldMeta.parse(field.fieldMeta);
|
||||
const errors = validateDropdownField(fieldValue.value, parsedDropdownFieldMeta, true);
|
||||
|
||||
// Todo: Envelopes
|
||||
if (errors.length > 0) {
|
||||
throw new AppError(AppErrorCode.INVALID_BODY, {
|
||||
message: 'Invalid dropdown value',
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
customText: fieldValue.value,
|
||||
inserted: true,
|
||||
};
|
||||
})
|
||||
.with({ type: FieldType.SIGNATURE }, (fieldValue) => {
|
||||
const { value } = fieldValue;
|
||||
|
||||
if (!value) {
|
||||
return {
|
||||
customText: '',
|
||||
inserted: false,
|
||||
};
|
||||
}
|
||||
|
||||
const isBase64 = isBase64Image(value);
|
||||
|
||||
if (documentMeta.typedSignatureEnabled === false && !isBase64) {
|
||||
throw new AppError(AppErrorCode.INVALID_BODY, {
|
||||
message: 'Typed signatures are not allowed. Please draw your signature',
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
customText: '',
|
||||
inserted: true,
|
||||
};
|
||||
})
|
||||
.exhaustive();
|
||||
};
|
||||
@@ -1,4 +1,6 @@
|
||||
import { type Envelope, type Field } from '@prisma/client';
|
||||
import type { I18n } from '@lingui/core';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { type Envelope, type Field, FieldType } from '@prisma/client';
|
||||
|
||||
import { extractLegacyIds } from '../universal/id';
|
||||
|
||||
@@ -77,3 +79,35 @@ export const mapFieldToLegacyField = (
|
||||
...legacyId,
|
||||
};
|
||||
};
|
||||
|
||||
export const parseCheckboxCustomText = (customText: string): number[] => {
|
||||
return JSON.parse(customText);
|
||||
};
|
||||
|
||||
export const toCheckboxCustomText = (checkedValues: number[]): string => {
|
||||
return JSON.stringify(checkedValues);
|
||||
};
|
||||
|
||||
export const parseRadioCustomText = (customText: string): number => {
|
||||
return Number(customText);
|
||||
};
|
||||
|
||||
export const toRadioCustomText = (value: number): string => {
|
||||
return value.toString();
|
||||
};
|
||||
|
||||
export const getClientSideFieldTranslations = ({ t }: I18n): Record<FieldType, string> => {
|
||||
return {
|
||||
[FieldType.TEXT]: t(msg`Text`),
|
||||
[FieldType.CHECKBOX]: t(msg`Checkbox`),
|
||||
[FieldType.RADIO]: t(msg`Radio`),
|
||||
[FieldType.DROPDOWN]: t(msg`Dropdown`),
|
||||
[FieldType.SIGNATURE]: t(msg`Signature`),
|
||||
[FieldType.FREE_SIGNATURE]: t(msg`Free Signature`),
|
||||
[FieldType.INITIALS]: t(msg`Initials`),
|
||||
[FieldType.NAME]: t(msg`Name`),
|
||||
[FieldType.NUMBER]: t(msg`Number`),
|
||||
[FieldType.DATE]: t(msg`Date`),
|
||||
[FieldType.EMAIL]: t(msg`Email`),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import type { SubscriptionClaim } from '@prisma/client';
|
||||
|
||||
import { DEFAULT_MINIMUM_ENVELOPE_ITEM_COUNT } from '@documenso/ee/server-only/limits/constants';
|
||||
|
||||
export const generateDefaultSubscriptionClaim = (): Omit<
|
||||
SubscriptionClaim,
|
||||
'id' | 'organisation' | 'createdAt' | 'updatedAt' | 'originalSubscriptionClaimId'
|
||||
@@ -8,6 +10,7 @@ export const generateDefaultSubscriptionClaim = (): Omit<
|
||||
name: '',
|
||||
teamCount: 1,
|
||||
memberCount: 1,
|
||||
envelopeItemCount: DEFAULT_MINIMUM_ENVELOPE_ITEM_COUNT,
|
||||
locked: false,
|
||||
flags: {},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user