merge: main into update-team-invite
This commit is contained in:
@@ -5,6 +5,8 @@
|
||||
/.pnp
|
||||
.pnp.js
|
||||
bun.lockb
|
||||
yarn.lock
|
||||
package-lock.json
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
+1
-6
@@ -21,7 +21,7 @@ NEXT_TELEMETRY_DISABLED=1
|
||||
|
||||
# Prisma
|
||||
# https://www.prisma.io/docs/reference/database-reference/connection-urls#env
|
||||
DATABASE_URL="postgres://captable:password@pg:5432/captable"
|
||||
DATABASE_URL="postgres://captable:password@127.0.0.1:54321/captable"
|
||||
|
||||
# Next Auth
|
||||
# You can generate a new secret on the command line with:
|
||||
@@ -57,8 +57,3 @@ UPLOAD_SECRET_ACCESS_KEY="password"
|
||||
UPLOAD_BUCKET_PUBLIC="captable-public-bucket"
|
||||
UPLOAD_BUCKET_PRIVATE="captable-private-bucket"
|
||||
|
||||
# Create a free trigger.dev account and get your API key from the dashboard
|
||||
TRIGGER_API_ID="xxxxxxxxxx"
|
||||
TRIGGER_API_KEY="xxxxxxxxxx"
|
||||
TRIGGER_API_URL=https://api.trigger.dev
|
||||
NEXT_PUBLIC_TRIGGER_PUBLIC_API_KEY=""
|
||||
|
||||
@@ -3,21 +3,27 @@ name: "Build docker image"
|
||||
on:
|
||||
pull_request:
|
||||
branches: ["main"]
|
||||
types: [opened, synchronize, reopened, ready_for_review]
|
||||
|
||||
concurrency:
|
||||
group: build-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build_docker:
|
||||
name: Build Docker Image
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.pull_request.draft == false
|
||||
steps:
|
||||
- name: Checkout
|
||||
- name: ⬇️ Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
- name: ⛅ Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Cache Docker layers
|
||||
- name: 🎁 Cache Docker layers
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: /tmp/.buildx-cache
|
||||
@@ -25,7 +31,7 @@ jobs:
|
||||
restore-keys: |
|
||||
${{ runner.os }}-buildx-
|
||||
|
||||
- name: Build Docker Image
|
||||
- name: 🚀 Build Docker Image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
push: false
|
||||
@@ -38,7 +44,7 @@ jobs:
|
||||
- # Temp fix
|
||||
# https://github.com/docker/build-push-action/issues/252
|
||||
# https://github.com/moby/buildkit/issues/1896
|
||||
name: Move cache
|
||||
name: 🚚 Move cache
|
||||
run: |
|
||||
rm -rf /tmp/.buildx-cache
|
||||
mv /tmp/.buildx-cache-new /tmp/.buildx-cache
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
name: cleanup caches by a branch
|
||||
name: Cleanup cache
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
name: first-interaction
|
||||
name: First interaction greeting
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
name: Fly Production Deploy
|
||||
env:
|
||||
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
name: 🚀 Deploy
|
||||
runs-on: ubuntu-latest
|
||||
concurrency: deploy-group # optional: ensure only one action runs at a time
|
||||
steps:
|
||||
- name: ⬇️ Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: 🚚 Move Dockerfile to root
|
||||
run: |
|
||||
mv ./docker/Dockerfile ./Dockerfile
|
||||
|
||||
- name: 🪰 Setup flyctl
|
||||
uses: superfly/flyctl-actions/setup-flyctl@master
|
||||
with:
|
||||
version: latest
|
||||
|
||||
- name: 🚀 Deploy Production
|
||||
run: flyctl deploy --remote-only --build-arg COMMIT_SHA=${{ github.sha }}
|
||||
env:
|
||||
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Publish Docker image
|
||||
name: Production
|
||||
|
||||
on:
|
||||
release:
|
||||
@@ -6,8 +6,11 @@ on:
|
||||
|
||||
jobs:
|
||||
push_to_registries:
|
||||
name: Push Docker image to multiple registries
|
||||
name: Publish docker image & deploy to production
|
||||
runs-on: ubuntu-latest
|
||||
concurrency:
|
||||
group: release
|
||||
cancel-in-progress: true
|
||||
permissions:
|
||||
packages: write
|
||||
contents: read
|
||||
@@ -48,3 +51,13 @@ jobs:
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
|
||||
- name: 🪰 Setup flyctl
|
||||
uses: superfly/flyctl-actions/setup-flyctl@master
|
||||
with:
|
||||
version: latest
|
||||
|
||||
- name: 🚀 Deploy Production
|
||||
run: flyctl deploy --remote-only --image captable/captable:latest
|
||||
env:
|
||||
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
name: "Validate PRs"
|
||||
name: Validate PR title
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
|
||||
@@ -10,7 +10,9 @@ jobs:
|
||||
deploy:
|
||||
name: 🚀 Deploy
|
||||
runs-on: ubuntu-latest
|
||||
concurrency: deploy-group # optional: ensure only one action runs at a time
|
||||
concurrency:
|
||||
group: staging
|
||||
cancel-in-progress: true
|
||||
steps:
|
||||
- name: ⬇️ Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
|
||||
@@ -4,7 +4,11 @@
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# lockfiles
|
||||
bun.lockb
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
+2
-3
@@ -5,9 +5,8 @@ tasks:
|
||||
export NEXTAUTH_SECRET="$(openssl rand -base64 32)" &&
|
||||
export BASE_URL="$(gp url 3000)" &&
|
||||
export NEXT_PUBLIC_BASE_URL="$(gp url 3000)" &&
|
||||
export EMAIL_SERVER_PORT=2500 &&
|
||||
npx prisma migrate dev
|
||||
command: pnpm dx
|
||||
export EMAIL_SERVER_PORT=2500
|
||||
command: pnpm db:migrate && pnpm dx
|
||||
|
||||
ports:
|
||||
- port: 3000
|
||||
|
||||
+4
-5
@@ -1,12 +1,11 @@
|
||||
// .lintstagedrc.cjs
|
||||
const path = require('path')
|
||||
const path = require("node:path");
|
||||
|
||||
const buildBiomeCommand = (filenames) =>
|
||||
`biome check --apply --no-errors-on-unmatched ${filenames
|
||||
.map((f) => path.relative(process.cwd(), f))
|
||||
.join(' ')}`
|
||||
.join(" ")}`;
|
||||
|
||||
/** @type {import('lint-staged').Config} */
|
||||
module.exports = {
|
||||
'*.{js,jsx,ts,tsx,json,cjs,mjs}': [buildBiomeCommand],
|
||||
}
|
||||
"*.{js,ts,cjs,mjs,d.cts,d.mts,jsx,tsx,json,jsonc}": [buildBiomeCommand],
|
||||
};
|
||||
|
||||
Vendored
+3
@@ -13,5 +13,8 @@
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.organizeImports": "explicit",
|
||||
"source.fixAll": "explicit"
|
||||
},
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "biomejs.biome"
|
||||
}
|
||||
}
|
||||
|
||||
-173
@@ -1,173 +0,0 @@
|
||||
# Contribution Guidelines
|
||||
|
||||
## Table of contents
|
||||
|
||||
- <a href="#stack">Stack</a>
|
||||
- <a href="#start">Getting started</a>
|
||||
- <a href="#setup">Setup development environment</a>
|
||||
- <a href="#changes">Implement your changes</a>
|
||||
- <a href="#pr">Open a pull request</a>
|
||||
- <a href="#community">Join our community</a>
|
||||
|
||||
|
||||
<h2 id="stack">Stack</h2>
|
||||
|
||||
- [Next.js](https://nextjs.org)
|
||||
- [Prisma](https://prisma.io)
|
||||
- [Tailwind CSS](https://tailwindcss.com)
|
||||
- [tRPC](https://trpc.io)
|
||||
- [@shadcn/ui](https://ui.shadcn.com/)
|
||||
|
||||
---
|
||||
|
||||
<h2 id="start">Getting started</h2>
|
||||
When contributing to **Captable, Inc.**, whether on GitHub or in other community spaces:
|
||||
|
||||
- Be respectful, civil, and open-minded.
|
||||
- Before opening a new pull request, try searching through the [issue tracker](https://github.com/captableinc/captable/issues) for known issues or fixes.
|
||||
- If you want to make code changes based on your personal opinion(s), make sure you open an issue first describing the changes you want to make, and open a pull request only when your suggestions get approved by maintainers.
|
||||
|
||||
In order to not waste your time implementing a change that has already been declined, or is generally not needed, start by [opening an issue](https://github.com/captableinc/captable/issues/new) describing the problem you would like to solve.
|
||||
|
||||
|
||||
<h2 id="setup">Setup development environment</h2>
|
||||
|
||||
- <a href="#with-docker">Development environment with Docker</a>
|
||||
- <a href="#without-docker">Development environment without Docker</a>
|
||||
- <a href="#commands">Frequently used commands</a>
|
||||
|
||||
<h3 id="with-docker">Development environment with Docker</h3>
|
||||
|
||||
- [Install Docker](https://docs.docker.com/get-docker/) on your machine.
|
||||
- [Install Docker Compose](https://docs.docker.com/compose/install/) on your machine.
|
||||
- [Fork the repository](https://github.com/captableinc/captable/fork)
|
||||
- [Install node and pnpm](https://pnpm.io/installation) on your machine. (optional)
|
||||
|
||||
- Clone the repository
|
||||
|
||||
```bash
|
||||
git clone https://github.com/<your-github-name>/captable.git
|
||||
```
|
||||
|
||||
- Copy `.env.example` to `.env`
|
||||
|
||||
```bash
|
||||
cp .env.example .env`
|
||||
```
|
||||
|
||||
- Run the following command to start the development environment
|
||||
|
||||
```bash
|
||||
# With pnpm installed
|
||||
pnpm dx
|
||||
|
||||
# Without pnpm installed
|
||||
docker compose up
|
||||
```
|
||||
|
||||
> **Note**
|
||||
> Everytime you make changes to Dockerfile.dev or compose.yml, you need to rebuild the docker image by running `docker compose up --build`
|
||||
|
||||
- Running `docker compose up` will start all the services on their respective ports.
|
||||
- App will be running on [http://localhost:3000](http://localhost:3000)
|
||||
- Emails will be intercepted: [http://localhost:8025](http://localhost:8025)
|
||||
- SMTP will be on PORT `http://localhost:1025`
|
||||
- Postgres will be on PORT `http://localhost:5432`
|
||||
|
||||
---
|
||||
|
||||
<h3 id="without-docker">Development environment without Docker</h3>
|
||||
|
||||
> This has been tested on Mac OS and works really well. If you are using Linux/Windows/WSL, you might need to install some additional dependencies.
|
||||
|
||||
- [Fork the repository](https://github.com/captableinc/captable/fork)
|
||||
|
||||
- Clone the repository
|
||||
|
||||
```bash
|
||||
git clone https://github.com/<your-github-name>/envless.git
|
||||
```
|
||||
|
||||
- Copy `.env.example` to `.env`
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
- Install latest version of node and pnpm
|
||||
- Install latest version of postgres database
|
||||
- Install [mailpit](https://mailpit.axllent.org/docs/install/) for SMTP and email interception
|
||||
- Create database `captable` in postgres database
|
||||
- Update `.env` file's `DATABASE_URL` with database credentials
|
||||
- For a quick start, you can use [Supabase](https://supabase.com/) or [Neon](https://neon.tech/) as well.
|
||||
- Run the following command to install dependencies
|
||||
|
||||
```bash
|
||||
pnpm install
|
||||
```
|
||||
|
||||
- Run the following command to migrate and seed the database
|
||||
|
||||
```bash
|
||||
pnpm db:migrate
|
||||
pnpm db:seed
|
||||
```
|
||||
|
||||
- Run the following command to start the development server
|
||||
|
||||
```bash
|
||||
pnpm dev
|
||||
|
||||
# On a different terminal, run the following command to start the mail server
|
||||
pnpm email:dev
|
||||
```
|
||||
|
||||
- App will be running on [http://localhost:3000](http://localhost:3000)
|
||||
- Emails will be intercepted: [http://localhost:8025](http://localhost:8025)
|
||||
- SMTP will be on PORT `http://localhost:1025`
|
||||
- Postgres will be on PORT `http://localhost:5432`
|
||||
|
||||
---
|
||||
|
||||
<h3 id="commands">Frequently used commands</h3>
|
||||
|
||||
> TODO - Please take a look at the `package.json` file for more commands.
|
||||
|
||||
---
|
||||
|
||||
|
||||
<h2 id="changes">Implement your changes</h2>
|
||||
|
||||
When making commits, make sure to follow the [conventional commit](https://www.conventionalcommits.org/en/v1.0.0/) guidelines, i.e. prepending the message with `feat:`, `fix:`, `chore:`, `docs:`, etc...
|
||||
|
||||
```bash
|
||||
git add <file> && git commit -m "feat/fix/chore/docs: commit message"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
<h2 id="pr">Open a pull request</h2>
|
||||
|
||||
### When you're done
|
||||
|
||||
Make a commit and push your code to your github fork and make a pull-request.
|
||||
|
||||
Thanks for your contributions. Much ❤️
|
||||
|
||||
---
|
||||
|
||||
<h2 id="community">Community</h2>
|
||||
|
||||
Please join us on our discord to get help, discuss features, or just hang out.
|
||||
|
||||
- [Join Captable on Discord](https://discord.gg/rCpqnD6G6p)
|
||||
- [Follow Captable on Twitter](https://twitter.com/captableinc)
|
||||
|
||||
---
|
||||
|
||||
<h2 id="contributors">Contributors</h2>
|
||||
<a href="https://github.com/captableinc/captable/graphs/contributors">
|
||||
<p>
|
||||
<img src="https://contrib.rocks/image?repo=captableinc/captable" alt="A table of avatars from the project's contributors" />
|
||||
</p>
|
||||
</a>
|
||||
@@ -30,14 +30,21 @@
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<h3 id="toc">Table of contents</h3>
|
||||
|
||||
- <a href="#features">Features</a>
|
||||
- <a href="#community">Community</a>
|
||||
- <a href="#contributing">Contibuting</a>
|
||||
- <a href="https://github.com/captableinc/captable/blob/main/SELF-HOSTING.md" target="_blank">Self hosting</a>
|
||||
|
||||
<h2 id="features">✨ Key features</h2>
|
||||
|
||||
> [!IMPORTANT]
|
||||
> We envision a world where cap table management is accessible, secure, and empowering for all. Captable, Inc. aims to democratize the handling of cap tables, securities, and stakeholder interactions. Through cutting-edge technology and a commitment to openness, we strive to be the catalyst for positive change in financial ecosystems.
|
||||
|
||||
👷 **Incorporation** - Captable, Inc. helps you incorporate your company in minutes, with all the necessary legal documents and filings taken care of. (coming soon...)
|
||||
👷 **Incorporation** (wip) - Captable, Inc. helps you incorporate your company in minutes, with all the necessary legal documents and filings taken care of.
|
||||
|
||||
👷 **Cap table management** - Captable, Inc. helps you keep track of your company’s ownership structure, including who owns what percentage of the company, how much stock/options has been issued, and more. (work in progress...)
|
||||
👷 **Cap table management** (wip) - Captable, Inc. helps you keep track of your company’s ownership structure, including who owns what percentage of the company, how much stock/options has been issued, and more.
|
||||
|
||||
✅ **Fundraise** - Captable, Inc. can help you raise capital, whether its signing standard or custom SAFE or creating and managing fundraising rounds, tracking investor commitments, and more.
|
||||
|
||||
@@ -47,16 +54,14 @@
|
||||
|
||||
✅ **Data rooms** - Captable, Inc. provides a secure virtual data room where you can store important documents and share them with investors, employees, and other stakeholders.
|
||||
|
||||
<br />
|
||||
|
||||
<h2 id="contributors">🤝 Community</h2>
|
||||
<h2 id="community">🤝 Community</h2>
|
||||
We have a community of developers, designers, and entrepreneurs who are passionate about building the future of finance. Join us on Discord to connect with like-minded individuals, share your ideas, and collaborate on projects.
|
||||
|
||||
* [Join us on Discord](https://discord.gg/rCpqnD6G6p)
|
||||
* [Follow us on Twitter](https://twitter.com/captableinc)
|
||||
* [Meet the Founder](https://captable.inc/schedule)
|
||||
|
||||
<br />
|
||||
|
||||
<h2 id="contributing">🫡 Contributing</h2>
|
||||
|
||||
@@ -66,19 +71,171 @@ We have a community of developers, designers, and entrepreneurs who are passiona
|
||||
- Any contributions you make are truly appreciated.
|
||||
- Please follow this [Contribution guideline](https://github.com/captableinc/captable/blob/main/CONTRIBUTING.md) to get started.
|
||||
|
||||
<br />
|
||||
|
||||
<h2 id="contributors">❤️ Contributors</h2>
|
||||
<h3 id="stack">Stack</h3>
|
||||
|
||||
- [Next.js](https://nextjs.org)
|
||||
- [Prisma](https://prisma.io)
|
||||
- [Tailwind CSS](https://tailwindcss.com)
|
||||
- [tRPC](https://trpc.io)
|
||||
- [@shadcn/ui](https://ui.shadcn.com/)
|
||||
|
||||
---
|
||||
|
||||
<h3 id="start">Getting started</h3>
|
||||
When contributing to **Captable, Inc.**, whether on GitHub or in other community spaces:
|
||||
|
||||
- Be respectful, civil, and open-minded.
|
||||
- Before opening a new pull request, try searching through the [issue tracker](https://github.com/captableinc/captable/issues) for known issues or fixes.
|
||||
|
||||
|
||||
<h3 id="setup">Setup development environment</h3>
|
||||
|
||||
- <a href="#gitpod">Development environment on Gitpod</a>
|
||||
- <a href="#with-docker">Development environment with Docker</a>
|
||||
- <a href="#without-docker">Development environment without Docker</a>
|
||||
|
||||
<h4 id="gitpod">Development environment on Gitpod</h4>
|
||||
|
||||
- Click the button below to open this project in Gitpod.
|
||||
|
||||
[](https://gitpod.io/#https://github.com/captableinc/captable)
|
||||
|
||||
---
|
||||
|
||||
<h4 id="with-docker">Development environment with Docker</h4>
|
||||
|
||||
- <a href="https://docs.docker.com/get-docker/" target="_blank">Install Docker</a> & <a href="https://docs.docker.com/compose/install/" target="_blank">Docker Compose</a>
|
||||
- <a href="https://github.com/captableinc/captable/fork" target="_blank">Fork</a> & clone the forked repository
|
||||
- <a href="https://pnpm.io/installation" target="_blank">Install node and pnpm</a>. (optional)
|
||||
- Copy `.env.example` to `.env`
|
||||
|
||||
```bash
|
||||
cp .env.example .env`
|
||||
```
|
||||
|
||||
- Run the following command to start the development environment
|
||||
|
||||
```bash
|
||||
|
||||
# With pnpm installed
|
||||
pnpm dx
|
||||
|
||||
# Without pnpm installed
|
||||
docker compose up
|
||||
|
||||
```
|
||||
|
||||
- Run the following command to migrate and seed the database
|
||||
|
||||
```bash
|
||||
|
||||
docker compose run app pnpm db:migrate
|
||||
docker compose run app pnpm db:seed
|
||||
|
||||
```
|
||||
|
||||
> **Note**
|
||||
> Everytime you make changes to Dockerfile.dev or compose.yml, you need to rebuild the docker image by running `docker compose up --build`
|
||||
|
||||
- Running `docker compose up` will start all the services on their respective ports.
|
||||
- App will be running on [http://localhost:3000](http://localhost:3000)
|
||||
- Emails will be intercepted: [http://localhost:8025](http://localhost:8025)
|
||||
- SMTP will be on PORT `http://localhost:1025`
|
||||
- Postgres will be on PORT `http://localhost:5432`
|
||||
|
||||
- Frequently used commands
|
||||
- `docker compose up` - Start the development environment
|
||||
- `docker compose down` - Stop the development environment
|
||||
- `docker compose logs -f` - View logs of the running services
|
||||
- `docker compose up --build` - Rebuild the docker image
|
||||
- `docker compose run app pnpm db:migrate` - Run database migrations
|
||||
- `docker compose run app pnpm db:seed` - Seed the database
|
||||
|
||||
---
|
||||
|
||||
<h4 id="without-docker">Development environment without Docker</h4>
|
||||
|
||||
> This has been tested on Mac OS and works really well. If you are using Linux/Windows/WSL, you might need to install some additional dependencies.
|
||||
|
||||
- [Fork the repository](https://github.com/captableinc/captable/fork)
|
||||
|
||||
- Clone the repository
|
||||
|
||||
```bash
|
||||
git clone https://github.com/<your-github-name>/envless.git
|
||||
```
|
||||
|
||||
- Copy `.env.example` to `.env`
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
- Install latest version of node and pnpm
|
||||
- Install latest version of postgres database
|
||||
- Install [mailpit](https://mailpit.axllent.org/docs/install/) for SMTP and email interception
|
||||
- Create database `captable` in postgres database
|
||||
- Update `.env` file's `DATABASE_URL` with database credentials
|
||||
- For a quick start, you can use [Supabase](https://supabase.com/) or [Neon](https://neon.tech/) as well.
|
||||
- Run the following command to install dependencies
|
||||
|
||||
```bash
|
||||
pnpm install
|
||||
```
|
||||
|
||||
- Run the following command to migrate and seed the database
|
||||
|
||||
```bash
|
||||
pnpm db:migrate
|
||||
pnpm db:seed
|
||||
```
|
||||
|
||||
- Run the following command to start the development server
|
||||
|
||||
```bash
|
||||
pnpm dev
|
||||
|
||||
# On a different terminal, run the following command to start the mail server
|
||||
pnpm email:dev
|
||||
```
|
||||
|
||||
- App will be running on [http://localhost:3000](http://localhost:3000)
|
||||
- Emails will be intercepted: [http://localhost:8025](http://localhost:8025)
|
||||
- SMTP will be on PORT `http://localhost:1025`
|
||||
- Postgres will be on PORT `http://localhost:5432`
|
||||
|
||||
- Frequently used commands
|
||||
- `pnpm dev` - Start the development server
|
||||
- `pnpm email:dev` - Start the mail server
|
||||
- `pnpm db:migrate` - Run database migrations
|
||||
- `pnpm db:seed` - Seed the database
|
||||
|
||||
<h4 id="changes">Implement your changes</h4>
|
||||
|
||||
When making commits, make sure to follow the [conventional commit](https://www.conventionalcommits.org/en/v1.0.0/) guidelines, i.e. prepending the message with `feat:`, `fix:`, `chore:`, `docs:`, etc...
|
||||
|
||||
```bash
|
||||
git add <file> && git commit -m "feat/fix/chore/docs: commit message"
|
||||
```
|
||||
|
||||
<h4 id="pr">Open a pull request</h4>
|
||||
|
||||
> When you're done
|
||||
|
||||
Make a commit and push your code to your github fork and make a pull-request.
|
||||
|
||||
Thanks for your contributions. Much ❤️
|
||||
|
||||
---
|
||||
|
||||
<h2 id="contributors">💌 Contributors</h2>
|
||||
<a href="https://github.com/captableinc/captable/graphs/contributors">
|
||||
<p>
|
||||
<img src="https://contrib.rocks/image?repo=captableinc/captable" alt="A table of avatars from the project's contributors" />
|
||||
</p>
|
||||
</a>
|
||||
|
||||
## Stack
|
||||
- [Next.js](https://nextjs.org)
|
||||
- [Prisma](https://prisma.io)
|
||||
- [Tailwind CSS](https://tailwindcss.com)
|
||||
- [tRPC](https://trpc.io)
|
||||
- [@shadcn/ui](https://ui.shadcn.com/)
|
||||
---
|
||||
|
||||

|
||||
@@ -0,0 +1,17 @@
|
||||
<h2 id="hosting">🚀 Self hosting</h2>
|
||||
|
||||
Captable, Inc. is an open-source project that you can host on your own server. This gives you full control over your data.
|
||||
|
||||
If you like to self-host Captable, Inc., please schedule a call with us, and we will help you set up your own instance.
|
||||
|
||||
<a href="https://captable.inc/schedule/"><img alt="Book us" src="https://cal.com/book-with-cal-dark.svg" /></a>
|
||||
|
||||
- **Prerequisites**:
|
||||
- Docker
|
||||
- Postgres database (RDS, Cloud SQL, Supabase, Neon etc.)
|
||||
- SMTP provider (Resend, AWS SES, Mailgun, Sendgrid etc.)
|
||||
- S3 Compatible storage (AWS S3, R2, Minio, Backblaze etc.)
|
||||
|
||||
- **Official Docker Images**:
|
||||
- <a href="https://hub.docker.com/r/captable/captable" target="_blank">Docker hub</a>
|
||||
- <a href="https://github.com/captableinc/captable/pkgs/container/captable" target="_blank">Github registry</a>
|
||||
+36
-113
@@ -1,5 +1,33 @@
|
||||
{
|
||||
"$schema": "https://biomejs.dev/schemas/1.7.3/schema.json",
|
||||
"organizeImports": {
|
||||
"enabled": true,
|
||||
"ignore": [
|
||||
"node_modules",
|
||||
".next",
|
||||
"dist",
|
||||
"public/pdf.worker.min.js",
|
||||
"./prisma/enums.ts"
|
||||
]
|
||||
},
|
||||
"linter": {
|
||||
"enabled": true,
|
||||
"rules": {
|
||||
"recommended": true,
|
||||
"suspicious": {
|
||||
"useAwait": "error"
|
||||
},
|
||||
"correctness": { "noUnusedVariables": "warn" },
|
||||
"style": { "useImportType": "error" }
|
||||
},
|
||||
"ignore": [
|
||||
"node_modules",
|
||||
".next",
|
||||
"dist",
|
||||
"public/pdf.worker.min.js",
|
||||
"./prisma/enums.ts"
|
||||
]
|
||||
},
|
||||
"formatter": {
|
||||
"enabled": true,
|
||||
"formatWithErrors": false,
|
||||
@@ -8,117 +36,12 @@
|
||||
"lineEnding": "lf",
|
||||
"lineWidth": 80,
|
||||
"attributePosition": "auto",
|
||||
"ignore": ["public/pdf.worker.min.js", "./prisma/enums.ts"]
|
||||
},
|
||||
"organizeImports": { "enabled": true },
|
||||
"linter": {
|
||||
"enabled": true,
|
||||
"rules": {
|
||||
"recommended": false,
|
||||
"complexity": {
|
||||
"noBannedTypes": "error",
|
||||
"noUselessTypeConstraint": "error",
|
||||
"useLiteralKeys": "error",
|
||||
"useOptionalChain": "off"
|
||||
},
|
||||
"correctness": { "noPrecisionLoss": "error", "noUnusedVariables": "off" },
|
||||
"style": {
|
||||
"noInferrableTypes": "error",
|
||||
"noNamespace": "error",
|
||||
"useAsConstAssertion": "error",
|
||||
"useBlockStatements": "off",
|
||||
"useConsistentArrayType": "off",
|
||||
"useForOf": "error",
|
||||
"useShorthandFunctionType": "error"
|
||||
},
|
||||
"suspicious": {
|
||||
"noEmptyBlockStatements": "error",
|
||||
"noExplicitAny": "error",
|
||||
"noExtraNonNullAssertion": "error",
|
||||
"noMisleadingInstantiator": "error",
|
||||
"noUnsafeDeclarationMerging": "error",
|
||||
"useAwait": "off",
|
||||
"useNamespaceKeyword": "error"
|
||||
}
|
||||
}
|
||||
},
|
||||
"javascript": {
|
||||
"formatter": {
|
||||
"jsxQuoteStyle": "double",
|
||||
"quoteProperties": "asNeeded",
|
||||
"trailingComma": "all",
|
||||
"semicolons": "always",
|
||||
"arrowParentheses": "always",
|
||||
"bracketSpacing": true,
|
||||
"bracketSameLine": false,
|
||||
"quoteStyle": "double",
|
||||
"attributePosition": "auto"
|
||||
}
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"include": ["*.ts", "*.tsx", "*.mts", "*.cts"],
|
||||
"linter": {
|
||||
"rules": {
|
||||
"correctness": {
|
||||
"noConstAssign": "off",
|
||||
"noGlobalObjectCalls": "off",
|
||||
"noInvalidConstructorSuper": "off",
|
||||
"noNewSymbol": "off",
|
||||
"noSetterReturn": "off",
|
||||
"noUndeclaredVariables": "off",
|
||||
"noUnreachable": "off",
|
||||
"noUnreachableSuper": "off"
|
||||
},
|
||||
"style": {
|
||||
"noArguments": "error",
|
||||
"noVar": "error",
|
||||
"useConst": "error"
|
||||
},
|
||||
"suspicious": {
|
||||
"noDuplicateClassMembers": "off",
|
||||
"noDuplicateObjectKeys": "off",
|
||||
"noDuplicateParameters": "off",
|
||||
"noFunctionAssign": "off",
|
||||
"noImportAssign": "off",
|
||||
"noRedeclare": "off",
|
||||
"noUnsafeNegation": "off",
|
||||
"useGetterReturn": "off"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"include": ["*.ts", "*.tsx", "*.mts", "*.cts"],
|
||||
"linter": {
|
||||
"rules": {
|
||||
"correctness": {
|
||||
"noConstAssign": "off",
|
||||
"noGlobalObjectCalls": "off",
|
||||
"noInvalidConstructorSuper": "off",
|
||||
"noNewSymbol": "off",
|
||||
"noSetterReturn": "off",
|
||||
"noUndeclaredVariables": "off",
|
||||
"noUnreachable": "off",
|
||||
"noUnreachableSuper": "off"
|
||||
},
|
||||
"style": {
|
||||
"noArguments": "error",
|
||||
"noVar": "error",
|
||||
"useConst": "error"
|
||||
},
|
||||
"suspicious": {
|
||||
"noDuplicateClassMembers": "off",
|
||||
"noDuplicateObjectKeys": "off",
|
||||
"noDuplicateParameters": "off",
|
||||
"noFunctionAssign": "off",
|
||||
"noImportAssign": "off",
|
||||
"noRedeclare": "off",
|
||||
"noUnsafeNegation": "off",
|
||||
"useGetterReturn": "off"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
"ignore": [
|
||||
"node_modules",
|
||||
".next",
|
||||
"dist",
|
||||
"public/pdf.worker.min.js",
|
||||
"./prisma/enums.ts"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,9 @@ const config = {
|
||||
|
||||
return config;
|
||||
},
|
||||
experimental: {
|
||||
instrumentationHook: true,
|
||||
},
|
||||
eslint: {
|
||||
// Warning: This allows production builds to successfully complete even if
|
||||
// your project has ESLint errors.
|
||||
|
||||
+8
-14
@@ -11,11 +11,11 @@
|
||||
"dx:restart": "docker-compose restart",
|
||||
"dx:migrate": "docker compose run app pnpm db:migrate",
|
||||
"dx:seed": "docker compose run app pnpm db:seed",
|
||||
"db:push": "pnpx prisma db push",
|
||||
"db:studio": "pnpx prisma studio",
|
||||
"db:migrate": "pnpx prisma migrate dev",
|
||||
"db:generate": "pnpx prisma generate",
|
||||
"db:seed": "NODE_ENV='test' pnpx tsx prisma/seeds/index.ts",
|
||||
"db:push": "prisma db push",
|
||||
"db:studio": "prisma studio",
|
||||
"db:migrate": "prisma migrate dev",
|
||||
"db:generate": "prisma generate",
|
||||
"db:seed": "NODE_ENV='test' tsx prisma/seeds/index.ts",
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
@@ -23,10 +23,9 @@
|
||||
"prepare": "husky install",
|
||||
"lint": "biome check --apply ./src",
|
||||
"email:dev": "mailpit",
|
||||
"format": "biome format --write ./src && pnpx prisma format",
|
||||
"format": "biome format --write ./src && prisma format",
|
||||
"email:preview": "email preview ./src/emails",
|
||||
"copy:pdfjs": "node scripts/copy-pdfjs-worker.cjs",
|
||||
"trigger": "pnpx @trigger.dev/cli@latest dev"
|
||||
"copy:pdfjs": "node scripts/copy-pdfjs-worker.cjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ark-ui/react": "^2.2.3",
|
||||
@@ -62,9 +61,6 @@
|
||||
"@tanstack/react-query": "^4.36.1",
|
||||
"@tanstack/react-table": "^8.16.0",
|
||||
"@tremor/react": "^3.15.1",
|
||||
"@trigger.dev/nextjs": "^2.3.18",
|
||||
"@trigger.dev/react": "^2.3.18",
|
||||
"@trigger.dev/sdk": "^2.3.18",
|
||||
"@trpc/client": "^10.43.6",
|
||||
"@trpc/next": "^10.45.2",
|
||||
"@trpc/react-query": "^10.43.6",
|
||||
@@ -91,6 +87,7 @@
|
||||
"nodemailer": "^6.9.8",
|
||||
"papaparse": "^5.4.1",
|
||||
"pdf-lib": "^1.17.1",
|
||||
"pg-boss": "^9.0.3",
|
||||
"prisma-json-types-generator": "^3.0.4",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.2.0",
|
||||
@@ -135,8 +132,5 @@
|
||||
},
|
||||
"ct3aMetadata": {
|
||||
"initVersion": "7.25.1"
|
||||
},
|
||||
"trigger.dev": {
|
||||
"endpointId": "captable-A0-I"
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+161
-214
@@ -104,15 +104,6 @@ dependencies:
|
||||
'@tremor/react':
|
||||
specifier: ^3.15.1
|
||||
version: 3.16.2(react-dom@18.2.0)(react@18.3.1)(tailwindcss@3.4.3)
|
||||
'@trigger.dev/nextjs':
|
||||
specifier: ^2.3.18
|
||||
version: 2.3.18(@trigger.dev/sdk@2.3.18)(next@14.2.3)
|
||||
'@trigger.dev/react':
|
||||
specifier: ^2.3.18
|
||||
version: 2.3.18(react-dom@18.2.0)(react@18.3.1)
|
||||
'@trigger.dev/sdk':
|
||||
specifier: ^2.3.18
|
||||
version: 2.3.18
|
||||
'@trpc/client':
|
||||
specifier: ^10.43.6
|
||||
version: 10.45.2(@trpc/server@10.45.2)
|
||||
@@ -191,6 +182,9 @@ dependencies:
|
||||
pdf-lib:
|
||||
specifier: ^1.17.1
|
||||
version: 1.17.1
|
||||
pg-boss:
|
||||
specifier: ^9.0.3
|
||||
version: 9.0.3
|
||||
prisma-json-types-generator:
|
||||
specifier: ^3.0.4
|
||||
version: 3.0.4(prisma@5.14.0)(typescript@5.4.5)
|
||||
@@ -4657,10 +4651,6 @@ packages:
|
||||
resolution: {integrity: sha512-DJSilV5+ytBP1FbFcEJovv4rnnm/CokuVvrBEtW/Va9DvuJ3HksbXUJEpI0aV1KtuL4ZoO9AVE6PyNLzF7tLeA==}
|
||||
dev: false
|
||||
|
||||
/@tanstack/query-core@5.0.0-beta.0:
|
||||
resolution: {integrity: sha512-VGq/H3PuRoj0shOcg1S5Flv3YD2qNz2ttk8w5xe5AHQE1I8NO9EHSBUxezIpk4dD6M7bQDtwHBMqqU2EwMwyUw==}
|
||||
dev: false
|
||||
|
||||
/@tanstack/react-query@4.36.1(react-dom@18.2.0)(react@18.3.1):
|
||||
resolution: {integrity: sha512-y7ySVHFyyQblPl3J3eQBWpXZkliroki3ARnBKsdJchlgt7yJLRDUcf4B8soufgiYt3pEQIkBWBx1N9/ZPIeUWw==}
|
||||
peerDependencies:
|
||||
@@ -4679,24 +4669,6 @@ packages:
|
||||
use-sync-external-store: 1.2.2(react@18.3.1)
|
||||
dev: false
|
||||
|
||||
/@tanstack/react-query@5.0.0-beta.2(react-dom@18.2.0)(react@18.3.1):
|
||||
resolution: {integrity: sha512-JdK1HRw20tuwg3GfT3QZTkuS7s2KDa9FeozuJ7jZULlwPczZagouqYmM6+PL0ad6jfCnw8NzmLFtZdlBx6cTmA==}
|
||||
peerDependencies:
|
||||
react: ^18.0.0
|
||||
react-dom: ^18.0.0
|
||||
react-native: '*'
|
||||
peerDependenciesMeta:
|
||||
react-dom:
|
||||
optional: true
|
||||
react-native:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@tanstack/query-core': 5.0.0-beta.0
|
||||
client-only: 0.0.1
|
||||
react: 18.3.1
|
||||
react-dom: 18.2.0(react@18.3.1)
|
||||
dev: false
|
||||
|
||||
/@tanstack/react-table@8.17.3(react-dom@18.2.0)(react@18.3.1):
|
||||
resolution: {integrity: sha512-5gwg5SvPD3lNAXPuJJz1fOCEZYk9/GeBFH3w/hCgnfyszOIzwkwgp5I7Q4MJtn0WECp84b5STQUDdmvGi8m3nA==}
|
||||
engines: {node: '>=12'}
|
||||
@@ -4959,75 +4931,6 @@ packages:
|
||||
- tailwindcss
|
||||
dev: false
|
||||
|
||||
/@trigger.dev/core-backend@2.3.18:
|
||||
resolution: {integrity: sha512-LVeeerraGeqKNd2gtajQY+mnGWqkYW7Q2r5oWpL5xIZ8aQg3HRhSIfZs1dryexwKlfqnRjGWueGTy2+j1tbzcg==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
dev: false
|
||||
|
||||
/@trigger.dev/core@2.3.18:
|
||||
resolution: {integrity: sha512-j2EdCeyMkZ+zlVnnHl5zmBb+YURSw4x75NqQU1G5X08pQAza7G0qEn8DDGIMR5ieUMiHP0WS9oYy/voYdNfibQ==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
dependencies:
|
||||
ulidx: 2.3.0
|
||||
zod: 3.22.3
|
||||
zod-error: 1.5.0
|
||||
dev: false
|
||||
|
||||
/@trigger.dev/nextjs@2.3.18(@trigger.dev/sdk@2.3.18)(next@14.2.3):
|
||||
resolution: {integrity: sha512-ZS0RTZNrzGEKfOLQLYt3iqlNquD7pd39Hpd/+2tvRCaPSQ3qPYQvdjBSueW0OURZSQSiNno5VUYR5vbVBcAaXA==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
peerDependencies:
|
||||
'@trigger.dev/sdk': ^2.3.18
|
||||
next: '>=12.0.0'
|
||||
dependencies:
|
||||
'@trigger.dev/sdk': 2.3.18
|
||||
debug: 4.3.4
|
||||
next: 14.2.3(@babel/core@7.24.5)(react-dom@18.2.0)(react@18.3.1)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/@trigger.dev/react@2.3.18(react-dom@18.2.0)(react@18.3.1):
|
||||
resolution: {integrity: sha512-S+frKgYJoT5JEzYejqlL5rkeHVxTR7wuJvvBMk+cFwA+nKV6isr8VnWmGQivGXGDlKMdF8IihwO+Guqs5tv/Hw==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18
|
||||
dependencies:
|
||||
'@tanstack/react-query': 5.0.0-beta.2(react-dom@18.2.0)(react@18.3.1)
|
||||
'@trigger.dev/core': 2.3.18
|
||||
debug: 4.3.4
|
||||
react: 18.3.1
|
||||
zod: 3.22.3
|
||||
transitivePeerDependencies:
|
||||
- react-dom
|
||||
- react-native
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/@trigger.dev/sdk@2.3.18:
|
||||
resolution: {integrity: sha512-Bjxgl4BbWOAL8rhxeBkl7SzvLLRBMJjiftq/7W7u96MDyPRFUoZZvVMSZzTJufnLBf/xS2JTi8LWU8gzhDJDvw==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
dependencies:
|
||||
'@trigger.dev/core': 2.3.18
|
||||
'@trigger.dev/core-backend': 2.3.18
|
||||
chalk: 5.3.0
|
||||
cronstrue: 2.50.0
|
||||
debug: 4.3.4
|
||||
evt: 2.5.7
|
||||
get-caller-file: 2.0.5
|
||||
git-remote-origin-url: 4.0.0
|
||||
git-repo-info: 2.1.1
|
||||
slug: 6.1.0
|
||||
terminal-link: 3.0.0
|
||||
ulid: 2.3.0
|
||||
uuid: 9.0.1
|
||||
ws: 8.17.0
|
||||
zod: 3.22.3
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
- supports-color
|
||||
- utf-8-validate
|
||||
dev: false
|
||||
|
||||
/@trpc/client@10.45.2(@trpc/server@10.45.2):
|
||||
resolution: {integrity: sha512-ykALM5kYWTLn1zYuUOZ2cPWlVfrXhc18HzBDyRhoPYN0jey4iQHEFSEowfnhg1RvYnrAVjNBgHNeSAXjrDbGwg==}
|
||||
peerDependencies:
|
||||
@@ -5979,6 +5882,14 @@ packages:
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/aggregate-error@3.1.0:
|
||||
resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==}
|
||||
engines: {node: '>=8'}
|
||||
dependencies:
|
||||
clean-stack: 2.2.0
|
||||
indent-string: 4.0.0
|
||||
dev: false
|
||||
|
||||
/ansi-escapes@4.3.2:
|
||||
resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -5986,13 +5897,6 @@ packages:
|
||||
type-fest: 0.21.3
|
||||
dev: true
|
||||
|
||||
/ansi-escapes@5.0.0:
|
||||
resolution: {integrity: sha512-5GFMVX8HqE/TB+FuBJGuO5XG0WrsA6ptUqoODaT/n9mmUaZFkqnBueB4leqGBCmrUHnCnC4PCZTCd0E7QQ83bA==}
|
||||
engines: {node: '>=12'}
|
||||
dependencies:
|
||||
type-fest: 1.4.0
|
||||
dev: false
|
||||
|
||||
/ansi-escapes@6.2.1:
|
||||
resolution: {integrity: sha512-4nJ3yixlEthEJ9Rk4vPcdBRkZvQZlYyu8j4/Mqz5sgIkddmEnH2Yj2ZrnP9S3tQOvSNRUIgVNF/1yPpRAGNRig==}
|
||||
engines: {node: '>=14.16'}
|
||||
@@ -6371,6 +6275,7 @@ packages:
|
||||
/chalk@5.3.0:
|
||||
resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==}
|
||||
engines: {node: ^12.17.0 || ^14.13 || >=16.0.0}
|
||||
dev: true
|
||||
|
||||
/character-entities-html4@2.1.0:
|
||||
resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==}
|
||||
@@ -6434,6 +6339,11 @@ packages:
|
||||
source-map: 0.6.1
|
||||
dev: false
|
||||
|
||||
/clean-stack@2.2.0:
|
||||
resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==}
|
||||
engines: {node: '>=6'}
|
||||
dev: false
|
||||
|
||||
/cli-cursor@3.1.0:
|
||||
resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -6678,9 +6588,11 @@ packages:
|
||||
/crelt@1.0.6:
|
||||
resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==}
|
||||
|
||||
/cronstrue@2.50.0:
|
||||
resolution: {integrity: sha512-ULYhWIonJzlScCCQrPUG5uMXzXxSixty4djud9SS37DoNxDdkeRocxzHuAo4ImRBUK+mAuU5X9TSwEDccnnuPg==}
|
||||
hasBin: true
|
||||
/cron-parser@4.9.0:
|
||||
resolution: {integrity: sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
dependencies:
|
||||
luxon: 3.4.4
|
||||
dev: false
|
||||
|
||||
/cross-fetch@3.1.8:
|
||||
@@ -6896,6 +6808,11 @@ packages:
|
||||
object-keys: 1.1.1
|
||||
dev: false
|
||||
|
||||
/delay@5.0.0:
|
||||
resolution: {integrity: sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw==}
|
||||
engines: {node: '>=10'}
|
||||
dev: false
|
||||
|
||||
/delegates@1.0.0:
|
||||
resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==}
|
||||
requiresBuild: true
|
||||
@@ -7194,14 +7111,6 @@ packages:
|
||||
safe-buffer: 5.2.1
|
||||
dev: false
|
||||
|
||||
/evt@2.5.7:
|
||||
resolution: {integrity: sha512-dr7Wd16ry5F8WNU1xXLKpFpO3HsoAGg8zC48e08vDdzMzGWCP9/QFGt1PQptEEDh8SwYP3EL8M+d/Gb0kgUp6g==}
|
||||
dependencies:
|
||||
minimal-polyfills: 2.2.3
|
||||
run-exclusive: 2.2.19
|
||||
tsafe: 1.6.6
|
||||
dev: false
|
||||
|
||||
/execa@8.0.1:
|
||||
resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==}
|
||||
engines: {node: '>=16.17'}
|
||||
@@ -7406,6 +7315,7 @@ packages:
|
||||
/get-caller-file@2.0.5:
|
||||
resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
|
||||
engines: {node: 6.* || 8.* || >= 10.*}
|
||||
dev: true
|
||||
|
||||
/get-east-asian-width@1.2.0:
|
||||
resolution: {integrity: sha512-2nk+7SIVb14QrgXFHcm84tD4bKQz0RxPuMT8Ag5KPOq7J5fEmAg0UbXdTOSHqNuHSU28k55qnceesxXRZGzKWA==}
|
||||
@@ -7438,24 +7348,6 @@ packages:
|
||||
resolve-pkg-maps: 1.0.0
|
||||
dev: true
|
||||
|
||||
/git-remote-origin-url@4.0.0:
|
||||
resolution: {integrity: sha512-EAxDksNdjuWgmVW9pVvA9jQDi/dmTaiDONktIy7qiRRhBZUI4FQK1YvBvteuTSX24aNKg9lfgxNYJEeeSXe6DA==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
dependencies:
|
||||
gitconfiglocal: 2.1.0
|
||||
dev: false
|
||||
|
||||
/git-repo-info@2.1.1:
|
||||
resolution: {integrity: sha512-8aCohiDo4jwjOwma4FmYFd3i97urZulL8XL24nIPxuE+GZnfsAyy/g2Shqx6OjUiFKUXZM+Yy+KHnOmmA3FVcg==}
|
||||
engines: {node: '>= 4.0'}
|
||||
dev: false
|
||||
|
||||
/gitconfiglocal@2.1.0:
|
||||
resolution: {integrity: sha512-qoerOEliJn3z+Zyn1HW2F6eoYJqKwS6MgC9cztTLUB/xLWX8gD/6T60pKn4+t/d6tP7JlybI7Z3z+I572CR/Vg==}
|
||||
dependencies:
|
||||
ini: 1.3.8
|
||||
dev: false
|
||||
|
||||
/glob-parent@5.1.2:
|
||||
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
|
||||
engines: {node: '>= 6'}
|
||||
@@ -8024,6 +7916,11 @@ packages:
|
||||
resolve-cwd: 3.0.0
|
||||
dev: false
|
||||
|
||||
/indent-string@4.0.0:
|
||||
resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==}
|
||||
engines: {node: '>=8'}
|
||||
dev: false
|
||||
|
||||
/inflight@1.0.6:
|
||||
resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
|
||||
requiresBuild: true
|
||||
@@ -8383,10 +8280,6 @@ packages:
|
||||
engines: {node: '>= 8'}
|
||||
dev: false
|
||||
|
||||
/layerr@2.1.0:
|
||||
resolution: {integrity: sha512-xDD9suWxfBYeXgqffRVH/Wqh+mqZrQcqPRn0I0ijl7iJQ7vu8gMGPt1Qop59pEW/jaIDNUN7+PX1Qk40+vuflg==}
|
||||
dev: false
|
||||
|
||||
/leac@0.6.0:
|
||||
resolution: {integrity: sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==}
|
||||
dev: false
|
||||
@@ -8540,6 +8433,11 @@ packages:
|
||||
yallist: 4.0.0
|
||||
dev: false
|
||||
|
||||
/luxon@3.4.4:
|
||||
resolution: {integrity: sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA==}
|
||||
engines: {node: '>=12'}
|
||||
dev: false
|
||||
|
||||
/magic-string@0.30.10:
|
||||
resolution: {integrity: sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==}
|
||||
dependencies:
|
||||
@@ -9054,10 +8952,6 @@ packages:
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/minimal-polyfills@2.2.3:
|
||||
resolution: {integrity: sha512-oxdmJ9cL+xV72h0xYxp4tP2d5/fTBpP45H8DIOn9pASuF8a3IYTf+25fMGDYGiWW+MFsuog6KD6nfmhZJQ+uUw==}
|
||||
dev: false
|
||||
|
||||
/minimalistic-assert@1.0.1:
|
||||
resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==}
|
||||
dev: false
|
||||
@@ -9516,6 +9410,13 @@ packages:
|
||||
p-limit: 3.1.0
|
||||
dev: false
|
||||
|
||||
/p-map@4.0.0:
|
||||
resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==}
|
||||
engines: {node: '>=10'}
|
||||
dependencies:
|
||||
aggregate-error: 3.1.0
|
||||
dev: false
|
||||
|
||||
/p-memoize@4.0.4:
|
||||
resolution: {integrity: sha512-ijdh0DP4Mk6J4FXlOM6vPPoCjPytcEseW8p/k5SDTSSfGV3E9bpt9Yzfifvzp6iohIieoLTkXRb32OWV0fB2Lw==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -9669,6 +9570,83 @@ packages:
|
||||
resolution: {integrity: sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ==}
|
||||
dev: false
|
||||
|
||||
/pg-boss@9.0.3:
|
||||
resolution: {integrity: sha512-cUWUiv3sr563yNy0nCZ25Tv5U0m59Y9MhX/flm0vTR012yeVCrqpfboaZP4xFOQPdWipMJpuu4g94HR0SncTgw==}
|
||||
engines: {node: '>=16'}
|
||||
dependencies:
|
||||
cron-parser: 4.9.0
|
||||
delay: 5.0.0
|
||||
lodash.debounce: 4.0.8
|
||||
p-map: 4.0.0
|
||||
pg: 8.11.5
|
||||
serialize-error: 8.1.0
|
||||
uuid: 9.0.1
|
||||
transitivePeerDependencies:
|
||||
- pg-native
|
||||
dev: false
|
||||
|
||||
/pg-cloudflare@1.1.1:
|
||||
resolution: {integrity: sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==}
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/pg-connection-string@2.6.4:
|
||||
resolution: {integrity: sha512-v+Z7W/0EO707aNMaAEfiGnGL9sxxumwLl2fJvCQtMn9Fxsg+lPpPkdcyBSv/KFgpGdYkMfn+EI1Or2EHjpgLCA==}
|
||||
dev: false
|
||||
|
||||
/pg-int8@1.0.1:
|
||||
resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==}
|
||||
engines: {node: '>=4.0.0'}
|
||||
dev: false
|
||||
|
||||
/pg-pool@3.6.2(pg@8.11.5):
|
||||
resolution: {integrity: sha512-Htjbg8BlwXqSBQ9V8Vjtc+vzf/6fVUuak/3/XXKA9oxZprwW3IMDQTGHP+KDmVL7rtd+R1QjbnCFPuTHm3G4hg==}
|
||||
peerDependencies:
|
||||
pg: '>=8.0'
|
||||
dependencies:
|
||||
pg: 8.11.5
|
||||
dev: false
|
||||
|
||||
/pg-protocol@1.6.1:
|
||||
resolution: {integrity: sha512-jPIlvgoD63hrEuihvIg+tJhoGjUsLPn6poJY9N5CnlPd91c2T18T/9zBtLxZSb1EhYxBRoZJtzScCaWlYLtktg==}
|
||||
dev: false
|
||||
|
||||
/pg-types@2.2.0:
|
||||
resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==}
|
||||
engines: {node: '>=4'}
|
||||
dependencies:
|
||||
pg-int8: 1.0.1
|
||||
postgres-array: 2.0.0
|
||||
postgres-bytea: 1.0.0
|
||||
postgres-date: 1.0.7
|
||||
postgres-interval: 1.2.0
|
||||
dev: false
|
||||
|
||||
/pg@8.11.5:
|
||||
resolution: {integrity: sha512-jqgNHSKL5cbDjFlHyYsCXmQDrfIX/3RsNwYqpd4N0Kt8niLuNoRNH+aazv6cOd43gPh9Y4DjQCtb+X0MH0Hvnw==}
|
||||
engines: {node: '>= 8.0.0'}
|
||||
peerDependencies:
|
||||
pg-native: '>=3.0.1'
|
||||
peerDependenciesMeta:
|
||||
pg-native:
|
||||
optional: true
|
||||
dependencies:
|
||||
pg-connection-string: 2.6.4
|
||||
pg-pool: 3.6.2(pg@8.11.5)
|
||||
pg-protocol: 1.6.1
|
||||
pg-types: 2.2.0
|
||||
pgpass: 1.0.5
|
||||
optionalDependencies:
|
||||
pg-cloudflare: 1.1.1
|
||||
dev: false
|
||||
|
||||
/pgpass@1.0.5:
|
||||
resolution: {integrity: sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==}
|
||||
dependencies:
|
||||
split2: 4.2.0
|
||||
dev: false
|
||||
|
||||
/picocolors@1.0.1:
|
||||
resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==}
|
||||
|
||||
@@ -9800,6 +9778,28 @@ packages:
|
||||
picocolors: 1.0.1
|
||||
source-map-js: 1.2.0
|
||||
|
||||
/postgres-array@2.0.0:
|
||||
resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==}
|
||||
engines: {node: '>=4'}
|
||||
dev: false
|
||||
|
||||
/postgres-bytea@1.0.0:
|
||||
resolution: {integrity: sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: false
|
||||
|
||||
/postgres-date@1.0.7:
|
||||
resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: false
|
||||
|
||||
/postgres-interval@1.2.0:
|
||||
resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dependencies:
|
||||
xtend: 4.0.2
|
||||
dev: false
|
||||
|
||||
/preact-render-to-string@5.2.6(preact@10.21.0):
|
||||
resolution: {integrity: sha512-JyhErpYOvBV1hEPwIxc/fHWXPfnEGdRKxc8gFdAZ7XV4tlzyzG847XAyEZqoDnynP88akM4eaHcSOzNcLWFguw==}
|
||||
peerDependencies:
|
||||
@@ -10725,12 +10725,6 @@ packages:
|
||||
engines: {node: '>=0.12.0'}
|
||||
dev: true
|
||||
|
||||
/run-exclusive@2.2.19:
|
||||
resolution: {integrity: sha512-K3mdoAi7tjJ/qT7Flj90L7QyPozwUaAG+CVhkdDje4HLKXUYC3N/Jzkau3flHVDLQVhiHBtcimVodMjN9egYbA==}
|
||||
dependencies:
|
||||
minimal-polyfills: 2.2.3
|
||||
dev: false
|
||||
|
||||
/run-parallel@1.2.0:
|
||||
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
|
||||
dependencies:
|
||||
@@ -10789,6 +10783,13 @@ packages:
|
||||
hasBin: true
|
||||
dev: false
|
||||
|
||||
/serialize-error@8.1.0:
|
||||
resolution: {integrity: sha512-3NnuWfM6vBYoy5gZFvHiYsVbafvI9vZv/+jlIigFn4oP4zjNPK3LhcY0xSCgeb1a5L8jO71Mit9LlNoi2UfDDQ==}
|
||||
engines: {node: '>=10'}
|
||||
dependencies:
|
||||
type-fest: 0.20.2
|
||||
dev: false
|
||||
|
||||
/server-only@0.0.1:
|
||||
resolution: {integrity: sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==}
|
||||
dev: false
|
||||
@@ -10942,10 +10943,6 @@ packages:
|
||||
is-fullwidth-code-point: 5.0.0
|
||||
dev: true
|
||||
|
||||
/slug@6.1.0:
|
||||
resolution: {integrity: sha512-x6vLHCMasg4DR2LPiyFGI0gJJhywY6DTiGhCrOMzb3SOk/0JVLIaL4UhyFSHu04SD3uAavrKY/K3zZ3i6iRcgA==}
|
||||
dev: false
|
||||
|
||||
/source-map-js@1.2.0:
|
||||
resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@@ -10973,6 +10970,11 @@ packages:
|
||||
/space-separated-tokens@2.0.2:
|
||||
resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==}
|
||||
|
||||
/split2@4.2.0:
|
||||
resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==}
|
||||
engines: {node: '>= 10.x'}
|
||||
dev: false
|
||||
|
||||
/std-env@3.7.0:
|
||||
resolution: {integrity: sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==}
|
||||
dev: false
|
||||
@@ -11128,14 +11130,6 @@ packages:
|
||||
dependencies:
|
||||
has-flag: 4.0.0
|
||||
|
||||
/supports-hyperlinks@2.3.0:
|
||||
resolution: {integrity: sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==}
|
||||
engines: {node: '>=8'}
|
||||
dependencies:
|
||||
has-flag: 4.0.0
|
||||
supports-color: 7.2.0
|
||||
dev: false
|
||||
|
||||
/supports-preserve-symlinks-flag@1.0.0:
|
||||
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -11241,14 +11235,6 @@ packages:
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/terminal-link@3.0.0:
|
||||
resolution: {integrity: sha512-flFL3m4wuixmf6IfhFJd1YPiLiMuxEc8uHRM1buzIeZPm22Au2pDqBJQgdo7n1WfPU1ONFGv7YDwpFBmHGF6lg==}
|
||||
engines: {node: '>=12'}
|
||||
dependencies:
|
||||
ansi-escapes: 5.0.0
|
||||
supports-hyperlinks: 2.3.0
|
||||
dev: false
|
||||
|
||||
/thenify-all@1.6.0:
|
||||
resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==}
|
||||
engines: {node: '>=0.8'}
|
||||
@@ -11324,10 +11310,6 @@ packages:
|
||||
resolution: {integrity: sha512-i+owkHr5RYdQxj8olUgRrqpiWH9x27PuWVfXwDmJ/n/CoF/SAa7WW1i2oUpPDMQpJ4U+bGRUcZkVq7i1m3zFCg==}
|
||||
dev: false
|
||||
|
||||
/tsafe@1.6.6:
|
||||
resolution: {integrity: sha512-gzkapsdbMNwBnTIjgO758GujLCj031IgHK/PKr2mrmkCSJMhSOR5FeOuSxKLMUoYc0vAA4RGEYYbjt/v6afD3g==}
|
||||
dev: false
|
||||
|
||||
/tslib@1.14.1:
|
||||
resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==}
|
||||
dev: false
|
||||
@@ -11350,16 +11332,16 @@ packages:
|
||||
resolution: {integrity: sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw==}
|
||||
dev: false
|
||||
|
||||
/type-fest@0.20.2:
|
||||
resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==}
|
||||
engines: {node: '>=10'}
|
||||
dev: false
|
||||
|
||||
/type-fest@0.21.3:
|
||||
resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==}
|
||||
engines: {node: '>=10'}
|
||||
dev: true
|
||||
|
||||
/type-fest@1.4.0:
|
||||
resolution: {integrity: sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==}
|
||||
engines: {node: '>=10'}
|
||||
dev: false
|
||||
|
||||
/type-fest@4.18.2:
|
||||
resolution: {integrity: sha512-+suCYpfJLAe4OXS6+PPXjW3urOS4IoP9waSiLuXfLgqZODKw/aWwASvzqE886wA0kQgGy0mIWyhd87VpqIy6Xg==}
|
||||
engines: {node: '>=16'}
|
||||
@@ -11377,18 +11359,6 @@ packages:
|
||||
/uc.micro@2.1.0:
|
||||
resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==}
|
||||
|
||||
/ulid@2.3.0:
|
||||
resolution: {integrity: sha512-keqHubrlpvT6G2wH0OEfSW4mquYRcbe/J8NMmveoQOjUqmo+hXtO+ORCpWhdbZ7k72UtY61BL7haGxW6enBnjw==}
|
||||
hasBin: true
|
||||
dev: false
|
||||
|
||||
/ulidx@2.3.0:
|
||||
resolution: {integrity: sha512-36piWNqcdp9hKlQewyeehCaALy4lyx3FodsCxHuV6i0YdexSkjDOubwxEVr2yi4kh62L/0MgyrxqG4K+qtovnw==}
|
||||
engines: {node: '>=16'}
|
||||
dependencies:
|
||||
layerr: 2.1.0
|
||||
dev: false
|
||||
|
||||
/undici-types@5.26.5:
|
||||
resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==}
|
||||
|
||||
@@ -11858,19 +11828,6 @@ packages:
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/ws@8.17.0:
|
||||
resolution: {integrity: sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
peerDependencies:
|
||||
bufferutil: ^4.0.1
|
||||
utf-8-validate: '>=5.0.2'
|
||||
peerDependenciesMeta:
|
||||
bufferutil:
|
||||
optional: true
|
||||
utf-8-validate:
|
||||
optional: true
|
||||
dev: false
|
||||
|
||||
/xtend@4.0.2:
|
||||
resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
|
||||
engines: {node: '>=0.4'}
|
||||
@@ -11956,16 +11913,6 @@ packages:
|
||||
resolution: {integrity: sha512-tT/oChyDXelLo2A+UVnlW9GU7CsvFMaEnd9kVFsaiCQonFAXd3xrHhkLYu+suwwosrAEQ746xBU+HvYtm1Zs2Q==}
|
||||
dev: false
|
||||
|
||||
/zod-error@1.5.0:
|
||||
resolution: {integrity: sha512-zzopKZ/skI9iXpqCEPj+iLCKl9b88E43ehcU+sbRoHuwGd9F1IDVGQ70TyO6kmfiRL1g4IXkjsXK+g1gLYl4WQ==}
|
||||
dependencies:
|
||||
zod: 3.23.8
|
||||
dev: false
|
||||
|
||||
/zod@3.22.3:
|
||||
resolution: {integrity: sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug==}
|
||||
dev: false
|
||||
|
||||
/zod@3.23.8:
|
||||
resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==}
|
||||
dev: false
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
import { client } from "@/trigger";
|
||||
import { createAppRoute } from "@trigger.dev/nextjs";
|
||||
|
||||
import "@/jobs/auth-verification-email";
|
||||
import "@/jobs/esign-confirmation-email";
|
||||
import "@/jobs/esign-email";
|
||||
import "@/jobs/esign-pdf";
|
||||
import "@/jobs/member-inivite-email";
|
||||
import "@/jobs/password-reset-email";
|
||||
import "@/jobs/share-data-room-email";
|
||||
import "@/jobs/share-update-email";
|
||||
|
||||
//this route is used to send and receive data with Trigger.dev
|
||||
export const { POST, dynamic } = createAppRoute(client);
|
||||
|
||||
//uncomment this to set a higher max duration (it must be inside your plan limits). Full docs: https://vercel.com/docs/functions/serverless-functions/runtimes#max-duration
|
||||
//export const maxDuration = 60;
|
||||
@@ -1,4 +1,5 @@
|
||||
"use client";
|
||||
import { fileType } from "@/lib/mime";
|
||||
|
||||
import {
|
||||
RiFileCloudFill,
|
||||
@@ -16,54 +17,52 @@ type FileIconProps = {
|
||||
};
|
||||
|
||||
const FileIcon = ({ type }: FileIconProps) => {
|
||||
switch (true) {
|
||||
case type.includes("image"):
|
||||
const _type = fileType(type);
|
||||
|
||||
switch (_type) {
|
||||
case "image":
|
||||
return (
|
||||
<div className="flex h-8 w-8 items-center justify-center rounded-md bg-pink-100">
|
||||
<RiFileImageFill className="h-5 w-5 text-pink-500" />
|
||||
</div>
|
||||
);
|
||||
|
||||
case type.includes("audio"):
|
||||
case "audio":
|
||||
return (
|
||||
<div className="flex h-8 w-8 items-center justify-center rounded-md bg-indigo-100">
|
||||
<RiFileMusicFill className="h-5 w-5 text-indigo-500" />
|
||||
</div>
|
||||
);
|
||||
|
||||
case type.includes("video"):
|
||||
case "video":
|
||||
return (
|
||||
<div className="flex h-8 w-8 items-center justify-center rounded-md bg-rose-100">
|
||||
<RiFileVideoFill className="h-5 w-5 text-rose-500" />
|
||||
</div>
|
||||
);
|
||||
|
||||
case type.includes("powerpoint") || type.includes("presentation"):
|
||||
case "powerpoint":
|
||||
return (
|
||||
<div className="flex h-8 w-8 items-center justify-center rounded-md bg-orange-100">
|
||||
<RiFilePptFill className="h-5 w-5 text-orange-500" />
|
||||
</div>
|
||||
);
|
||||
|
||||
case type.includes("rtf"):
|
||||
case type.includes("doc"):
|
||||
case type.includes("word"):
|
||||
case "doc":
|
||||
return (
|
||||
<div className="flex h-8 w-8 items-center justify-center rounded-md bg-blue-100">
|
||||
<RiFileWordFill className="h-5 w-5 text-blue-500" />
|
||||
</div>
|
||||
);
|
||||
|
||||
case type.includes("csv"):
|
||||
case type.includes("excel"):
|
||||
case type.includes("sheet"):
|
||||
case "excel":
|
||||
return (
|
||||
<div className="flex h-8 w-8 items-center justify-center rounded-md bg-emerald-100">
|
||||
<RiFileExcelFill className="h-5 w-5 text-emerald-500" />
|
||||
</div>
|
||||
);
|
||||
|
||||
case type.includes("pdf"):
|
||||
case "pdf":
|
||||
return (
|
||||
<div className="flex h-8 w-8 items-center justify-center rounded-md bg-red-100">
|
||||
<RiFilePdf2Fill className="h-5 w-5 text-red-500" />
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
type OfficeViewerProps = {
|
||||
url: string;
|
||||
};
|
||||
|
||||
const OfficeViewer = ({ url }: OfficeViewerProps) => {
|
||||
return (
|
||||
// biome-ignore lint/a11y/useIframeTitle: <explanation>
|
||||
<iframe
|
||||
src={`https://view.officeapps.live.com/op/view.aspx?src=${encodeURIComponent(
|
||||
url,
|
||||
)}`}
|
||||
className="w-full h-full min-h-screen"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export { OfficeViewer };
|
||||
@@ -1,6 +1,8 @@
|
||||
import EmptyState from "@/components/common/empty-state";
|
||||
import { OfficeViewer } from "@/components/file/office-viewer";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { PdfViewer } from "@/components/ui/pdf-viewer";
|
||||
import { fileType } from "@/lib/mime";
|
||||
import { RiFileUnknowFill as UnknownFileIcon } from "@remixicon/react";
|
||||
|
||||
type FilePreviewProps = {
|
||||
@@ -15,6 +17,7 @@ const ImagePreview = ({ url, name }: FilePreviewProps) => {
|
||||
|
||||
const AuditPreview = ({ url, name, mimeType }: FilePreviewProps) => {
|
||||
return (
|
||||
// biome-ignore lint/a11y/useMediaCaption: <explanation>
|
||||
<audio controls className="w-full">
|
||||
<source src={url} type={mimeType} />
|
||||
Your browser does not support the audio element.
|
||||
@@ -24,6 +27,7 @@ const AuditPreview = ({ url, name, mimeType }: FilePreviewProps) => {
|
||||
|
||||
const VideoPreview = ({ url, name, mimeType }: FilePreviewProps) => {
|
||||
return (
|
||||
// biome-ignore lint/a11y/useMediaCaption: <explanation>
|
||||
<video controls className="w-full rounded">
|
||||
<source src={url} type={mimeType} />
|
||||
Your browser does not support the video type.
|
||||
@@ -34,7 +38,7 @@ const VideoPreview = ({ url, name, mimeType }: FilePreviewProps) => {
|
||||
const UnknownPreview = ({ url, name, mimeType }: FilePreviewProps) => {
|
||||
return (
|
||||
<EmptyState
|
||||
title="File type not supported"
|
||||
title="Preview not available"
|
||||
subtitle={`This file type - ${mimeType} is not yet supported by the previewer. You can download the file by clicking the button below.`}
|
||||
icon={<UnknownFileIcon />}
|
||||
>
|
||||
@@ -47,16 +51,21 @@ const UnknownPreview = ({ url, name, mimeType }: FilePreviewProps) => {
|
||||
|
||||
const FilePreview = ({ url, name, mimeType }: FilePreviewProps) => {
|
||||
mimeType = mimeType || "";
|
||||
const type = fileType(mimeType);
|
||||
|
||||
switch (true) {
|
||||
case mimeType.includes("pdf"):
|
||||
switch (type) {
|
||||
case "pdf":
|
||||
return <PdfViewer file={url} />;
|
||||
case mimeType.startsWith("image"):
|
||||
case "image":
|
||||
return <ImagePreview url={url} name={name} />;
|
||||
case mimeType.startsWith("audio"):
|
||||
case "audio":
|
||||
return <AuditPreview url={url} name={name} mimeType={mimeType} />;
|
||||
case mimeType.startsWith("video"):
|
||||
case "video":
|
||||
return <VideoPreview url={url} name={name} mimeType={mimeType} />;
|
||||
case "doc":
|
||||
case "excel":
|
||||
case "powerpoint":
|
||||
return <OfficeViewer url={url} />;
|
||||
default:
|
||||
return <UnknownPreview url={url} name={name} mimeType={mimeType} />;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
export const JOB_TYPES = {
|
||||
email: [
|
||||
"auth-verify",
|
||||
"share-update",
|
||||
"share-data-room",
|
||||
"password-reset",
|
||||
"esign-confirmation",
|
||||
"esign-notification",
|
||||
],
|
||||
generate: ["esign-pdf"],
|
||||
} as const;
|
||||
@@ -10,9 +10,9 @@ import {
|
||||
Text,
|
||||
} from "jsx-email";
|
||||
|
||||
import { type TConfirmationEmailPayload } from "../jobs/esign-confirmation-email";
|
||||
import type { ConfirmationEmailPayloadType } from "../jobs/esign-confirmation-email";
|
||||
|
||||
type Payload = Omit<TConfirmationEmailPayload, "fileUrl">;
|
||||
type Payload = Omit<ConfirmationEmailPayloadType, "fileUrl">;
|
||||
|
||||
const ESignConfirmationEmail = ({
|
||||
documentName,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { TEmailPayload } from "@/jobs/esign-email";
|
||||
import type { EsignEmailPayloadType } from "@/jobs/esign-email";
|
||||
import {
|
||||
Body,
|
||||
Button,
|
||||
@@ -21,7 +21,7 @@ import {
|
||||
UserAvatarIcon,
|
||||
} from "../components/common/icons";
|
||||
|
||||
type TEsignEmailProps = TEmailPayload & { signingLink: string };
|
||||
type TEsignEmailProps = EsignEmailPayloadType & { signingLink: string };
|
||||
|
||||
const EsignEmail = ({
|
||||
signingLink,
|
||||
|
||||
-12
@@ -59,11 +59,6 @@ export const env = createEnv({
|
||||
// google
|
||||
GOOGLE_CLIENT_ID: z.string().optional(),
|
||||
GOOGLE_CLIENT_SECRET: z.string().optional(),
|
||||
|
||||
// trigger.dev
|
||||
TRIGGER_API_ID: z.string(),
|
||||
TRIGGER_API_KEY: z.string().optional(),
|
||||
TRIGGER_API_URL: z.string().optional(),
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -76,7 +71,6 @@ export const env = createEnv({
|
||||
NEXT_PUBLIC_BASE_URL: z.string(),
|
||||
NEXT_PUBLIC_NODE_ENV: z.string().default("development"),
|
||||
NEXT_PUBLIC_UPLOAD_DOMAIN: z.string().optional(),
|
||||
NEXT_PUBLIC_TRIGGER_PUBLIC_API_KEY: z.string().optional(),
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -111,12 +105,6 @@ export const env = createEnv({
|
||||
|
||||
GOOGLE_CLIENT_ID: process.env.GOOGLE_CLIENT_ID,
|
||||
GOOGLE_CLIENT_SECRET: process.env.GOOGLE_CLIENT_SECRET,
|
||||
|
||||
TRIGGER_API_ID: process.env.TRIGGER_API_ID,
|
||||
TRIGGER_API_KEY: process.env.TRIGGER_API_KEY,
|
||||
TRIGGER_API_URL: process.env.TRIGGER_API_URL,
|
||||
NEXT_PUBLIC_TRIGGER_PUBLIC_API_KEY:
|
||||
process.env.NEXT_PUBLIC_TRIGGER_PUBLIC_API_KEY,
|
||||
},
|
||||
/**
|
||||
* Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
export async function register() {
|
||||
if (process.env.NEXT_RUNTIME === "nodejs") {
|
||||
const { initPgBoss } = await import("./lib/pg-boss-init");
|
||||
|
||||
await initPgBoss();
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,17 @@
|
||||
import AccountVerificationEmail from "@/emails/AccountVerificationEmail";
|
||||
import { env } from "@/env";
|
||||
import { BaseJob } from "@/lib/pg-boss-base";
|
||||
import { sendMail } from "@/server/mailer";
|
||||
import { client } from "@/trigger";
|
||||
import { eventTrigger } from "@trigger.dev/sdk";
|
||||
import { render } from "jsx-email";
|
||||
import { z } from "zod";
|
||||
import type { Job } from "pg-boss";
|
||||
|
||||
const schema = z.object({
|
||||
email: z.string().email(),
|
||||
token: z.string(),
|
||||
});
|
||||
|
||||
export type TAuthVerificationPayloadSchema = z.infer<typeof schema>;
|
||||
export type AuthVerificationPayloadType = {
|
||||
email: string;
|
||||
token: string;
|
||||
};
|
||||
|
||||
export const sendAuthVerificationEmail = async (
|
||||
payload: TAuthVerificationPayloadSchema,
|
||||
payload: AuthVerificationPayloadType,
|
||||
) => {
|
||||
const { email, token } = payload;
|
||||
const baseUrl = env.BASE_URL;
|
||||
@@ -34,20 +31,10 @@ export const sendAuthVerificationEmail = async (
|
||||
});
|
||||
};
|
||||
|
||||
export const triggerName = "email.auth-verify";
|
||||
export class AuthVerificationEmailJob extends BaseJob<AuthVerificationPayloadType> {
|
||||
readonly type = "email.auth-verify";
|
||||
|
||||
client.defineJob({
|
||||
id: "auth-verification-email",
|
||||
name: "authentication verification email",
|
||||
version: "0.0.1",
|
||||
trigger: eventTrigger({
|
||||
name: triggerName,
|
||||
schema,
|
||||
}),
|
||||
|
||||
run: async (payload, io) => {
|
||||
await io.runTask("send auth verification email", async () => {
|
||||
await sendAuthVerificationEmail(payload);
|
||||
});
|
||||
},
|
||||
});
|
||||
async work(job: Job<AuthVerificationPayloadType>): Promise<void> {
|
||||
await sendAuthVerificationEmail(job.data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import ESignConfirmationEmail from "@/emails/EsignConfirmationEmail";
|
||||
import { BaseJob } from "@/lib/pg-boss-base";
|
||||
import { sendMail } from "@/server/mailer";
|
||||
import { client } from "@/trigger";
|
||||
import { eventTrigger } from "@trigger.dev/sdk";
|
||||
import { render } from "jsx-email";
|
||||
import type { Job } from "pg-boss";
|
||||
|
||||
export type TConfirmationEmailPayload = {
|
||||
export type ConfirmationEmailPayloadType = {
|
||||
fileUrl: string;
|
||||
documentName: string;
|
||||
senderName: string | null;
|
||||
@@ -16,7 +16,7 @@ export type TConfirmationEmailPayload = {
|
||||
};
|
||||
|
||||
export const sendEsignConfirmationEmail = async (
|
||||
payload: TConfirmationEmailPayload,
|
||||
payload: ConfirmationEmailPayloadType,
|
||||
) => {
|
||||
const html = await render(
|
||||
ESignConfirmationEmail({
|
||||
@@ -39,17 +39,10 @@ export const sendEsignConfirmationEmail = async (
|
||||
});
|
||||
};
|
||||
|
||||
client.defineJob({
|
||||
id: "esign-confirmation-email",
|
||||
name: "esign confirmation email",
|
||||
version: "0.0.1",
|
||||
trigger: eventTrigger({
|
||||
name: "esign.send-confirmation",
|
||||
}),
|
||||
export class EsignConfirmationEmailJob extends BaseJob<ConfirmationEmailPayloadType> {
|
||||
readonly type = "email.esign-confirmation";
|
||||
|
||||
run: async (payload: TConfirmationEmailPayload, io) => {
|
||||
await io.runTask("send confirmation email", async () => {
|
||||
await sendEsignConfirmationEmail(payload);
|
||||
});
|
||||
},
|
||||
});
|
||||
async work(job: Job<ConfirmationEmailPayloadType>): Promise<void> {
|
||||
await sendEsignConfirmationEmail(job.data);
|
||||
}
|
||||
}
|
||||
|
||||
+21
-29
@@ -1,12 +1,12 @@
|
||||
import EsignEmail from "@/emails/EsignEmail";
|
||||
import { env } from "@/env";
|
||||
import { BaseJob } from "@/lib/pg-boss-base";
|
||||
import { db } from "@/server/db";
|
||||
import { sendMail } from "@/server/mailer";
|
||||
import { client } from "@/trigger";
|
||||
import { eventTrigger } from "@trigger.dev/sdk";
|
||||
import { render } from "jsx-email";
|
||||
import type { Job } from "pg-boss";
|
||||
|
||||
export interface TEmailPayload {
|
||||
export interface EsignEmailPayloadType {
|
||||
documentName?: string;
|
||||
message?: string | null;
|
||||
recipient: {
|
||||
@@ -24,14 +24,15 @@ export interface TEmailPayload {
|
||||
};
|
||||
}
|
||||
|
||||
interface TCorePayload {
|
||||
interface AdditionalPayloadType {
|
||||
email: string;
|
||||
token: string;
|
||||
}
|
||||
|
||||
export type TEsignEmailJob = TEmailPayload & TCorePayload;
|
||||
export type ExtendedEsignPayloadType = EsignEmailPayloadType &
|
||||
AdditionalPayloadType;
|
||||
|
||||
export const sendEsignEmail = async (payload: TEsignEmailJob) => {
|
||||
export const sendEsignEmail = async (payload: ExtendedEsignPayloadType) => {
|
||||
const { email, token, ...rest } = payload;
|
||||
const baseUrl = env.BASE_URL;
|
||||
const html = await render(
|
||||
@@ -42,33 +43,24 @@ export const sendEsignEmail = async (payload: TEsignEmailJob) => {
|
||||
);
|
||||
await sendMail({
|
||||
to: email,
|
||||
subject: "esign Link",
|
||||
subject: "eSign Link",
|
||||
html,
|
||||
});
|
||||
};
|
||||
|
||||
client.defineJob({
|
||||
id: "esign-notification-email",
|
||||
name: "E-sign notification email",
|
||||
version: "0.0.1",
|
||||
trigger: eventTrigger({
|
||||
name: "email.esign",
|
||||
}),
|
||||
export class EsignNotificationEmailJob extends BaseJob<ExtendedEsignPayloadType> {
|
||||
readonly type = "email.esign-notification";
|
||||
|
||||
run: async (payload: TEsignEmailJob, io) => {
|
||||
await io.runTask("send esign email", async () => {
|
||||
await sendEsignEmail(payload);
|
||||
async work(job: Job<ExtendedEsignPayloadType>): Promise<void> {
|
||||
await db.esignRecipient.update({
|
||||
where: {
|
||||
id: job.data.recipient.id,
|
||||
},
|
||||
data: {
|
||||
status: "SENT",
|
||||
},
|
||||
});
|
||||
|
||||
await io.runTask("update recipient status", async () => {
|
||||
await db.esignRecipient.update({
|
||||
where: {
|
||||
id: payload.recipient.id,
|
||||
},
|
||||
data: {
|
||||
status: "SENT",
|
||||
},
|
||||
});
|
||||
});
|
||||
},
|
||||
});
|
||||
await sendEsignEmail(job.data);
|
||||
}
|
||||
}
|
||||
|
||||
+64
-80
@@ -1,50 +1,43 @@
|
||||
import { BaseJob } from "@/lib/pg-boss-base";
|
||||
import { db } from "@/server/db";
|
||||
import {
|
||||
type TEsignGetTemplate,
|
||||
type EsignGetTemplateType,
|
||||
completeEsignDocuments,
|
||||
generateEsignPdf,
|
||||
uploadEsignDocuments,
|
||||
} from "@/server/esign";
|
||||
import { getPresignedGetUrl } from "@/server/file-uploads";
|
||||
import { client } from "@/trigger";
|
||||
import { eventTrigger } from "@trigger.dev/sdk";
|
||||
import { z } from "zod";
|
||||
import { type TConfirmationEmailPayload } from "./esign-confirmation-email";
|
||||
import type { Job } from "pg-boss";
|
||||
import {
|
||||
type ConfirmationEmailPayloadType,
|
||||
EsignConfirmationEmailJob,
|
||||
} from "./esign-confirmation-email";
|
||||
|
||||
const schema = z.object({
|
||||
bucketKey: z.string(),
|
||||
data: z.record(z.string()),
|
||||
audits: z.array(z.object({ id: z.string(), summary: z.string() })),
|
||||
fields: z.array(z.any()),
|
||||
companyId: z.string(),
|
||||
templateName: z.string(),
|
||||
requestIp: z.string(),
|
||||
templateId: z.string(),
|
||||
uploaderName: z.string(),
|
||||
userAgent: z.string(),
|
||||
recipients: z.array(
|
||||
z.object({ email: z.string(), name: z.string().nullish() }),
|
||||
),
|
||||
company: z.object({
|
||||
name: z.string(),
|
||||
logo: z.string().nullish(),
|
||||
}),
|
||||
});
|
||||
|
||||
export type TESignPdfSchema = Omit<z.infer<typeof schema>, "fields"> & {
|
||||
fields: TEsignGetTemplate["fields"];
|
||||
export type EsignPdfPayloadType = {
|
||||
fields: EsignGetTemplateType["fields"];
|
||||
company: {
|
||||
name: string;
|
||||
logo?: string | null | undefined;
|
||||
};
|
||||
data: Record<string, string>;
|
||||
templateId: string;
|
||||
requestIp: string;
|
||||
userAgent: string;
|
||||
audits: {
|
||||
id: string;
|
||||
summary: string;
|
||||
}[];
|
||||
bucketKey: string;
|
||||
templateName: string;
|
||||
companyId: string;
|
||||
uploaderName: string;
|
||||
recipients: { email: string; name?: string | null }[];
|
||||
};
|
||||
|
||||
client.defineJob({
|
||||
id: "esign.complete-pdf",
|
||||
name: "esign_complete_pdf",
|
||||
version: "0.0.1",
|
||||
trigger: eventTrigger({
|
||||
name: "esign.sign-pdf",
|
||||
schema,
|
||||
}),
|
||||
export class EsignPdfJob extends BaseJob<EsignPdfPayloadType> {
|
||||
readonly type = "generate.esign-pdf";
|
||||
|
||||
run: async (payload, io) => {
|
||||
async work(job: Job<EsignPdfPayloadType>): Promise<void> {
|
||||
const {
|
||||
bucketKey,
|
||||
data,
|
||||
@@ -58,55 +51,46 @@ client.defineJob({
|
||||
templateId,
|
||||
recipients,
|
||||
company,
|
||||
} = payload;
|
||||
} = job.data;
|
||||
|
||||
const uploadedDocument = await io.runTask("upload documents", async () => {
|
||||
const modifiedPdfBytes = await generateEsignPdf({
|
||||
bucketKey,
|
||||
data,
|
||||
fields,
|
||||
audits,
|
||||
});
|
||||
const { fileUrl: _fileUrl, ...bucketData } = await uploadEsignDocuments({
|
||||
buffer: Buffer.from(modifiedPdfBytes),
|
||||
const modifiedPdfBytes = await generateEsignPdf({
|
||||
bucketKey,
|
||||
data,
|
||||
fields,
|
||||
audits,
|
||||
});
|
||||
const { fileUrl: _fileUrl, ...bucketData } = await uploadEsignDocuments({
|
||||
buffer: Buffer.from(modifiedPdfBytes),
|
||||
companyId,
|
||||
templateName,
|
||||
});
|
||||
|
||||
await db.$transaction(async (tx) => {
|
||||
await completeEsignDocuments({
|
||||
bucketData: bucketData,
|
||||
companyId,
|
||||
db: tx,
|
||||
requestIp,
|
||||
templateId,
|
||||
templateName,
|
||||
});
|
||||
|
||||
return { bucketData, _fileUrl };
|
||||
});
|
||||
|
||||
await io.runTask("complete document", async () => {
|
||||
await db.$transaction(async (tx) => {
|
||||
await completeEsignDocuments({
|
||||
bucketData: uploadedDocument.bucketData,
|
||||
companyId,
|
||||
db: tx,
|
||||
requestIp,
|
||||
templateId,
|
||||
templateName,
|
||||
uploaderName,
|
||||
userAgent,
|
||||
});
|
||||
uploaderName,
|
||||
userAgent,
|
||||
});
|
||||
});
|
||||
|
||||
await io.runTask("send all recipients confirmation email", async () => {
|
||||
const file = await getPresignedGetUrl(uploadedDocument.bucketData.key);
|
||||
const file = await getPresignedGetUrl(bucketData.key);
|
||||
|
||||
const data: { name: string; payload: TConfirmationEmailPayload }[] =
|
||||
recipients.map((recipient) => ({
|
||||
payload: {
|
||||
fileUrl: file.url,
|
||||
documentName: templateName,
|
||||
recipient,
|
||||
company,
|
||||
senderName: uploaderName,
|
||||
},
|
||||
name: "esign.send-confirmation",
|
||||
}));
|
||||
const recipientData: { data: ConfirmationEmailPayloadType }[] =
|
||||
recipients.map((recipient) => ({
|
||||
data: {
|
||||
fileUrl: file.url,
|
||||
documentName: templateName,
|
||||
recipient,
|
||||
company,
|
||||
senderName: uploaderName,
|
||||
},
|
||||
}));
|
||||
|
||||
await io.sendEvents(`trigger-confirmation-email`, data);
|
||||
});
|
||||
},
|
||||
});
|
||||
await new EsignConfirmationEmailJob().bulkEmit(recipientData);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,29 +1,28 @@
|
||||
import MemberInviteEmail from "@/emails/MemberInviteEmail";
|
||||
import { env } from "@/env";
|
||||
import { constants } from "@/lib/constants";
|
||||
import { BaseJob } from "@/lib/pg-boss-base";
|
||||
import { sendMail } from "@/server/mailer";
|
||||
import { client } from "@/trigger";
|
||||
import { eventTrigger } from "@trigger.dev/sdk";
|
||||
import { render } from "jsx-email";
|
||||
import { z } from "zod";
|
||||
import type { Job } from "pg-boss";
|
||||
|
||||
const schema = z.object({
|
||||
email: z.string().email(),
|
||||
passwordResetToken: z.string(),
|
||||
verificationToken: z.string(),
|
||||
company: z.object({
|
||||
name: z.string(),
|
||||
id: z.string(),
|
||||
}),
|
||||
user: z.object({
|
||||
email: z.string().email().nullish(),
|
||||
name: z.string().nullish(),
|
||||
}),
|
||||
});
|
||||
type MemberInvitePayloadType = {
|
||||
email: string;
|
||||
passwordResetToken: string;
|
||||
user: {
|
||||
email?: string | null | undefined;
|
||||
name?: string | null | undefined;
|
||||
};
|
||||
verificationToken: string;
|
||||
company: {
|
||||
name: string;
|
||||
id: string;
|
||||
};
|
||||
};
|
||||
|
||||
type TSchema = z.infer<typeof schema>;
|
||||
|
||||
export const sendMemberInviteEmail = async (payload: TSchema) => {
|
||||
export const sendMemberInviteEmail = async (
|
||||
payload: MemberInvitePayloadType,
|
||||
) => {
|
||||
const { email, passwordResetToken, verificationToken, company, user } =
|
||||
payload;
|
||||
|
||||
@@ -43,24 +42,16 @@ export const sendMemberInviteEmail = async (payload: TSchema) => {
|
||||
MemberInviteEmail({
|
||||
inviteLink,
|
||||
companyName: company.name,
|
||||
invitedBy: (user.name ?? user.email)!,
|
||||
invitedBy: `${user.name || user.email}`,
|
||||
}),
|
||||
),
|
||||
});
|
||||
};
|
||||
|
||||
client.defineJob({
|
||||
id: "member-inivte-email",
|
||||
name: "member invite email",
|
||||
version: "0.0.1",
|
||||
trigger: eventTrigger({
|
||||
name: "email.member-invite",
|
||||
schema,
|
||||
}),
|
||||
export class SendMemberInviteEmailJob extends BaseJob<MemberInvitePayloadType> {
|
||||
readonly type = "email.password-reset";
|
||||
|
||||
run: async (payload, io) => {
|
||||
await io.runTask("send member invite email", async () => {
|
||||
await sendMemberInviteEmail(payload);
|
||||
});
|
||||
},
|
||||
});
|
||||
async work(job: Job<MemberInvitePayloadType>): Promise<void> {
|
||||
await sendMemberInviteEmail(job.data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,17 @@
|
||||
import PasswordResetEmail from "@/emails/PasswordResetEmail";
|
||||
import { env } from "@/env";
|
||||
import { BaseJob } from "@/lib/pg-boss-base";
|
||||
import { sendMail } from "@/server/mailer";
|
||||
import { client } from "@/trigger";
|
||||
import { eventTrigger } from "@trigger.dev/sdk";
|
||||
import { render } from "jsx-email";
|
||||
import { z } from "zod";
|
||||
import type { Job } from "pg-boss";
|
||||
|
||||
const schema = z.object({
|
||||
email: z.string().email(),
|
||||
token: z.string(),
|
||||
});
|
||||
|
||||
export type TPasswordResetPayloadSchema = z.infer<typeof schema>;
|
||||
export type PasswordResetPayloadType = {
|
||||
email: string;
|
||||
token: string;
|
||||
};
|
||||
|
||||
export const sendPasswordResetEmail = async (
|
||||
payload: TPasswordResetPayloadSchema,
|
||||
payload: PasswordResetPayloadType,
|
||||
) => {
|
||||
const { email, token } = payload;
|
||||
const baseUrl = env.BASE_URL;
|
||||
@@ -34,20 +31,10 @@ export const sendPasswordResetEmail = async (
|
||||
});
|
||||
};
|
||||
|
||||
export const triggerName = "email.password-reset";
|
||||
export class PasswordResetEmailJob extends BaseJob<PasswordResetPayloadType> {
|
||||
readonly type = "email.password-reset";
|
||||
|
||||
client.defineJob({
|
||||
id: "password-reset-email",
|
||||
name: "password reset email",
|
||||
version: "0.0.1",
|
||||
trigger: eventTrigger({
|
||||
name: triggerName,
|
||||
schema,
|
||||
}),
|
||||
|
||||
run: async (payload, io) => {
|
||||
await io.runTask("send password reset email", async () => {
|
||||
await sendPasswordResetEmail(payload);
|
||||
});
|
||||
},
|
||||
});
|
||||
async work(job: Job<PasswordResetPayloadType>): Promise<void> {
|
||||
await sendPasswordResetEmail(job.data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,18 @@
|
||||
import ShareDataRoomEmail from "@/emails/ShareDataRoomEmail";
|
||||
import { BaseJob } from "@/lib/pg-boss-base";
|
||||
import { sendMail } from "@/server/mailer";
|
||||
import { client } from "@/trigger";
|
||||
import { eventTrigger } from "@trigger.dev/sdk";
|
||||
import { render } from "jsx-email";
|
||||
import { z } from "zod";
|
||||
import type { Job } from "pg-boss";
|
||||
|
||||
const schema = z.object({
|
||||
dataRoom: z.string(),
|
||||
link: z.string(),
|
||||
companyName: z.string(),
|
||||
recipientName: z.string().nullish(),
|
||||
senderName: z.string(),
|
||||
senderEmail: z.string().nullish(),
|
||||
email: z.string(),
|
||||
});
|
||||
|
||||
export type DataRoomEmailPayloadType = z.infer<typeof schema>;
|
||||
export type DataRoomEmailPayloadType = {
|
||||
link: string;
|
||||
dataRoom: string;
|
||||
email: string;
|
||||
senderName: string;
|
||||
companyName: string;
|
||||
recipientName?: string | null | undefined;
|
||||
senderEmail?: string | null | undefined;
|
||||
};
|
||||
|
||||
export const sendShareDataRoomEmail = async (
|
||||
payload: DataRoomEmailPayloadType,
|
||||
@@ -45,20 +42,10 @@ export const sendShareDataRoomEmail = async (
|
||||
});
|
||||
};
|
||||
|
||||
export const triggerName = "email.share-data-room-email";
|
||||
export class ShareDataRoomEmailJob extends BaseJob<DataRoomEmailPayloadType> {
|
||||
readonly type = "email.share-data-room";
|
||||
|
||||
client.defineJob({
|
||||
id: "share-data-room-email",
|
||||
name: "data room share email",
|
||||
version: "0.0.1",
|
||||
trigger: eventTrigger({
|
||||
name: triggerName,
|
||||
schema,
|
||||
}),
|
||||
|
||||
run: async (payload, io) => {
|
||||
await io.runTask("send data room share email", async () => {
|
||||
await sendShareDataRoomEmail(payload);
|
||||
});
|
||||
},
|
||||
});
|
||||
async work(job: Job<DataRoomEmailPayloadType>): Promise<void> {
|
||||
await sendShareDataRoomEmail(job.data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
import ShareUpdateEmail from "@/emails/ShareUpdateEmail";
|
||||
import { BaseJob } from "@/lib/pg-boss-base";
|
||||
import { sendMail } from "@/server/mailer";
|
||||
import { client } from "@/trigger";
|
||||
import { eventTrigger } from "@trigger.dev/sdk";
|
||||
import { render } from "jsx-email";
|
||||
import { z } from "zod";
|
||||
import type { Job } from "pg-boss";
|
||||
|
||||
const schema = z.object({
|
||||
update: z.object({
|
||||
title: z.string(),
|
||||
}),
|
||||
link: z.string(),
|
||||
companyName: z.string(),
|
||||
recipientName: z.string().nullish(),
|
||||
senderName: z.string(),
|
||||
senderEmail: z.string().nullish(),
|
||||
email: z.string(),
|
||||
});
|
||||
|
||||
export type UpdateSharePayloadType = z.infer<typeof schema>;
|
||||
export type UpdateSharePayloadType = {
|
||||
update: {
|
||||
title: string;
|
||||
};
|
||||
link: string;
|
||||
companyName: string;
|
||||
senderName: string;
|
||||
email: string;
|
||||
recipientName?: string | null | undefined;
|
||||
senderEmail?: string | null | undefined;
|
||||
};
|
||||
|
||||
export const sendShareUpdateEmail = async (payload: UpdateSharePayloadType) => {
|
||||
const {
|
||||
@@ -45,20 +42,10 @@ export const sendShareUpdateEmail = async (payload: UpdateSharePayloadType) => {
|
||||
});
|
||||
};
|
||||
|
||||
export const triggerName = "email.share-update-email";
|
||||
export class ShareUpdateEmailJob extends BaseJob<UpdateSharePayloadType> {
|
||||
readonly type = "email.share-update";
|
||||
|
||||
client.defineJob({
|
||||
id: "share-update-email",
|
||||
name: "Investor update share email",
|
||||
version: "0.0.1",
|
||||
trigger: eventTrigger({
|
||||
name: triggerName,
|
||||
schema,
|
||||
}),
|
||||
|
||||
run: async (payload, io) => {
|
||||
await io.runTask("Send investor update", async () => {
|
||||
await sendShareUpdateEmail(payload);
|
||||
});
|
||||
},
|
||||
});
|
||||
async work(job: Job<UpdateSharePayloadType>): Promise<void> {
|
||||
await sendShareUpdateEmail(job.data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
type FileProps = {
|
||||
mimeType: string;
|
||||
};
|
||||
|
||||
const fileType = (mimeType: string) => {
|
||||
switch (true) {
|
||||
case mimeType.includes("pdf"):
|
||||
return "pdf";
|
||||
case mimeType.includes("image"):
|
||||
return "image";
|
||||
case mimeType.includes("audio"):
|
||||
return "audio";
|
||||
case mimeType.includes("video"):
|
||||
return "video";
|
||||
case mimeType.includes("powerpoint") || mimeType.includes("presentation"):
|
||||
return "powerpoint";
|
||||
case mimeType.includes("csv") ||
|
||||
mimeType.includes("excel") ||
|
||||
mimeType.includes("sheet"):
|
||||
return "excel";
|
||||
case mimeType.includes("doc"):
|
||||
case mimeType.includes("word"):
|
||||
return "doc";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
};
|
||||
|
||||
export { fileType };
|
||||
@@ -0,0 +1,76 @@
|
||||
import type { JOB_TYPES } from "@/constants/job";
|
||||
import { env } from "@/env";
|
||||
import pgBoss from "pg-boss";
|
||||
|
||||
import { singleton } from "./singleton";
|
||||
|
||||
type JobTypes = typeof JOB_TYPES;
|
||||
|
||||
export type JobType = {
|
||||
[Key in keyof JobTypes]: `${Key}.${JobTypes[Key][number]}`;
|
||||
}[keyof JobTypes];
|
||||
|
||||
interface Job<T extends object> {
|
||||
type: JobType;
|
||||
options: pgBoss.SendOptions;
|
||||
start: () => Promise<void>;
|
||||
work: (job: pgBoss.Job<T>) => Promise<void>;
|
||||
emit: (data: T) => Promise<void>;
|
||||
}
|
||||
|
||||
export abstract class BaseJob<T extends object> implements Job<T> {
|
||||
protected boss: pgBoss;
|
||||
abstract readonly type: JobType;
|
||||
readonly options = { retryLimit: 3, retryDelay: 1000 };
|
||||
|
||||
constructor() {
|
||||
this.boss = boss;
|
||||
}
|
||||
|
||||
async start(): Promise<void> {
|
||||
await this.boss.work(this.type, this.work);
|
||||
}
|
||||
|
||||
abstract work(job: pgBoss.Job<T>): Promise<void>;
|
||||
|
||||
async emit(data: T): Promise<void> {
|
||||
await this.boss.send(this.type, data, this.options);
|
||||
}
|
||||
async bulkEmit(data: Omit<pgBoss.JobInsert<T>, "name">[]): Promise<void> {
|
||||
await this.boss.insert(
|
||||
data.map((items) => ({ ...items, name: this.type })),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class JobManager {
|
||||
private readonly boss: pgBoss;
|
||||
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
||||
private jobs = new Map<string, Job<any>>();
|
||||
|
||||
constructor(boss: pgBoss) {
|
||||
this.boss = boss;
|
||||
}
|
||||
|
||||
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
||||
register(job: new (boss: pgBoss) => Job<any>): JobManager {
|
||||
const jobInstance = new job(this.boss);
|
||||
this.jobs.set(jobInstance.type, jobInstance);
|
||||
return this;
|
||||
}
|
||||
|
||||
async start(): Promise<void> {
|
||||
await this.boss.start();
|
||||
for (const job of this.jobs.values()) {
|
||||
await job.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const boss = singleton(
|
||||
"pg-boss",
|
||||
() =>
|
||||
new pgBoss({
|
||||
connectionString: env.DATABASE_URL,
|
||||
}),
|
||||
);
|
||||
@@ -0,0 +1,23 @@
|
||||
import { AuthVerificationEmailJob } from "@/jobs/auth-verification-email";
|
||||
import { EsignConfirmationEmailJob } from "@/jobs/esign-confirmation-email";
|
||||
import { EsignNotificationEmailJob } from "@/jobs/esign-email";
|
||||
import { EsignPdfJob } from "@/jobs/esign-pdf";
|
||||
import { SendMemberInviteEmailJob } from "@/jobs/member-inivite-email";
|
||||
import { PasswordResetEmailJob } from "@/jobs/password-reset-email";
|
||||
import { ShareDataRoomEmailJob } from "@/jobs/share-data-room-email";
|
||||
import { ShareUpdateEmailJob } from "@/jobs/share-update-email";
|
||||
import { JobManager, boss } from "./pg-boss-base";
|
||||
|
||||
export async function initPgBoss() {
|
||||
const jobs = new JobManager(boss)
|
||||
.register(AuthVerificationEmailJob)
|
||||
.register(ShareUpdateEmailJob)
|
||||
.register(ShareDataRoomEmailJob)
|
||||
.register(SendMemberInviteEmailJob)
|
||||
.register(PasswordResetEmailJob)
|
||||
.register(EsignNotificationEmailJob)
|
||||
.register(EsignConfirmationEmailJob)
|
||||
.register(EsignPdfJob);
|
||||
|
||||
await jobs.start();
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// credit https://github.com/epicweb-dev/remember/blob/main/index.js
|
||||
|
||||
export function singleton<Value>(name: string, getValue: () => Value): Value {
|
||||
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
||||
const thusly = globalThis as any;
|
||||
thusly.__remember_singleton ??= new Map<string, Value>();
|
||||
if (!thusly.__remember_singleton.has(name)) {
|
||||
thusly.__remember_singleton.set(name, getValue());
|
||||
}
|
||||
// biome-ignore lint/style/noNonNullAssertion: <explanation>
|
||||
return thusly.__remember_singleton.get(name)!;
|
||||
}
|
||||
+11
-12
@@ -8,7 +8,7 @@ import { createDocumentHandler } from "@/trpc/routers/document-router/procedures
|
||||
import { renderToBuffer } from "@react-pdf/renderer";
|
||||
import { PDFDocument, StandardFonts } from "pdf-lib";
|
||||
import { EsignAudit } from "./audit";
|
||||
import { type PrismaTransactionalClient } from "./db";
|
||||
import type { PrismaTransactionalClient } from "./db";
|
||||
|
||||
interface getEsignAuditsOptions {
|
||||
templateId: string;
|
||||
@@ -73,9 +73,9 @@ export function getEsignTemplate({ tx, templateId }: getEsignTemplateOptions) {
|
||||
});
|
||||
}
|
||||
|
||||
export type TEsignGetTemplate = Awaited<ReturnType<typeof getEsignTemplate>>;
|
||||
export type EsignGetTemplateType = Awaited<ReturnType<typeof getEsignTemplate>>;
|
||||
|
||||
type Field = TEsignGetTemplate["fields"][number];
|
||||
type Field = EsignGetTemplateType["fields"][number];
|
||||
interface TGetFieldValue {
|
||||
type: Field["type"];
|
||||
id: Field["id"];
|
||||
@@ -86,10 +86,9 @@ interface TGetFieldValue {
|
||||
export const getFieldValue = ({ type, id, data, meta }: TGetFieldValue) => {
|
||||
const value = data?.[id];
|
||||
|
||||
const selectValue =
|
||||
meta && meta.options
|
||||
? meta.options.find((val) => val.id === value)?.value
|
||||
: undefined;
|
||||
const selectValue = meta?.options
|
||||
? meta.options.find((val) => val.id === value)?.value
|
||||
: undefined;
|
||||
|
||||
return value
|
||||
? type === "DATE"
|
||||
@@ -100,10 +99,10 @@ export const getFieldValue = ({ type, id, data, meta }: TGetFieldValue) => {
|
||||
: undefined;
|
||||
};
|
||||
|
||||
export interface TGenerateEsignSignPdfOptions {
|
||||
export interface GenerateEsignSignPdfOptionsType {
|
||||
bucketKey: string;
|
||||
data: Record<string, string>;
|
||||
fields: TEsignGetTemplate["fields"];
|
||||
fields: EsignGetTemplateType["fields"];
|
||||
audits: TGetEsignAudits;
|
||||
}
|
||||
|
||||
@@ -112,7 +111,7 @@ export async function generateEsignPdf({
|
||||
data,
|
||||
fields,
|
||||
audits,
|
||||
}: TGenerateEsignSignPdfOptions) {
|
||||
}: GenerateEsignSignPdfOptionsType) {
|
||||
const docBuffer = await getFileFromS3(bucketKey);
|
||||
const pdfDoc = await PDFDocument.load(docBuffer);
|
||||
|
||||
@@ -244,7 +243,7 @@ export async function uploadEsignDocuments({
|
||||
return data;
|
||||
}
|
||||
|
||||
export interface TCompleteEsignDocumentsOptions {
|
||||
export interface CompleteEsignDocumentsOptionsType {
|
||||
templateName: string;
|
||||
companyId: string;
|
||||
db: PrismaTransactionalClient;
|
||||
@@ -264,7 +263,7 @@ export async function completeEsignDocuments({
|
||||
uploaderName,
|
||||
userAgent,
|
||||
bucketData,
|
||||
}: TCompleteEsignDocumentsOptions) {
|
||||
}: CompleteEsignDocumentsOptionsType) {
|
||||
await db.template.update({
|
||||
where: {
|
||||
id: templateId,
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
import { TriggerClient } from "@trigger.dev/sdk";
|
||||
import { env } from "./env";
|
||||
|
||||
const apiKey = env.TRIGGER_API_KEY;
|
||||
const apiUrl = env.TRIGGER_API_URL;
|
||||
const id = env.TRIGGER_API_ID;
|
||||
|
||||
const getClient = () =>
|
||||
new TriggerClient({
|
||||
id,
|
||||
apiKey,
|
||||
apiUrl,
|
||||
});
|
||||
|
||||
export const client = getClient();
|
||||
|
||||
export function getTriggerClient(): TriggerClient | null {
|
||||
if (!apiKey || !apiUrl || !id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const client = getClient();
|
||||
|
||||
return client;
|
||||
}
|
||||
@@ -1,11 +1,6 @@
|
||||
import {
|
||||
type TPasswordResetPayloadSchema,
|
||||
sendPasswordResetEmail,
|
||||
triggerName,
|
||||
} from "@/jobs/password-reset-email";
|
||||
import { PasswordResetEmailJob } from "@/jobs/password-reset-email";
|
||||
import { generatePasswordResetToken } from "@/lib/token";
|
||||
import { getUserByEmail } from "@/server/user";
|
||||
import { getTriggerClient } from "@/trigger";
|
||||
import { withoutAuth } from "@/trpc/api/trpc";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { z } from "zod";
|
||||
@@ -13,8 +8,6 @@ import { z } from "zod";
|
||||
export const forgotPasswordProcedure = withoutAuth
|
||||
.input(z.string().email())
|
||||
.mutation(async ({ input }) => {
|
||||
const trigger = getTriggerClient();
|
||||
|
||||
const existingUser = await getUserByEmail(input);
|
||||
|
||||
if (!existingUser) {
|
||||
@@ -25,16 +18,7 @@ export const forgotPasswordProcedure = withoutAuth
|
||||
}
|
||||
const { email, token } = await generatePasswordResetToken(input);
|
||||
|
||||
const payload: TPasswordResetPayloadSchema = {
|
||||
email,
|
||||
token,
|
||||
};
|
||||
|
||||
if (trigger) {
|
||||
await trigger.sendEvent({ name: triggerName, payload });
|
||||
} else {
|
||||
await sendPasswordResetEmail(payload);
|
||||
}
|
||||
await new PasswordResetEmailJob().emit({ email, token });
|
||||
|
||||
return {
|
||||
success: true,
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
import {
|
||||
type TAuthVerificationPayloadSchema,
|
||||
sendAuthVerificationEmail,
|
||||
triggerName,
|
||||
} from "@/jobs/auth-verification-email";
|
||||
import { AuthVerificationEmailJob } from "@/jobs/auth-verification-email";
|
||||
|
||||
import { generateVerificationToken } from "@/lib/token";
|
||||
import { getVerificationTokenByEmail } from "@/server/verification-token";
|
||||
import { getTriggerClient } from "@/trigger";
|
||||
import { withoutAuth } from "@/trpc/api/trpc";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { z } from "zod";
|
||||
@@ -14,7 +9,6 @@ import { z } from "zod";
|
||||
export const resendEmailProcedure = withoutAuth
|
||||
.input(z.string().email())
|
||||
.mutation(async ({ input }) => {
|
||||
const trigger = getTriggerClient();
|
||||
const oldVerificationToken = await getVerificationTokenByEmail(input);
|
||||
|
||||
if (!oldVerificationToken) {
|
||||
@@ -25,17 +19,10 @@ export const resendEmailProcedure = withoutAuth
|
||||
}
|
||||
const verificationToken = await generateVerificationToken(input);
|
||||
|
||||
const payload: TAuthVerificationPayloadSchema = {
|
||||
await new AuthVerificationEmailJob().emit({
|
||||
email: verificationToken.identifier,
|
||||
token: verificationToken.token,
|
||||
};
|
||||
|
||||
if (trigger) {
|
||||
await trigger.sendEvent({ name: triggerName, payload });
|
||||
} else {
|
||||
await sendAuthVerificationEmail(payload);
|
||||
}
|
||||
|
||||
});
|
||||
return {
|
||||
success: true,
|
||||
message:
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
import {
|
||||
type TAuthVerificationPayloadSchema,
|
||||
sendAuthVerificationEmail,
|
||||
triggerName,
|
||||
} from "@/jobs/auth-verification-email";
|
||||
import { AuthVerificationEmailJob } from "@/jobs/auth-verification-email";
|
||||
|
||||
import { generateVerificationToken } from "@/lib/token";
|
||||
import { getTriggerClient } from "@/trigger";
|
||||
import { withoutAuth } from "@/trpc/api/trpc";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import bcrypt from "bcryptjs";
|
||||
@@ -14,8 +9,6 @@ import { ZSignUpMutationSchema } from "../schema";
|
||||
export const signupProcedure = withoutAuth
|
||||
.input(ZSignUpMutationSchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const trigger = getTriggerClient();
|
||||
|
||||
const { name, email, password } = input;
|
||||
|
||||
const userExists = await ctx.db.user.findFirst({
|
||||
@@ -42,16 +35,10 @@ export const signupProcedure = withoutAuth
|
||||
});
|
||||
const verificationToken = await generateVerificationToken(email);
|
||||
|
||||
const payload: TAuthVerificationPayloadSchema = {
|
||||
await new AuthVerificationEmailJob().emit({
|
||||
email: verificationToken.identifier,
|
||||
token: verificationToken.token,
|
||||
};
|
||||
|
||||
if (trigger) {
|
||||
await trigger.sendEvent({ name: triggerName, payload });
|
||||
} else {
|
||||
await sendAuthVerificationEmail(payload);
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
|
||||
@@ -2,13 +2,11 @@ import { generatePublicId } from "@/common/id";
|
||||
import { env } from "@/env";
|
||||
import {
|
||||
type DataRoomEmailPayloadType,
|
||||
sendShareDataRoomEmail,
|
||||
triggerName,
|
||||
ShareDataRoomEmailJob,
|
||||
} from "@/jobs/share-data-room-email";
|
||||
import { encode } from "@/lib/jwt";
|
||||
import { ShareRecipientSchema } from "@/schema/contacts";
|
||||
import { checkMembership } from "@/server/auth";
|
||||
import { getTriggerClient } from "@/trigger";
|
||||
import { createTRPCRouter, withAuth } from "@/trpc/api/trpc";
|
||||
import type { DataRoom } from "@prisma/client";
|
||||
import { z } from "zod";
|
||||
@@ -169,7 +167,7 @@ export const dataRoomRouter = createTRPCRouter({
|
||||
message: "Successfully updated data room",
|
||||
data: room,
|
||||
};
|
||||
} catch (error) {
|
||||
} catch (_error) {
|
||||
return {
|
||||
success: false,
|
||||
message:
|
||||
@@ -187,7 +185,6 @@ export const dataRoomRouter = createTRPCRouter({
|
||||
}),
|
||||
)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const trigger = getTriggerClient();
|
||||
const { session, db } = ctx;
|
||||
const { dataRoomId, others, selectedContacts } = input;
|
||||
const { name: senderName, email: senderEmail, companyId } = session.user;
|
||||
@@ -254,6 +251,7 @@ export const dataRoomRouter = createTRPCRouter({
|
||||
const link = `${baseUrl}/data-rooms/${dataRoom.publicId}?token=${token}`;
|
||||
|
||||
const payload: DataRoomEmailPayloadType = {
|
||||
// biome-ignore lint/style/noNonNullAssertion: <explanation>
|
||||
senderName: senderName!,
|
||||
recipientName: recipient.name,
|
||||
companyName: company.name,
|
||||
@@ -263,11 +261,7 @@ export const dataRoomRouter = createTRPCRouter({
|
||||
senderEmail,
|
||||
};
|
||||
|
||||
if (trigger) {
|
||||
await trigger.sendEvent({ name: triggerName, payload });
|
||||
} else {
|
||||
await sendShareDataRoomEmail(payload);
|
||||
}
|
||||
await new ShareDataRoomEmailJob().emit(payload);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { sendMemberInviteEmail } from "@/jobs/member-inivite-email";
|
||||
import { SendMemberInviteEmailJob } from "@/jobs/member-inivite-email";
|
||||
import { generatePasswordResetToken } from "@/lib/token";
|
||||
import { Audit } from "@/server/audit";
|
||||
import { checkMembership } from "@/server/auth";
|
||||
import { generateInviteToken, generateMemberIdentifier } from "@/server/member";
|
||||
import { getTriggerClient } from "@/trigger";
|
||||
import { withAuth } from "@/trpc/api/trpc";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { ZodInviteMemberMutationSchema } from "../schema";
|
||||
@@ -11,7 +10,6 @@ import { ZodInviteMemberMutationSchema } from "../schema";
|
||||
export const inviteMemberProcedure = withAuth
|
||||
.input(ZodInviteMemberMutationSchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const trigger = getTriggerClient();
|
||||
const user = ctx.session.user;
|
||||
const { name, email, title } = input;
|
||||
const { userAgent, requestIp, session } = ctx;
|
||||
@@ -143,11 +141,7 @@ export const inviteMemberProcedure = withAuth
|
||||
},
|
||||
};
|
||||
|
||||
// if (trigger) {
|
||||
// await trigger.sendEvent({ name: "email.member-invite", payload });
|
||||
// } else {
|
||||
await sendMemberInviteEmail(payload);
|
||||
// }
|
||||
await new SendMemberInviteEmailJob().emit(payload);
|
||||
|
||||
return { success: true };
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { sendMemberInviteEmail } from "@/jobs/member-inivite-email";
|
||||
import { SendMemberInviteEmailJob } from "@/jobs/member-inivite-email";
|
||||
import { generatePasswordResetToken } from "@/lib/token";
|
||||
import { Audit } from "@/server/audit";
|
||||
import { checkMembership } from "@/server/auth";
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
generateMemberIdentifier,
|
||||
revokeExistingInviteTokens,
|
||||
} from "@/server/member";
|
||||
import { getTriggerClient } from "@/trigger";
|
||||
import { withAuth } from "@/trpc/api/trpc";
|
||||
import { ZodReInviteMutationSchema } from "../schema";
|
||||
|
||||
@@ -15,7 +14,6 @@ export const reInviteProcedure = withAuth
|
||||
.input(ZodReInviteMutationSchema)
|
||||
.mutation(async ({ ctx: { session, db, requestIp, userAgent }, input }) => {
|
||||
const user = session.user;
|
||||
const trigger = getTriggerClient();
|
||||
|
||||
const { expires, memberInviteTokenHash } = await generateInviteToken();
|
||||
|
||||
@@ -106,11 +104,7 @@ export const reInviteProcedure = withAuth
|
||||
},
|
||||
};
|
||||
|
||||
if (trigger) {
|
||||
await trigger.sendEvent({ name: "email.member-invite", payload });
|
||||
} else {
|
||||
await sendMemberInviteEmail(payload);
|
||||
}
|
||||
await new SendMemberInviteEmailJob().emit(payload);
|
||||
|
||||
return { success: true };
|
||||
});
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
/* eslint-disable @typescript-eslint/prefer-for-of */
|
||||
import { withAuth } from "@/trpc/api/trpc";
|
||||
import { ZodAddFieldMutationSchema } from "../schema";
|
||||
|
||||
import type { TEsignEmailJob } from "@/jobs/esign-email";
|
||||
import { sendEsignEmail } from "@/jobs/esign-email";
|
||||
import {
|
||||
EsignNotificationEmailJob,
|
||||
type ExtendedEsignPayloadType,
|
||||
} from "@/jobs/esign-email";
|
||||
import { decode, encode } from "@/lib/jwt";
|
||||
import { checkMembership } from "@/server/auth";
|
||||
import { getTriggerClient } from "@/trigger";
|
||||
import { withAuth } from "@/trpc/api/trpc";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { z } from "zod";
|
||||
import { ZodAddFieldMutationSchema } from "../schema";
|
||||
|
||||
const emailTokenPayloadSchema = z.object({
|
||||
id: z.string(),
|
||||
@@ -44,9 +44,8 @@ export const addFieldProcedure = withAuth
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
try {
|
||||
const user = ctx.session.user;
|
||||
const triggerClient = getTriggerClient();
|
||||
|
||||
const mails: TEsignEmailJob[] = [];
|
||||
const mails: ExtendedEsignPayloadType[] = [];
|
||||
|
||||
if (input.status === "COMPLETE" && (!user.email || !user.name)) {
|
||||
return {
|
||||
@@ -56,7 +55,7 @@ export const addFieldProcedure = withAuth
|
||||
};
|
||||
}
|
||||
|
||||
await ctx.db.$transaction(async (tx) => {
|
||||
const template = await ctx.db.$transaction(async (tx) => {
|
||||
const { companyId } = await checkMembership({
|
||||
tx,
|
||||
session: ctx.session,
|
||||
@@ -182,29 +181,17 @@ export const addFieldProcedure = withAuth
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return template;
|
||||
});
|
||||
|
||||
if (mails.length) {
|
||||
if (triggerClient) {
|
||||
await triggerClient.sendEvents(
|
||||
mails.map((payload) => ({
|
||||
name: "email.esign",
|
||||
payload,
|
||||
})),
|
||||
);
|
||||
} else {
|
||||
await Promise.all(mails.map((payload) => sendEsignEmail(payload)));
|
||||
await ctx.db.$transaction(
|
||||
mails.map((payload) =>
|
||||
ctx.db.esignRecipient.update({
|
||||
where: {
|
||||
id: payload.recipient.id,
|
||||
},
|
||||
data: { status: "SENT" },
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
new EsignNotificationEmailJob().bulkEmit(
|
||||
mails.map((data) => ({
|
||||
data,
|
||||
singletonKey: `esign-notify-${template.id}-${data.recipient.id}`,
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -217,18 +204,12 @@ export const addFieldProcedure = withAuth
|
||||
: "Your template fields has been created.",
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof TRPCError) {
|
||||
return {
|
||||
success: false,
|
||||
title: "Bad request",
|
||||
message: error.message,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
success: false,
|
||||
title: "Error",
|
||||
message: "Uh ohh! Something went wrong. Please try again later.",
|
||||
};
|
||||
}
|
||||
console.error(error);
|
||||
|
||||
return {
|
||||
success: false,
|
||||
title: "Error",
|
||||
message: "Uh ohh! Something went wrong. Please try again later.",
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,33 +1,25 @@
|
||||
/* eslint-disable @typescript-eslint/prefer-for-of */
|
||||
|
||||
import { dayjsExt } from "@/common/dayjs";
|
||||
import { sendEsignConfirmationEmail } from "@/jobs/esign-confirmation-email";
|
||||
import { type TEsignEmailJob, sendEsignEmail } from "@/jobs/esign-email";
|
||||
import { type TESignPdfSchema } from "@/jobs/esign-pdf";
|
||||
import { EsignNotificationEmailJob } from "@/jobs/esign-email";
|
||||
import { EsignPdfJob } from "@/jobs/esign-pdf";
|
||||
import { EsignAudit } from "@/server/audit";
|
||||
import {
|
||||
type TCompleteEsignDocumentsOptions,
|
||||
type TGenerateEsignSignPdfOptions,
|
||||
completeEsignDocuments,
|
||||
generateEsignPdf,
|
||||
type CompleteEsignDocumentsOptionsType,
|
||||
type GenerateEsignSignPdfOptionsType,
|
||||
getEsignAudits,
|
||||
getEsignTemplate,
|
||||
uploadEsignDocuments,
|
||||
type uploadEsignDocumentsOptions,
|
||||
} from "@/server/esign";
|
||||
import { getPresignedGetUrl } from "@/server/file-uploads";
|
||||
import { getTriggerClient } from "@/trigger";
|
||||
import { withoutAuth } from "@/trpc/api/trpc";
|
||||
import { EncodeEmailToken } from "../../template-field-router/procedures/add-fields";
|
||||
import { ZodSignTemplateMutationSchema } from "../schema";
|
||||
import { SignTemplateMutationSchema } from "../schema";
|
||||
|
||||
export const signTemplateProcedure = withoutAuth
|
||||
.input(ZodSignTemplateMutationSchema)
|
||||
.input(SignTemplateMutationSchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const { db, requestIp, userAgent } = ctx;
|
||||
|
||||
const triggerClient = getTriggerClient();
|
||||
|
||||
await db.$transaction(async (tx) => {
|
||||
const template = await getEsignTemplate({
|
||||
templateId: input.templateId,
|
||||
@@ -141,7 +133,7 @@ export const signTemplateProcedure = withoutAuth
|
||||
companyId,
|
||||
templateName,
|
||||
fields: template.fields,
|
||||
uploaderName: "open cap",
|
||||
uploaderName: "Captable, Inc.",
|
||||
data,
|
||||
templateId: template.id,
|
||||
db: tx,
|
||||
@@ -231,7 +223,7 @@ export const signTemplateProcedure = withoutAuth
|
||||
tx,
|
||||
);
|
||||
|
||||
const payload: TEsignEmailJob = {
|
||||
await new EsignNotificationEmailJob().emit({
|
||||
email,
|
||||
token,
|
||||
message: template.message,
|
||||
@@ -239,16 +231,7 @@ export const signTemplateProcedure = withoutAuth
|
||||
recipient: nextDelivery,
|
||||
company: template.company,
|
||||
sender: template.uploader.user,
|
||||
};
|
||||
|
||||
if (triggerClient) {
|
||||
await triggerClient.sendEvent({
|
||||
name: "email.esign",
|
||||
payload,
|
||||
});
|
||||
} else {
|
||||
await sendEsignEmail(payload);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -257,9 +240,9 @@ export const signTemplateProcedure = withoutAuth
|
||||
});
|
||||
|
||||
interface TSignPdfOptions
|
||||
extends Omit<TGenerateEsignSignPdfOptions, "audits">,
|
||||
extends Omit<GenerateEsignSignPdfOptionsType, "audits">,
|
||||
Omit<uploadEsignDocumentsOptions, "buffer">,
|
||||
Omit<TCompleteEsignDocumentsOptions, "bucketData"> {
|
||||
Omit<CompleteEsignDocumentsOptionsType, "bucketData"> {
|
||||
templateId: string;
|
||||
company: {
|
||||
name: string;
|
||||
@@ -282,69 +265,20 @@ async function signPdf({
|
||||
recipients,
|
||||
company,
|
||||
}: TSignPdfOptions) {
|
||||
const trigger = getTriggerClient();
|
||||
|
||||
const audits = await getEsignAudits({ templateId, tx: db });
|
||||
|
||||
if (trigger) {
|
||||
const payload: TESignPdfSchema = {
|
||||
bucketKey,
|
||||
data,
|
||||
fields,
|
||||
audits,
|
||||
companyId,
|
||||
requestIp,
|
||||
templateId,
|
||||
templateName,
|
||||
uploaderName,
|
||||
userAgent,
|
||||
recipients,
|
||||
company,
|
||||
};
|
||||
|
||||
await trigger.sendEvent({
|
||||
name: "esign.sign-pdf",
|
||||
payload,
|
||||
});
|
||||
} else {
|
||||
const modifiedPdfBytes = await generateEsignPdf({
|
||||
bucketKey,
|
||||
data,
|
||||
fields,
|
||||
audits,
|
||||
});
|
||||
|
||||
const pdfBuffer = Buffer.from(modifiedPdfBytes);
|
||||
|
||||
const { fileUrl: _fileUrl, ...bucketData } = await uploadEsignDocuments({
|
||||
buffer: pdfBuffer,
|
||||
companyId,
|
||||
templateName,
|
||||
});
|
||||
|
||||
await completeEsignDocuments({
|
||||
bucketData,
|
||||
companyId,
|
||||
db,
|
||||
requestIp,
|
||||
templateId,
|
||||
templateName,
|
||||
uploaderName,
|
||||
userAgent,
|
||||
});
|
||||
|
||||
const file = await getPresignedGetUrl(bucketData.key);
|
||||
|
||||
await Promise.all(
|
||||
recipients.map((recipient) =>
|
||||
sendEsignConfirmationEmail({
|
||||
fileUrl: file.url,
|
||||
senderName: uploaderName,
|
||||
documentName: templateName,
|
||||
company,
|
||||
recipient,
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
await new EsignPdfJob().emit({
|
||||
bucketKey,
|
||||
data,
|
||||
fields,
|
||||
audits,
|
||||
companyId,
|
||||
requestIp,
|
||||
templateId,
|
||||
templateName,
|
||||
uploaderName,
|
||||
userAgent,
|
||||
recipients,
|
||||
company,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ export const ZodGetTemplateQuerySchema = z.object({
|
||||
isDraftOnly: z.boolean(),
|
||||
});
|
||||
|
||||
export const ZodSignTemplateMutationSchema = z.object({
|
||||
export const SignTemplateMutationSchema = z.object({
|
||||
templateId: z.string(),
|
||||
data: z.record(z.string()),
|
||||
recipientId: z.string(),
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import { env } from "@/env";
|
||||
import {
|
||||
ShareUpdateEmailJob,
|
||||
type UpdateSharePayloadType,
|
||||
sendShareUpdateEmail,
|
||||
triggerName,
|
||||
} from "@/jobs/share-update-email";
|
||||
import { encode } from "@/lib/jwt";
|
||||
import { ShareRecipientSchema } from "@/schema/contacts";
|
||||
import { getTriggerClient } from "@/trigger";
|
||||
import { withAuth } from "@/trpc/api/trpc";
|
||||
import { z } from "zod";
|
||||
|
||||
@@ -19,7 +17,6 @@ export const shareUpdateProcedure = withAuth
|
||||
}),
|
||||
)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const trigger = getTriggerClient();
|
||||
const { session, db } = ctx;
|
||||
const { updateId, others, selectedContacts } = input;
|
||||
const { name: senderName, email: senderEmail, companyId } = session.user;
|
||||
@@ -87,6 +84,7 @@ export const shareUpdateProcedure = withAuth
|
||||
const link = `${baseUrl}/updates/${update.publicId}?token=${token}`;
|
||||
|
||||
const payload: UpdateSharePayloadType = {
|
||||
// biome-ignore lint/style/noNonNullAssertion: <explanation>
|
||||
senderName: senderName!,
|
||||
recipientName: recipient.name,
|
||||
companyName: company.name,
|
||||
@@ -98,11 +96,7 @@ export const shareUpdateProcedure = withAuth
|
||||
senderEmail,
|
||||
};
|
||||
|
||||
if (trigger) {
|
||||
await trigger.sendEvent({ name: triggerName, payload });
|
||||
} else {
|
||||
await sendShareUpdateEmail(payload);
|
||||
}
|
||||
await new ShareUpdateEmailJob().emit(payload);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user