feat: swagger

This commit is contained in:
Nevo David 2024-03-11 20:16:37 +07:00
parent e734b6298a
commit 767a6f118a
79 changed files with 1775 additions and 34 deletions

View File

@ -15,7 +15,18 @@
"main": "apps/backend/src/main.ts",
"tsConfig": "apps/backend/tsconfig.app.json",
"assets": ["apps/backend/src/assets"],
"webpackConfig": "apps/backend/webpack.config.js"
"webpackConfig": "apps/backend/webpack.config.js",
"transformers": [
{
"name": "@nestjs/swagger/plugin",
"options": {
"dtoFileNameSuffix": [".dto.ts"],
"controllerFileNameSuffix": [".controller.ts"],
"introspectComments": true,
"classValidatorShim": true
}
}
]
},
"configurations": {
"development": {},

View File

@ -1,3 +1,5 @@
import {loadSwagger} from "@gitroom/helpers/swagger/load.swagger";
process.env.TZ='UTC';
import cookieParser from 'cookie-parser';
@ -23,6 +25,8 @@ async function bootstrap() {
app.use(cookieParser());
app.useGlobalFilters(new SubscriptionExceptionFilter());
loadSwagger(app);
const port = process.env.PORT || 3000;
await app.listen(port);
Logger.log(

View File

@ -11,6 +11,7 @@
}
],
"compilerOptions": {
"esModuleInterop": true
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true
}
}

32
apps/docs/README.md Normal file
View File

@ -0,0 +1,32 @@
# Mintlify Starter Kit
Click on `Use this template` to copy the Mintlify starter kit. The starter kit contains examples including
- Guide pages
- Navigation
- Customizations
- API Reference pages
- Use of popular components
### Development
Install the [Mintlify CLI](https://www.npmjs.com/package/mintlify) to preview the documentation changes locally. To install, use the following command
```
npm i -g mintlify
```
Run the following command at the root of your documentation (where mint.json is)
```
mintlify dev
```
### Publishing Changes
Install our Github App to autopropagate changes from youre repo to your deployment. Changes will be deployed to production automatically after pushing to the default branch. Find the link to install on your dashboard.
#### Troubleshooting
- Mintlify dev isn't running - Run `mintlify install` it'll re-install dependencies.
- Page loads as a 404 - Make sure you are running in a folder with `mint.json`

View File

@ -0,0 +1,3 @@
## My Snippet
<Info>This is an example of a reusable snippet</Info>

View File

@ -0,0 +1,3 @@
---
openapi: post /auth
---

View File

@ -0,0 +1,3 @@
---
openapi: post /auth/login
---

View File

@ -0,0 +1,3 @@
---
openapi: post /auth/registration
---

View File

@ -0,0 +1,3 @@
---
openapi: get /billing
---

View File

@ -0,0 +1,3 @@
---
openapi: get /billing/check/{id}
---

View File

@ -0,0 +1,3 @@
---
openapi: post /billing/cancel
---

View File

@ -0,0 +1,3 @@
---
openapi: post /billing/modify
---

View File

@ -0,0 +1,3 @@
---
openapi: post /billing/subscribe
---

View File

@ -0,0 +1,3 @@
---
openapi: delete /categories/{id}
---

View File

@ -0,0 +1,3 @@
---
openapi: get /categories/{id}
---

View File

@ -0,0 +1,3 @@
---
openapi: get /categories
---

View File

@ -0,0 +1,3 @@
---
openapi: get /categories/faq
---

View File

@ -0,0 +1,3 @@
---
openapi: post /categories
---

View File

@ -0,0 +1,3 @@
---
openapi: post /categories/order
---

View File

@ -0,0 +1,3 @@
---
openapi: put /categories/{id}
---

View File

@ -0,0 +1,3 @@
---
openapi: delete /faq/{id}
---

View File

@ -0,0 +1,3 @@
---
openapi: get /faq/{id}
---

View File

@ -0,0 +1,3 @@
---
openapi: get /faq/jobs/{id}
---

View File

@ -0,0 +1,3 @@
---
openapi: post /faq
---

View File

@ -0,0 +1,3 @@
---
openapi: post /faq/answers
---

View File

@ -0,0 +1,3 @@
---
openapi: post /faq/job
---

View File

@ -0,0 +1,3 @@
---
openapi: post /faq/jobs/questions
---

View File

@ -0,0 +1,3 @@
---
openapi: post /faq/order
---

View File

@ -0,0 +1,3 @@
---
openapi: put /faq/{id}/category/{category}
---

View File

@ -0,0 +1,3 @@
---
openapi: put /faq/{id}
---

View File

@ -0,0 +1,3 @@
---
openapi: delete /integrations/{id}
---

View File

@ -0,0 +1,3 @@
---
openapi: get /integrations/{type}/add
---

View File

@ -0,0 +1,3 @@
---
openapi: get /integrations
---

View File

@ -0,0 +1,3 @@
---
openapi: post /integrations
---

View File

@ -0,0 +1,3 @@
---
openapi: post /invite
---

View File

@ -0,0 +1,3 @@
---
openapi: post /invite/add
---

View File

@ -0,0 +1,3 @@
---
openapi: delete /settings/delete-domain/{id}
---

View File

@ -0,0 +1,3 @@
---
openapi: get /settings
---

View File

@ -0,0 +1,3 @@
---
openapi: get /settings/check-domain/{id}
---

View File

@ -0,0 +1,3 @@
---
openapi: post /settings/check-subdomain
---

View File

@ -0,0 +1,3 @@
---
openapi: post /settings/domain
---

View File

@ -0,0 +1,3 @@
---
openapi: post /settings/subDomain
---

View File

@ -0,0 +1,3 @@
---
openapi: post /stripe
---

View File

@ -0,0 +1,3 @@
---
openapi: get /styles
---

View File

@ -0,0 +1,3 @@
---
openapi: put /styles
---

View File

@ -0,0 +1,3 @@
---
openapi: get /users/self
---

View File

@ -0,0 +1,3 @@
---
openapi: get /users/sign-in
---

View File

@ -0,0 +1,3 @@
---
openapi: post /users/revalidate
---

View File

@ -0,0 +1,18 @@
---
title: 'Introduction'
---
<Note>
If you're not looking to build API reference documentation, you can delete
this section by removing the api-reference folder.
</Note>
## Welcome
## How to use the API
There are two main options to use the API:
1. You can send an header called `auth` with the token you received when you logged in.
2. You can send a cookie called `auth` with the token you received when you logged in.
The dashboard mainly uses the cookie method to authenticate requests.<br />
But if you want to test the API with Swagger or Postman, you can use the `auth` header.

View File

@ -0,0 +1,37 @@
---
title: 'Code Blocks'
description: 'Display inline code and code blocks'
icon: 'code'
---
## Basic
### Inline Code
To denote a `word` or `phrase` as code, enclose it in backticks (`).
```
To denote a `word` or `phrase` as code, enclose it in backticks (`).
```
### Code Block
Use [fenced code blocks](https://www.markdownguide.org/extended-syntax/#fenced-code-blocks) by enclosing code in three backticks and follow the leading ticks with the programming language of your snippet to get syntax highlighting. Optionally, you can also write the name of your code after the programming language.
```java HelloWorld.java
class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}
```
````md
```java HelloWorld.java
class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}
```
````

View File

@ -0,0 +1,59 @@
---
title: 'Images and Embeds'
description: 'Add image, video, and other HTML elements'
icon: 'image'
---
<img
style={{ borderRadius: '0.5rem' }}
src="https://mintlify-assets.b-cdn.net/bigbend.jpg"
/>
## Image
### Using Markdown
The [markdown syntax](https://www.markdownguide.org/basic-syntax/#images) lets you add images using the following code
```md
![title](/path/image.jpg)
```
Note that the image file size must be less than 5MB. Otherwise, we recommend hosting on a service like [Cloudinary](https://cloudinary.com/) or [S3](https://aws.amazon.com/s3/). You can then use that URL and embed.
### Using Embeds
To get more customizability with images, you can also use [embeds](/writing-content/embed) to add images
```html
<img height="200" src="/path/image.jpg" />
```
## Embeds and HTML elements
<iframe
width="560"
height="315"
src="https://www.youtube.com/embed/4KzFe50RQkQ"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen
style={{ width: '100%', borderRadius: '0.5rem' }}
></iframe>
<br />
<Tip>
Mintlify supports [HTML tags in Markdown](https://www.markdownguide.org/basic-syntax/#html). This is helpful if you prefer HTML tags to Markdown syntax, and lets you create documentation with infinite flexibility.
</Tip>
### iFrames
Loads another HTML page within the document. Most commonly used for embedding videos.
```html
<iframe src="https://www.youtube.com/embed/4KzFe50RQkQ"> </iframe>
```

View File

@ -0,0 +1,88 @@
---
title: 'Markdown Syntax'
description: 'Text, title, and styling in standard markdown'
icon: 'text-size'
---
## Titles
Best used for section headers.
```md
## Titles
```
### Subtitles
Best use to subsection headers.
```md
### Subtitles
```
<Tip>
Each **title** and **subtitle** creates an anchor and also shows up on the table of contents on the right.
</Tip>
## Text Formatting
We support most markdown formatting. Simply add `**`, `_`, or `~` around text to format it.
| Style | How to write it | Result |
| ------------- | ----------------- | --------------- |
| Bold | `**bold**` | **bold** |
| Italic | `_italic_` | _italic_ |
| Strikethrough | `~strikethrough~` | ~strikethrough~ |
You can combine these. For example, write `**_bold and italic_**` to get **_bold and italic_** text.
You need to use HTML to write superscript and subscript text. That is, add `<sup>` or `<sub>` around your text.
| Text Size | How to write it | Result |
| ----------- | ------------------------ | ---------------------- |
| Superscript | `<sup>superscript</sup>` | <sup>superscript</sup> |
| Subscript | `<sub>subscript</sub>` | <sub>subscript</sub> |
## Linking to Pages
You can add a link by wrapping text in `[]()`. You would write `[link to google](https://google.com)` to [link to google](https://google.com).
Links to pages in your docs need to be root-relative. Basically, you should include the entire folder path. For example, `[link to text](/writing-content/text)` links to the page "Text" in our components section.
Relative links like `[link to text](../text)` will open slower because we cannot optimize them as easily.
## Blockquotes
### Singleline
To create a blockquote, add a `>` in front of a paragraph.
> Dorothy followed her through many of the beautiful rooms in her castle.
```md
> Dorothy followed her through many of the beautiful rooms in her castle.
```
### Multiline
> Dorothy followed her through many of the beautiful rooms in her castle.
>
> The Witch bade her clean the pots and kettles and sweep the floor and keep the fire fed with wood.
```md
> Dorothy followed her through many of the beautiful rooms in her castle.
>
> The Witch bade her clean the pots and kettles and sweep the floor and keep the fire fed with wood.
```
### LaTeX
Mintlify supports [LaTeX](https://www.latex-project.org) through the Latex component.
<Latex>8 x (vk x H1 - H2) = (0,1)</Latex>
```md
<Latex>8 x (vk x H1 - H2) = (0,1)</Latex>
```

View File

@ -0,0 +1,66 @@
---
title: 'Navigation'
description: 'The navigation field in mint.json defines the pages that go in the navigation menu'
icon: 'map'
---
The navigation menu is the list of links on every website.
You will likely update `mint.json` every time you add a new page. Pages do not show up automatically.
## Navigation syntax
Our navigation syntax is recursive which means you can make nested navigation groups. You don't need to include `.mdx` in page names.
<CodeGroup>
```json Regular Navigation
"navigation": [
{
"group": "Getting Started",
"pages": ["quickstart"]
}
]
```
```json Nested Navigation
"navigation": [
{
"group": "Getting Started",
"pages": [
"quickstart",
{
"group": "Nested Reference Pages",
"pages": ["nested-reference-page"]
}
]
}
]
```
</CodeGroup>
## Folders
Simply put your MDX files in folders and update the paths in `mint.json`.
For example, to have a page at `https://yoursite.com/your-folder/your-page` you would make a folder called `your-folder` containing an MDX file called `your-page.mdx`.
<Warning>
You cannot use `api` for the name of a folder unless you nest it inside another folder. Mintlify uses Next.js which reserves the top-level `api` folder for internal server calls. A folder name such as `api-reference` would be accepted.
</Warning>
```json Navigation With Folder
"navigation": [
{
"group": "Group Name",
"pages": ["your-folder/your-page"]
}
]
```
## Hidden Pages
MDX files not included in `mint.json` will not show up in the sidebar but are accessible through the search bar and by linking directly to them.

View File

@ -0,0 +1,318 @@
---
title: 'Global Settings'
description: 'Mintlify gives you complete control over the look and feel of your documentation using the mint.json file'
icon: 'gear'
---
Every Mintlify site needs a `mint.json` file with the core configuration settings. Learn more about the [properties](#properties) below.
## Properties
<ResponseField name="name" type="string" required>
Name of your project. Used for the global title.
Example: `mintlify`
</ResponseField>
<ResponseField name="navigation" type="Navigation[]" required>
An array of groups with all the pages within that group
<Expandable title="Navigation">
<ResponseField name="group" type="string">
The name of the group.
Example: `Settings`
</ResponseField>
<ResponseField name="pages" type="string[]">
The relative paths to the markdown files that will serve as pages.
Example: `["customization", "page"]`
</ResponseField>
</Expandable>
</ResponseField>
<ResponseField name="logo" type="string or object">
Path to logo image or object with path to "light" and "dark" mode logo images
<Expandable title="Logo">
<ResponseField name="light" type="string">
Path to the logo in light mode
</ResponseField>
<ResponseField name="dark" type="string">
Path to the logo in dark mode
</ResponseField>
<ResponseField name="href" type="string" default="/">
Where clicking on the logo links you to
</ResponseField>
</Expandable>
</ResponseField>
<ResponseField name="favicon" type="string">
Path to the favicon image
</ResponseField>
<ResponseField name="colors" type="Colors">
Hex color codes for your global theme
<Expandable title="Colors">
<ResponseField name="primary" type="string" required>
The primary color. Used for most often for highlighted content, section
headers, accents, in light mode
</ResponseField>
<ResponseField name="light" type="string">
The primary color for dark mode. Used for most often for highlighted
content, section headers, accents, in dark mode
</ResponseField>
<ResponseField name="dark" type="string">
The primary color for important buttons
</ResponseField>
<ResponseField name="background" type="object">
The color of the background in both light and dark mode
<Expandable title="Object">
<ResponseField name="light" type="string" required>
The hex color code of the background in light mode
</ResponseField>
<ResponseField name="dark" type="string" required>
The hex color code of the background in dark mode
</ResponseField>
</Expandable>
</ResponseField>
</Expandable>
</ResponseField>
<ResponseField name="topbarLinks" type="TopbarLink[]">
Array of `name`s and `url`s of links you want to include in the topbar
<Expandable title="TopbarLink">
<ResponseField name="name" type="string">
The name of the button.
Example: `Contact us`
</ResponseField>
<ResponseField name="url" type="string">
The url once you click on the button. Example: `https://mintlify.com/contact`
</ResponseField>
</Expandable>
</ResponseField>
<ResponseField name="topbarCtaButton" type="Call to Action">
<Expandable title="Topbar Call to Action">
<ResponseField name="type" type={'"link" or "github"'} default="link">
Link shows a button. GitHub shows the repo information at the url provided including the number of GitHub stars.
</ResponseField>
<ResponseField name="url" type="string">
If `link`: What the button links to.
If `github`: Link to the repository to load GitHub information from.
</ResponseField>
<ResponseField name="name" type="string">
Text inside the button. Only required if `type` is a `link`.
</ResponseField>
</Expandable>
</ResponseField>
<ResponseField name="versions" type="string[]">
Array of version names. Only use this if you want to show different versions
of docs with a dropdown in the navigation bar.
</ResponseField>
<ResponseField name="anchors" type="Anchor[]">
An array of the anchors, includes the `icon`, `color`, and `url`.
<Expandable title="Anchor">
<ResponseField name="icon" type="string">
The [Font Awesome](https://fontawesome.com/search?s=brands%2Cduotone) icon used to feature the anchor.
Example: `comments`
</ResponseField>
<ResponseField name="name" type="string">
The name of the anchor label.
Example: `Community`
</ResponseField>
<ResponseField name="url" type="string">
The start of the URL that marks what pages go in the anchor. Generally, this is the name of the folder you put your pages in.
</ResponseField>
<ResponseField name="color" type="string">
The hex color of the anchor icon background. Can also be a gradient if you pass an object with the properties `from` and `to` that are each a hex color.
</ResponseField>
<ResponseField name="version" type="string">
Used if you want to hide an anchor until the correct docs version is selected.
</ResponseField>
<ResponseField name="isDefaultHidden" type="boolean" default="false">
Pass `true` if you want to hide the anchor until you directly link someone to docs inside it.
</ResponseField>
<ResponseField name="iconType" default="duotone" type="string">
One of: "brands", "duotone", "light", "sharp-solid", "solid", or "thin"
</ResponseField>
</Expandable>
</ResponseField>
<ResponseField name="topAnchor" type="Object">
Override the default configurations for the top-most anchor.
<Expandable title="Object">
<ResponseField name="name" default="Documentation" type="string">
The name of the top-most anchor
</ResponseField>
<ResponseField name="icon" default="book-open" type="string">
Font Awesome icon.
</ResponseField>
<ResponseField name="iconType" default="duotone" type="string">
One of: "brands", "duotone", "light", "sharp-solid", "solid", or "thin"
</ResponseField>
</Expandable>
</ResponseField>
<ResponseField name="tabs" type="Tabs[]">
An array of navigational tabs.
<Expandable title="Tabs">
<ResponseField name="name" type="string">
The name of the tab label.
</ResponseField>
<ResponseField name="url" type="string">
The start of the URL that marks what pages go in the tab. Generally, this
is the name of the folder you put your pages in.
</ResponseField>
</Expandable>
</ResponseField>
<ResponseField name="api" type="API">
Configuration for API settings. Learn more about API pages at [API Components](/api-playground/demo).
<Expandable title="API">
<ResponseField name="baseUrl" type="string">
The base url for all API endpoints. If `baseUrl` is an array, it will enable for multiple base url
options that the user can toggle.
</ResponseField>
<ResponseField name="auth" type="Auth">
<Expandable title="Auth">
<ResponseField name="method" type='"bearer" | "basic" | "key"'>
The authentication strategy used for all API endpoints.
</ResponseField>
<ResponseField name="name" type="string">
The name of the authentication parameter used in the API playground.
If method is `basic`, the format should be `[usernameName]:[passwordName]`
</ResponseField>
<ResponseField name="inputPrefix" type="string">
The default value that's designed to be a prefix for the authentication input field.
E.g. If an `inputPrefix` of `AuthKey` would inherit the default input result of the authentication field as `AuthKey`.
</ResponseField>
</Expandable>
</ResponseField>
<ResponseField name="playground" type="Playground">
Configurations for the API playground
<Expandable title="Playground">
<ResponseField name="mode" default="show" type='"show" | "simple" | "hide"'>
Whether the playground is showing, hidden, or only displaying the endpoint with no added user interactivity `simple`
Learn more at the [playground guides](/api-playground/demo)
</ResponseField>
</Expandable>
</ResponseField>
<ResponseField name="maintainOrder" type="boolean">
Enabling this flag ensures that key ordering in OpenAPI pages matches the key ordering defined in the OpenAPI file.
<Warning>This behavior will soon be enabled by default, at which point this field will be deprecated.</Warning>
</ResponseField>
</Expandable>
</ResponseField>
<ResponseField name="openapi" type="string | string[]">
A string or an array of strings of URL(s) or relative path(s) pointing to your
OpenAPI file.
Examples:
<CodeGroup>
```json Absolute
"openapi": "https://example.com/openapi.json"
```
```json Relative
"openapi": "/openapi.json"
```
```json Multiple
"openapi": ["https://example.com/openapi1.json", "/openapi2.json", "/openapi3.json"]
```
</CodeGroup>
</ResponseField>
<ResponseField name="footerSocials" type="FooterSocials">
An object of social media accounts where the key:property pair represents the social media platform and the account url.
Example:
```json
{
"twitter": "https://twitter.com/mintlify",
"website": "https://mintlify.com"
}
```
<Expandable title="FooterSocials">
<ResponseField name="[key]" type="string">
One of the following values `website`, `facebook`, `twitter`, `discord`, `slack`, `github`, `linkedin`, `instagram`, `hacker-news`
Example: `twitter`
</ResponseField>
<ResponseField name="property" type="string">
The URL to the social platform.
Example: `https://twitter.com/mintlify`
</ResponseField>
</Expandable>
</ResponseField>
<ResponseField name="feedback" type="Feedback">
Configurations to enable feedback buttons
<Expandable title="Feedback">
<ResponseField name="suggestEdit" type="boolean" default="false">
Enables a button to allow users to suggest edits via pull requests
</ResponseField>
<ResponseField name="raiseIssue" type="boolean" default="false">
Enables a button to allow users to raise an issue about the documentation
</ResponseField>
</Expandable>
</ResponseField>
<ResponseField name="modeToggle" type="ModeToggle">
Customize the dark mode toggle.
<Expandable title="ModeToggle">
<ResponseField name="default" type={'"light" or "dark"'}>
Set if you always want to show light or dark mode for new users. When not
set, we default to the same mode as the user's operating system.
</ResponseField>
<ResponseField name="isHidden" type="boolean" default="false">
Set to true to hide the dark/light mode toggle. You can combine `isHidden` with `default` to force your docs to only use light or dark mode. For example:
<CodeGroup>
```json Only Dark Mode
"modeToggle": {
"default": "dark",
"isHidden": true
}
```
```json Only Light Mode
"modeToggle": {
"default": "light",
"isHidden": true
}
```
</CodeGroup>
</ResponseField>
</Expandable>
</ResponseField>
<ResponseField name="backgroundImage" type="string">
A background image to be displayed behind every page. See example with
[Infisical](https://infisical.com/docs) and [FRPC](https://frpc.io).
</ResponseField>

BIN
apps/docs/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

68
apps/docs/howitworks.mdx Normal file
View File

@ -0,0 +1,68 @@
---
title: How it works
description: 'Learn the architecture of the project'
---
crosspublic is a tool to convert private communication into public knowledge.<br />
The entire project is built under [NX](https://nx.dev/) to have a monorepo with multiple projects.<br /><br />
Unlike other NX project, this project has one `.env` file that is shared between all the apps.<br />
It makes it easier to develop and deploy the project.<br /><br />
When deploying to websites like [Railway](https://railway.app) or [Heroku](https://heroku.com), you can use a shared environment variables for all the apps.<br /><br />
**At the moment it has 6 project:**
- **Backend** - NestJS based system
- **Panel** - NextJS based control panel.
- **Tenants** - NextJS based multi-tenant website.
- **Discord** - NestJS based Discord bot.
- **Marketing** - NextJS based marketing website.
- **Docs** - Mintlify based documentation website.
<img
src="/images/arch.png"
alt="Architecture of crosspublic"
/>
## Architecture
### Backend
The project has a centralized backend that both the bots and the client interact with.<br />
The backend is built with [NestJS](https://nestjs.com/) with a basic architecture of controllers, services, repositories and dtos.<br /><br />
It uses [Prisma](https://www.prisma.io/) as an ORM to interact with the database.<br />
By default Prisma uses [Postgres](https://www.postgresql.org/) as a database, but it can be easily changed to any other database since there are no native queries.<br /><br />
The project doesn't have a control panel login, it works with an SSO using the different bots.<br />
Once somebody uses a command like `/add` or `/signin` the backend will receive the server id and the internal user id from the bot.<br /><br />
The backend will auto-create an organization and a user if it doesn't exist.<br />
It will return a URL with the JWT token embedded in the url to sign in into the control panel.
### Panel
The panel is built with [NextJS](https://nextjs.org/) and [TailwindCSS](https://tailwindcss.com/).<br />
The panel has 4 main pages:
- The main control panel to create and manage the FAQ.
- A style page to customize the FAQ.
- A "Create an faq" page, it takes an entire conversation and converts it into an FAQ with OpenAI ChatGPT.
- Domains page to manage the domains and subdomains that lead to the tenant website.
- An integrations page to add bots to different platforms such as: Discord, Slack, Intercom, etc.
### Tenants
The tenants is built with [NextJS](https://nextjs.org/) and [TailwindCSS](https://tailwindcss.com/).<br />
It's the website for the end-users to see the FAQ.<br />
It contains a simple search bar and a list of questions.<br />
The search bar uses [Algolia](https://www.algolia.com/) to search through the questions.<br />
It uses the style from the control panel to customize the website.<br />
The tenants cache each pages on the `crosspublic.com/[domainName]` path, it uses a middleware to rewrite the Vercel apex domain or the custom domain to the route.<br />
### Discord
The Discord bot is built with [Discord.JS](https://discord.js.org/) and [NestJS](https://nestjs.com/).<br />
It runs in a loop and listens to every message in every server it's in.<br />
Once somebody runs a command such as `/add` or `/signin` it will send a request to the backend with the server id and the user id.<br />
This is a simple server and in the future there will be many other bots for different platforms.
### Marketing
The marketing website is built with [NextJS](https://nextjs.org/) and [TailwindCSS](https://tailwindcss.com/).<br />
It's a simple static websites with a few pages.
### Docs
The documentation website is built with [Mintlify](https://www.mintlify.com/).<br />
The reference in the documentation is being auto-generated by the backend.<br />
NestJS has a built-in Swagger module that generates a JSON file with all the routes and their documentation.<br />
It makes it very easy to track API changes and deploy them.

BIN
apps/docs/images/arch.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 421 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

View File

@ -0,0 +1,161 @@
<svg width="700" height="320" viewBox="0 0 700 320" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_2862_30)">
<rect width="700" height="320" rx="16" fill="url(#paint0_linear_2862_30)"/>
<path d="M311.889 247.3C283.097 247.215 258.226 231.466 246.292 201.629C234.357 171.793 238.02 134.523 253.414 101.112C282.206 101.197 307.077 116.945 319.011 146.782C330.946 176.619 327.283 213.888 311.889 247.3Z" fill="white"/>
<path d="M311.889 247.3C283.097 247.215 258.226 231.466 246.292 201.629C234.357 171.793 238.02 134.523 253.414 101.112C282.206 101.197 307.077 116.945 319.011 146.782C330.946 176.619 327.283 213.888 311.889 247.3Z" fill="url(#paint1_radial_2862_30)"/>
<path d="M311.889 247.3C283.097 247.215 258.226 231.466 246.292 201.629C234.357 171.793 238.02 134.523 253.414 101.112C282.206 101.197 307.077 116.945 319.011 146.782C330.946 176.619 327.283 213.888 311.889 247.3Z" fill="black" fill-opacity="0.5" style="mix-blend-mode:hard-light"/>
<path d="M311.889 247.3C283.097 247.215 258.226 231.466 246.292 201.629C234.357 171.793 238.02 134.523 253.414 101.112C282.206 101.197 307.077 116.945 319.011 146.782C330.946 176.619 327.283 213.888 311.889 247.3Z" fill="url(#paint2_linear_2862_30)" fill-opacity="0.5" style="mix-blend-mode:hard-light"/>
<path d="M311.72 247.034C283.108 246.887 258.409 231.208 246.538 201.531C234.656 171.825 238.271 134.702 253.583 101.377C282.195 101.524 306.894 117.203 318.765 146.88C330.647 176.586 327.031 213.709 311.72 247.034Z" stroke="url(#paint3_linear_2862_30)" stroke-opacity="0.05" stroke-width="0.530516"/>
<path d="M305.839 247.174C343.92 237.419 377.154 210.619 393.585 171.64C410.017 132.661 405.98 90.1988 386.347 56.1934C348.266 65.9477 315.032 92.7486 298.601 131.728C282.169 170.706 286.206 213.168 305.839 247.174Z" fill="white"/>
<path d="M305.839 247.174C343.92 237.419 377.154 210.619 393.585 171.64C410.017 132.661 405.98 90.1988 386.347 56.1934C348.266 65.9477 315.032 92.7486 298.601 131.728C282.169 170.706 286.206 213.168 305.839 247.174Z" fill="url(#paint4_radial_2862_30)"/>
<path d="M393.341 171.537C376.971 210.369 343.89 237.091 305.969 246.867C286.462 212.959 282.476 170.663 298.845 131.831C315.215 92.9978 348.295 66.2765 386.217 56.5004C405.724 90.4077 409.71 132.704 393.341 171.537Z" stroke="url(#paint5_linear_2862_30)" stroke-opacity="0.05" stroke-width="0.530516"/>
<path d="M305.686 246.995C329.749 266.114 361.965 272.832 393.67 262.129C425.376 251.426 449.499 225.691 461.03 194.556C436.967 175.437 404.751 168.719 373.045 179.422C341.34 190.125 317.217 215.86 305.686 246.995Z" fill="white"/>
<path d="M305.686 246.995C329.749 266.114 361.965 272.832 393.67 262.129C425.376 251.426 449.499 225.691 461.03 194.556C436.967 175.437 404.751 168.719 373.045 179.422C341.34 190.125 317.217 215.86 305.686 246.995Z" fill="url(#paint6_radial_2862_30)"/>
<path d="M305.686 246.995C329.749 266.114 361.965 272.832 393.67 262.129C425.376 251.426 449.499 225.691 461.03 194.556C436.967 175.437 404.751 168.719 373.045 179.422C341.34 190.125 317.217 215.86 305.686 246.995Z" fill="black" fill-opacity="0.2" style="mix-blend-mode:hard-light"/>
<path d="M305.686 246.995C329.749 266.114 361.965 272.832 393.67 262.129C425.376 251.426 449.499 225.691 461.03 194.556C436.967 175.437 404.751 168.719 373.045 179.422C341.34 190.125 317.217 215.86 305.686 246.995Z" fill="url(#paint7_linear_2862_30)" fill-opacity="0.5" style="mix-blend-mode:hard-light"/>
<path d="M393.586 261.878C362.034 272.529 329.98 265.88 306.002 246.907C317.534 215.919 341.57 190.327 373.13 179.673C404.681 169.023 436.735 175.671 460.714 194.644C449.181 225.632 425.145 251.224 393.586 261.878Z" stroke="url(#paint8_linear_2862_30)" stroke-opacity="0.05" stroke-width="0.530516"/>
<g opacity="0.8" filter="url(#filter0_f_2862_30)">
<circle cx="660" cy="-60" r="160" fill="#18E244" fill-opacity="0.4"/>
</g>
<g opacity="0.8" filter="url(#filter1_f_2862_30)">
<circle cx="20" cy="213" r="160" fill="#18CAE2" fill-opacity="0.33"/>
</g>
<g opacity="0.8" filter="url(#filter2_f_2862_30)">
<circle cx="660" cy="480" r="160" fill="#18E2B2" fill-opacity="0.52"/>
</g>
<g opacity="0.8" filter="url(#filter3_f_2862_30)">
<circle cx="20" cy="413" r="160" fill="#4018E2" fill-opacity="0.22"/>
</g>
<path opacity="0.2" d="M0 50H700" stroke="url(#paint9_radial_2862_30)" stroke-dasharray="4 4"/>
<path opacity="0.1" d="M0 82H700" stroke="url(#paint10_radial_2862_30)" stroke-dasharray="4 4"/>
<path opacity="0.2" d="M239 0L239 320" stroke="url(#paint11_radial_2862_30)" stroke-dasharray="4 4"/>
<path opacity="0.1" d="M271 0L271 320" stroke="url(#paint12_radial_2862_30)" stroke-dasharray="4 4"/>
<path opacity="0.2" d="M461 0L461 320" stroke="url(#paint13_radial_2862_30)" stroke-dasharray="4 4"/>
<path opacity="0.1" d="M429 0L429 320" stroke="url(#paint14_radial_2862_30)" stroke-dasharray="4 4"/>
<path opacity="0.2" d="M0 271H700" stroke="url(#paint15_radial_2862_30)" stroke-dasharray="4 4"/>
<path opacity="0.1" d="M0 239H700" stroke="url(#paint16_radial_2862_30)" stroke-dasharray="4 4"/>
<g style="mix-blend-mode:overlay" opacity="0.1">
<path d="M0 160H700" stroke="url(#paint17_linear_2862_30)"/>
</g>
<g style="mix-blend-mode:overlay" opacity="0.2">
<path d="M511 -1L189 321" stroke="url(#paint18_linear_2862_30)"/>
</g>
<g style="mix-blend-mode:overlay" opacity="0.2">
<path d="M511 321L189 -1" stroke="url(#paint19_linear_2862_30)"/>
</g>
<g style="mix-blend-mode:overlay" opacity="0.1">
<circle cx="350" cy="160" r="111" stroke="white"/>
</g>
<g style="mix-blend-mode:overlay" opacity="0.1">
<circle cx="350" cy="160" r="79" stroke="white"/>
</g>
</g>
<defs>
<filter id="filter0_f_2862_30" x="260" y="-460" width="800" height="800" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="120" result="effect1_foregroundBlur_2862_30"/>
</filter>
<filter id="filter1_f_2862_30" x="-380" y="-187" width="800" height="800" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="120" result="effect1_foregroundBlur_2862_30"/>
</filter>
<filter id="filter2_f_2862_30" x="260" y="80" width="800" height="800" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="120" result="effect1_foregroundBlur_2862_30"/>
</filter>
<filter id="filter3_f_2862_30" x="-380" y="13" width="800" height="800" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="120" result="effect1_foregroundBlur_2862_30"/>
</filter>
<linearGradient id="paint0_linear_2862_30" x1="1.04308e-05" y1="320" x2="710.784" y2="26.0793" gradientUnits="userSpaceOnUse">
<stop stop-color="#18E299" stop-opacity="0.09"/>
<stop offset="0.729167" stop-color="#0D9373" stop-opacity="0.08"/>
</linearGradient>
<radialGradient id="paint1_radial_2862_30" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(208.697 189.703) rotate(-10.029) scale(169.097 167.466)">
<stop stop-color="#00B0BB"/>
<stop offset="1" stop-color="#00DB65"/>
</radialGradient>
<linearGradient id="paint2_linear_2862_30" x1="306.587" y1="93.5598" x2="252.341" y2="224.228" gradientUnits="userSpaceOnUse">
<stop stop-color="#18E299"/>
<stop offset="1"/>
</linearGradient>
<linearGradient id="paint3_linear_2862_30" x1="311.84" y1="123.717" x2="253.579" y2="224.761" gradientUnits="userSpaceOnUse">
<stop/>
<stop offset="1" stop-opacity="0"/>
</linearGradient>
<radialGradient id="paint4_radial_2862_30" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(313.407 243.64) rotate(-75.7542) scale(203.632 223.902)">
<stop stop-color="#00BBBB"/>
<stop offset="0.712616" stop-color="#00DB65"/>
</radialGradient>
<linearGradient id="paint5_linear_2862_30" x1="308.586" y1="102.284" x2="383.487" y2="201.169" gradientUnits="userSpaceOnUse">
<stop/>
<stop offset="1" stop-opacity="0"/>
</linearGradient>
<radialGradient id="paint6_radial_2862_30" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(311.446 249.925) rotate(-20.3524) scale(174.776 163.096)">
<stop stop-color="#00B0BB"/>
<stop offset="1" stop-color="#00DB65"/>
</radialGradient>
<linearGradient id="paint7_linear_2862_30" x1="395.842" y1="169.781" x2="332.121" y2="263.82" gradientUnits="userSpaceOnUse">
<stop stop-color="#00B1BC"/>
<stop offset="1"/>
</linearGradient>
<linearGradient id="paint8_linear_2862_30" x1="395.842" y1="169.781" x2="370.99" y2="271.799" gradientUnits="userSpaceOnUse">
<stop/>
<stop offset="1" stop-opacity="0"/>
</linearGradient>
<radialGradient id="paint9_radial_2862_30" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(350 50) scale(398.125 182)">
<stop offset="0.348958" stop-color="#84FFD3"/>
<stop offset="0.880208" stop-color="#18E299" stop-opacity="0"/>
</radialGradient>
<radialGradient id="paint10_radial_2862_30" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(350 82) scale(398.125 182)">
<stop offset="0.348958" stop-color="#84FFD3"/>
<stop offset="0.880208" stop-color="#18E299" stop-opacity="0"/>
</radialGradient>
<radialGradient id="paint11_radial_2862_30" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(239 160) rotate(90) scale(182 182)">
<stop offset="0.348958" stop-color="#84FFD3"/>
<stop offset="0.880208" stop-color="#18E299" stop-opacity="0"/>
</radialGradient>
<radialGradient id="paint12_radial_2862_30" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(271 160) rotate(90) scale(182 182)">
<stop offset="0.348958" stop-color="#84FFD3"/>
<stop offset="0.880208" stop-color="#18E299" stop-opacity="0"/>
</radialGradient>
<radialGradient id="paint13_radial_2862_30" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(461 160) rotate(90) scale(182 182)">
<stop offset="0.348958" stop-color="#84FFD3"/>
<stop offset="0.880208" stop-color="#18E299" stop-opacity="0"/>
</radialGradient>
<radialGradient id="paint14_radial_2862_30" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(429 160) rotate(90) scale(182 182)">
<stop offset="0.348958" stop-color="#84FFD3"/>
<stop offset="0.880208" stop-color="#18E299" stop-opacity="0"/>
</radialGradient>
<radialGradient id="paint15_radial_2862_30" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(350 271) scale(398.125 182)">
<stop offset="0.348958" stop-color="#84FFD3"/>
<stop offset="0.880208" stop-color="#18E299" stop-opacity="0"/>
</radialGradient>
<radialGradient id="paint16_radial_2862_30" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(350 239) scale(398.125 182)">
<stop offset="0.348958" stop-color="#84FFD3"/>
<stop offset="0.880208" stop-color="#18E299" stop-opacity="0"/>
</radialGradient>
<linearGradient id="paint17_linear_2862_30" x1="0" y1="160" x2="700" y2="160" gradientUnits="userSpaceOnUse">
<stop stop-color="white" stop-opacity="0.1"/>
<stop offset="0.5" stop-color="white"/>
<stop offset="1" stop-color="white" stop-opacity="0.1"/>
</linearGradient>
<linearGradient id="paint18_linear_2862_30" x1="511" y1="-1" x2="189" y2="321" gradientUnits="userSpaceOnUse">
<stop stop-color="white" stop-opacity="0.1"/>
<stop offset="0.5" stop-color="white"/>
<stop offset="1" stop-color="white" stop-opacity="0.1"/>
</linearGradient>
<linearGradient id="paint19_linear_2862_30" x1="511" y1="321" x2="189" y2="-0.999997" gradientUnits="userSpaceOnUse">
<stop stop-color="white" stop-opacity="0.1"/>
<stop offset="0.5" stop-color="white"/>
<stop offset="1" stop-color="white" stop-opacity="0.1"/>
</linearGradient>
<clipPath id="clip0_2862_30">
<rect width="700" height="320" rx="16" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -0,0 +1,155 @@
<svg width="700" height="320" viewBox="0 0 700 320" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_2862_278)">
<rect width="700" height="320" rx="16" fill="url(#paint0_linear_2862_278)"/>
<path d="M311.889 247.3C283.097 247.215 258.226 231.466 246.292 201.629C234.357 171.793 238.02 134.523 253.414 101.112C282.206 101.197 307.077 116.945 319.011 146.782C330.946 176.619 327.283 213.888 311.889 247.3Z" fill="white"/>
<path d="M311.889 247.3C283.097 247.215 258.226 231.466 246.292 201.629C234.357 171.793 238.02 134.523 253.414 101.112C282.206 101.197 307.077 116.945 319.011 146.782C330.946 176.619 327.283 213.888 311.889 247.3Z" fill="url(#paint1_radial_2862_278)"/>
<path d="M311.889 247.3C283.097 247.215 258.226 231.466 246.292 201.629C234.357 171.793 238.02 134.523 253.414 101.112C282.206 101.197 307.077 116.945 319.011 146.782C330.946 176.619 327.283 213.888 311.889 247.3Z" fill="black" fill-opacity="0.5" style="mix-blend-mode:hard-light"/>
<path d="M311.889 247.3C283.097 247.215 258.226 231.466 246.292 201.629C234.357 171.793 238.02 134.523 253.414 101.112C282.206 101.197 307.077 116.945 319.011 146.782C330.946 176.619 327.283 213.888 311.889 247.3Z" fill="url(#paint2_linear_2862_278)" fill-opacity="0.5" style="mix-blend-mode:hard-light"/>
<path d="M311.72 247.034C283.108 246.887 258.409 231.208 246.538 201.531C234.656 171.825 238.271 134.702 253.583 101.377C282.195 101.524 306.894 117.203 318.765 146.88C330.647 176.586 327.031 213.709 311.72 247.034Z" stroke="url(#paint3_linear_2862_278)" stroke-opacity="0.05" stroke-width="0.530516"/>
<path d="M305.839 247.174C343.92 237.419 377.154 210.619 393.585 171.64C410.017 132.661 405.98 90.1988 386.347 56.1934C348.266 65.9477 315.032 92.7486 298.601 131.728C282.169 170.706 286.206 213.168 305.839 247.174Z" fill="white"/>
<path d="M305.839 247.174C343.92 237.419 377.154 210.619 393.585 171.64C410.017 132.661 405.98 90.1988 386.347 56.1934C348.266 65.9477 315.032 92.7486 298.601 131.728C282.169 170.706 286.206 213.168 305.839 247.174Z" fill="url(#paint4_radial_2862_278)"/>
<path d="M393.341 171.537C376.971 210.369 343.89 237.091 305.969 246.867C286.462 212.959 282.476 170.663 298.845 131.831C315.215 92.9978 348.295 66.2765 386.217 56.5004C405.724 90.4077 409.71 132.704 393.341 171.537Z" stroke="url(#paint5_linear_2862_278)" stroke-opacity="0.05" stroke-width="0.530516"/>
<path d="M305.686 246.995C329.75 266.114 361.965 272.832 393.671 262.129C425.376 251.426 449.499 225.691 461.03 194.556C436.967 175.437 404.751 168.719 373.046 179.422C341.34 190.125 317.217 215.86 305.686 246.995Z" fill="white"/>
<path d="M305.686 246.995C329.75 266.114 361.965 272.832 393.671 262.129C425.376 251.426 449.499 225.691 461.03 194.556C436.967 175.437 404.751 168.719 373.046 179.422C341.34 190.125 317.217 215.86 305.686 246.995Z" fill="url(#paint6_radial_2862_278)"/>
<path d="M305.686 246.995C329.75 266.114 361.965 272.832 393.671 262.129C425.376 251.426 449.499 225.691 461.03 194.556C436.967 175.437 404.751 168.719 373.046 179.422C341.34 190.125 317.217 215.86 305.686 246.995Z" fill="black" fill-opacity="0.2" style="mix-blend-mode:hard-light"/>
<path d="M305.686 246.995C329.75 266.114 361.965 272.832 393.671 262.129C425.376 251.426 449.499 225.691 461.03 194.556C436.967 175.437 404.751 168.719 373.046 179.422C341.34 190.125 317.217 215.86 305.686 246.995Z" fill="url(#paint7_linear_2862_278)" fill-opacity="0.5" style="mix-blend-mode:hard-light"/>
<path d="M393.586 261.878C362.035 272.529 329.981 265.88 306.002 246.907C317.535 215.919 341.571 190.327 373.13 179.673C404.682 169.023 436.736 175.671 460.715 194.644C449.182 225.632 425.146 251.224 393.586 261.878Z" stroke="url(#paint8_linear_2862_278)" stroke-opacity="0.05" stroke-width="0.530516"/>
<g opacity="0.8" filter="url(#filter0_f_2862_278)">
<circle cx="660" cy="-60" r="160" fill="#18E299" fill-opacity="0.4"/>
</g>
<g opacity="0.8" filter="url(#filter1_f_2862_278)">
<circle cx="20" cy="213" r="160" fill="#18E299" fill-opacity="0.33"/>
</g>
<g opacity="0.8" filter="url(#filter2_f_2862_278)">
<circle cx="660" cy="480" r="160" fill="#18E299" fill-opacity="0.52"/>
</g>
<g opacity="0.8" filter="url(#filter3_f_2862_278)">
<circle cx="20" cy="413" r="160" fill="#18E299" fill-opacity="0.22"/>
</g>
<g style="mix-blend-mode:overlay" opacity="0.1">
<path d="M0 50H700" stroke="black" stroke-dasharray="4 4"/>
</g>
<g style="mix-blend-mode:overlay" opacity="0.1">
<path d="M0 82H700" stroke="black" stroke-dasharray="4 4"/>
</g>
<g style="mix-blend-mode:overlay" opacity="0.1">
<path d="M239 0L239 320" stroke="black" stroke-dasharray="4 4"/>
</g>
<g style="mix-blend-mode:overlay" opacity="0.1">
<path d="M271 0L271 320" stroke="black" stroke-dasharray="4 4"/>
</g>
<g style="mix-blend-mode:overlay" opacity="0.1">
<path d="M461 0L461 320" stroke="black" stroke-dasharray="4 4"/>
</g>
<g style="mix-blend-mode:overlay" opacity="0.1">
<path d="M350 0L350 320" stroke="url(#paint9_linear_2862_278)"/>
</g>
<g style="mix-blend-mode:overlay" opacity="0.1">
<path d="M429 0L429 320" stroke="black" stroke-dasharray="4 4"/>
</g>
<g style="mix-blend-mode:overlay" opacity="0.1">
<path d="M0 271H700" stroke="black" stroke-dasharray="4 4"/>
</g>
<g style="mix-blend-mode:overlay" opacity="0.1">
<path d="M0 239H700" stroke="black" stroke-dasharray="4 4"/>
</g>
<g style="mix-blend-mode:overlay" opacity="0.1">
<path d="M0 160H700" stroke="url(#paint10_linear_2862_278)"/>
</g>
<g style="mix-blend-mode:overlay" opacity="0.1">
<path d="M511 -1L189 321" stroke="url(#paint11_linear_2862_278)"/>
</g>
<g style="mix-blend-mode:overlay" opacity="0.1">
<path d="M511 321L189 -1" stroke="url(#paint12_linear_2862_278)"/>
</g>
<g style="mix-blend-mode:overlay" opacity="0.05">
<circle cx="350" cy="160" r="111" stroke="black"/>
</g>
<g style="mix-blend-mode:overlay" opacity="0.05">
<circle cx="350" cy="160" r="79" stroke="black"/>
</g>
</g>
<defs>
<filter id="filter0_f_2862_278" x="260" y="-460" width="800" height="800" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="120" result="effect1_foregroundBlur_2862_278"/>
</filter>
<filter id="filter1_f_2862_278" x="-380" y="-187" width="800" height="800" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="120" result="effect1_foregroundBlur_2862_278"/>
</filter>
<filter id="filter2_f_2862_278" x="260" y="80" width="800" height="800" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="120" result="effect1_foregroundBlur_2862_278"/>
</filter>
<filter id="filter3_f_2862_278" x="-380" y="13" width="800" height="800" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="120" result="effect1_foregroundBlur_2862_278"/>
</filter>
<linearGradient id="paint0_linear_2862_278" x1="1.04308e-05" y1="320" x2="710.784" y2="26.0793" gradientUnits="userSpaceOnUse">
<stop stop-color="#18E299" stop-opacity="0.09"/>
<stop offset="0.729167" stop-color="#0D9373" stop-opacity="0.08"/>
</linearGradient>
<radialGradient id="paint1_radial_2862_278" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(208.697 189.703) rotate(-10.029) scale(169.097 167.466)">
<stop stop-color="#00B0BB"/>
<stop offset="1" stop-color="#00DB65"/>
</radialGradient>
<linearGradient id="paint2_linear_2862_278" x1="306.587" y1="93.5598" x2="252.341" y2="224.228" gradientUnits="userSpaceOnUse">
<stop stop-color="#18E299"/>
<stop offset="1"/>
</linearGradient>
<linearGradient id="paint3_linear_2862_278" x1="311.84" y1="123.717" x2="253.579" y2="224.761" gradientUnits="userSpaceOnUse">
<stop/>
<stop offset="1" stop-opacity="0"/>
</linearGradient>
<radialGradient id="paint4_radial_2862_278" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(313.407 243.64) rotate(-75.7542) scale(203.632 223.902)">
<stop stop-color="#00BBBB"/>
<stop offset="0.712616" stop-color="#00DB65"/>
</radialGradient>
<linearGradient id="paint5_linear_2862_278" x1="308.586" y1="102.284" x2="383.487" y2="201.169" gradientUnits="userSpaceOnUse">
<stop/>
<stop offset="1" stop-opacity="0"/>
</linearGradient>
<radialGradient id="paint6_radial_2862_278" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(311.447 249.925) rotate(-20.3524) scale(174.776 163.096)">
<stop stop-color="#00B0BB"/>
<stop offset="1" stop-color="#00DB65"/>
</radialGradient>
<linearGradient id="paint7_linear_2862_278" x1="395.843" y1="169.781" x2="332.121" y2="263.82" gradientUnits="userSpaceOnUse">
<stop stop-color="#00B1BC"/>
<stop offset="1"/>
</linearGradient>
<linearGradient id="paint8_linear_2862_278" x1="395.843" y1="169.781" x2="370.991" y2="271.799" gradientUnits="userSpaceOnUse">
<stop/>
<stop offset="1" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint9_linear_2862_278" x1="350" y1="0" x2="350" y2="320" gradientUnits="userSpaceOnUse">
<stop stop-opacity="0"/>
<stop offset="0.0001" stop-opacity="0.3"/>
<stop offset="0.333333"/>
<stop offset="0.666667"/>
<stop offset="1" stop-opacity="0.3"/>
</linearGradient>
<linearGradient id="paint10_linear_2862_278" x1="0" y1="160" x2="700" y2="160" gradientUnits="userSpaceOnUse">
<stop stop-opacity="0.1"/>
<stop offset="0.5"/>
<stop offset="1" stop-opacity="0.1"/>
</linearGradient>
<linearGradient id="paint11_linear_2862_278" x1="511" y1="-1" x2="189" y2="321" gradientUnits="userSpaceOnUse">
<stop stop-opacity="0.1"/>
<stop offset="0.5"/>
<stop offset="1" stop-opacity="0.1"/>
</linearGradient>
<linearGradient id="paint12_linear_2862_278" x1="511" y1="321" x2="189" y2="-0.999997" gradientUnits="userSpaceOnUse">
<stop stop-opacity="0.1"/>
<stop offset="0.5"/>
<stop offset="1" stop-opacity="0.1"/>
</linearGradient>
<clipPath id="clip0_2862_278">
<rect width="700" height="320" rx="16" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -0,0 +1,53 @@
---
title: Introduction
description: 'Welcome to crosspublic documentation'
---
<div className="w-full flex justify-center rounded-full">
<img
className="block dark:hidden"
src="/logo/light.png"
alt="Hero Light"
/>
<img
className="hidden dark:block"
src="/logo/dark.png"
alt="Hero Dark"
/>
</div>
## What is crosspublic?
crosspublic is a tool to convert private communication into public knowledge.<br />
Most of your users are not on your communication channels, and they are not aware of the questions that have already been asked and answered.<br />
## Architecture
crosspublic is an [NX monorepo](https://nx.dev) that contains the backend, control panel, discord bot, marketing website and the docs.<br /><br />
Unlike other NX project, this project has one `.env` file that is shared between all the apps.<br />
It makes it easier to develop and deploy the project.<br /><br />
When deploying to websites like [Railway](https://railway.app) or [Heroku](https://heroku.com), you can use a shared environment variables for all the apps.<br />
**It has four main components:**
- `Panel` - NextJS control panel to manage your FAQ, it also contains the public website of every user FAQ page.
- `Tenants` - NextJS website that contains the FAQ of every end-user.
- `Backend` - NestJS that operate as the server for both the bots and the website.
- `Discord` - Discord bot that listen to messages, and send them to the backend.
In the future there will also be a bot of Slack, Intercom, Telegram, and more.
<CardGroup cols={2}>
<Card
title="Quick start"
icon="pen-to-square"
href="/quickstart"
>
Learn how to install the project and start using it
</Card>
<Card
title="How it works"
icon="screwdriver-wrench"
href="/howitworks"
>
Learn the architecture of the project
</Card>
</CardGroup>

BIN
apps/docs/logo/dark.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
apps/docs/logo/light.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

85
apps/docs/mint.js Normal file
View File

@ -0,0 +1,85 @@
exports.default = {
"$schema": "https://mintlify.com/schema.json",
"name": "Starter Kit",
"logo": {
"dark": "/logo/dark.png",
"light": "/logo/light.png"
},
"favicon": "/favicon.png",
"colors": {
"primary": "#9333EA",
"light": "#9333EA",
"dark": "#9333EA",
"anchors": {
"from": "#9333EA",
"to": "#9333EA"
}
},
"openapi": "https://api.crosspublic.com/docs-json",
"api": {
"baseUrl": "https://api.crosspublic.com",
},
"topbarLinks": [
{
"name": "Support",
"url": "mailto:nevo@crosspublic.com"
},
{
"name": "Cloud",
"url": "https://panel.crosspublic.com"
}
],
"modeToggle": {
"default": "light"
},
"topbarCtaButton": {
"type": "github",
"url": "https://github.com/github-20k/crosspublic"
},
"tabs": [
{
"name": "Public API Reference",
"url": "public-api-reference"
},
{
"name": "Internal API Reference (Self-Hosted)",
"url": "api-reference"
}
],
"anchors": [
{
"name": "Documentation",
"icon": "book-open-cover",
"url": "https://docs.crosspublic.com"
},
{
"name": "Community",
"icon": "discord",
"url": "https://discord.gitroom.com"
},
{
"name": "Blog",
"icon": "newspaper",
"url": "https://gitroom.com/blog"
}
],
"navigation": [
{
"group": "Get Started",
"pages": ["introduction", "quickstart", "howitworks"]
},
{
"group": "API Documentation",
"pages": ["api-reference/introduction"]
},
{
"group": "Public api Documentation",
"pages": ["public-api-reference/introduction"]
}
],
"footerSocials": {
"twitter": "https://twitter.com/nevodavid",
"github": "https://github.com/github-20k/crosspublic",
"linkedin": "https://www.linkedin.com/nevodavid"
}
};

191
apps/docs/mint.json Normal file
View File

@ -0,0 +1,191 @@
{
"$schema": "https://mintlify.com/schema.json",
"name": "Starter Kit",
"logo": {
"dark": "/logo/dark.png",
"light": "/logo/light.png"
},
"favicon": "/favicon.png",
"colors": {
"primary": "#9333EA",
"light": "#9333EA",
"dark": "#9333EA",
"anchors": {
"from": "#9333EA",
"to": "#9333EA"
}
},
"openapi": "https://api.crosspublic.com/docs-json",
"api": {
"baseUrl": "https://api.crosspublic.com"
},
"topbarLinks": [
{
"name": "Support",
"url": "mailto:nevo@crosspublic.com"
}
],
"modeToggle": {
"default": "light"
},
"topbarCtaButton": {
"type": "github",
"url": "https://github.com/github-20k/crosspublic"
},
"tabs": [
{
"name": "Public API Reference",
"url": "public-api-reference"
},
{
"name": "Internal API Reference (Self-Hosted)",
"url": "api-reference"
}
],
"anchors": [
{
"name": "Documentation",
"icon": "book-open-cover",
"url": "https://docs.crosspublic.com"
},
{
"name": "Community",
"icon": "discord",
"url": "https://discord.gitroom.com"
},
{
"name": "Blog",
"icon": "newspaper",
"url": "https://gitroom.com/blog"
}
],
"navigation": [
{
"group": "Get Started",
"pages": [
"introduction",
"quickstart",
"howitworks"
]
},
{
"group": "API Documentation",
"pages": [
"api-reference/introduction"
]
},
{
"group": "Public api Documentation",
"pages": [
"public-api-reference/introduction"
]
},
{
"group": "Authentications",
"pages": [
"api-reference/custom/authentications/check-org-and-user",
"api-reference/custom/authentications/register-to-crosspublic",
"api-reference/custom/authentications/login-to-crosspublic"
]
},
{
"group": "Stripe",
"pages": [
"api-reference/custom/stripe/post-stripe"
]
},
{
"group": "Public",
"pages": [
"public-api-reference/custom/public/categories-list",
"public-api-reference/custom/public/faq-list",
"public-api-reference/custom/public/retrieve-faq",
"public-api-reference/custom/public/get-organization-style"
]
},
{
"group": "Users",
"pages": [
"api-reference/custom/users/get-userssign-in",
"api-reference/custom/users/get-usersself",
"api-reference/custom/users/post-usersrevalidate"
]
},
{
"group": "Faq",
"pages": [
"api-reference/custom/faq/get-faq",
"api-reference/custom/faq/put-faq",
"api-reference/custom/faq/delete-faq",
"api-reference/custom/faq/post-faqorder",
"api-reference/custom/faq/put-faq-category",
"api-reference/custom/faq/post-faq",
"api-reference/custom/faq/post-faqjob",
"api-reference/custom/faq/post-faqjobsquestions",
"api-reference/custom/faq/post-faqanswers",
"api-reference/custom/faq/get-faqjobs"
]
},
{
"group": "Categories",
"pages": [
"api-reference/custom/categories/post-categoriesorder",
"api-reference/custom/categories/get-categories",
"api-reference/custom/categories/post-categories",
"api-reference/custom/categories/get-categoriesfaq",
"api-reference/custom/categories/get-categories-1",
"api-reference/custom/categories/put-categories",
"api-reference/custom/categories/delete-categories"
]
},
{
"group": "Settings",
"pages": [
"api-reference/custom/settings/get-settings",
"api-reference/custom/settings/post-settingscheck-subdomain",
"api-reference/custom/settings/get-settingscheck-domain",
"api-reference/custom/settings/delete-settingsdelete-domain",
"api-reference/custom/settings/post-settingsdomain",
"api-reference/custom/settings/post-settingssubdomain"
]
},
{
"group": "Billings",
"pages": [
"api-reference/custom/billings/get-billingcheck",
"api-reference/custom/billings/subscribe-to-a-plan",
"api-reference/custom/billings/post-billingmodify",
"api-reference/custom/billings/get-billing",
"api-reference/custom/billings/post-billingcancel"
]
},
{
"group": "Styles",
"pages": [
"api-reference/custom/styles/get-styles",
"api-reference/custom/styles/put-styles"
]
},
{
"group": "Integrations",
"pages": [
"api-reference/custom/integrations/get-integrations",
"api-reference/custom/integrations/post-integrations",
"api-reference/custom/integrations/get-integrations-add",
"api-reference/custom/integrations/delete-integrations"
]
},
{
"group": "Invitations",
"pages": [
"api-reference/custom/invitations/post-invite",
"api-reference/custom/invitations/post-inviteadd"
]
}
],
"footerSocials": {
"twitter": "https://twitter.com/nevodavid",
"github": "https://github.com/github-20k/crosspublic",
"linkedin": "https://www.linkedin.com/nevodavid"
}
}

1
apps/docs/openapi.json Normal file

File diff suppressed because one or more lines are too long

21
apps/docs/project.json Normal file
View File

@ -0,0 +1,21 @@
{
"name": "docs",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "apps/docs",
"projectType": "application",
"targets": {
"build": {
"command": "node apps/docs/test.js",
"options": {
"type": "module"
}
},
"serve": {
"command": "cd apps/docs && npx mintlify dev",
"options": {
"type": "module"
}
}
},
"tags": []
}

View File

@ -0,0 +1,3 @@
---
openapi: get /public/categories
---

View File

@ -0,0 +1,3 @@
---
openapi: get /public/categories/{slug}/faqs
---

View File

@ -0,0 +1,3 @@
---
openapi: get /public/styles
---

View File

@ -0,0 +1,3 @@
---
openapi: get /public/faq/{slug}
---

View File

@ -0,0 +1,21 @@
---
title: 'Introduction'
description: 'How to use the public API'
---
<Note>
The Public API is only available for the PRO plan and the self-hosted plan.
</Note>
## Why public API
if you want to build a custom implementation of the FAQ page, or you want to use it in different service, you can use the public API to get the data.
## How to use the public API
There are two main options to use the Public API:
1. For the **PRO plan**, go to [Settings](https://app.crosspublic.com/dashboard/settings) and copy the API key.<br />
For every request to the `/public` API URL send a header called `apikey` with the value of the API key.
2. For the **self-hosted plan**, you can also pass `serverkey` to bypass package check.<br />
The `serverkey` is the value of `BACKEND_TOKEN_PROTECTOR` in the `.env` file.
Check the endpoints on the left for API reference.

109
apps/docs/quickstart.mdx Normal file
View File

@ -0,0 +1,109 @@
---
title: 'Quickstart'
---
## Prerequisites
To run the project you need to have multiple things:
- Node.js
- PostgresSQL
- Discord client id and token
- Vercel Blob Store (optional)
- Vercel Project ID, Team ID and Token (if you want to have multi-tenancy)
- Stripe account (optional)
- OpenAI API key (optional)
- Algolia account (optional)
### NodeJS
A complete guide of how to install NodeJS can be found [here](https://nodejs.org/en/download/).
### PostgresSQL
Make sure you have PostgreSQL installed on your machine.<br />
If you don't, you can install [Docker](https://www.docker.com/products/docker-desktop) and run:
```bash
docker run -e POSTGRES_USER=root -e POSTGRES_PASSWORD=your_password --name postgres -p 5432:5432 -d postgres
```
### Discord client id and token
Head over to the [Discord Developer Portal](https://discord.com/developers/applications) and create a new application.<br />
After that, go to the bot tab and create a new bot.<br />
Save the token and client id for later.
### Vercel Blob Store
Head over to the [Vercel Blob Store](https://vercel.com/dashboard/stores) and create a new database.<br />
Choose Blob (currently in beta) and save the token for later.
### Vercel Project ID, Team ID and Token
1. Create a new Vercel empty project.
2. Head over to Vercel teams settings, scroll down to "Team ID" copy the id and save it for later.
3. Head over to Vercel project settings, scroll down to "Project ID" copy the id and save it for later.
4. Head over to Vercel account settings, scroll down to "Tokens" and create a new token, copy the token and save it for later.
### Stripe account
If you don't provide the Stripe `PAYMENT_PUBLIC_KEY`, you will be automatically granted all the features without limitations.
1. Register a new account on [Stripe](https://stripe.com/).<br />
2. Head over to the [Stripe API keys](https://dashboard.stripe.com/test/apikeys) and copy the Publishable and Secret key, save it for later.
3. Head over to the [Stripe Webhooks](https://dashboard.stripe.com/test/webhooks) and create a new local webhook.<br />
Copy the token and save it for later.
### OpenAI API key
Create a new [OpenAI](https://platform.openai.com) account, head over to the [API keys](https://platform.openai.com/api-keys), copy the key, and save it for later.
### Algolia account
Create a new [Algolia](https://www.algolia.com/) account, head over to the [API keys](https://www.algolia.com/api-keys), copy the APP ID, Search Key, and Admin API Key, and save it for later.
## Installation
### Clone the repository
```bash
git clone https://github.com/github-20k/crosspublic
```
### Copy environment variables
Copy the `.env.example` file to `.env` and fill in the values
```bash
DISCORD_CLIENT=<copied discord client key>
DISCORD_TOKEN=<copied discord token key>
DATABASE_URL=postgres://root:your_password@localhost:5432/crosspublic
BACKEND_URL=http://localhost:3000
NEXT_PUBLIC_BACKEND_URL=http://localhost:3000
BACKEND_TOKEN_PROTECTOR=<anything you want, better be long>
OPENAI_API_KEY=<open_ai key>
JWT_SECRET=<anything you want, better be long>
FRONTEND_URL=http://localhost:4200
BLOB_READ_WRITE_TOKEN=<copied blob store key>
PROJECT_ID_VERCEL=<copied vercel project id>
TEAM_ID_VERCEL=<copied vercel team id>
PAYMENT_PUBLIC_KEY=<copied stripe publishable key>
PAYMENT_SECRET_KEY=<copied stripe secret key>
PAYMENT_SIGNING_SECRET=<copied stripe webhook token>
MARKETING_WEBSITE_URL=http://localhost:4201
DOCS_URL=https://localhost:3000
NEXT_PUBLIC_ALGOLIA_APP_ID=<algolia id>
NEXT_PUBLIC_ALGOLIA_SEARCH_KEY=<algolia search key>
ALGOLIA_ADMIN_API_KEY=<algolia admin api>
```
### Install the dependencies
```bash
npm install
```
### Generate the prisma client and run the migrations
```bash
npm run update-prisma
```
### Run the project
```bash
npm run dev
```

32
apps/docs/test.js Normal file
View File

@ -0,0 +1,32 @@
const process = require('process')
process.chdir(__dirname);
const {writeFileSync, renameSync, rmdirSync, mkdirSync} = require("fs");
const { config } = require('dotenv');
const prod = require('./mint.js').default;
config();
(async () => {
const {generateOpenApiPages} = await import("@mintlify/scraping");
try {
rmdirSync('./api-reference/custom', { recursive: true });
rmdirSync('./public-api-reference/custom', { recursive: true });
await new Promise((resolve) => setTimeout(resolve, 2000));
} catch (e) {
}
await generateOpenApiPages(process.env.BACKEND_URL + '/docs-json', true, 'api-reference/custom');
const generate = await generateOpenApiPages(process.env.BACKEND_URL + '/docs-json');
await new Promise((resolve) => setTimeout(resolve, 3000));
mkdirSync('./public-api-reference/custom', { recursive: true });
renameSync('./api-reference/custom/public', './public-api-reference/custom/public/');
prod.navigation.push(...generate.nav.map((item) => ({
...item,
pages: item.pages.map((page) => (page.indexOf('public') > -1 ? 'public-api-reference/custom/' : 'api-reference/custom/') + page)
})));
writeFileSync('./mint.json', JSON.stringify(prod, null, 2));
const text = await (await fetch(process.env.BACKEND_URL + '/docs-json')).text();
writeFileSync('./openapi.json', text);
})();

View File

@ -0,0 +1,13 @@
import {DocumentBuilder, SwaggerModule} from "@nestjs/swagger";
import {INestApplication} from "@nestjs/common";
export const loadSwagger = (app: INestApplication) => {
const config = new DocumentBuilder()
.setTitle('crosspublic Swagger file')
.setDescription('API description')
.setVersion('1.0')
.build();
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('docs', app, document);
}

View File

@ -1,5 +1,6 @@
import { Global, Module } from '@nestjs/common';
import {MulterModule} from "@nestjs/platform-express";
// @ts-ignore
import {diskStorage} from "multer";
import {mkdirSync} from 'fs';
import {extname} from 'path';

111
package-lock.json generated
View File

@ -24,6 +24,7 @@
"@nestjs/platform-express": "^10.0.2",
"@nestjs/schedule": "^4.0.0",
"@nestjs/serve-static": "^4.0.1",
"@nestjs/swagger": "^7.3.0",
"@prisma/client": "^5.8.1",
"@swc/helpers": "~0.5.2",
"@sweetalert2/theme-dark": "^5.0.16",
@ -33,6 +34,7 @@
"@types/lodash": "^4.14.202",
"@types/md5": "^2.3.5",
"@types/mime-types": "^2.1.4",
"@types/multer": "^1.4.11",
"@types/remove-markdown": "^0.3.4",
"@types/stripe": "^8.0.417",
"@uidotdev/usehooks": "^2.4.1",
@ -108,7 +110,6 @@
"@testing-library/react": "14.0.0",
"@types/cookie-parser": "^1.4.6",
"@types/jest": "^29.4.0",
"@types/multer": "^1.4.11",
"@types/node": "18.16.9",
"@types/react": "18.2.33",
"@types/react-dom": "18.2.14",
@ -4429,6 +4430,11 @@
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
},
"node_modules/@microsoft/tsdoc": {
"version": "0.14.2",
"resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.14.2.tgz",
"integrity": "sha512-9b8mPpKrfeGRuhFH5iO1iwCLeIIsV6+H1sRfxbkoGXIyQE2BTsPd9zqSqQJ+pv5sJ/hT5M1zvOFL02MnEezFug=="
},
"node_modules/@mole-inc/bin-wrapper": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/@mole-inc/bin-wrapper/-/bin-wrapper-8.0.1.tgz",
@ -4596,6 +4602,25 @@
}
}
},
"node_modules/@nestjs/mapped-types": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/@nestjs/mapped-types/-/mapped-types-2.0.5.tgz",
"integrity": "sha512-bSJv4pd6EY99NX9CjBIyn4TVDoSit82DUZlL4I3bqNfy5Gt+gXTa86i3I/i0iIV9P4hntcGM5GyO+FhZAhxtyg==",
"peerDependencies": {
"@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0",
"class-transformer": "^0.4.0 || ^0.5.0",
"class-validator": "^0.13.0 || ^0.14.0",
"reflect-metadata": "^0.1.12 || ^0.2.0"
},
"peerDependenciesMeta": {
"class-transformer": {
"optional": true
},
"class-validator": {
"optional": true
}
}
},
"node_modules/@nestjs/microservices": {
"version": "10.3.3",
"resolved": "https://registry.npmjs.org/@nestjs/microservices/-/microservices-10.3.3.tgz",
@ -4750,6 +4775,38 @@
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.2.5.tgz",
"integrity": "sha512-l6qtdDPIkmAmzEO6egquYDfqQGPMRNGjYtrU13HAXb3YSRrt7HSb1sJY0pKp6o2bAa86tSB6iwaW2JbthPKr7Q=="
},
"node_modules/@nestjs/swagger": {
"version": "7.3.0",
"resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-7.3.0.tgz",
"integrity": "sha512-zLkfKZ+ioYsIZ3dfv7Bj8YHnZMNAGWFUmx2ZDuLp/fBE4P8BSjB7hldzDueFXsmwaPL90v7lgyd82P+s7KME1Q==",
"dependencies": {
"@microsoft/tsdoc": "^0.14.2",
"@nestjs/mapped-types": "2.0.5",
"js-yaml": "4.1.0",
"lodash": "4.17.21",
"path-to-regexp": "3.2.0",
"swagger-ui-dist": "5.11.2"
},
"peerDependencies": {
"@fastify/static": "^6.0.0 || ^7.0.0",
"@nestjs/common": "^9.0.0 || ^10.0.0",
"@nestjs/core": "^9.0.0 || ^10.0.0",
"class-transformer": "*",
"class-validator": "*",
"reflect-metadata": "^0.1.12 || ^0.2.0"
},
"peerDependenciesMeta": {
"@fastify/static": {
"optional": true
},
"class-transformer": {
"optional": true
},
"class-validator": {
"optional": true
}
}
},
"node_modules/@nestjs/testing": {
"version": "10.3.3",
"resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-10.3.3.tgz",
@ -7987,7 +8044,6 @@
"version": "1.19.5",
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz",
"integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==",
"dev": true,
"dependencies": {
"@types/connect": "*",
"@types/node": "*"
@ -8018,7 +8074,6 @@
"version": "3.4.38",
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz",
"integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==",
"dev": true,
"dependencies": {
"@types/node": "*"
}
@ -8087,7 +8142,6 @@
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz",
"integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==",
"dev": true,
"dependencies": {
"@types/body-parser": "*",
"@types/express-serve-static-core": "^4.17.33",
@ -8099,7 +8153,6 @@
"version": "4.17.43",
"resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.43.tgz",
"integrity": "sha512-oaYtiBirUOPQGSWNGPWnzyAFJ0BP3cwvN4oWZQY+zUBwpVIGsKUkpBpSztp74drYcjavs7SKFZ4DX1V2QeN8rg==",
"dev": true,
"dependencies": {
"@types/node": "*",
"@types/qs": "*",
@ -8133,8 +8186,7 @@
"node_modules/@types/http-errors": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz",
"integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==",
"dev": true
"integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA=="
},
"node_modules/@types/http-proxy": {
"version": "1.17.14",
@ -8289,8 +8341,7 @@
"node_modules/@types/mime": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz",
"integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==",
"dev": true
"integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w=="
},
"node_modules/@types/mime-types": {
"version": "2.1.4",
@ -8306,7 +8357,6 @@
"version": "1.4.11",
"resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.11.tgz",
"integrity": "sha512-svK240gr6LVWvv3YGyhLlA+6LRRWA4mnGIU7RcNmgjBYFl6665wcXrRfxGp5tEPVHUNm5FMcmq7too9bxCwX/w==",
"dev": true,
"dependencies": {
"@types/express": "*"
}
@ -8343,14 +8393,12 @@
"node_modules/@types/qs": {
"version": "6.9.12",
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.12.tgz",
"integrity": "sha512-bZcOkJ6uWrL0Qb2NAWKa7TBU+mJHPzhx9jjLL1KHF+XpzEcR7EXHvjbHlGtR/IsP1vyPrehuS6XqkmaePy//mg==",
"dev": true
"integrity": "sha512-bZcOkJ6uWrL0Qb2NAWKa7TBU+mJHPzhx9jjLL1KHF+XpzEcR7EXHvjbHlGtR/IsP1vyPrehuS6XqkmaePy//mg=="
},
"node_modules/@types/range-parser": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz",
"integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==",
"dev": true
"integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ=="
},
"node_modules/@types/react": {
"version": "18.2.33",
@ -8406,7 +8454,6 @@
"version": "0.17.4",
"resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz",
"integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==",
"dev": true,
"dependencies": {
"@types/mime": "^1",
"@types/node": "*"
@ -8425,7 +8472,6 @@
"version": "1.15.5",
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.5.tgz",
"integrity": "sha512-PDRk21MnK70hja/YF8AHfC7yIsiQHn1rcXx7ijCFBX/k+XQJhQT/gw3xekXKJvx+5SXaMMS8oqQy09Mzvz2TuQ==",
"dev": true,
"dependencies": {
"@types/http-errors": "*",
"@types/mime": "*",
@ -9677,8 +9723,7 @@
"node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"dev": true
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
},
"node_modules/aria-hidden": {
"version": "1.2.3",
@ -11036,9 +11081,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001596",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001596.tgz",
"integrity": "sha512-zpkZ+kEr6We7w63ORkoJ2pOfBwBkY/bJrG/UZ90qNb45Isblu8wzDgevEOrRL1r9dWayHjYiiyCMEXPn4DweGQ==",
"version": "1.0.30001597",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001597.tgz",
"integrity": "sha512-7LjJvmQU6Sj7bL0j5b5WY/3n7utXUJvAe1lxhsHDbLmwX9mdL86Yjtr+5SRCyf8qME4M7pU2hswj0FpyBVCv9w==",
"funding": [
{
"type": "opencollective",
@ -15654,9 +15699,9 @@
"integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ=="
},
"node_modules/hasown": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz",
"integrity": "sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==",
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"dependencies": {
"function-bind": "^1.1.2"
},
@ -19593,7 +19638,6 @@
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
"dev": true,
"dependencies": {
"argparse": "^2.0.1"
},
@ -26478,6 +26522,11 @@
"url": "https://opencollective.com/svgo"
}
},
"node_modules/swagger-ui-dist": {
"version": "5.11.2",
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.11.2.tgz",
"integrity": "sha512-jQG0cRgJNMZ7aCoiFofnoojeSaa/+KgWaDlfgs8QN+BXoGMpxeMVY5OEnjq4OlNvF3yjftO8c9GRAgcHlO+u7A=="
},
"node_modules/sweetalert2": {
"version": "11.10.6",
"resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.10.6.tgz",
@ -28840,16 +28889,16 @@
}
},
"node_modules/which-typed-array": {
"version": "1.1.14",
"resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.14.tgz",
"integrity": "sha512-VnXFiIW8yNn9kIHN88xvZ4yOWchftKDsRJ8fEPacX/wl1lOvBrhsJ/OeJCXq7B0AaijRuqgzSKalJoPk+D8MPg==",
"version": "1.1.15",
"resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz",
"integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==",
"dev": true,
"dependencies": {
"available-typed-arrays": "^1.0.6",
"call-bind": "^1.0.5",
"available-typed-arrays": "^1.0.7",
"call-bind": "^1.0.7",
"for-each": "^0.3.3",
"gopd": "^1.0.1",
"has-tostringtag": "^1.0.1"
"has-tostringtag": "^1.0.2"
},
"engines": {
"node": ">= 0.4"

View File

@ -4,6 +4,7 @@
"license": "MIT",
"scripts": {
"dev": "concurrently \"stripe listen --forward-to localhost:3000/stripe\" \"nx run-many --target=serve --projects=frontend,backend,workers --parallel=4\"",
"docs": "nx run-many --target=serve --projects=backend,docs --parallel=2",
"workers": "nx run workers:serve:development",
"cron": "nx run cron:serve:development",
"command": "nx run commands:build && nx run commands:command",
@ -24,6 +25,7 @@
"@nestjs/platform-express": "^10.0.2",
"@nestjs/schedule": "^4.0.0",
"@nestjs/serve-static": "^4.0.1",
"@nestjs/swagger": "^7.3.0",
"@prisma/client": "^5.8.1",
"@swc/helpers": "~0.5.2",
"@sweetalert2/theme-dark": "^5.0.16",
@ -33,6 +35,7 @@
"@types/lodash": "^4.14.202",
"@types/md5": "^2.3.5",
"@types/mime-types": "^2.1.4",
"@types/multer": "^1.4.11",
"@types/remove-markdown": "^0.3.4",
"@types/stripe": "^8.0.417",
"@uidotdev/usehooks": "^2.4.1",
@ -108,7 +111,6 @@
"@testing-library/react": "14.0.0",
"@types/cookie-parser": "^1.4.6",
"@types/jest": "^29.4.0",
"@types/multer": "^1.4.11",
"@types/node": "18.16.9",
"@types/react": "18.2.33",
"@types/react-dom": "18.2.14",

View File

@ -11,6 +11,7 @@
"noPropertyAccessFromIndexSignature": false,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"importHelpers": true,
"target": "es2015",
"module": "esnext",