chore: merged api

This commit is contained in:
Catalin Pit
2024-02-21 13:44:08 +02:00
57 changed files with 4728 additions and 19 deletions

View File

@ -1,5 +1,11 @@
import { Duration } from 'luxon';
export const ONE_SECOND = 1000;
export const ONE_MINUTE = ONE_SECOND * 60;
export const ONE_HOUR = ONE_MINUTE * 60;
export const ONE_DAY = ONE_HOUR * 24;
export const ONE_WEEK = ONE_DAY * 7;
export const ONE_MONTH = Duration.fromObject({ months: 1 });
export const THREE_MONTHS = Duration.fromObject({ months: 3 });
export const SIX_MONTHS = Duration.fromObject({ months: 6 });
export const ONE_YEAR = Duration.fromObject({ years: 1 });

View File

@ -1,4 +1,5 @@
import { compareSync as bcryptCompareSync, hashSync as bcryptHashSync } from 'bcrypt';
import crypto from 'crypto';
import { SALT_ROUNDS } from '../../constants/auth';
@ -12,3 +13,7 @@ export const hashSync = (password: string) => {
export const compareSync = (password: string, hash: string) => {
return bcryptCompareSync(password, hash);
};
export const hashString = (input: string) => {
return crypto.createHash('sha512').update(input).digest('hex');
};

View File

@ -0,0 +1,41 @@
import { prisma } from '@documenso/prisma';
import type { FieldType } from '@documenso/prisma/client';
export type CreateFieldOptions = {
documentId: number;
recipientId: number;
type: FieldType;
pageNumber: number;
pageX: number;
pageY: number;
pageWidth: number;
pageHeight: number;
};
export const createField = async ({
documentId,
recipientId,
type,
pageNumber,
pageX,
pageY,
pageWidth,
pageHeight,
}: CreateFieldOptions) => {
const field = await prisma.field.create({
data: {
documentId,
recipientId,
type,
page: pageNumber,
positionX: pageX,
positionY: pageY,
width: pageWidth,
height: pageHeight,
customText: '',
inserted: false,
},
});
return field;
};

View File

@ -0,0 +1,17 @@
import { prisma } from '@documenso/prisma';
export type DeleteFieldOptions = {
fieldId: number;
documentId: number;
};
export const deleteField = async ({ fieldId, documentId }: DeleteFieldOptions) => {
const field = await prisma.field.delete({
where: {
id: fieldId,
documentId,
},
});
return field;
};

View File

@ -0,0 +1,17 @@
import { prisma } from '@documenso/prisma';
export type GetFieldByIdOptions = {
fieldId: number;
documentId: number;
};
export const getFieldById = async ({ fieldId, documentId }: GetFieldByIdOptions) => {
const field = await prisma.field.findFirst({
where: {
id: fieldId,
documentId,
},
});
return field;
};

View File

@ -0,0 +1,44 @@
import { prisma } from '@documenso/prisma';
import type { FieldType } from '@documenso/prisma/client';
export type UpdateFieldOptions = {
fieldId: number;
documentId: number;
recipientId?: number;
type?: FieldType;
pageNumber?: number;
pageX?: number;
pageY?: number;
pageWidth?: number;
pageHeight?: number;
};
export const updateField = async ({
fieldId,
documentId,
recipientId,
type,
pageNumber,
pageX,
pageY,
pageWidth,
pageHeight,
}: UpdateFieldOptions) => {
const field = await prisma.field.update({
where: {
id: fieldId,
documentId,
},
data: {
recipientId,
type,
page: pageNumber,
positionX: pageX,
positionY: pageY,
width: pageWidth,
height: pageHeight,
},
});
return field;
};

View File

@ -0,0 +1,51 @@
import type { Duration } from 'luxon';
import { DateTime } from 'luxon';
import { prisma } from '@documenso/prisma';
// temporary choice for testing only
import * as timeConstants from '../../constants/time';
import { alphaid } from '../../universal/id';
import { hashString } from '../auth/hash';
type TimeConstants = typeof timeConstants & {
[key: string]: number | Duration;
};
type CreateApiTokenInput = {
userId: number;
tokenName: string;
expirationDate: string | null;
};
export const createApiToken = async ({
userId,
tokenName,
expirationDate,
}: CreateApiTokenInput) => {
const apiToken = `api_${alphaid(16)}`;
const hashedToken = hashString(apiToken);
const timeConstantsRecords: TimeConstants = timeConstants;
const dbToken = await prisma.apiToken.create({
data: {
token: hashedToken,
name: tokenName,
userId,
expires: expirationDate
? DateTime.now().plus(timeConstantsRecords[expirationDate]).toJSDate()
: null,
},
});
if (!dbToken) {
throw new Error('Failed to create the API token');
}
return {
id: dbToken.id,
token: apiToken,
};
};

View File

@ -0,0 +1,15 @@
import { prisma } from '@documenso/prisma';
export type DeleteTokenByIdOptions = {
id: number;
userId: number;
};
export const deleteTokenById = async ({ id, userId }: DeleteTokenByIdOptions) => {
return await prisma.apiToken.delete({
where: {
id,
userId,
},
});
};

View File

@ -0,0 +1,23 @@
import { prisma } from '@documenso/prisma';
export type GetUserTokensOptions = {
userId: number;
};
export const getUserTokens = async ({ userId }: GetUserTokensOptions) => {
return await prisma.apiToken.findMany({
where: {
userId,
},
select: {
id: true,
name: true,
algorithm: true,
createdAt: true,
expires: true,
},
orderBy: {
createdAt: 'desc',
},
});
};

View File

@ -0,0 +1,15 @@
import { prisma } from '@documenso/prisma';
export type GetApiTokenByIdOptions = {
id: number;
userId: number;
};
export const getApiTokenById = async ({ id, userId }: GetApiTokenByIdOptions) => {
return await prisma.apiToken.findFirstOrThrow({
where: {
id,
userId,
},
});
};

View File

@ -0,0 +1,37 @@
import { prisma } from '@documenso/prisma';
import { hashString } from '../auth/hash';
export const getUserByApiToken = async ({ token }: { token: string }) => {
const hashedToken = hashString(token);
const user = await prisma.user.findFirst({
where: {
ApiToken: {
some: {
token: hashedToken,
},
},
},
include: {
ApiToken: true,
},
});
if (!user) {
throw new Error('Invalid token');
}
const retrievedToken = user.ApiToken.find((apiToken) => apiToken.token === hashedToken);
// This should be impossible but we need to satisfy TypeScript
if (!retrievedToken) {
throw new Error('Invalid token');
}
if (retrievedToken.expires && retrievedToken.expires < new Date()) {
throw new Error('Expired token');
}
return user;
};

View File

@ -0,0 +1,32 @@
import { prisma } from '@documenso/prisma';
import { SendStatus } from '@documenso/prisma/client';
export type DeleteRecipientOptions = {
documentId: number;
recipientId: number;
};
export const deleteRecipient = async ({ documentId, recipientId }: DeleteRecipientOptions) => {
const recipient = await prisma.recipient.findFirst({
where: {
id: recipientId,
documentId,
},
});
if (!recipient) {
throw new Error('Recipient not found');
}
if (recipient.sendStatus !== SendStatus.NOT_SENT) {
throw new Error('Can not delete a recipient that has already been sent a document');
}
const deletedRecipient = await prisma.recipient.delete({
where: {
id: recipient.id,
},
});
return deletedRecipient;
};

View File

@ -0,0 +1,21 @@
import { prisma } from '@documenso/prisma';
export type GetRecipientByEmailOptions = {
documentId: number;
email: string;
};
export const getRecipientByEmail = async ({ documentId, email }: GetRecipientByEmailOptions) => {
const recipient = await prisma.recipient.findFirst({
where: {
documentId,
email: email.toLowerCase(),
},
});
if (!recipient) {
throw new Error('Recipient not found');
}
return recipient;
};

View File

@ -0,0 +1,21 @@
import { prisma } from '@documenso/prisma';
export type GetRecipientByIdOptions = {
id: number;
documentId: number;
};
export const getRecipientById = async ({ documentId, id }: GetRecipientByIdOptions) => {
const recipient = await prisma.recipient.findFirst({
where: {
documentId,
id,
},
});
if (!recipient) {
throw new Error('Recipient not found');
}
return recipient;
};

View File

@ -0,0 +1,42 @@
import { prisma } from '@documenso/prisma';
import type { RecipientRole } from '@documenso/prisma/client';
export type UpdateRecipientOptions = {
documentId: number;
recipientId: number;
email?: string;
name?: string;
role?: RecipientRole;
};
export const updateRecipient = async ({
documentId,
recipientId,
email,
name,
role,
}: UpdateRecipientOptions) => {
const recipient = await prisma.recipient.findFirst({
where: {
id: recipientId,
documentId,
},
});
if (!recipient) {
throw new Error('Recipient not found');
}
const updatedRecipient = await prisma.recipient.update({
where: {
id: recipient.id,
},
data: {
email: email?.toLowerCase() ?? recipient.email,
name: name ?? recipient.name,
role: role ?? recipient.role,
},
});
return updatedRecipient;
};

View File

@ -1,14 +1,21 @@
import { nanoid } from '@documenso/lib/universal/id';
import { prisma } from '@documenso/prisma';
import type { TCreateDocumentFromTemplateMutationSchema } from '@documenso/trpc/server/template-router/schema';
import type { RecipientRole } from '@documenso/prisma/client';
export type CreateDocumentFromTemplateOptions = TCreateDocumentFromTemplateMutationSchema & {
export type CreateDocumentFromTemplateOptions = {
templateId: number;
userId: number;
recipients?: {
name?: string;
email: string;
role?: RecipientRole;
}[];
};
export const createDocumentFromTemplate = async ({
templateId,
userId,
recipients,
}: CreateDocumentFromTemplateOptions) => {
const template = await prisma.template.findUnique({
where: {
@ -63,7 +70,11 @@ export const createDocumentFromTemplate = async ({
},
include: {
Recipient: true,
Recipient: {
orderBy: {
id: 'asc',
},
},
},
});
@ -88,5 +99,34 @@ export const createDocumentFromTemplate = async ({
}),
});
if (recipients && recipients.length > 0) {
document.Recipient = await Promise.all(
recipients.map(async (recipient, index) => {
const existingRecipient = document.Recipient.at(index);
return await prisma.recipient.upsert({
where: {
documentId_email: {
documentId: document.id,
email: existingRecipient?.email ?? recipient.email,
},
},
update: {
name: recipient.name,
email: recipient.email,
role: recipient.role,
},
create: {
documentId: document.id,
email: recipient.email,
name: recipient.name,
role: recipient.role,
token: nanoid(),
},
});
}),
);
}
return document;
};