mirror of
https://github.com/documenso/documenso.git
synced 2025-11-20 19:51:32 +10:00
fix: merge conflicts
This commit is contained in:
51
packages/lib/utils/advanced-fields-helpers.ts
Normal file
51
packages/lib/utils/advanced-fields-helpers.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import { type Field, FieldType } from '@documenso/prisma/client';
|
||||
|
||||
import { ZFieldMetaSchema } from '../types/field-meta';
|
||||
|
||||
// Currently it seems that the majority of fields have advanced fields for font reasons.
|
||||
// This array should only contain fields that have an optional setting in the fieldMeta.
|
||||
export const ADVANCED_FIELD_TYPES_WITH_OPTIONAL_SETTING: FieldType[] = [
|
||||
FieldType.NUMBER,
|
||||
FieldType.TEXT,
|
||||
FieldType.DROPDOWN,
|
||||
FieldType.RADIO,
|
||||
FieldType.CHECKBOX,
|
||||
];
|
||||
|
||||
/**
|
||||
* Whether a field is required to be inserted.
|
||||
*/
|
||||
export const isRequiredField = (field: Field) => {
|
||||
// All fields without the optional metadata are assumed to be required.
|
||||
if (!ADVANCED_FIELD_TYPES_WITH_OPTIONAL_SETTING.includes(field.type)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Not sure why fieldMeta can be optional for advanced fields, but it is.
|
||||
// Therefore we must assume if there is no fieldMeta, then the field is optional.
|
||||
if (!field.fieldMeta) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const parsedData = ZFieldMetaSchema.safeParse(field.fieldMeta);
|
||||
|
||||
// If it fails, assume the field is optional.
|
||||
// This needs to be logged somewhere.
|
||||
if (!parsedData.success) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return parsedData.data?.required === true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Whether the provided field is required and not inserted.
|
||||
*/
|
||||
export const isFieldUnsignedAndRequired = (field: Field) =>
|
||||
isRequiredField(field) && !field.inserted;
|
||||
|
||||
/**
|
||||
* Whether the provided fields contains a field that is required to be inserted.
|
||||
*/
|
||||
export const fieldsContainUnsignedRequiredField = (fields: Field[]) =>
|
||||
fields.some(isFieldUnsignedAndRequired);
|
||||
@ -1,6 +1,3 @@
|
||||
import { env } from 'next-runtime-env';
|
||||
|
||||
import { IS_BILLING_ENABLED } from '../constants/app';
|
||||
import type { Subscription } from '.prisma/client';
|
||||
import { SubscriptionStatus } from '.prisma/client';
|
||||
|
||||
@ -10,12 +7,21 @@ import { SubscriptionStatus } from '.prisma/client';
|
||||
export const subscriptionsContainsActivePlan = (
|
||||
subscriptions: Subscription[],
|
||||
priceIds: string[],
|
||||
allowPastDue?: boolean,
|
||||
) => {
|
||||
const allowedSubscriptionStatuses: SubscriptionStatus[] = [SubscriptionStatus.ACTIVE];
|
||||
|
||||
if (allowPastDue) {
|
||||
allowedSubscriptionStatuses.push(SubscriptionStatus.PAST_DUE);
|
||||
}
|
||||
|
||||
return subscriptions.some(
|
||||
(subscription) =>
|
||||
subscription.status === SubscriptionStatus.ACTIVE && priceIds.includes(subscription.priceId),
|
||||
allowedSubscriptionStatuses.includes(subscription.status) &&
|
||||
priceIds.includes(subscription.priceId),
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns true if there is a subscription that is active and is one of the provided product IDs.
|
||||
*/
|
||||
@ -28,15 +34,3 @@ export const subscriptionsContainsActiveProductId = (
|
||||
subscription.status === SubscriptionStatus.ACTIVE && productId.includes(subscription.planId),
|
||||
);
|
||||
};
|
||||
|
||||
export const subscriptionsContainActiveEnterprisePlan = (
|
||||
subscriptions?: Subscription[],
|
||||
): boolean => {
|
||||
const enterprisePlanId = env('NEXT_PUBLIC_STRIPE_ENTERPRISE_PLAN_MONTHLY_PRICE_ID');
|
||||
|
||||
if (!enterprisePlanId || !subscriptions || !IS_BILLING_ENABLED()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return subscriptionsContainsActivePlan(subscriptions, [enterprisePlanId]);
|
||||
};
|
||||
|
||||
@ -19,14 +19,15 @@ import {
|
||||
ZDocumentAuditLogSchema,
|
||||
} from '../types/document-audit-logs';
|
||||
import { ZRecipientAuthOptionsSchema } from '../types/document-auth';
|
||||
import type { RequestMetadata } from '../universal/extract-request-metadata';
|
||||
import type { ApiRequestMetadata, RequestMetadata } from '../universal/extract-request-metadata';
|
||||
|
||||
type CreateDocumentAuditLogDataOptions<T = TDocumentAuditLog['type']> = {
|
||||
documentId: number;
|
||||
type: T;
|
||||
data: Extract<TDocumentAuditLog, { type: T }>['data'];
|
||||
user: { email?: string; id?: number | null; name?: string | null } | null;
|
||||
user?: { email?: string | null; id?: number | null; name?: string | null } | null;
|
||||
requestMetadata?: RequestMetadata;
|
||||
metadata?: ApiRequestMetadata;
|
||||
};
|
||||
|
||||
export type CreateDocumentAuditLogDataResponse = Pick<
|
||||
@ -42,16 +43,31 @@ export const createDocumentAuditLogData = <T extends TDocumentAuditLog['type']>(
|
||||
data,
|
||||
user,
|
||||
requestMetadata,
|
||||
metadata,
|
||||
}: CreateDocumentAuditLogDataOptions<T>): CreateDocumentAuditLogDataResponse => {
|
||||
let userId: number | null = metadata?.auditUser?.id || null;
|
||||
let email: string | null = metadata?.auditUser?.email || null;
|
||||
let name: string | null = metadata?.auditUser?.name || null;
|
||||
|
||||
// Prioritize explicit user parameter over metadata audit user.
|
||||
if (user) {
|
||||
userId = user.id || null;
|
||||
email = user.email || null;
|
||||
name = user.name || null;
|
||||
}
|
||||
|
||||
const ipAddress = metadata?.requestMetadata.ipAddress ?? requestMetadata?.ipAddress ?? null;
|
||||
const userAgent = metadata?.requestMetadata.userAgent ?? requestMetadata?.userAgent ?? null;
|
||||
|
||||
return {
|
||||
type,
|
||||
data,
|
||||
documentId,
|
||||
userId: user?.id ?? null,
|
||||
email: user?.email ?? null,
|
||||
name: user?.name ?? null,
|
||||
userAgent: requestMetadata?.userAgent ?? null,
|
||||
ipAddress: requestMetadata?.ipAddress ?? null,
|
||||
userId,
|
||||
email,
|
||||
name,
|
||||
userAgent,
|
||||
ipAddress,
|
||||
};
|
||||
};
|
||||
|
||||
@ -298,6 +314,10 @@ export const formatDocumentAuditLogAction = (
|
||||
anonymous: msg`Field unsigned`,
|
||||
identified: msg`${prefix} unsigned a field`,
|
||||
}))
|
||||
.with({ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_FIELD_PREFILLED }, () => ({
|
||||
anonymous: msg`Field prefilled by assistant`,
|
||||
identified: msg`${prefix} prefilled a field`,
|
||||
}))
|
||||
.with({ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_VISIBILITY_UPDATED }, () => ({
|
||||
anonymous: msg`Document visibility updated`,
|
||||
identified: msg`${prefix} updated the document visibility`,
|
||||
|
||||
20
packages/lib/utils/document-visibility.ts
Normal file
20
packages/lib/utils/document-visibility.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { DocumentVisibility, TeamMemberRole } from '@documenso/prisma/client';
|
||||
|
||||
export const determineDocumentVisibility = (
|
||||
globalVisibility: DocumentVisibility | null | undefined,
|
||||
userRole: TeamMemberRole,
|
||||
): DocumentVisibility => {
|
||||
if (globalVisibility) {
|
||||
return globalVisibility;
|
||||
}
|
||||
|
||||
if (userRole === TeamMemberRole.ADMIN) {
|
||||
return DocumentVisibility.ADMIN;
|
||||
}
|
||||
|
||||
if (userRole === TeamMemberRole.MANAGER) {
|
||||
return DocumentVisibility.MANAGER_AND_ABOVE;
|
||||
}
|
||||
|
||||
return DocumentVisibility.EVERYONE;
|
||||
};
|
||||
@ -8,19 +8,8 @@ import { APP_I18N_OPTIONS } from '../constants/i18n';
|
||||
|
||||
export async function dynamicActivate(i18nInstance: I18n, locale: string) {
|
||||
const extension = process.env.NODE_ENV === 'development' ? 'po' : 'js';
|
||||
const context = IS_APP_WEB ? 'web' : 'marketing';
|
||||
|
||||
let { messages } = await import(`../translations/${locale}/${context}.${extension}`);
|
||||
|
||||
// Dirty way to load common messages for development since it's not compiled.
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
const commonMessages = await import(`../translations/${locale}/common.${extension}`);
|
||||
|
||||
messages = {
|
||||
...messages,
|
||||
...commonMessages.messages,
|
||||
};
|
||||
}
|
||||
const { messages } = await import(`../translations/${locale}/web.${extension}`);
|
||||
|
||||
i18nInstance.loadAndActivate({ locale, messages });
|
||||
}
|
||||
|
||||
110
packages/lib/utils/logger.ts
Normal file
110
packages/lib/utils/logger.ts
Normal file
@ -0,0 +1,110 @@
|
||||
import Honeybadger from '@honeybadger-io/js';
|
||||
|
||||
export const buildLogger = () => {
|
||||
if (process.env.NEXT_PRIVATE_LOGGER_HONEY_BADGER_API_KEY) {
|
||||
return new HoneybadgerLogger();
|
||||
}
|
||||
|
||||
return new DefaultLogger();
|
||||
};
|
||||
|
||||
interface LoggerDescriptionOptions {
|
||||
method?: string;
|
||||
path?: string;
|
||||
context?: Record<string, unknown>;
|
||||
|
||||
/**
|
||||
* The type of log to be captured.
|
||||
*
|
||||
* Defaults to `info`.
|
||||
*/
|
||||
level?: 'info' | 'error' | 'critical';
|
||||
}
|
||||
|
||||
/**
|
||||
* Basic logger implementation intended to be used in the server side for capturing
|
||||
* explicit errors and other logs.
|
||||
*
|
||||
* Not intended to capture the request and responses.
|
||||
*/
|
||||
interface Logger {
|
||||
log(message: string, options?: LoggerDescriptionOptions): void;
|
||||
|
||||
error(error: Error, options?: LoggerDescriptionOptions): void;
|
||||
}
|
||||
|
||||
class DefaultLogger implements Logger {
|
||||
log(_message: string, _options?: LoggerDescriptionOptions) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
error(_error: Error, _options?: LoggerDescriptionOptions): void {
|
||||
// Do nothing.
|
||||
}
|
||||
}
|
||||
|
||||
class HoneybadgerLogger implements Logger {
|
||||
constructor() {
|
||||
if (!process.env.NEXT_PRIVATE_LOGGER_HONEY_BADGER_API_KEY) {
|
||||
throw new Error('NEXT_PRIVATE_LOGGER_HONEY_BADGER_API_KEY is not set');
|
||||
}
|
||||
|
||||
Honeybadger.configure({
|
||||
apiKey: process.env.NEXT_PRIVATE_LOGGER_HONEY_BADGER_API_KEY,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Honeybadger doesn't really have a non-error logging system.
|
||||
*/
|
||||
log(message: string, options?: LoggerDescriptionOptions) {
|
||||
const { context = {}, level = 'info' } = options || {};
|
||||
|
||||
try {
|
||||
Honeybadger.event({
|
||||
message,
|
||||
context: {
|
||||
level,
|
||||
...context,
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
// Do nothing.
|
||||
}
|
||||
}
|
||||
|
||||
error(error: Error, options?: LoggerDescriptionOptions): void {
|
||||
const { context = {}, level = 'error', method, path } = options || {};
|
||||
|
||||
// const tags = [`level:${level}`];
|
||||
const tags = [];
|
||||
|
||||
let errorMessage = error.message;
|
||||
|
||||
if (method) {
|
||||
tags.push(`method:${method}`);
|
||||
|
||||
errorMessage = `[${method}]: ${error.message}`;
|
||||
}
|
||||
|
||||
if (path) {
|
||||
tags.push(`path:${path}`);
|
||||
}
|
||||
|
||||
try {
|
||||
Honeybadger.notify(errorMessage, {
|
||||
context: {
|
||||
level,
|
||||
...context,
|
||||
},
|
||||
tags,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
// Do nothing.
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -12,7 +12,7 @@ export const maskRecipientTokensForDocument = <T extends DocumentWithRecipients>
|
||||
user,
|
||||
token,
|
||||
}: MaskRecipientTokensForDocumentOptions<T>) => {
|
||||
const maskedRecipients = document.Recipient.map((recipient) => {
|
||||
const maskedRecipients = document.recipients.map((recipient) => {
|
||||
if (document.userId === user?.id) {
|
||||
return recipient;
|
||||
}
|
||||
|
||||
@ -8,8 +8,17 @@ export const formatSigningLink = (token: string) => `${NEXT_PUBLIC_WEBAPP_URL()}
|
||||
* Whether a recipient can be modified by the document owner.
|
||||
*/
|
||||
export const canRecipientBeModified = (recipient: Recipient, fields: Field[]) => {
|
||||
if (!recipient) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// CCers can always be modified (unless document is completed).
|
||||
if (recipient.role === RecipientRole.CC) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Deny if the recipient has already signed the document.
|
||||
if (!recipient || recipient.signingStatus === SigningStatus.SIGNED) {
|
||||
if (recipient.signingStatus === SigningStatus.SIGNED) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user