├── .gitignore
├── .prettierrc.json
├── LICENSE
├── README.md
├── package.json
├── src
├── config
│ └── db.ts
├── controllers
│ ├── auth.ts
│ ├── channel.ts
│ ├── conversations.ts
│ ├── message.ts
│ ├── organisation.ts
│ ├── teammates.ts
│ └── thread.ts
├── helpers
│ ├── createTodaysFirstMessage.ts
│ ├── sendEmail.ts
│ ├── successResponse.ts
│ └── updateConversationStatus.ts
├── html
│ ├── confirmation-code-email.ts
│ └── join-teammates-email.ts
├── index.ts
├── middleware
│ ├── errorResponse.ts
│ └── protect.ts
├── models
│ ├── channel.ts
│ ├── conversation.ts
│ ├── message.ts
│ ├── organisation.ts
│ ├── thread.ts
│ └── user.ts
└── routes
│ ├── auth.ts
│ ├── channel.ts
│ ├── conversations.ts
│ ├── message.ts
│ ├── organisation.ts
│ ├── teammates.ts
│ └── thread.ts
├── tsconfig.json
├── tslint.json
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | node_modules/
6 | /.pnp
7 | .pnp.js
8 |
9 | # testing
10 | /coverage
11 |
12 | # production
13 | /build
14 |
15 | # misc
16 | .DS_Store
17 | .env
18 | .env.local
19 | .env.development.local
20 | .env.test.local
21 | .env.production.local
22 | __DateNight
23 | old-teammates.text
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "semi": false
4 | }
5 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Adeola Adeoti
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Slack Clone Api Repository
2 | - Client repository https://github.com/adeolaadeoti/slack-clone-client
3 |
4 | ## Table of Contents
5 |
6 | - [Introduction](#introduction)
7 | - [Features](#features)
8 | - [Getting Started](#getting-started)
9 | - [Installation](#installation)
10 | - [Usage](#usage)
11 | - [User Registration](#user-registration)
12 | - [Creating Channels](#creating-channels)
13 | - [Sending Messages](#sending-messages)
14 | - [Message Replies (Threads)](#message-replies-threads)
15 | - [Huddle with Other Users](#huddle-with-other-users)
16 | - [Contributing](#contributing)
17 | - [License](#license)
18 |
19 | ## Introduction
20 |
21 | Slack clone is a powerful team collaboration platform that allows you to communicate and collaborate with your team members in real-time. Whether you're working remotely or in the same office, our application provides a seamless and efficient way to stay connected.
22 |
23 | 
24 |
25 | ## Features
26 |
27 | - Real-time chat and messaging
28 | - Channel-based communication
29 | - Direct messaging between users
30 | - File and media sharing
31 | - Customizable notifications
32 | - **Message Replies (Threads)**
33 | - Start threads to reply to specific messages in a conversation.
34 | - Keep discussions organized and focused.
35 | - **Huddle with Other Users**
36 | - Create private huddles for group discussions.
37 | - Collaborate with select team members in a secure environment.
38 |
39 | ## Getting Started
40 |
41 | ### Installation
42 |
43 | 1. Clone the repository:
44 |
45 | ```bash
46 | git clone https://github.com/adeolaadeoti/slack-clone-api.git
47 | ```
48 |
49 | 2. Change to the project directory:
50 |
51 | ```bash
52 | cd slack-clone-api
53 | ```
54 |
55 | 3. Install dependencies:
56 |
57 | ```bash
58 | yarn
59 | ```
60 |
61 | 4. Set up environment variables:
62 |
63 | Create a `.env` file in the root directory of the project and add the necessary environment variables, including your database connection details and any API keys.
64 |
65 | ```plaintext
66 | MONGODB_URI=
67 | JWT_SECRET=
68 | JWT_EXPIRE=30d
69 | SMTP_USERNAME=
70 | SMTP_PASSWORD=
71 | MONGODB_DB_NAME=slack-clone-dev
72 | NODE_ENV=development
73 | GOOGLE_CLIENT_ID=
74 | GOOGLE_CLIENT_SECRET=
75 | CLIENT_URL=http://localhost:3000
76 | API_URL=http://localhost:8080/api/v1
77 | ```
78 |
79 | 5. Start the application:
80 |
81 | ```bash
82 | yarn start
83 | ```
84 |
85 | 6. Access the application at `http://localhost:8080`.
86 |
87 | ## Usage
88 |
89 | ### User Registration
90 |
91 | 1. Visit the application's URL.
92 |
93 | 2. Click on the "Sign Up" or "Register" button to create a new user account.
94 |
95 | 3. Fill out the registration form with your details.
96 |
97 | 4. Once registered, you can log in with your credentials.
98 |
99 | ### Creating Channels
100 |
101 | 1. After logging in, you can create a new channel by clicking on the "Create Channel" button.
102 |
103 | 2. Choose a name and description for your channel.
104 |
105 | 3. Invite team members to join the channel.
106 |
107 | ### Sending Messages
108 |
109 | 1. To send a message in a channel, click on the channel's name in the left sidebar.
110 |
111 | 2. Type your message in the input field at the bottom of the chat window and press Enter to send.
112 |
113 | 3. You can also send direct messages to other users by clicking on their name in the user list.
114 |
115 | ### Message Replies (Threads)
116 |
117 | 1. To start a thread in response to a specific message:
118 |
119 | - Hover over the message you want to reply to.
120 |
121 | - Click on the "Reply" or "Start Thread" button.
122 |
123 | - Type your reply in the thread and send it.
124 |
125 | 2. Keep discussions organized by using threads to respond to messages.
126 |
127 | ### Huddle with Other Users
128 |
129 | 1. To create a private huddle:
130 |
131 | - Click on the "Huddle" button in the sidebar.
132 |
133 | - Select the users you want to include in the huddle.
134 |
135 | - Start your private conversation.
136 |
137 | 2. Huddles provide a secure environment for group discussions with select team members.
138 |
139 | ## Contributing
140 |
141 | We welcome contributions from the community.
142 |
143 | ## License
144 |
145 | This project is licensed under the [MIT License](LICENSE).
146 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "slack-clone",
3 | "version": "1.0.0",
4 | "description": "an even better version of slack",
5 | "main": "index.ts",
6 | "author": "adeola adeoti",
7 | "license": "MIT",
8 | "scripts": {
9 | "start": "nodemon --exec ts-node src/index.ts",
10 | "build": "tsc -p .",
11 | "lint": "tslint -c tslint.json -p tsconfig.json --fix"
12 | },
13 | "dependencies": {
14 | "@types/socket.io": "^3.0.2",
15 | "body-parser": "^1.20.2",
16 | "cookie-parser": "^1.4.6",
17 | "cookie-session": "^2.0.0",
18 | "cors": "^2.8.5",
19 | "dotenv": "^16.1.4",
20 | "express": "^4.18.2",
21 | "express-rate-limit": "^6.7.0",
22 | "express-session": "^1.17.3",
23 | "helmet": "^7.0.0",
24 | "hpp": "^0.2.3",
25 | "jsonwebtoken": "^9.0.0",
26 | "mongodb": "^5.6.0",
27 | "mongoose": "^7.2.4",
28 | "morgan": "^1.10.0",
29 | "nodemailer": "^6.9.3",
30 | "passport": "0.5",
31 | "passport-google-oauth20": "^2.0.0",
32 | "randomatic": "^3.1.1",
33 | "socket.io": "^4.7.2",
34 | "ts-node": "^10.9.1",
35 | "tslib": "^2.5.3",
36 | "uuid": "^9.0.0",
37 | "xss-clean": "^0.1.4"
38 | },
39 | "devDependencies": {
40 | "nodemon": "^3.0.1",
41 | "typescript": "^5.1.3"
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/config/db.ts:
--------------------------------------------------------------------------------
1 | import mongoose from 'mongoose'
2 |
3 | export default async function connectDB() {
4 | try {
5 | const conn = await mongoose.connect(process.env.MONGODB_URI, {
6 | dbName: process.env.MONGODB_DB_NAME,
7 | })
8 | console.log(`MongoDB connected: ${conn.connection.host}`)
9 | } catch (error) {
10 | console.error(`Error: ${(error as Error).message}`)
11 | process.exit(1)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/controllers/auth.ts:
--------------------------------------------------------------------------------
1 | import { Request, Response, NextFunction } from 'express'
2 | import User from '../models/user'
3 | import sendEmail from '../helpers/sendEmail'
4 | import crypto from 'crypto'
5 | import { verificationHtml } from '../html/confirmation-code-email'
6 | import passport from 'passport'
7 | import { Strategy as GoogleStrategy } from 'passport-google-oauth20'
8 |
9 | // Configure Google OAuth strategy
10 | passport.use(
11 | new GoogleStrategy(
12 | {
13 | clientID: process.env.GOOGLE_CLIENT_ID,
14 | clientSecret: process.env.GOOGLE_CLIENT_SECRET,
15 | callbackURL: `${process.env.API_URL}/auth/google/callback`,
16 | scope: ['profile', 'email'],
17 | },
18 | async (accessToken, refreshToken, profile, done) => {
19 | try {
20 | // Find or create a user based on the Google profile information
21 | let user = await User.findOne({ googleId: profile.id })
22 | if (!user) {
23 | user = new User({
24 | googleId: profile.id,
25 | username: profile.displayName,
26 | email: profile.emails[0].value,
27 | })
28 | await user.save()
29 | }
30 |
31 | return done(null, user)
32 | } catch (error) {
33 | return done(error, false)
34 | }
35 | }
36 | )
37 | )
38 |
39 | passport.serializeUser((user, done) => {
40 | done(null, user)
41 | })
42 |
43 | passport.deserializeUser((user, done) => {
44 | done(null, user)
45 | })
46 |
47 | // @desc Register user
48 | // @route POST /api/v1/auth/register
49 | // @access Public
50 | export const register = async (
51 | req: Request,
52 | res: Response,
53 | next: NextFunction
54 | ) => {
55 | const { username, email } = req.body
56 |
57 | if (!email) {
58 | return res.status(400).json({
59 | success: false,
60 | data: {
61 | name: 'Please provide your email address',
62 | },
63 | })
64 | }
65 |
66 | const emailExist = await User.findOne({ email })
67 |
68 | if (emailExist) {
69 | return res.status(400).json({
70 | success: false,
71 | data: {
72 | name: 'User already exists',
73 | },
74 | })
75 | }
76 |
77 | const user = await User.create({
78 | username,
79 | email,
80 | })
81 | try {
82 | const verificationToken = user.getVerificationCode()
83 | await user.save()
84 | sendEmail(
85 | email,
86 | 'Slack confirmation code',
87 | verificationHtml(verificationToken)
88 | )
89 |
90 | res.status(201).json({
91 | success: true,
92 | data: {
93 | name: 'Verification token sent to email',
94 | },
95 | })
96 | } catch (err) {
97 | user.loginVerificationCode = undefined
98 | user.loginVerificationCodeExpires = undefined
99 | await user.save({ validateBeforeSave: false })
100 | next(err)
101 | }
102 | }
103 |
104 | // @desc Signin user
105 | // @route POST /api/v1/auth/signin
106 | // @access Public
107 | export const signin = async (
108 | req: Request,
109 | res: Response,
110 | next: NextFunction
111 | ) => {
112 | const { email } = req.body
113 |
114 | if (!email) {
115 | return res.status(400).json({
116 | success: false,
117 | data: {
118 | name: 'Please provide your email address',
119 | },
120 | })
121 | }
122 |
123 | const user = await User.findOne({ email })
124 |
125 | if (!user) {
126 | return res.status(400).json({
127 | success: false,
128 | data: {
129 | name: "User doesn't exist",
130 | },
131 | })
132 | }
133 |
134 | try {
135 | const verificationToken = user.getVerificationCode()
136 | await user.save()
137 |
138 | sendEmail(
139 | email,
140 | 'Slack confirmation code',
141 | verificationHtml(verificationToken)
142 | )
143 |
144 | res.status(201).json({
145 | success: true,
146 | data: {
147 | name: 'Verification token sent to email',
148 | },
149 | })
150 | } catch (err) {
151 | user.loginVerificationCode = undefined
152 | user.loginVerificationCodeExpires = undefined
153 | await user.save({ validateBeforeSave: false })
154 | next(err)
155 | }
156 | }
157 |
158 | // @desc Verify user
159 | // @route POST /api/v1/auth/verify
160 | // @access Public
161 | export const verify = async (
162 | req: Request,
163 | res: Response,
164 | next: NextFunction
165 | ) => {
166 | try {
167 | if (!req.body.loginVerificationCode) {
168 | return res.status(400).json({
169 | success: false,
170 | data: {
171 | name: 'Please provide verification token',
172 | },
173 | })
174 | }
175 | // Get hashed token
176 | const loginVerificationCode = crypto
177 | .createHash('sha256')
178 | .update(req.body.loginVerificationCode)
179 | .digest('hex')
180 |
181 | const user = await User.findOne({
182 | loginVerificationCode,
183 | loginVerificationCodeExpires: { $gt: Date.now() },
184 | })
185 |
186 | if (!user) {
187 | return res.status(400).json({
188 | success: false,
189 | data: {
190 | name: 'Invalid verification token',
191 | },
192 | })
193 | }
194 |
195 | res.status(200).json({
196 | success: true,
197 | data: {
198 | username: user.username,
199 | email: user.email,
200 | token: user.getSignedJwtToken(),
201 | },
202 | })
203 |
204 | user.loginVerificationCode = undefined
205 | user.loginVerificationCodeExpires = undefined
206 | await user.save({ validateBeforeSave: false })
207 | } catch (err) {
208 | next(err)
209 | }
210 | }
211 |
212 | // @desc Google OAuth Callback
213 | // @route GET /auth/google/callback
214 | // @access Public
215 | export const googleCallback = async (
216 | req: Request,
217 | res: Response,
218 | next: NextFunction
219 | ) => {
220 | passport.authenticate('google', (err, user) => {
221 | if (err) {
222 | // Handle error
223 | return next(err)
224 | }
225 |
226 | if (!user) {
227 | // Handle user not found
228 | return res.status(401).json({ message: 'Authentication failed' })
229 | }
230 |
231 | const token = user.getSignedJwtToken()
232 |
233 | res.redirect(
234 | `${process.env.CLIENT_URL}?token=${token}&email=${user.email}&username=${user.username}`
235 | )
236 | })(req, res, next)
237 | }
238 |
--------------------------------------------------------------------------------
/src/controllers/channel.ts:
--------------------------------------------------------------------------------
1 | import { Request, Response, NextFunction } from 'express'
2 | import Channel from '../models/channel'
3 | import successResponse from '../helpers/successResponse'
4 |
5 | // @desc create channel
6 | // @route POST /api/v1/channel/create
7 | // @access Private
8 | export async function createChannel(
9 | req: Request,
10 | res: Response,
11 | next: NextFunction
12 | ) {
13 | try {
14 | const { name, organisationId } = req.body
15 | const channel = await Channel.create({
16 | name,
17 | collaborators: [req.user.id],
18 | organisation: organisationId,
19 | })
20 | successResponse(res, channel)
21 | } catch (error) {
22 | next(error)
23 | }
24 | }
25 |
26 | export async function getChannels(
27 | req: Request,
28 | res: Response,
29 | next: NextFunction
30 | ) {
31 | try {
32 | const id = req.params.id
33 | const channels = await Channel.find({ organisation: id })
34 | .populate({
35 | path: 'organisation',
36 | populate: [{ path: 'owner' }, { path: 'coWorkers' }],
37 | })
38 | .populate('collaborators')
39 | .sort({ _id: -1 })
40 |
41 | successResponse(res, channels)
42 | } catch (error) {
43 | next(error)
44 | }
45 | }
46 |
47 | export async function getChannel(
48 | req: Request,
49 | res: Response,
50 | next: NextFunction
51 | ) {
52 | try {
53 | const id = req.params.id
54 | const channel = await Channel.findById(id)
55 | .populate('collaborators')
56 | .sort({ _id: -1 })
57 |
58 | if (!channel) {
59 | return res.status(400).json({
60 | name: 'not found',
61 | })
62 | }
63 |
64 | const updatedChannel = {
65 | ...channel.toObject(),
66 | isChannel: true,
67 | }
68 |
69 | successResponse(res, updatedChannel)
70 | } catch (error) {
71 | next(error)
72 | }
73 | }
74 |
75 | export async function updateChannel(
76 | req: Request,
77 | res: Response,
78 | next: NextFunction
79 | ) {
80 | try {
81 | const id = req.params.id
82 | const channel = await Channel.findById(id)
83 | if (!channel) {
84 | return res.status(400).json({
85 | name: 'not found',
86 | })
87 | }
88 |
89 | const updatedChannel = await Channel.findByIdAndUpdate(
90 | id,
91 | { $addToSet: { collaborators: req.body.userId } },
92 | {
93 | new: true,
94 | }
95 | )
96 |
97 | successResponse(res, updatedChannel)
98 | } catch (error) {
99 | next(error)
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/src/controllers/conversations.ts:
--------------------------------------------------------------------------------
1 | import { Request, Response, NextFunction } from 'express'
2 | import Conversations from '../models/conversation'
3 | import successResponse from '../helpers/successResponse'
4 | import { UserSchemaType } from 'src/models/user'
5 |
6 | export async function getConversations(
7 | req: Request,
8 | res: Response,
9 | next: NextFunction
10 | ) {
11 | try {
12 | const { id } = req.body
13 | const conversations = await Conversations.find({ organisation: id }).sort({
14 | _id: -1,
15 | })
16 | successResponse(res, conversations)
17 | } catch (error) {
18 | next(error)
19 | }
20 | }
21 |
22 | export async function getConversation(
23 | req: Request,
24 | res: Response,
25 | next: NextFunction
26 | ) {
27 | try {
28 | const id = req.params.id
29 | const conversation = await Conversations.findById(id)
30 | .populate('collaborators')
31 | .sort({ _id: -1 })
32 |
33 | if (!conversation) {
34 | return res.status(400).json({
35 | name: 'not found',
36 | })
37 | }
38 |
39 | // const conversations = await Conversations.findById(id).populate(
40 | // 'collaborators'
41 | // )
42 | const collaborators = [...conversation.collaborators]
43 | // Find the index of the collaborator with the current user's ID
44 | const currentUserIndex = conversation.collaborators.findIndex(
45 | (coworker: UserSchemaType) => coworker._id.toString() === req.user.id
46 | )
47 |
48 | // // Remove the current user collaborator from the array
49 | conversation.collaborators.splice(currentUserIndex, 1)
50 |
51 | // // Create the name field based on the other collaborator's username
52 | const name = conversation.collaborators[0]?.username || conversation.name
53 |
54 | successResponse(res, {
55 | ...conversation.toObject(),
56 | name,
57 | collaborators,
58 | })
59 | } catch (error) {
60 | next(error)
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/controllers/message.ts:
--------------------------------------------------------------------------------
1 | import { Request, Response, NextFunction } from 'express'
2 | import Message from '../models/message'
3 | import successResponse from '../helpers/successResponse'
4 |
5 | // @desc get messages
6 | // @route POST /api/v1/message
7 | // @access Private
8 | export async function getMessages(
9 | req: Request,
10 | res: Response,
11 | next: NextFunction
12 | ) {
13 | try {
14 | const channelId = req.query.channelId
15 | const conversationId = req.query.conversation
16 | const isSelf = req.query.isSelf
17 | const organisation = req.query.organisation
18 |
19 | if (channelId) {
20 | const channel = await Message.find({
21 | channel: channelId,
22 | organisation,
23 | }).populate(['sender', 'reactions.reactedToBy', 'threadReplies'])
24 | successResponse(res, channel)
25 | } else if (conversationId) {
26 | let conversation
27 |
28 | if (isSelf) {
29 | conversation = await Message.find({
30 | organisation,
31 | conversation: conversationId,
32 | isSelf,
33 | }).populate(['sender', 'reactions.reactedToBy', 'threadReplies'])
34 | } else {
35 | conversation = await Message.find({
36 | organisation,
37 | conversation: conversationId,
38 | }).populate(['sender', 'reactions.reactedToBy', 'threadReplies'])
39 | }
40 | successResponse(res, conversation)
41 | } else {
42 | res.status(400).json({})
43 | }
44 | } catch (error) {
45 | next(error)
46 | }
47 | }
48 |
49 | // @desc get message
50 | // @route POST /api/v1/message
51 | // @access Private
52 | export async function getMessage(
53 | req: Request,
54 | res: Response,
55 | next: NextFunction
56 | ) {
57 | try {
58 | const id = req.params.id
59 |
60 | if (id) {
61 | const message = await Message.findById(id).populate([
62 | 'sender',
63 | 'threadReplies',
64 | 'reactions.reactedToBy',
65 | ])
66 | successResponse(res, message)
67 | } else {
68 | res.status(400).json({
69 | name: 'message not found',
70 | })
71 | }
72 | } catch (error) {
73 | next(error)
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/controllers/organisation.ts:
--------------------------------------------------------------------------------
1 | import { Request, Response, NextFunction } from 'express'
2 | import Organisation from '../models/organisation'
3 | import successResponse from '../helpers/successResponse'
4 | import Channel from '../models/channel'
5 | import Conversations from '../models/conversation'
6 | import { UserSchemaType } from 'src/models/user'
7 |
8 | // @desc get organisation
9 | // @route GET /api/v1/organisation/:id
10 | // @access Private
11 | export async function getOrganisation(
12 | req: Request,
13 | res: Response,
14 | next: NextFunction
15 | ) {
16 | try {
17 | const id = req.params.id
18 | if (id) {
19 | let organisation = await Organisation.findById(id).populate([
20 | 'coWorkers',
21 | 'owner',
22 | ])
23 |
24 | if (!organisation) {
25 | res.status(400, {
26 | name: 'no organisation found',
27 | })
28 | }
29 |
30 | const channels = await Channel.find({
31 | organisation: id,
32 | }).populate('collaborators')
33 |
34 | const conversations = await Conversations.find({
35 | organisation: id,
36 | }).populate('collaborators')
37 |
38 | const conversationsWithCurrentUser = conversations.filter(
39 | (conversation) =>
40 | conversation.collaborators.some(
41 | (collaborator: UserSchemaType) =>
42 | collaborator._id.toString() === req.user.id
43 | )
44 | )
45 |
46 | const updatedConversations = conversationsWithCurrentUser.map((convo) => {
47 | // Find the index of the collaborator with the current user's ID
48 | const currentUserIndex = convo.collaborators.findIndex(
49 | (coworker: UserSchemaType) => coworker._id.toString() === req.user.id
50 | )
51 |
52 | const collaborators = [...convo.collaborators]
53 |
54 | // Remove the current user collaborator from the array
55 | convo.collaborators.splice(currentUserIndex, 1)
56 |
57 | // Create the name field based on the other collaborator's username
58 | const name = convo.collaborators[0]?.username || convo.name
59 |
60 | return {
61 | ...convo.toObject(),
62 | name,
63 | createdBy: convo.collaborators[0]?._id,
64 | collaborators,
65 | }
66 | })
67 |
68 | // Check if the authenticated user is a co-worker of the organisation
69 | const currentUserIsCoWorker = organisation.coWorkers.some(
70 | (coworker: UserSchemaType) => coworker._id.toString() === req.user.id
71 | )
72 |
73 | // Replace the profile object with the corresponding co-worker's values
74 | let profile: UserSchemaType
75 | if (currentUserIsCoWorker) {
76 | const currentUser = organisation.coWorkers.find(
77 | (coworker: UserSchemaType) => coworker._id.toString() === req.user.id
78 | )
79 | profile = currentUser
80 | }
81 |
82 | // Update the coWorkers array in the organisation object
83 | const updatedOrganisation = {
84 | ...organisation.toObject(),
85 | conversations: updatedConversations,
86 | channels,
87 | profile,
88 | }
89 |
90 | successResponse(res, updatedOrganisation)
91 | }
92 | } catch (error) {
93 | next(error)
94 | }
95 | }
96 | // @desc get organisation
97 | // @route POST /api/v1/organisation
98 | // @access Private
99 | export async function createOrganisation(
100 | req: Request,
101 | res: Response,
102 | next: NextFunction
103 | ) {
104 | try {
105 | const { name, id } = req.body
106 |
107 | if (!name && !id) {
108 | const organisation = await Organisation.create({
109 | owner: req.user.id,
110 | coWorkers: [req.user.id],
111 | })
112 |
113 | successResponse(res, organisation)
114 | }
115 |
116 | if (name && id) {
117 | const organisation = await Organisation.findOneAndUpdate(
118 | { _id: id },
119 | { $set: { name } },
120 | { new: true }
121 | ).populate(['coWorkers', 'owner'])
122 |
123 | organisation.generateJoinLink()
124 | await organisation.save()
125 | successResponse(res, organisation)
126 | }
127 | } catch (error) {
128 | next(error)
129 | }
130 | }
131 |
132 | // @desc get organisations associated with an email
133 | // @route POST /api/v1/organisation/workspaces
134 | // @access Private
135 | export async function getWorkspaces(
136 | req: Request,
137 | res: Response,
138 | next: NextFunction
139 | ) {
140 | try {
141 | const id = req.user.id
142 | // Find all organizations where the user is a co-worker
143 | const workspaces = await Organisation.find({ coWorkers: id })
144 | // Fetch channels for each organization
145 | const workspacesWithChannels = await Promise.all(
146 | workspaces.map(async (workspace) => {
147 | const channels = await Channel.find({ organisation: workspace._id })
148 | return {
149 | ...workspace.toObject(),
150 | channels,
151 | }
152 | })
153 | )
154 |
155 | successResponse(res, workspacesWithChannels)
156 |
157 | // successResponse(res, workspaces);
158 | } catch (error) {
159 | next(error)
160 | }
161 | }
162 |
--------------------------------------------------------------------------------
/src/controllers/teammates.ts:
--------------------------------------------------------------------------------
1 | import { Request, Response, NextFunction } from 'express'
2 | import Organisation, { OrganisationSchemaType } from '../models/organisation'
3 | import Conversation from '../models/conversation'
4 | import successResponse from '../helpers/successResponse'
5 | import Channel, { ChannelSchemaType } from '../models/channel'
6 | import User from '../models/user'
7 | import sendEmail from '../helpers/sendEmail'
8 | import { joinTeammatesEmail } from '../html/join-teammates-email'
9 |
10 | // @desc add teammates to either organisation or a channel
11 | // @route POST /api/v1/teammates
12 | // @access Private
13 | export async function createTeammates(
14 | req: Request,
15 | res: Response,
16 | next: NextFunction
17 | ) {
18 | try {
19 | const { emails, channelId, organisationId, userIds } = req.body
20 | const channelExist = await Channel.findById(channelId)
21 | let organisation: OrganisationSchemaType
22 |
23 | const invitedBy = await User.findById(req.user.id)
24 |
25 | // add existed teammates to channel
26 | if (userIds?.length > 0 && channelExist) {
27 | let channel: ChannelSchemaType
28 |
29 | for (const id of userIds) {
30 | try {
31 | channel = await Channel.findOneAndUpdate(
32 | { _id: channelId },
33 | { $addToSet: { collaborators: id } },
34 | { new: true }
35 | ).populate('collaborators')
36 |
37 | const user = await User.findById(id)
38 |
39 | sendEmail(
40 | user.email,
41 | `${invitedBy.email} has invited you to work with them in Slack`,
42 | joinTeammatesEmail(
43 | invitedBy.username,
44 | invitedBy.email,
45 | organisation.name,
46 | req.user.id,
47 | organisation.joinLink,
48 | organisation.url
49 | )
50 | )
51 | } catch (error) {
52 | next(error)
53 | }
54 | }
55 | successResponse(res, channel)
56 | } else if (channelId && channelExist) {
57 | // add new teammates to channel
58 | let channel: ChannelSchemaType
59 |
60 | for (const email of emails) {
61 | try {
62 | const newUser = await User.create({ email })
63 | channel = await Channel.findOneAndUpdate(
64 | { _id: channelId },
65 | { $push: { collaborators: newUser._id } },
66 | { new: true }
67 | ).populate('collaborators')
68 | await Organisation.findOneAndUpdate(
69 | { _id: organisationId },
70 | { $push: { coWorkers: newUser._id } }
71 | )
72 | // send email to the ids
73 | sendEmail(
74 | email,
75 | `${invitedBy.email} has invited you to work with them in Slack`,
76 | joinTeammatesEmail(
77 | invitedBy.username,
78 | invitedBy.email,
79 | organisation.name,
80 | req.user.id,
81 | organisation.joinLink,
82 | organisation.url
83 | )
84 | )
85 | } catch (error) {
86 | next(error)
87 | }
88 | }
89 | successResponse(res, channel)
90 | } else if (organisationId) {
91 | // add new teammates to organisation
92 | const organisationExist = await Organisation.findById(organisationId)
93 |
94 | if (organisationExist) {
95 | for (const email of emails) {
96 | try {
97 | const existingUser = await User.findOne({ email })
98 |
99 | if (existingUser) {
100 | // Check if existingUser is not part of coWorkers field before pushing
101 | const isUserAlreadyInCoWorkers =
102 | organisationExist.coWorkers.includes(existingUser._id)
103 |
104 | if (!isUserAlreadyInCoWorkers) {
105 | organisation = await Organisation.findOneAndUpdate(
106 | { _id: organisationId },
107 | {
108 | $addToSet: { coWorkers: existingUser._id },
109 | },
110 | { new: true }
111 | ).populate(['coWorkers', 'owner'])
112 | }
113 | } else {
114 | const newUser = await User.create({ email })
115 | organisation = await Organisation.findOneAndUpdate(
116 | { _id: organisationId },
117 | {
118 | $push: {
119 | coWorkers: newUser._id,
120 | },
121 | },
122 | { new: true }
123 | ).populate(['coWorkers', 'owner'])
124 | }
125 |
126 | // vibe and inshallah
127 | sendEmail(
128 | email,
129 | `${invitedBy.email} has invited you to work with them in Slack`,
130 | joinTeammatesEmail(
131 | invitedBy.username,
132 | invitedBy.email,
133 | organisation.name,
134 | req.user.id,
135 | organisation.joinLink,
136 | organisation.url
137 | )
138 | )
139 | } catch (error) {
140 | next(error)
141 | }
142 | }
143 |
144 | if (!channelId) {
145 | successResponse(res, organisation)
146 | }
147 |
148 | // Create separate conversations for each unique pair of coWorkers
149 | for (let i = 0; i < organisation.coWorkers.length; i++) {
150 | for (let j = i + 1; j < organisation.coWorkers.length; j++) {
151 | // Check if a conversation with these collaborators already exists
152 | const existingConversation = await Conversation.findOne({
153 | collaborators: {
154 | $all: [
155 | organisation.coWorkers[i]._id,
156 | organisation.coWorkers[j]._id,
157 | ],
158 | },
159 | organisation: organisationId,
160 | })
161 |
162 | // If no conversation exists, create a new one
163 | if (!existingConversation) {
164 | await Conversation.create({
165 | name: `${organisation.coWorkers[i].username}, ${organisation.coWorkers[j].username}`,
166 | description: `This conversation is between ${organisation.coWorkers[i].username} and ${organisation.coWorkers[j].username}`,
167 | organisation: organisationId,
168 | isSelf:
169 | organisation.coWorkers[i]._id ===
170 | organisation.coWorkers[j]._id,
171 | collaborators: [
172 | organisation.coWorkers[i]._id,
173 | organisation.coWorkers[j]._id,
174 | ],
175 | })
176 | }
177 | }
178 | }
179 |
180 | // Create self-conversations for each coworker
181 | for (let i = 0; i < organisation.coWorkers.length; i++) {
182 | const selfConversationExists = await Conversation.findOne({
183 | collaborators: {
184 | $all: [organisation.coWorkers[i]._id],
185 | },
186 | organisation: organisationId,
187 | isSelf: true,
188 | })
189 |
190 | // If no self-conversation exists, create one
191 | if (!selfConversationExists) {
192 | await Conversation.create({
193 | name: `${organisation.coWorkers[i].username}`,
194 | description: `This is a conversation with oneself (${organisation.coWorkers[i].username}).`,
195 | organisation: organisationId,
196 | isSelf: true,
197 | collaborators: [organisation.coWorkers[i]._id],
198 | })
199 | }
200 | }
201 | }
202 | }
203 | } catch (error) {
204 | next(error)
205 | }
206 | }
207 |
208 | // @desc get a teammate of an organisation
209 | // @route GET /api/v1/teammates
210 | // @access Private
211 | export async function getTeammate(
212 | req: Request,
213 | res: Response,
214 | next: NextFunction
215 | ) {
216 | try {
217 | const coworkerId = req.params.id
218 | const coworker = await User.findById(coworkerId)
219 | // console.log(coworker);
220 | if (!coworker) {
221 | return res.status(400).json({
222 | name: 'Coworker not found',
223 | })
224 | }
225 |
226 | return successResponse(res, coworker)
227 | } catch (error) {
228 | next(error)
229 | }
230 | }
231 |
--------------------------------------------------------------------------------
/src/controllers/thread.ts:
--------------------------------------------------------------------------------
1 | import { Request, Response, NextFunction } from 'express'
2 | import Thread from '../models/thread'
3 | import successResponse from '../helpers/successResponse'
4 |
5 | // @desc get threads
6 | // @route POST /api/v1/threads
7 | // @access Private
8 | export async function getThreads(
9 | req: Request,
10 | res: Response,
11 | next: NextFunction
12 | ) {
13 | try {
14 | const messageId = req.query.message
15 |
16 | if (messageId) {
17 | const threads = await Thread.find({
18 | message: messageId,
19 | })
20 | .populate('sender')
21 | .populate('reactions.reactedToBy')
22 | successResponse(res, threads)
23 | } else {
24 | res.status(400).json({})
25 | }
26 | } catch (error) {
27 | next(error)
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/helpers/createTodaysFirstMessage.ts:
--------------------------------------------------------------------------------
1 | import Message from '../models/message'
2 |
3 | function formatDate(date) {
4 | const options = { weekday: 'long', month: 'long', day: 'numeric' }
5 | const formattedDate = date.toLocaleDateString('en-US', options)
6 | return formattedDate.charAt(0).toUpperCase() + formattedDate.slice(1) // Capitalize the first letter
7 | }
8 |
9 | interface CreateTodaysFirstMessage {
10 | channelId?: string
11 | conversationId?: string
12 | organisation: string
13 | }
14 |
15 | export default async function createTodaysFirstMessage({
16 | channelId,
17 | conversationId,
18 | organisation,
19 | }: CreateTodaysFirstMessage) {
20 | try {
21 | // Check if there are any messages for today in the channel
22 | const today = new Date()
23 | today.setHours(0, 0, 0, 0)
24 | let existingMessages
25 |
26 | if (channelId) {
27 | existingMessages = await Message.find({
28 | channel: channelId,
29 | createdAt: { $gte: today },
30 | })
31 | } else if (conversationId) {
32 | existingMessages = await Message.find({
33 | conversation: conversationId,
34 | createdAt: { $gte: today },
35 | })
36 | }
37 |
38 | if (existingMessages.length === 0) {
39 | await Message.create({
40 | organisation,
41 | content: formatDate(today),
42 | ...(channelId
43 | ? { channel: channelId }
44 | : { conversation: conversationId }),
45 | hasRead: false,
46 | type: 'date',
47 | })
48 | }
49 | } catch (error) {}
50 | }
51 |
--------------------------------------------------------------------------------
/src/helpers/sendEmail.ts:
--------------------------------------------------------------------------------
1 | import nodemailer from 'nodemailer'
2 |
3 | export default async function sendEmail(
4 | to: string,
5 | subject: string,
6 | html?: string,
7 | text?: string
8 | ) {
9 | try {
10 | const transporter = nodemailer.createTransport({
11 | service: 'hotmail',
12 | auth: {
13 | user: process.env.SMTP_USERNAME,
14 | pass: process.env.SMTP_PASSWORD,
15 | },
16 | })
17 |
18 | await transporter.sendMail({
19 | from: process.env.SMTP_USERNAME,
20 | to,
21 | subject,
22 | html,
23 | text,
24 | })
25 | } catch (error) {
26 | console.log(error)
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/helpers/successResponse.ts:
--------------------------------------------------------------------------------
1 | import { Response } from 'express'
2 |
3 | export default function successResponse(res: Response, data) {
4 | res.status(201).json({
5 | data,
6 | })
7 | }
8 |
--------------------------------------------------------------------------------
/src/helpers/updateConversationStatus.ts:
--------------------------------------------------------------------------------
1 | import Conversations from '../models/conversation'
2 | import User from '../models/user'
3 |
4 | async function updateUserStatus(id, isOnline) {
5 | try {
6 | // Update the user's "isOnline" field
7 | const updatedUser = await User.findByIdAndUpdate(
8 | id,
9 | { isOnline },
10 | { new: true }
11 | )
12 | return updatedUser
13 | } catch (error) {
14 | console.error('Error updating user status:', error)
15 | throw error
16 | }
17 | }
18 |
19 | export default async function updateConversationStatus(id, isOnline) {
20 | try {
21 | await updateUserStatus(id, isOnline)
22 |
23 | // Find all conversations where the user is part of the collaborators
24 | const conversations = await Conversations.find({
25 | collaborators: { $in: id },
26 | }).populate('collaborators')
27 |
28 | // Check if all collaborators in each conversation are online
29 | for (const conversation of conversations) {
30 | let allCollaboratorsOnline = true
31 |
32 | for (const collaborator of conversation.collaborators) {
33 | const user = await User.findById(collaborator._id)
34 | if (!(user && user.isOnline)) {
35 | allCollaboratorsOnline = false
36 | break // No need to check further if one is offline
37 | }
38 | }
39 |
40 | if (allCollaboratorsOnline) {
41 | // Update the Conversation model's "isOnline" field to true
42 | await Conversations.findByIdAndUpdate(
43 | conversation._id,
44 | { isOnline: true },
45 | { new: true }
46 | )
47 | } else {
48 | await Conversations.findByIdAndUpdate(
49 | conversation._id,
50 | { isOnline: false },
51 | { new: true }
52 | )
53 | }
54 | }
55 | } catch (error) {
56 | // Handle any errors here
57 | console.error('Error updating conversation status:', error)
58 | throw error
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/html/confirmation-code-email.ts:
--------------------------------------------------------------------------------
1 | export function verificationHtml(code: string) {
2 | return `
Slack confirmation code: ${code}
Confirm your email address Your confirmation code is below — enter it in your open browser window and we'll help you get signed in.
If you didn’t request this email, there’s nothing to worry about — you can safely ignore it.
452 | `
453 | }
454 |
--------------------------------------------------------------------------------
/src/html/join-teammates-email.ts:
--------------------------------------------------------------------------------
1 | export function joinTeammatesEmail(
2 | name: string,
3 | email: string,
4 | organisationName: string,
5 | invitedBy: string,
6 | organisationLink: string,
7 | joinLink: string
8 | ) {
9 | return `${name} has invited you to work with them in Slack
Join your team on Slack ${name} (${email} ) has invited you to use Slack with them, in a workspace called ${organisationName} .
Workspace name: ${organisationName} M
${organisationName} Join the conversation with ${invitedBy} .
What is Slack? Slack is a messaging app for teams, a place you can collaborate on projects and organize conversations — so you can work together, no matter where you are.
491 | `
492 | }
493 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import express from 'express'
2 | import dotenv from 'dotenv'
3 | import connectDB from './config/db'
4 | dotenv.config()
5 | import auth from './routes/auth'
6 | import cookieParser from 'cookie-parser'
7 | import morgan from 'morgan'
8 | import helmet from 'helmet'
9 | import xss from 'xss-clean'
10 | import rateLimit from 'express-rate-limit'
11 | import hpp from 'hpp'
12 | import cors from 'cors'
13 | import channel from './routes/channel'
14 | import message from './routes/message'
15 | import thread from './routes/thread'
16 | import teammates from './routes/teammates'
17 | import organisation from './routes/organisation'
18 | import errorResponse from './middleware/errorResponse'
19 | import Message from '../src/models/message'
20 | import Channels from '../src/models/channel'
21 | import Conversations from './models/conversation'
22 | import conversations from './routes/conversations'
23 | import { Server } from 'socket.io'
24 | import http from 'http'
25 | import updateConversationStatus from './helpers/updateConversationStatus'
26 | import Thread from './models/thread'
27 | import createTodaysFirstMessage from './helpers/createTodaysFirstMessage'
28 | import passport from 'passport'
29 | import cookieSession from 'cookie-session'
30 |
31 | const app = express()
32 | const server = http.createServer(app)
33 | const io = new Server(server, {
34 | cors: {
35 | origin: process.env.CLIENT_URL,
36 | methods: ['GET', 'POST'],
37 | },
38 | })
39 |
40 | // Connect to MongoDB
41 | connectDB()
42 |
43 | // Express configuration
44 | app.use(
45 | cookieSession({
46 | name: 'session',
47 | keys: ['cyberwolve'],
48 | maxAge: 24 * 60 * 60 * 100,
49 | })
50 | )
51 |
52 | app.use(passport.initialize())
53 | app.use(passport.session())
54 |
55 | app.use(express.json())
56 | app.use(express.urlencoded({ extended: true }))
57 |
58 | // cookie-parser configuration
59 | app.use(cookieParser())
60 |
61 | // Dev logging middleware
62 | if (process.env.NODE_ENV === 'development') {
63 | app.use(morgan('dev'))
64 | }
65 |
66 | // Set security headers
67 | app.use(helmet())
68 |
69 | // Prevent XSS attacks
70 | app.use(xss())
71 |
72 | // Rate limiting
73 | const limiter = rateLimit({
74 | windowMs: 10 * 60 * 1000, // 10 mins
75 | max: 1000,
76 | })
77 | app.use(limiter)
78 |
79 | // Prevent http param pollution
80 | app.use(hpp())
81 |
82 | // Enable CORS
83 | app.use(cors())
84 |
85 | // Store users' sockets by their user IDs
86 | const users = {}
87 |
88 | // Set up WebSocket connections
89 | io.on('connection', (socket) => {
90 | socket.on('user-join', async ({ id, isOnline }) => {
91 | socket.join(id)
92 | await updateConversationStatus(id, isOnline)
93 | io.emit('user-join', { id, isOnline })
94 | })
95 |
96 | socket.on('user-leave', async ({ id, isOnline }) => {
97 | socket.leave(id)
98 | await updateConversationStatus(id, isOnline)
99 | io.emit('user-leave', { id, isOnline })
100 | })
101 |
102 | socket.on('channel-open', async ({ id, userId }) => {
103 | if (id) {
104 | socket.join(id)
105 | const updatedChannel = await Channels.findByIdAndUpdate(
106 | id,
107 | { $pull: { hasNotOpen: userId } },
108 | { new: true }
109 | )
110 | io.to(id).emit('channel-updated', updatedChannel)
111 | }
112 | })
113 | socket.on('convo-open', async ({ id, userId }) => {
114 | if (id) {
115 | socket.join(id)
116 | const updatedConversation = await Conversations.findByIdAndUpdate(
117 | id,
118 | { $pull: { hasNotOpen: userId } },
119 | { new: true }
120 | )
121 | io.to(id).emit('convo-updated', updatedConversation)
122 | }
123 | })
124 |
125 | socket.on('thread-message', async ({ userId, messageId, message }) => {
126 | try {
127 | socket.join(messageId)
128 | let newMessage = await Thread.create({
129 | sender: message.sender,
130 | content: message.content,
131 | message: messageId,
132 | hasRead: false,
133 | })
134 | newMessage = await newMessage.populate('sender')
135 | io.to(messageId).emit('thread-message', { newMessage })
136 | const updatedMessage = await Message.findByIdAndUpdate(
137 | messageId,
138 | {
139 | threadLastReplyDate: newMessage.createdAt,
140 | $addToSet: { threadReplies: userId },
141 | $inc: { threadRepliesCount: 1 },
142 | },
143 | { new: true }
144 | ).populate(['threadReplies', 'sender', 'reactions.reactedToBy'])
145 |
146 | io.to(messageId).emit('message-updated', {
147 | id: messageId,
148 | message: updatedMessage,
149 | })
150 |
151 | // socket.emit("message-updated", { messageId, message: updatedMessage });
152 | } catch (error) {
153 | console.log(error)
154 | }
155 | })
156 |
157 | socket.on(
158 | 'message',
159 | async ({
160 | channelId,
161 | channelName,
162 | conversationId,
163 | collaborators,
164 | isSelf,
165 | message,
166 | organisation,
167 | hasNotOpen,
168 | }) => {
169 | try {
170 | if (channelId) {
171 | socket.join(channelId)
172 | // Check if there are any messages for today in the channel
173 | await createTodaysFirstMessage({ channelId, organisation })
174 |
175 | let newMessage = await Message.create({
176 | organisation,
177 | sender: message.sender,
178 | content: message.content,
179 | channel: channelId,
180 | hasRead: false,
181 | })
182 |
183 | newMessage = await newMessage.populate('sender')
184 | io.to(channelId).emit('message', { newMessage, organisation })
185 |
186 | const updatedChannel = await Channels.findByIdAndUpdate(
187 | channelId,
188 | { hasNotOpen },
189 | { new: true }
190 | )
191 |
192 | io.to(channelId).emit('channel-updated', updatedChannel)
193 | socket.broadcast.emit('notification', {
194 | channelName,
195 | channelId,
196 | collaborators,
197 | newMessage,
198 | organisation,
199 | })
200 | } else if (conversationId) {
201 | socket.join(conversationId)
202 | // Check if there are any messages for today in the channel
203 | await createTodaysFirstMessage({ conversationId, organisation })
204 | let newMessage = await Message.create({
205 | organisation,
206 | sender: message.sender,
207 | content: message.content,
208 | conversation: conversationId,
209 | collaborators,
210 | isSelf,
211 | hasRead: false,
212 | })
213 | newMessage = await newMessage.populate('sender')
214 |
215 | io.to(conversationId).emit('message', {
216 | collaborators,
217 | organisation,
218 | newMessage,
219 | })
220 | const updatedConversation = await Conversations.findByIdAndUpdate(
221 | conversationId,
222 | { hasNotOpen },
223 | { new: true }
224 | )
225 | io.to(conversationId).emit('convo-updated', updatedConversation)
226 | socket.broadcast.emit('notification', {
227 | collaborators,
228 | organisation,
229 | newMessage,
230 | conversationId,
231 | })
232 | }
233 | } catch (error) {
234 | console.log(error)
235 | }
236 | }
237 | )
238 |
239 | socket.on('message-view', async (messageId) => {
240 | await Message.findByIdAndUpdate(messageId, {
241 | hasRead: true,
242 | })
243 | if (message) {
244 | io.emit('message-view', messageId)
245 | } else {
246 | console.log('message not found')
247 | }
248 | })
249 |
250 | socket.on('reaction', async ({ emoji, id, isThread, userId }) => {
251 | // 1. Message.findbyid(id)
252 | let message
253 | if (isThread) {
254 | message = await Thread.findById(id)
255 | } else {
256 | message = await Message.findById(id)
257 | }
258 |
259 | if (!message) {
260 | // Handle the case where the model with the given id is not found
261 | return
262 | }
263 | // 2. check if emoji already exists in Message.reactions array
264 | if (message.reactions.some((r) => r.emoji === emoji)) {
265 | // 3. if it does, check if userId exists in reactedToBy array
266 | if (
267 | message.reactions.some(
268 | (r) =>
269 | r.emoji === emoji &&
270 | r.reactedToBy.some((v) => v.toString() === userId)
271 | )
272 | ) {
273 | // Find the reaction that matches the emoji and remove userId from its reactedToBy array
274 | const reactionToUpdate = message.reactions.find(
275 | (r) => r.emoji === emoji
276 | )
277 | if (reactionToUpdate) {
278 | reactionToUpdate.reactedToBy = reactionToUpdate.reactedToBy.filter(
279 | (v) => v.toString() !== userId
280 | )
281 |
282 | // If reactedToBy array is empty after removing userId, remove the reaction object
283 | if (reactionToUpdate.reactedToBy.length === 0) {
284 | message.reactions = message.reactions.filter(
285 | (r) => r !== reactionToUpdate
286 | )
287 | }
288 | // await message.populate([
289 | // "reactions.reactedToBy",
290 | // "sender",
291 | // // "threadReplies",
292 | // ]);
293 | if (isThread) {
294 | await message.populate(['reactions.reactedToBy', 'sender'])
295 | } else {
296 | await message.populate([
297 | 'reactions.reactedToBy',
298 | 'sender',
299 | 'threadReplies',
300 | ])
301 | }
302 | socket.emit('message-updated', { id, message, isThread })
303 | await message.save()
304 | }
305 | } else {
306 | // Find the reaction that matches the emoji and push userId to its reactedToBy array
307 | const reactionToUpdate = message.reactions.find(
308 | (r) => r.emoji === emoji
309 | )
310 | if (reactionToUpdate) {
311 | reactionToUpdate.reactedToBy.push(userId)
312 | // await message.populate([
313 | // "reactions.reactedToBy",
314 | // "sender",
315 | // // isThread && "threadReplies",
316 |
317 | // // "threadReplies",
318 | // ]);
319 | if (isThread) {
320 | await message.populate(['reactions.reactedToBy', 'sender'])
321 | } else {
322 | await message.populate([
323 | 'reactions.reactedToBy',
324 | 'sender',
325 | 'threadReplies',
326 | ])
327 | }
328 | socket.emit('message-updated', { id, message, isThread })
329 | await message.save()
330 | }
331 | }
332 | } else {
333 | // 4. if it doesn't exists, create a new reaction like this {emoji, reactedToBy: [userId]}
334 | message.reactions.push({ emoji, reactedToBy: [userId] })
335 | // await message.populate([
336 | // "reactions.reactedToBy",
337 | // "sender",
338 | // // isThread && "threadReplies",
339 | // // "threadReplies",
340 | // ]);
341 | if (isThread) {
342 | await message.populate(['reactions.reactedToBy', 'sender'])
343 | } else {
344 | await message.populate([
345 | 'reactions.reactedToBy',
346 | 'sender',
347 | 'threadReplies',
348 | ])
349 | }
350 | socket.emit('message-updated', { id, message, isThread })
351 | await message.save()
352 | }
353 | })
354 | // Event handler for joining a room
355 | socket.on('join-room', ({ roomId, userId }) => {
356 | // Join the specified room
357 | socket.join(roomId)
358 | // Store the user's socket by their user ID
359 | users[userId] = socket
360 | // Broadcast the "join-room" event to notify other users in the room
361 | socket.to(roomId).emit('join-room', { roomId, otherUserId: userId })
362 |
363 | console.log(`User ${userId} joined room ${roomId}`)
364 | })
365 |
366 | // Event handler for sending an SDP offer to another user
367 | socket.on('offer', ({ offer, targetUserId }) => {
368 | // Find the target user's socket by their user ID
369 | const targetSocket = users[targetUserId]
370 | if (targetSocket) {
371 | targetSocket.emit('offer', { offer, senderUserId: targetUserId })
372 | }
373 | })
374 |
375 | // Event handler for sending an SDP answer to another user
376 | socket.on('answer', ({ answer, senderUserId }) => {
377 | socket.broadcast.emit('answer', { answer, senderUserId })
378 | })
379 |
380 | // Event handler for sending ICE candidates to the appropriate user (the answerer)
381 | socket.on('ice-candidate', ({ candidate, senderUserId }) => {
382 | // Find the target user's socket by their user ID
383 | const targetSocket = users[senderUserId]
384 | if (targetSocket) {
385 | targetSocket.emit('ice-candidate', candidate, senderUserId)
386 | }
387 | })
388 |
389 | // Event handler for leaving a room
390 | socket.on('room-leave', ({ roomId, userId }) => {
391 | socket.leave(roomId)
392 | // Remove the user's socket from the users object
393 | delete users[userId]
394 | // Broadcast the "room-leave" event to notify other users in the room
395 | socket.to(roomId).emit('room-leave', { roomId, leftUserId: userId })
396 | console.log(`User ${userId} left room ${roomId}`)
397 | })
398 | })
399 |
400 | // Routes
401 | app.use('/api/v1/auth', auth)
402 | app.use('/api/v1/channel', channel)
403 | app.use('/api/v1/messages', message)
404 | app.use('/api/v1/threads', thread)
405 | app.use('/api/v1/teammates', teammates)
406 | app.use('/api/v1/organisation', organisation)
407 | app.use('/api/v1/conversations', conversations)
408 |
409 | // error handler
410 | app.use(errorResponse)
411 |
412 | // Start the server
413 | const port = process.env.PORT || 8080
414 | server.listen(port, () => {
415 | console.log(`Server is running on port ${port}`)
416 | })
417 |
--------------------------------------------------------------------------------
/src/middleware/errorResponse.ts:
--------------------------------------------------------------------------------
1 | import { Response } from 'express'
2 |
3 | interface CustomError extends Error {
4 | name: string
5 | errors?: Record
6 | path?: string
7 | value?: string
8 | }
9 |
10 | function errorResponse(error: CustomError, res: Response) {
11 | if (error.name === 'ValidationError') {
12 | // Mongoose validation error: Required field missing
13 | const requiredFieldError = {}
14 | for (const field of Object.keys(error.errors)) {
15 | requiredFieldError[field] = error.errors[field].message
16 | }
17 | return res
18 | .status(400)
19 | .json({ name: 'Validation Failed', data: requiredFieldError })
20 | } else if (error.name === 'CastError') {
21 | // Mongoose cast error (e.g., invalid ObjectId)
22 | res.status(400).json({ name: `Invalid ${error.path}: ${error.value}` })
23 | }
24 | // Handle other types of errors if needed
25 | console.error(error)
26 | return res.status(500).json({ name: 'Internal Server Error' })
27 | }
28 |
29 | export default errorResponse
30 |
--------------------------------------------------------------------------------
/src/middleware/protect.ts:
--------------------------------------------------------------------------------
1 | import jwt from 'jsonwebtoken'
2 |
3 | export const protect = (req, res, next) => {
4 | let token
5 |
6 | if (
7 | req.headers.authorization &&
8 | req.headers.authorization.startsWith('Bearer')
9 | ) {
10 | // Set token from Bearer token in header
11 | token = req.headers.authorization.split(' ')[1]
12 | }
13 |
14 | // Make sure token exists
15 | if (!token) {
16 | return res.status(401).json({
17 | success: false,
18 | data: {
19 | name: 'Not authorized to access this route',
20 | },
21 | })
22 | }
23 |
24 | try {
25 | // Verify token
26 | const decoded = jwt.verify(token, process.env.JWT_SECRET)
27 | req.user = decoded
28 |
29 | next()
30 | } catch (err) {
31 | return res.status(401).json({
32 | success: false,
33 | data: {
34 | name: 'Not authorized to access this route',
35 | },
36 | })
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/models/channel.ts:
--------------------------------------------------------------------------------
1 | import mongoose from 'mongoose'
2 | // import { v4 as uuidv4 } from "uuid";
3 |
4 | export interface ChannelSchemaType {
5 | name: string
6 | collaborators: mongoose.Schema.Types.ObjectId[]
7 | title: string
8 | description: string
9 | organisation: mongoose.Schema.Types.ObjectId
10 | hasNotOpen: mongoose.Schema.Types.ObjectId[]
11 | isChannel: boolean
12 | // conversations: mongoose.Schema.Types.ObjectId[];
13 | createdAt: Date
14 | updatedAt: Date
15 | }
16 |
17 | const channelSchema = new mongoose.Schema(
18 | {
19 | name: {
20 | type: String,
21 | required: [true, 'Please enter your channel name'],
22 | },
23 | collaborators: [
24 | {
25 | type: mongoose.Schema.Types.ObjectId,
26 | ref: 'User',
27 | },
28 | ],
29 | title: {
30 | type: String,
31 | default() {
32 | return `This is the very first begining of the ${this.name} channel`
33 | },
34 | },
35 | description: {
36 | type: String,
37 | },
38 | organisation: {
39 | type: mongoose.Schema.Types.ObjectId,
40 | ref: 'Organisation',
41 | },
42 | hasNotOpen: [
43 | {
44 | type: mongoose.Schema.Types.ObjectId,
45 | ref: 'User',
46 | },
47 | ],
48 | isChannel: {
49 | type: Boolean,
50 | default: true,
51 | },
52 | // conversations: [
53 | // {
54 | // type: mongoose.Schema.Types.ObjectId,
55 | // ref: "Message",
56 | // },
57 | // ],
58 | },
59 | {
60 | timestamps: true,
61 | versionKey: false,
62 | }
63 | )
64 |
65 | export default mongoose.model('Channel', channelSchema)
66 |
--------------------------------------------------------------------------------
/src/models/conversation.ts:
--------------------------------------------------------------------------------
1 | import mongoose from 'mongoose'
2 | import { UserSchemaType } from './user'
3 |
4 | export interface ConversationSchemaType {
5 | name: string
6 | collaborators: mongoose.Schema.Types.ObjectId[] & UserSchemaType[]
7 | description: string
8 | isSelf: boolean
9 | organisation: mongoose.Schema.Types.ObjectId
10 | createdBy: mongoose.Schema.Types.ObjectId
11 | hasNotOpen: mongoose.Schema.Types.ObjectId[]
12 | isConversation: boolean
13 | isOnline: boolean
14 | createdAt: Date
15 | updatedAt: Date
16 | }
17 |
18 | const conversationSchema = new mongoose.Schema(
19 | {
20 | name: {
21 | type: String,
22 | default() {
23 | if (this.createdBy) {
24 | return this.createdBy.username
25 | }
26 | return ''
27 | },
28 | },
29 | collaborators: [
30 | {
31 | type: mongoose.Schema.Types.ObjectId,
32 | ref: 'User',
33 | },
34 | ],
35 | description: {
36 | type: String,
37 | default() {
38 | return `This conversation is just between ${this.name} and you`
39 | },
40 | },
41 | isSelf: {
42 | type: Boolean,
43 | default: false,
44 | },
45 | isConversation: {
46 | type: Boolean,
47 | default: true,
48 | },
49 | organisation: {
50 | type: mongoose.Schema.Types.ObjectId,
51 | ref: 'Organisation',
52 | },
53 | createdBy: {
54 | type: mongoose.Schema.Types.ObjectId,
55 | ref: 'User',
56 | },
57 | isOnline: {
58 | type: Boolean,
59 | default: false,
60 | },
61 | hasNotOpen: [
62 | {
63 | type: mongoose.Schema.Types.ObjectId,
64 | ref: 'User',
65 | },
66 | ],
67 | },
68 | {
69 | timestamps: true,
70 | versionKey: false,
71 | }
72 | )
73 |
74 | // Define a compound index on the collaborators field
75 | conversationSchema.index({ collaborators: 1 })
76 |
77 | export default mongoose.model('Conversation', conversationSchema)
78 |
--------------------------------------------------------------------------------
/src/models/message.ts:
--------------------------------------------------------------------------------
1 | import mongoose from 'mongoose'
2 |
3 | interface MessageSchemaType {
4 | sender: mongoose.Schema.Types.ObjectId
5 | content: string
6 | channel: mongoose.Schema.Types.ObjectId
7 | organisation: mongoose.Schema.Types.ObjectId
8 | conversation: mongoose.Schema.Types.ObjectId
9 | collaborators: mongoose.Schema.Types.ObjectId[]
10 | reactions: {
11 | emoji: string
12 | reactedToBy: [mongoose.Schema.Types.ObjectId]
13 | }[]
14 | threadReplies: mongoose.Schema.Types.ObjectId[]
15 | threadRepliesCount: number
16 | threadLastReplyDate: Date
17 | isBookmarked: boolean
18 | isSelf: boolean
19 | hasRead: boolean
20 | type: string
21 | createdAt: Date
22 | updatedAt: Date
23 | }
24 |
25 | const messageSchema = new mongoose.Schema(
26 | {
27 | sender: {
28 | type: mongoose.Schema.Types.ObjectId,
29 | ref: 'User',
30 | },
31 | content: String,
32 | channel: {
33 | type: mongoose.Schema.Types.ObjectId,
34 | ref: 'Channel',
35 | },
36 | organisation: {
37 | type: mongoose.Schema.Types.ObjectId,
38 | ref: 'Organisation',
39 | },
40 | conversation: {
41 | type: mongoose.Schema.Types.ObjectId,
42 | ref: 'Conversation',
43 | },
44 | collaborators: [
45 | {
46 | type: mongoose.Schema.Types.ObjectId,
47 | ref: 'User',
48 | },
49 | ],
50 | reactions: [
51 | {
52 | emoji: String,
53 | reactedToBy: [
54 | {
55 | type: mongoose.Schema.Types.ObjectId,
56 | ref: 'User',
57 | },
58 | ],
59 | },
60 | ],
61 | threadReplies: [
62 | {
63 | type: mongoose.Schema.Types.ObjectId,
64 | ref: 'User',
65 | },
66 | ],
67 | threadRepliesCount: Number,
68 | threadLastReplyDate: Date,
69 | isBookmarked: {
70 | type: Boolean,
71 | default: false,
72 | },
73 | isSelf: {
74 | type: Boolean,
75 | default: false,
76 | },
77 | hasRead: {
78 | type: Boolean,
79 | default: false,
80 | },
81 | type: String,
82 | },
83 | {
84 | timestamps: true,
85 | versionKey: false,
86 | }
87 | )
88 |
89 | export default mongoose.model('Message', messageSchema)
90 |
--------------------------------------------------------------------------------
/src/models/organisation.ts:
--------------------------------------------------------------------------------
1 | import mongoose from 'mongoose'
2 | import { UserSchemaType } from './user'
3 |
4 | export interface OrganisationSchemaType {
5 | owner: mongoose.Schema.Types.ObjectId
6 | name: string
7 | hobbies: string[]
8 | coWorkers: mongoose.Schema.Types.ObjectId[] & UserSchemaType[]
9 | generateJoinLink: () => string
10 | joinLink: string
11 | url: string
12 | }
13 |
14 | const organisationSchema = new mongoose.Schema(
15 | {
16 | owner: {
17 | type: mongoose.Schema.Types.ObjectId,
18 | ref: 'User',
19 | },
20 | name: {
21 | type: String,
22 | },
23 | coWorkers: [
24 | {
25 | type: mongoose.Schema.Types.ObjectId,
26 | ref: 'User',
27 | },
28 | ],
29 | joinLink: String,
30 | url: String,
31 | },
32 | {
33 | timestamps: true,
34 | versionKey: false,
35 | }
36 | )
37 |
38 | // Generate orgnaisation join link
39 | organisationSchema.methods.generateJoinLink = function () {
40 | const url =
41 | process.env.NODE_ENV === 'development'
42 | ? process.env.STAGING_URL
43 | : process.env.PRODUCTION_URL
44 | this.joinLink = `${url}/${this._id}`
45 | this.url = `${url}/${this.name}`
46 | }
47 |
48 | export default mongoose.model('Organisation', organisationSchema)
49 |
--------------------------------------------------------------------------------
/src/models/thread.ts:
--------------------------------------------------------------------------------
1 | import mongoose from 'mongoose'
2 |
3 | interface ThreadSchemaType {
4 | sender: mongoose.Schema.Types.ObjectId
5 | content: string
6 | message: mongoose.Schema.Types.ObjectId
7 | reactions: {
8 | emoji: string
9 | reactedToBy: [mongoose.Schema.Types.ObjectId]
10 | }[]
11 | isBookmarked: boolean
12 | hasRead: boolean
13 | createdAt: Date
14 | updatedAt: Date
15 | }
16 |
17 | const threadSchema = new mongoose.Schema(
18 | {
19 | sender: {
20 | type: mongoose.Schema.Types.ObjectId,
21 | ref: 'User',
22 | },
23 | content: String,
24 | message: mongoose.Schema.Types.ObjectId,
25 | reactions: [
26 | {
27 | emoji: String,
28 | reactedToBy: [
29 | {
30 | type: mongoose.Schema.Types.ObjectId,
31 | ref: 'User',
32 | },
33 | ],
34 | },
35 | ],
36 | isBookmarked: {
37 | type: Boolean,
38 | default: false,
39 | },
40 | hasRead: {
41 | type: Boolean,
42 | default: false,
43 | },
44 | },
45 | {
46 | timestamps: true,
47 | versionKey: false,
48 | }
49 | )
50 |
51 | export default mongoose.model('Thread', threadSchema)
52 |
--------------------------------------------------------------------------------
/src/models/user.ts:
--------------------------------------------------------------------------------
1 | import mongoose from 'mongoose'
2 | import jwt from 'jsonwebtoken'
3 | import crypto from 'crypto'
4 | import randomize from 'randomatic'
5 |
6 | export interface UserSchemaType {
7 | _id: mongoose.Types.ObjectId
8 | username?: string
9 | email?: string
10 | role?: string
11 | phone?: string
12 | profilePicture?: string
13 | isOnline?: boolean
14 | loginVerificationCode?: string
15 | loginVerificationCodeExpires?: Date
16 | googleId?: string
17 | getSignedJwtToken?: () => string
18 | getVerificationCode?: () => string
19 | }
20 |
21 | const userSchema = new mongoose.Schema(
22 | {
23 | username: {
24 | type: String,
25 | default() {
26 | return this.email.split('@')[0]
27 | },
28 | },
29 | email: {
30 | type: String,
31 | required: [true, 'Please enter your email'],
32 | unique: true,
33 | match: [/^\w+@[a-zA-Z_]+?\.[a-zA-Z]{2,3}$/, 'Please enter a valid email'],
34 | },
35 | googleId: String,
36 | isOnline: Boolean,
37 | role: String,
38 | phone: String,
39 | profilePicture: String,
40 | loginVerificationCode: String,
41 | loginVerificationCodeExpires: Date,
42 | },
43 | {
44 | timestamps: true,
45 | versionKey: false,
46 | }
47 | )
48 |
49 | // Sign JWT and return
50 | userSchema.methods.getSignedJwtToken = function () {
51 | return jwt.sign({ id: this._id }, process.env.JWT_SECRET, {
52 | expiresIn: process.env.JWT_EXPIRE,
53 | })
54 | }
55 |
56 | // Generate login verification code
57 | userSchema.methods.getVerificationCode = function () {
58 | const verificationCode = randomize('Aa0', 6)
59 |
60 | this.loginVerificationCode = crypto
61 | .createHash('sha256')
62 | .update(verificationCode)
63 | .digest('hex')
64 |
65 | this.loginVerificationCodeExpires = Date.now() + 10 * 60 * 1000 // 10 minutes
66 |
67 | return verificationCode
68 | }
69 |
70 | export default mongoose.model('User', userSchema)
71 |
--------------------------------------------------------------------------------
/src/routes/auth.ts:
--------------------------------------------------------------------------------
1 | import express from 'express'
2 | import { googleCallback, register, signin, verify } from '../controllers/auth'
3 | import passport from 'passport'
4 |
5 | const router = express.Router()
6 |
7 | router.post('/register', register)
8 | router.post('/signin', signin)
9 | router.post('/verify', verify)
10 | router.get('/google', passport.authenticate('google', ['profile', 'email']))
11 |
12 | router.get('/google/callback', googleCallback)
13 |
14 | export default router
15 |
--------------------------------------------------------------------------------
/src/routes/channel.ts:
--------------------------------------------------------------------------------
1 | import express from 'express'
2 | import {
3 | createChannel,
4 | getChannel,
5 | updateChannel,
6 | } from '../controllers/channel'
7 | import { protect } from '../middleware/protect'
8 |
9 | const router = express.Router()
10 |
11 | router.post('/', protect, createChannel)
12 | router.get('/:id', protect, getChannel)
13 | router.post('/:id', protect, updateChannel)
14 |
15 | export default router
16 |
--------------------------------------------------------------------------------
/src/routes/conversations.ts:
--------------------------------------------------------------------------------
1 | import express from 'express'
2 | import { getConversation, getConversations } from '../controllers/conversations'
3 | import { protect } from '../middleware/protect'
4 |
5 | const router = express.Router()
6 |
7 | router.get('/', protect, getConversations)
8 | router.get('/:id', protect, getConversation)
9 |
10 | export default router
11 |
--------------------------------------------------------------------------------
/src/routes/message.ts:
--------------------------------------------------------------------------------
1 | import express from 'express'
2 | import { protect } from '../middleware/protect'
3 | import { getMessage, getMessages } from '../controllers/message'
4 |
5 | const router = express.Router()
6 |
7 | router.get('/', protect, getMessages)
8 | router.get('/:id', protect, getMessage)
9 |
10 | export default router
11 |
--------------------------------------------------------------------------------
/src/routes/organisation.ts:
--------------------------------------------------------------------------------
1 | import express from 'express'
2 | import { protect } from '../middleware/protect'
3 | import {
4 | createOrganisation,
5 | getOrganisation,
6 | getWorkspaces,
7 | } from '../controllers/organisation'
8 |
9 | const router = express.Router()
10 |
11 | router.get('/workspaces', protect, getWorkspaces)
12 | router.get('/:id', protect, getOrganisation)
13 | router.post('/', protect, createOrganisation)
14 |
15 | export default router
16 |
--------------------------------------------------------------------------------
/src/routes/teammates.ts:
--------------------------------------------------------------------------------
1 | import express from 'express'
2 | import { createTeammates, getTeammate } from '../controllers/teammates'
3 | import { protect } from '../middleware/protect'
4 |
5 | const router = express.Router()
6 |
7 | router.get('/:id', protect, getTeammate)
8 | router.post('/', protect, createTeammates)
9 |
10 | export default router
11 |
--------------------------------------------------------------------------------
/src/routes/thread.ts:
--------------------------------------------------------------------------------
1 | import express from 'express'
2 | import { protect } from '../middleware/protect'
3 | import { getThreads } from '../controllers/thread'
4 |
5 | const router = express.Router()
6 |
7 | router.get('/', protect, getThreads)
8 |
9 | export default router
10 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es6",
4 | "module": "commonjs",
5 | "pretty": true,
6 | "sourceMap": true,
7 | "outDir": "dist",
8 | "importHelpers": true,
9 | "strict": true,
10 | "noImplicitAny": false,
11 | "strictNullChecks": false,
12 | "noImplicitThis": true,
13 | "alwaysStrict": true,
14 | "noUnusedLocals": true,
15 | "noUnusedParameters": false,
16 | "noImplicitReturns": true,
17 | "noFallthroughCasesInSwitch": true,
18 | "moduleResolution": "node",
19 | "baseUrl": ".",
20 | "allowSyntheticDefaultImports": true,
21 | "experimentalDecorators": true,
22 | "emitDecoratorMetadata": true,
23 | "resolveJsonModule": true,
24 | "esModuleInterop": true,
25 | "lib": [
26 | "es5",
27 | "es6",
28 | "dom",
29 | "es2015.core",
30 | "es2015.collection",
31 | "es2015.generator",
32 | "es2015.iterable",
33 | "es2015.promise",
34 | "es2015.proxy",
35 | "es2015.reflect",
36 | "es2015.symbol",
37 | "es2015.symbol.wellknown",
38 | "esnext.asynciterable"
39 | ]
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "tslint:recommended",
3 | "rules": {
4 | "max-classes-per-file": false,
5 | "max-line-length": [true, 160],
6 | "no-unnecessary-initializer": false,
7 | "no-var-requires": true,
8 | "no-null-keyword": true,
9 | "no-consecutive-blank-lines": true,
10 | "quotemark": [true, "single", "avoid-escape"],
11 | "interface-name": false,
12 | "no-empty-interface": false,
13 | "no-namespace": false,
14 | "ordered-imports": false,
15 | "object-literal-sort-keys": false,
16 | "arrow-parens": false,
17 | "member-ordering": [
18 | true,
19 | {
20 | "order": [
21 | "public-static-field",
22 | "public-static-method",
23 | "protected-static-field",
24 | "protected-static-method",
25 | "private-static-field",
26 | "private-static-method",
27 | "public-instance-field",
28 | "protected-instance-field",
29 | "private-instance-field",
30 | "public-constructor",
31 | "protected-constructor",
32 | "private-constructor",
33 | "public-instance-method",
34 | "protected-instance-method",
35 | "private-instance-method"
36 | ]
37 | }
38 | ],
39 | "no-console": [true, "debug", "info", "time", "timeEnd", "trace"],
40 | "no-inferrable-types": [true, "ignore-params"],
41 | "no-switch-case-fall-through": true,
42 | "typedef": [true, "call-signature", "parameter"],
43 | "trailing-comma": [
44 | true,
45 | {
46 | "multiline": {
47 | "objects": "always",
48 | "arrays": "always",
49 | "functions": "never",
50 | "typeLiterals": "ignore"
51 | },
52 | "singleline": "never"
53 | }
54 | ],
55 | "align": [true, "parameters"],
56 | "class-name": true,
57 | "curly": true,
58 | "eofline": true,
59 | "jsdoc-format": true,
60 | "member-access": true,
61 | "no-arg": true,
62 | "no-construct": true,
63 | "no-duplicate-variable": true,
64 | "no-empty": true,
65 | "no-eval": true,
66 | "no-internal-module": true,
67 | "no-string-literal": true,
68 | "no-trailing-whitespace": true,
69 | "no-unused-expression": true,
70 | "no-var-keyword": true,
71 | "one-line": [
72 | true,
73 | "check-open-brace",
74 | "check-catch",
75 | "check-else",
76 | "check-finally",
77 | "check-whitespace"
78 | ],
79 | "semicolon": true,
80 | "switch-default": true,
81 | "triple-equals": [true, "allow-null-check"],
82 | "typedef-whitespace": [
83 | true,
84 | {
85 | "call-signature": "nospace",
86 | "index-signature": "nospace",
87 | "parameter": "nospace",
88 | "property-declaration": "nospace",
89 | "variable-declaration": "nospace"
90 | }
91 | ],
92 | "variable-name": false,
93 | "whitespace": [
94 | true,
95 | "check-branch",
96 | "check-decl",
97 | "check-operator",
98 | "check-separator",
99 | "check-type"
100 | ]
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/yarn.lock:
--------------------------------------------------------------------------------
1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2 | # yarn lockfile v1
3 |
4 |
5 | "@cspotcode/source-map-support@^0.8.0":
6 | version "0.8.1"
7 | resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1"
8 | integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==
9 | dependencies:
10 | "@jridgewell/trace-mapping" "0.3.9"
11 |
12 | "@jridgewell/resolve-uri@^3.0.3":
13 | version "3.1.1"
14 | resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz#c08679063f279615a3326583ba3a90d1d82cc721"
15 | integrity sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==
16 |
17 | "@jridgewell/sourcemap-codec@^1.4.10":
18 | version "1.4.15"
19 | resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32"
20 | integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==
21 |
22 | "@jridgewell/trace-mapping@0.3.9":
23 | version "0.3.9"
24 | resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9"
25 | integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==
26 | dependencies:
27 | "@jridgewell/resolve-uri" "^3.0.3"
28 | "@jridgewell/sourcemap-codec" "^1.4.10"
29 |
30 | "@socket.io/component-emitter@~3.1.0":
31 | version "3.1.0"
32 | resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz#96116f2a912e0c02817345b3c10751069920d553"
33 | integrity sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==
34 |
35 | "@tsconfig/node10@^1.0.7":
36 | version "1.0.9"
37 | resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.9.tgz#df4907fc07a886922637b15e02d4cebc4c0021b2"
38 | integrity sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==
39 |
40 | "@tsconfig/node12@^1.0.7":
41 | version "1.0.11"
42 | resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d"
43 | integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==
44 |
45 | "@tsconfig/node14@^1.0.0":
46 | version "1.0.3"
47 | resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1"
48 | integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==
49 |
50 | "@tsconfig/node16@^1.0.2":
51 | version "1.0.4"
52 | resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9"
53 | integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==
54 |
55 | "@types/cookie@^0.4.1":
56 | version "0.4.1"
57 | resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.4.1.tgz#bfd02c1f2224567676c1545199f87c3a861d878d"
58 | integrity sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==
59 |
60 | "@types/cors@^2.8.12":
61 | version "2.8.13"
62 | resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.13.tgz#b8ade22ba455a1b8cb3b5d3f35910fd204f84f94"
63 | integrity sha512-RG8AStHlUiV5ysZQKq97copd2UmVYw3/pRMLefISZ3S1hK104Cwm7iLQ3fTKx+lsUH2CE8FlLaYeEA2LSeqYUA==
64 | dependencies:
65 | "@types/node" "*"
66 |
67 | "@types/node@*":
68 | version "20.3.0"
69 | resolved "https://registry.yarnpkg.com/@types/node/-/node-20.3.0.tgz#719498898d5defab83c3560f45d8498f58d11938"
70 | integrity sha512-cumHmIAf6On83X7yP+LrsEyUOf/YlociZelmpRYaGFydoaPdxdt80MAbu6vWerQT2COCp2nPvHdsbD7tHn/YlQ==
71 |
72 | "@types/node@>=10.0.0":
73 | version "20.5.0"
74 | resolved "https://registry.yarnpkg.com/@types/node/-/node-20.5.0.tgz#7fc8636d5f1aaa3b21e6245e97d56b7f56702313"
75 | integrity sha512-Mgq7eCtoTjT89FqNoTzzXg2XvCi5VMhRV6+I2aYanc6kQCBImeNaAYRs/DyoVqk1YEUJK5gN9VO7HRIdz4Wo3Q==
76 |
77 | "@types/socket.io@^3.0.2":
78 | version "3.0.2"
79 | resolved "https://registry.yarnpkg.com/@types/socket.io/-/socket.io-3.0.2.tgz#606c9639e3f93bb8454cba8f5f0a283d47917759"
80 | integrity sha512-pu0sN9m5VjCxBZVK8hW37ZcMe8rjn4HHggBN5CbaRTvFwv5jOmuIRZEuddsBPa9Th0ts0SIo3Niukq+95cMBbQ==
81 | dependencies:
82 | socket.io "*"
83 |
84 | "@types/webidl-conversions@*":
85 | version "7.0.0"
86 | resolved "https://registry.yarnpkg.com/@types/webidl-conversions/-/webidl-conversions-7.0.0.tgz#2b8e60e33906459219aa587e9d1a612ae994cfe7"
87 | integrity sha512-xTE1E+YF4aWPJJeUzaZI5DRntlkY3+BCVJi0axFptnjGmAoWxkyREIh/XMrfxVLejwQxMCfDXdICo0VLxThrog==
88 |
89 | "@types/whatwg-url@^8.2.1":
90 | version "8.2.2"
91 | resolved "https://registry.yarnpkg.com/@types/whatwg-url/-/whatwg-url-8.2.2.tgz#749d5b3873e845897ada99be4448041d4cc39e63"
92 | integrity sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA==
93 | dependencies:
94 | "@types/node" "*"
95 | "@types/webidl-conversions" "*"
96 |
97 | abbrev@1:
98 | version "1.1.1"
99 | resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
100 | integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==
101 |
102 | accepts@~1.3.4, accepts@~1.3.8:
103 | version "1.3.8"
104 | resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e"
105 | integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==
106 | dependencies:
107 | mime-types "~2.1.34"
108 | negotiator "0.6.3"
109 |
110 | acorn-walk@^8.1.1:
111 | version "8.2.0"
112 | resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1"
113 | integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==
114 |
115 | acorn@^8.4.1:
116 | version "8.8.2"
117 | resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.2.tgz#1b2f25db02af965399b9776b0c2c391276d37c4a"
118 | integrity sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==
119 |
120 | anymatch@~3.1.2:
121 | version "3.1.3"
122 | resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e"
123 | integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==
124 | dependencies:
125 | normalize-path "^3.0.0"
126 | picomatch "^2.0.4"
127 |
128 | arg@^4.1.0:
129 | version "4.1.3"
130 | resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089"
131 | integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==
132 |
133 | array-flatten@1.1.1:
134 | version "1.1.1"
135 | resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
136 | integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==
137 |
138 | balanced-match@^1.0.0:
139 | version "1.0.2"
140 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
141 | integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
142 |
143 | base64id@2.0.0, base64id@~2.0.0:
144 | version "2.0.0"
145 | resolved "https://registry.yarnpkg.com/base64id/-/base64id-2.0.0.tgz#2770ac6bc47d312af97a8bf9a634342e0cd25cb6"
146 | integrity sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==
147 |
148 | base64url@3.x.x:
149 | version "3.0.1"
150 | resolved "https://registry.yarnpkg.com/base64url/-/base64url-3.0.1.tgz#6399d572e2bc3f90a9a8b22d5dbb0a32d33f788d"
151 | integrity sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==
152 |
153 | basic-auth@~2.0.1:
154 | version "2.0.1"
155 | resolved "https://registry.yarnpkg.com/basic-auth/-/basic-auth-2.0.1.tgz#b998279bf47ce38344b4f3cf916d4679bbf51e3a"
156 | integrity sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==
157 | dependencies:
158 | safe-buffer "5.1.2"
159 |
160 | binary-extensions@^2.0.0:
161 | version "2.2.0"
162 | resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d"
163 | integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==
164 |
165 | body-parser@1.20.1:
166 | version "1.20.1"
167 | resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.1.tgz#b1812a8912c195cd371a3ee5e66faa2338a5c668"
168 | integrity sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==
169 | dependencies:
170 | bytes "3.1.2"
171 | content-type "~1.0.4"
172 | debug "2.6.9"
173 | depd "2.0.0"
174 | destroy "1.2.0"
175 | http-errors "2.0.0"
176 | iconv-lite "0.4.24"
177 | on-finished "2.4.1"
178 | qs "6.11.0"
179 | raw-body "2.5.1"
180 | type-is "~1.6.18"
181 | unpipe "1.0.0"
182 |
183 | body-parser@^1.20.2:
184 | version "1.20.2"
185 | resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.2.tgz#6feb0e21c4724d06de7ff38da36dad4f57a747fd"
186 | integrity sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==
187 | dependencies:
188 | bytes "3.1.2"
189 | content-type "~1.0.5"
190 | debug "2.6.9"
191 | depd "2.0.0"
192 | destroy "1.2.0"
193 | http-errors "2.0.0"
194 | iconv-lite "0.4.24"
195 | on-finished "2.4.1"
196 | qs "6.11.0"
197 | raw-body "2.5.2"
198 | type-is "~1.6.18"
199 | unpipe "1.0.0"
200 |
201 | brace-expansion@^1.1.7:
202 | version "1.1.11"
203 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
204 | integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
205 | dependencies:
206 | balanced-match "^1.0.0"
207 | concat-map "0.0.1"
208 |
209 | braces@~3.0.2:
210 | version "3.0.2"
211 | resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
212 | integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
213 | dependencies:
214 | fill-range "^7.0.1"
215 |
216 | bson@^5.3.0:
217 | version "5.3.0"
218 | resolved "https://registry.yarnpkg.com/bson/-/bson-5.3.0.tgz#37b006df4cd91ed125cb686467c1dd6d4606b514"
219 | integrity sha512-ukmCZMneMlaC5ebPHXIkP8YJzNl5DC41N5MAIvKDqLggdao342t4McltoJBQfQya/nHBWAcSsYRqlXPoQkTJag==
220 |
221 | buffer-equal-constant-time@1.0.1:
222 | version "1.0.1"
223 | resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819"
224 | integrity sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==
225 |
226 | bytes@3.1.2:
227 | version "3.1.2"
228 | resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5"
229 | integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==
230 |
231 | call-bind@^1.0.0:
232 | version "1.0.2"
233 | resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c"
234 | integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==
235 | dependencies:
236 | function-bind "^1.1.1"
237 | get-intrinsic "^1.0.2"
238 |
239 | chokidar@^3.5.2:
240 | version "3.5.3"
241 | resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd"
242 | integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==
243 | dependencies:
244 | anymatch "~3.1.2"
245 | braces "~3.0.2"
246 | glob-parent "~5.1.2"
247 | is-binary-path "~2.1.0"
248 | is-glob "~4.0.1"
249 | normalize-path "~3.0.0"
250 | readdirp "~3.6.0"
251 | optionalDependencies:
252 | fsevents "~2.3.2"
253 |
254 | concat-map@0.0.1:
255 | version "0.0.1"
256 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
257 | integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
258 |
259 | content-disposition@0.5.4:
260 | version "0.5.4"
261 | resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe"
262 | integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==
263 | dependencies:
264 | safe-buffer "5.2.1"
265 |
266 | content-type@~1.0.4, content-type@~1.0.5:
267 | version "1.0.5"
268 | resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918"
269 | integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==
270 |
271 | cookie-parser@^1.4.6:
272 | version "1.4.6"
273 | resolved "https://registry.yarnpkg.com/cookie-parser/-/cookie-parser-1.4.6.tgz#3ac3a7d35a7a03bbc7e365073a26074824214594"
274 | integrity sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==
275 | dependencies:
276 | cookie "0.4.1"
277 | cookie-signature "1.0.6"
278 |
279 | cookie-session@^2.0.0:
280 | version "2.0.0"
281 | resolved "https://registry.yarnpkg.com/cookie-session/-/cookie-session-2.0.0.tgz#d07aa27822f43619e4342df1342268c849833089"
282 | integrity sha512-hKvgoThbw00zQOleSlUr2qpvuNweoqBtxrmx0UFosx6AGi9lYtLoA+RbsvknrEX8Pr6MDbdWAb2j6SnMn+lPsg==
283 | dependencies:
284 | cookies "0.8.0"
285 | debug "3.2.7"
286 | on-headers "~1.0.2"
287 | safe-buffer "5.2.1"
288 |
289 | cookie-signature@1.0.6:
290 | version "1.0.6"
291 | resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
292 | integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==
293 |
294 | cookie@0.4.1:
295 | version "0.4.1"
296 | resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.1.tgz#afd713fe26ebd21ba95ceb61f9a8116e50a537d1"
297 | integrity sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==
298 |
299 | cookie@0.4.2, cookie@~0.4.1:
300 | version "0.4.2"
301 | resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432"
302 | integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==
303 |
304 | cookie@0.5.0:
305 | version "0.5.0"
306 | resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b"
307 | integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==
308 |
309 | cookies@0.8.0:
310 | version "0.8.0"
311 | resolved "https://registry.yarnpkg.com/cookies/-/cookies-0.8.0.tgz#1293ce4b391740a8406e3c9870e828c4b54f3f90"
312 | integrity sha512-8aPsApQfebXnuI+537McwYsDtjVxGm8gTIzQI3FDW6t5t/DAhERxtnbEPN/8RX+uZthoz4eCOgloXaE5cYyNow==
313 | dependencies:
314 | depd "~2.0.0"
315 | keygrip "~1.1.0"
316 |
317 | cors@^2.8.5, cors@~2.8.5:
318 | version "2.8.5"
319 | resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29"
320 | integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==
321 | dependencies:
322 | object-assign "^4"
323 | vary "^1"
324 |
325 | create-require@^1.1.0:
326 | version "1.1.1"
327 | resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333"
328 | integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==
329 |
330 | debug@2.6.9:
331 | version "2.6.9"
332 | resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
333 | integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
334 | dependencies:
335 | ms "2.0.0"
336 |
337 | debug@3.2.7, debug@^3.2.7:
338 | version "3.2.7"
339 | resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a"
340 | integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==
341 | dependencies:
342 | ms "^2.1.1"
343 |
344 | debug@4.x, debug@~4.3.1, debug@~4.3.2:
345 | version "4.3.4"
346 | resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
347 | integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
348 | dependencies:
349 | ms "2.1.2"
350 |
351 | depd@2.0.0, depd@~2.0.0:
352 | version "2.0.0"
353 | resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df"
354 | integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==
355 |
356 | destroy@1.2.0:
357 | version "1.2.0"
358 | resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015"
359 | integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==
360 |
361 | diff@^4.0.1:
362 | version "4.0.2"
363 | resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
364 | integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==
365 |
366 | dotenv@^16.1.4:
367 | version "16.1.4"
368 | resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.1.4.tgz#67ac1a10cd9c25f5ba604e4e08bc77c0ebe0ca8c"
369 | integrity sha512-m55RtE8AsPeJBpOIFKihEmqUcoVncQIwo7x9U8ZwLEZw9ZpXboz2c+rvog+jUaJvVrZ5kBOeYQBX5+8Aa/OZQw==
370 |
371 | ecdsa-sig-formatter@1.0.11:
372 | version "1.0.11"
373 | resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf"
374 | integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==
375 | dependencies:
376 | safe-buffer "^5.0.1"
377 |
378 | ee-first@1.1.1:
379 | version "1.1.1"
380 | resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
381 | integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==
382 |
383 | encodeurl@~1.0.2:
384 | version "1.0.2"
385 | resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
386 | integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==
387 |
388 | engine.io-parser@~5.2.1:
389 | version "5.2.1"
390 | resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-5.2.1.tgz#9f213c77512ff1a6cc0c7a86108a7ffceb16fcfb"
391 | integrity sha512-9JktcM3u18nU9N2Lz3bWeBgxVgOKpw7yhRaoxQA3FUDZzzw+9WlA6p4G4u0RixNkg14fH7EfEc/RhpurtiROTQ==
392 |
393 | engine.io@~6.5.2:
394 | version "6.5.2"
395 | resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-6.5.2.tgz#769348ced9d56bd47bd83d308ec1c3375e85937c"
396 | integrity sha512-IXsMcGpw/xRfjra46sVZVHiSWo/nJ/3g1337q9KNXtS6YRzbW5yIzTCb9DjhrBe7r3GZQR0I4+nq+4ODk5g/cA==
397 | dependencies:
398 | "@types/cookie" "^0.4.1"
399 | "@types/cors" "^2.8.12"
400 | "@types/node" ">=10.0.0"
401 | accepts "~1.3.4"
402 | base64id "2.0.0"
403 | cookie "~0.4.1"
404 | cors "~2.8.5"
405 | debug "~4.3.1"
406 | engine.io-parser "~5.2.1"
407 | ws "~8.11.0"
408 |
409 | escape-html@~1.0.3:
410 | version "1.0.3"
411 | resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
412 | integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==
413 |
414 | etag@~1.8.1:
415 | version "1.8.1"
416 | resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
417 | integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==
418 |
419 | express-rate-limit@^6.7.0:
420 | version "6.7.0"
421 | resolved "https://registry.yarnpkg.com/express-rate-limit/-/express-rate-limit-6.7.0.tgz#6aa8a1bd63dfe79702267b3af1161a93afc1d3c2"
422 | integrity sha512-vhwIdRoqcYB/72TK3tRZI+0ttS8Ytrk24GfmsxDXK9o9IhHNO5bXRiXQSExPQ4GbaE5tvIS7j1SGrxsuWs+sGA==
423 |
424 | express-session@^1.17.3:
425 | version "1.17.3"
426 | resolved "https://registry.yarnpkg.com/express-session/-/express-session-1.17.3.tgz#14b997a15ed43e5949cb1d073725675dd2777f36"
427 | integrity sha512-4+otWXlShYlG1Ma+2Jnn+xgKUZTMJ5QD3YvfilX3AcocOAbIkVylSWEklzALe/+Pu4qV6TYBj5GwOBFfdKqLBw==
428 | dependencies:
429 | cookie "0.4.2"
430 | cookie-signature "1.0.6"
431 | debug "2.6.9"
432 | depd "~2.0.0"
433 | on-headers "~1.0.2"
434 | parseurl "~1.3.3"
435 | safe-buffer "5.2.1"
436 | uid-safe "~2.1.5"
437 |
438 | express@^4.18.2:
439 | version "4.18.2"
440 | resolved "https://registry.yarnpkg.com/express/-/express-4.18.2.tgz#3fabe08296e930c796c19e3c516979386ba9fd59"
441 | integrity sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==
442 | dependencies:
443 | accepts "~1.3.8"
444 | array-flatten "1.1.1"
445 | body-parser "1.20.1"
446 | content-disposition "0.5.4"
447 | content-type "~1.0.4"
448 | cookie "0.5.0"
449 | cookie-signature "1.0.6"
450 | debug "2.6.9"
451 | depd "2.0.0"
452 | encodeurl "~1.0.2"
453 | escape-html "~1.0.3"
454 | etag "~1.8.1"
455 | finalhandler "1.2.0"
456 | fresh "0.5.2"
457 | http-errors "2.0.0"
458 | merge-descriptors "1.0.1"
459 | methods "~1.1.2"
460 | on-finished "2.4.1"
461 | parseurl "~1.3.3"
462 | path-to-regexp "0.1.7"
463 | proxy-addr "~2.0.7"
464 | qs "6.11.0"
465 | range-parser "~1.2.1"
466 | safe-buffer "5.2.1"
467 | send "0.18.0"
468 | serve-static "1.15.0"
469 | setprototypeof "1.2.0"
470 | statuses "2.0.1"
471 | type-is "~1.6.18"
472 | utils-merge "1.0.1"
473 | vary "~1.1.2"
474 |
475 | fill-range@^7.0.1:
476 | version "7.0.1"
477 | resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
478 | integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==
479 | dependencies:
480 | to-regex-range "^5.0.1"
481 |
482 | finalhandler@1.2.0:
483 | version "1.2.0"
484 | resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.2.0.tgz#7d23fe5731b207b4640e4fcd00aec1f9207a7b32"
485 | integrity sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==
486 | dependencies:
487 | debug "2.6.9"
488 | encodeurl "~1.0.2"
489 | escape-html "~1.0.3"
490 | on-finished "2.4.1"
491 | parseurl "~1.3.3"
492 | statuses "2.0.1"
493 | unpipe "~1.0.0"
494 |
495 | forwarded@0.2.0:
496 | version "0.2.0"
497 | resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811"
498 | integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==
499 |
500 | fresh@0.5.2:
501 | version "0.5.2"
502 | resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
503 | integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==
504 |
505 | fsevents@~2.3.2:
506 | version "2.3.3"
507 | resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
508 | integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
509 |
510 | function-bind@^1.1.1:
511 | version "1.1.1"
512 | resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
513 | integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
514 |
515 | get-intrinsic@^1.0.2:
516 | version "1.2.1"
517 | resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.1.tgz#d295644fed4505fc9cde952c37ee12b477a83d82"
518 | integrity sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==
519 | dependencies:
520 | function-bind "^1.1.1"
521 | has "^1.0.3"
522 | has-proto "^1.0.1"
523 | has-symbols "^1.0.3"
524 |
525 | glob-parent@~5.1.2:
526 | version "5.1.2"
527 | resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
528 | integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
529 | dependencies:
530 | is-glob "^4.0.1"
531 |
532 | has-flag@^3.0.0:
533 | version "3.0.0"
534 | resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
535 | integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==
536 |
537 | has-proto@^1.0.1:
538 | version "1.0.1"
539 | resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.1.tgz#1885c1305538958aff469fef37937c22795408e0"
540 | integrity sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==
541 |
542 | has-symbols@^1.0.3:
543 | version "1.0.3"
544 | resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8"
545 | integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==
546 |
547 | has@^1.0.3:
548 | version "1.0.3"
549 | resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
550 | integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==
551 | dependencies:
552 | function-bind "^1.1.1"
553 |
554 | helmet@^7.0.0:
555 | version "7.0.0"
556 | resolved "https://registry.yarnpkg.com/helmet/-/helmet-7.0.0.tgz#ac3011ba82fa2467f58075afa58a49427ba6212d"
557 | integrity sha512-MsIgYmdBh460ZZ8cJC81q4XJknjG567wzEmv46WOBblDb6TUd3z8/GhgmsM9pn8g2B80tAJ4m5/d3Bi1KrSUBQ==
558 |
559 | hpp@^0.2.3:
560 | version "0.2.3"
561 | resolved "https://registry.yarnpkg.com/hpp/-/hpp-0.2.3.tgz#33bcc5fda713d2a962173c84f79a915a739b57bc"
562 | integrity sha512-4zDZypjQcxK/8pfFNR7jaON7zEUpXZxz4viyFmqjb3kWNWAHsLEUmWXcdn25c5l76ISvnD6hbOGO97cXUI3Ryw==
563 | dependencies:
564 | lodash "^4.17.12"
565 | type-is "^1.6.12"
566 |
567 | http-errors@2.0.0:
568 | version "2.0.0"
569 | resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3"
570 | integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==
571 | dependencies:
572 | depd "2.0.0"
573 | inherits "2.0.4"
574 | setprototypeof "1.2.0"
575 | statuses "2.0.1"
576 | toidentifier "1.0.1"
577 |
578 | iconv-lite@0.4.24:
579 | version "0.4.24"
580 | resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
581 | integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
582 | dependencies:
583 | safer-buffer ">= 2.1.2 < 3"
584 |
585 | ignore-by-default@^1.0.1:
586 | version "1.0.1"
587 | resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09"
588 | integrity sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==
589 |
590 | inherits@2.0.4:
591 | version "2.0.4"
592 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
593 | integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
594 |
595 | ip@^2.0.0:
596 | version "2.0.0"
597 | resolved "https://registry.yarnpkg.com/ip/-/ip-2.0.0.tgz#4cf4ab182fee2314c75ede1276f8c80b479936da"
598 | integrity sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==
599 |
600 | ipaddr.js@1.9.1:
601 | version "1.9.1"
602 | resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3"
603 | integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==
604 |
605 | is-binary-path@~2.1.0:
606 | version "2.1.0"
607 | resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09"
608 | integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==
609 | dependencies:
610 | binary-extensions "^2.0.0"
611 |
612 | is-extglob@^2.1.1:
613 | version "2.1.1"
614 | resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
615 | integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==
616 |
617 | is-glob@^4.0.1, is-glob@~4.0.1:
618 | version "4.0.3"
619 | resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084"
620 | integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
621 | dependencies:
622 | is-extglob "^2.1.1"
623 |
624 | is-number@^4.0.0:
625 | version "4.0.0"
626 | resolved "https://registry.yarnpkg.com/is-number/-/is-number-4.0.0.tgz#0026e37f5454d73e356dfe6564699867c6a7f0ff"
627 | integrity sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==
628 |
629 | is-number@^7.0.0:
630 | version "7.0.0"
631 | resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
632 | integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
633 |
634 | jsonwebtoken@^9.0.0:
635 | version "9.0.0"
636 | resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz#d0faf9ba1cc3a56255fe49c0961a67e520c1926d"
637 | integrity sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==
638 | dependencies:
639 | jws "^3.2.2"
640 | lodash "^4.17.21"
641 | ms "^2.1.1"
642 | semver "^7.3.8"
643 |
644 | jwa@^1.4.1:
645 | version "1.4.1"
646 | resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a"
647 | integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==
648 | dependencies:
649 | buffer-equal-constant-time "1.0.1"
650 | ecdsa-sig-formatter "1.0.11"
651 | safe-buffer "^5.0.1"
652 |
653 | jws@^3.2.2:
654 | version "3.2.2"
655 | resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304"
656 | integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==
657 | dependencies:
658 | jwa "^1.4.1"
659 | safe-buffer "^5.0.1"
660 |
661 | kareem@2.5.1:
662 | version "2.5.1"
663 | resolved "https://registry.yarnpkg.com/kareem/-/kareem-2.5.1.tgz#7b8203e11819a8e77a34b3517d3ead206764d15d"
664 | integrity sha512-7jFxRVm+jD+rkq3kY0iZDJfsO2/t4BBPeEb2qKn2lR/9KhuksYk5hxzfRYWMPV8P/x2d0kHD306YyWLzjjH+uA==
665 |
666 | keygrip@~1.1.0:
667 | version "1.1.0"
668 | resolved "https://registry.yarnpkg.com/keygrip/-/keygrip-1.1.0.tgz#871b1681d5e159c62a445b0c74b615e0917e7226"
669 | integrity sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==
670 | dependencies:
671 | tsscmp "1.0.6"
672 |
673 | kind-of@^6.0.0:
674 | version "6.0.3"
675 | resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd"
676 | integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==
677 |
678 | lodash@^4.17.12, lodash@^4.17.21:
679 | version "4.17.21"
680 | resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
681 | integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
682 |
683 | lru-cache@^6.0.0:
684 | version "6.0.0"
685 | resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"
686 | integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==
687 | dependencies:
688 | yallist "^4.0.0"
689 |
690 | make-error@^1.1.1:
691 | version "1.3.6"
692 | resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2"
693 | integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==
694 |
695 | math-random@^1.0.1:
696 | version "1.0.4"
697 | resolved "https://registry.yarnpkg.com/math-random/-/math-random-1.0.4.tgz#5dd6943c938548267016d4e34f057583080c514c"
698 | integrity sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A==
699 |
700 | media-typer@0.3.0:
701 | version "0.3.0"
702 | resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
703 | integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==
704 |
705 | memory-pager@^1.0.2:
706 | version "1.5.0"
707 | resolved "https://registry.yarnpkg.com/memory-pager/-/memory-pager-1.5.0.tgz#d8751655d22d384682741c972f2c3d6dfa3e66b5"
708 | integrity sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==
709 |
710 | merge-descriptors@1.0.1:
711 | version "1.0.1"
712 | resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
713 | integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==
714 |
715 | methods@~1.1.2:
716 | version "1.1.2"
717 | resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
718 | integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==
719 |
720 | mime-db@1.52.0:
721 | version "1.52.0"
722 | resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
723 | integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
724 |
725 | mime-types@~2.1.24, mime-types@~2.1.34:
726 | version "2.1.35"
727 | resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
728 | integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
729 | dependencies:
730 | mime-db "1.52.0"
731 |
732 | mime@1.6.0:
733 | version "1.6.0"
734 | resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
735 | integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
736 |
737 | minimatch@^3.1.2:
738 | version "3.1.2"
739 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
740 | integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
741 | dependencies:
742 | brace-expansion "^1.1.7"
743 |
744 | mongodb-connection-string-url@^2.6.0:
745 | version "2.6.0"
746 | resolved "https://registry.yarnpkg.com/mongodb-connection-string-url/-/mongodb-connection-string-url-2.6.0.tgz#57901bf352372abdde812c81be47b75c6b2ec5cf"
747 | integrity sha512-WvTZlI9ab0QYtTYnuMLgobULWhokRjtC7db9LtcVfJ+Hsnyr5eo6ZtNAt3Ly24XZScGMelOcGtm7lSn0332tPQ==
748 | dependencies:
749 | "@types/whatwg-url" "^8.2.1"
750 | whatwg-url "^11.0.0"
751 |
752 | mongodb@5.5.0:
753 | version "5.5.0"
754 | resolved "https://registry.yarnpkg.com/mongodb/-/mongodb-5.5.0.tgz#5d213ef68f6b48610909d98d537059f2d7f374a1"
755 | integrity sha512-XgrkUgAAdfnZKQfk5AsYL8j7O99WHd4YXPxYxnh8dZxD+ekYWFRA3JktUsBnfg+455Smf75/+asoU/YLwNGoQQ==
756 | dependencies:
757 | bson "^5.3.0"
758 | mongodb-connection-string-url "^2.6.0"
759 | socks "^2.7.1"
760 | optionalDependencies:
761 | saslprep "^1.0.3"
762 |
763 | mongodb@^5.6.0:
764 | version "5.6.0"
765 | resolved "https://registry.yarnpkg.com/mongodb/-/mongodb-5.6.0.tgz#caff5278341bfc0f1ef6f394bb403d207de03d1e"
766 | integrity sha512-z8qVs9NfobHJm6uzK56XBZF8XwM9H294iRnB7wNjF0SnY93si5HPziIJn+qqvUR5QOff/4L0gCD6SShdR/GtVQ==
767 | dependencies:
768 | bson "^5.3.0"
769 | mongodb-connection-string-url "^2.6.0"
770 | socks "^2.7.1"
771 | optionalDependencies:
772 | saslprep "^1.0.3"
773 |
774 | mongoose@^7.2.4:
775 | version "7.2.4"
776 | resolved "https://registry.yarnpkg.com/mongoose/-/mongoose-7.2.4.tgz#0cbccd23746c5bd82a4330e83ea2b2c52e03c044"
777 | integrity sha512-BWcgShV2WH1rspICiJKLPi7QssTebpGJ23Nyk7qG0TMEE/OEAlsQKEhI7VlrXg4ZnoOcHgG+N+upW9tj17TTQg==
778 | dependencies:
779 | bson "^5.3.0"
780 | kareem "2.5.1"
781 | mongodb "5.5.0"
782 | mpath "0.9.0"
783 | mquery "5.0.0"
784 | ms "2.1.3"
785 | sift "16.0.1"
786 |
787 | morgan@^1.10.0:
788 | version "1.10.0"
789 | resolved "https://registry.yarnpkg.com/morgan/-/morgan-1.10.0.tgz#091778abc1fc47cd3509824653dae1faab6b17d7"
790 | integrity sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==
791 | dependencies:
792 | basic-auth "~2.0.1"
793 | debug "2.6.9"
794 | depd "~2.0.0"
795 | on-finished "~2.3.0"
796 | on-headers "~1.0.2"
797 |
798 | mpath@0.9.0:
799 | version "0.9.0"
800 | resolved "https://registry.yarnpkg.com/mpath/-/mpath-0.9.0.tgz#0c122fe107846e31fc58c75b09c35514b3871904"
801 | integrity sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==
802 |
803 | mquery@5.0.0:
804 | version "5.0.0"
805 | resolved "https://registry.yarnpkg.com/mquery/-/mquery-5.0.0.tgz#a95be5dfc610b23862df34a47d3e5d60e110695d"
806 | integrity sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==
807 | dependencies:
808 | debug "4.x"
809 |
810 | ms@2.0.0:
811 | version "2.0.0"
812 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
813 | integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==
814 |
815 | ms@2.1.2:
816 | version "2.1.2"
817 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
818 | integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
819 |
820 | ms@2.1.3, ms@^2.1.1:
821 | version "2.1.3"
822 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
823 | integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
824 |
825 | negotiator@0.6.3:
826 | version "0.6.3"
827 | resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd"
828 | integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==
829 |
830 | nodemailer@^6.9.3:
831 | version "6.9.3"
832 | resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.9.3.tgz#e4425b85f05d83c43c5cd81bf84ab968f8ef5cbe"
833 | integrity sha512-fy9v3NgTzBngrMFkDsKEj0r02U7jm6XfC3b52eoNV+GCrGj+s8pt5OqhiJdWKuw51zCTdiNR/IUD1z33LIIGpg==
834 |
835 | nodemon@^3.0.1:
836 | version "3.0.1"
837 | resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-3.0.1.tgz#affe822a2c5f21354466b2fc8ae83277d27dadc7"
838 | integrity sha512-g9AZ7HmkhQkqXkRc20w+ZfQ73cHLbE8hnPbtaFbFtCumZsjyMhKk9LajQ07U5Ux28lvFjZ5X7HvWR1xzU8jHVw==
839 | dependencies:
840 | chokidar "^3.5.2"
841 | debug "^3.2.7"
842 | ignore-by-default "^1.0.1"
843 | minimatch "^3.1.2"
844 | pstree.remy "^1.1.8"
845 | semver "^7.5.3"
846 | simple-update-notifier "^2.0.0"
847 | supports-color "^5.5.0"
848 | touch "^3.1.0"
849 | undefsafe "^2.0.5"
850 |
851 | nopt@~1.0.10:
852 | version "1.0.10"
853 | resolved "https://registry.yarnpkg.com/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee"
854 | integrity sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==
855 | dependencies:
856 | abbrev "1"
857 |
858 | normalize-path@^3.0.0, normalize-path@~3.0.0:
859 | version "3.0.0"
860 | resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
861 | integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
862 |
863 | oauth@0.9.x:
864 | version "0.9.15"
865 | resolved "https://registry.yarnpkg.com/oauth/-/oauth-0.9.15.tgz#bd1fefaf686c96b75475aed5196412ff60cfb9c1"
866 | integrity sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA==
867 |
868 | object-assign@^4:
869 | version "4.1.1"
870 | resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
871 | integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==
872 |
873 | object-inspect@^1.9.0:
874 | version "1.12.3"
875 | resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.3.tgz#ba62dffd67ee256c8c086dfae69e016cd1f198b9"
876 | integrity sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==
877 |
878 | on-finished@2.4.1:
879 | version "2.4.1"
880 | resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f"
881 | integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==
882 | dependencies:
883 | ee-first "1.1.1"
884 |
885 | on-finished@~2.3.0:
886 | version "2.3.0"
887 | resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947"
888 | integrity sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==
889 | dependencies:
890 | ee-first "1.1.1"
891 |
892 | on-headers@~1.0.2:
893 | version "1.0.2"
894 | resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f"
895 | integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==
896 |
897 | parseurl@~1.3.3:
898 | version "1.3.3"
899 | resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
900 | integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==
901 |
902 | passport-google-oauth20@^2.0.0:
903 | version "2.0.0"
904 | resolved "https://registry.yarnpkg.com/passport-google-oauth20/-/passport-google-oauth20-2.0.0.tgz#0d241b2d21ebd3dc7f2b60669ec4d587e3a674ef"
905 | integrity sha512-KSk6IJ15RoxuGq7D1UKK/8qKhNfzbLeLrG3gkLZ7p4A6DBCcv7xpyQwuXtWdpyR0+E0mwkpjY1VfPOhxQrKzdQ==
906 | dependencies:
907 | passport-oauth2 "1.x.x"
908 |
909 | passport-oauth2@1.x.x:
910 | version "1.7.0"
911 | resolved "https://registry.yarnpkg.com/passport-oauth2/-/passport-oauth2-1.7.0.tgz#5c4766c8531ac45ffe9ec2c09de9809e2c841fc4"
912 | integrity sha512-j2gf34szdTF2Onw3+76alNnaAExlUmHvkc7cL+cmaS5NzHzDP/BvFHJruueQ9XAeNOdpI+CH+PWid8RA7KCwAQ==
913 | dependencies:
914 | base64url "3.x.x"
915 | oauth "0.9.x"
916 | passport-strategy "1.x.x"
917 | uid2 "0.0.x"
918 | utils-merge "1.x.x"
919 |
920 | passport-strategy@1.x.x:
921 | version "1.0.0"
922 | resolved "https://registry.yarnpkg.com/passport-strategy/-/passport-strategy-1.0.0.tgz#b5539aa8fc225a3d1ad179476ddf236b440f52e4"
923 | integrity sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==
924 |
925 | passport@0.5:
926 | version "0.5.3"
927 | resolved "https://registry.yarnpkg.com/passport/-/passport-0.5.3.tgz#e69b46c9bb3290660bc2b3299330d78710b198cc"
928 | integrity sha512-gGc+70h4gGdBWNsR3FuV3byLDY6KBTJAIExGFXTpQaYfbbcHCBlRRKx7RBQSpqEqc5Hh2qVzRs7ssvSfOpkUEA==
929 | dependencies:
930 | passport-strategy "1.x.x"
931 | pause "0.0.1"
932 |
933 | path-to-regexp@0.1.7:
934 | version "0.1.7"
935 | resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
936 | integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==
937 |
938 | pause@0.0.1:
939 | version "0.0.1"
940 | resolved "https://registry.yarnpkg.com/pause/-/pause-0.0.1.tgz#1d408b3fdb76923b9543d96fb4c9dfd535d9cb5d"
941 | integrity sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==
942 |
943 | picomatch@^2.0.4, picomatch@^2.2.1:
944 | version "2.3.1"
945 | resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
946 | integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
947 |
948 | proxy-addr@~2.0.7:
949 | version "2.0.7"
950 | resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025"
951 | integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==
952 | dependencies:
953 | forwarded "0.2.0"
954 | ipaddr.js "1.9.1"
955 |
956 | pstree.remy@^1.1.8:
957 | version "1.1.8"
958 | resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.8.tgz#c242224f4a67c21f686839bbdb4ac282b8373d3a"
959 | integrity sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==
960 |
961 | punycode@^2.1.1:
962 | version "2.3.0"
963 | resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f"
964 | integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==
965 |
966 | qs@6.11.0:
967 | version "6.11.0"
968 | resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a"
969 | integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==
970 | dependencies:
971 | side-channel "^1.0.4"
972 |
973 | random-bytes@~1.0.0:
974 | version "1.0.0"
975 | resolved "https://registry.yarnpkg.com/random-bytes/-/random-bytes-1.0.0.tgz#4f68a1dc0ae58bd3fb95848c30324db75d64360b"
976 | integrity sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==
977 |
978 | randomatic@^3.1.1:
979 | version "3.1.1"
980 | resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-3.1.1.tgz#b776efc59375984e36c537b2f51a1f0aff0da1ed"
981 | integrity sha512-TuDE5KxZ0J461RVjrJZCJc+J+zCkTb1MbH9AQUq68sMhOMcy9jLcb3BrZKgp9q9Ncltdg4QVqWrH02W2EFFVYw==
982 | dependencies:
983 | is-number "^4.0.0"
984 | kind-of "^6.0.0"
985 | math-random "^1.0.1"
986 |
987 | range-parser@~1.2.1:
988 | version "1.2.1"
989 | resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031"
990 | integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==
991 |
992 | raw-body@2.5.1:
993 | version "2.5.1"
994 | resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.1.tgz#fe1b1628b181b700215e5fd42389f98b71392857"
995 | integrity sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==
996 | dependencies:
997 | bytes "3.1.2"
998 | http-errors "2.0.0"
999 | iconv-lite "0.4.24"
1000 | unpipe "1.0.0"
1001 |
1002 | raw-body@2.5.2:
1003 | version "2.5.2"
1004 | resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.2.tgz#99febd83b90e08975087e8f1f9419a149366b68a"
1005 | integrity sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==
1006 | dependencies:
1007 | bytes "3.1.2"
1008 | http-errors "2.0.0"
1009 | iconv-lite "0.4.24"
1010 | unpipe "1.0.0"
1011 |
1012 | readdirp@~3.6.0:
1013 | version "3.6.0"
1014 | resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7"
1015 | integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==
1016 | dependencies:
1017 | picomatch "^2.2.1"
1018 |
1019 | safe-buffer@5.1.2:
1020 | version "5.1.2"
1021 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
1022 | integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
1023 |
1024 | safe-buffer@5.2.1, safe-buffer@^5.0.1:
1025 | version "5.2.1"
1026 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
1027 | integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
1028 |
1029 | "safer-buffer@>= 2.1.2 < 3":
1030 | version "2.1.2"
1031 | resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
1032 | integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
1033 |
1034 | saslprep@^1.0.3:
1035 | version "1.0.3"
1036 | resolved "https://registry.yarnpkg.com/saslprep/-/saslprep-1.0.3.tgz#4c02f946b56cf54297e347ba1093e7acac4cf226"
1037 | integrity sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==
1038 | dependencies:
1039 | sparse-bitfield "^3.0.3"
1040 |
1041 | semver@^7.3.8:
1042 | version "7.5.1"
1043 | resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.1.tgz#c90c4d631cf74720e46b21c1d37ea07edfab91ec"
1044 | integrity sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==
1045 | dependencies:
1046 | lru-cache "^6.0.0"
1047 |
1048 | semver@^7.5.3:
1049 | version "7.5.4"
1050 | resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e"
1051 | integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==
1052 | dependencies:
1053 | lru-cache "^6.0.0"
1054 |
1055 | send@0.18.0:
1056 | version "0.18.0"
1057 | resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be"
1058 | integrity sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==
1059 | dependencies:
1060 | debug "2.6.9"
1061 | depd "2.0.0"
1062 | destroy "1.2.0"
1063 | encodeurl "~1.0.2"
1064 | escape-html "~1.0.3"
1065 | etag "~1.8.1"
1066 | fresh "0.5.2"
1067 | http-errors "2.0.0"
1068 | mime "1.6.0"
1069 | ms "2.1.3"
1070 | on-finished "2.4.1"
1071 | range-parser "~1.2.1"
1072 | statuses "2.0.1"
1073 |
1074 | serve-static@1.15.0:
1075 | version "1.15.0"
1076 | resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540"
1077 | integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==
1078 | dependencies:
1079 | encodeurl "~1.0.2"
1080 | escape-html "~1.0.3"
1081 | parseurl "~1.3.3"
1082 | send "0.18.0"
1083 |
1084 | setprototypeof@1.2.0:
1085 | version "1.2.0"
1086 | resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424"
1087 | integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==
1088 |
1089 | side-channel@^1.0.4:
1090 | version "1.0.4"
1091 | resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf"
1092 | integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==
1093 | dependencies:
1094 | call-bind "^1.0.0"
1095 | get-intrinsic "^1.0.2"
1096 | object-inspect "^1.9.0"
1097 |
1098 | sift@16.0.1:
1099 | version "16.0.1"
1100 | resolved "https://registry.yarnpkg.com/sift/-/sift-16.0.1.tgz#e9c2ccc72191585008cf3e36fc447b2d2633a053"
1101 | integrity sha512-Wv6BjQ5zbhW7VFefWusVP33T/EM0vYikCaQ2qR8yULbsilAT8/wQaXvuQ3ptGLpoKx+lihJE3y2UTgKDyyNHZQ==
1102 |
1103 | simple-update-notifier@^2.0.0:
1104 | version "2.0.0"
1105 | resolved "https://registry.yarnpkg.com/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz#d70b92bdab7d6d90dfd73931195a30b6e3d7cebb"
1106 | integrity sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==
1107 | dependencies:
1108 | semver "^7.5.3"
1109 |
1110 | smart-buffer@^4.2.0:
1111 | version "4.2.0"
1112 | resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae"
1113 | integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==
1114 |
1115 | socket.io-adapter@~2.5.2:
1116 | version "2.5.2"
1117 | resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-2.5.2.tgz#5de9477c9182fdc171cd8c8364b9a8894ec75d12"
1118 | integrity sha512-87C3LO/NOMc+eMcpcxUBebGjkpMDkNBS9tf7KJqcDsmL936EChtVva71Dw2q4tQcuVC+hAUy4an2NO/sYXmwRA==
1119 | dependencies:
1120 | ws "~8.11.0"
1121 |
1122 | socket.io-parser@~4.2.4:
1123 | version "4.2.4"
1124 | resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-4.2.4.tgz#c806966cf7270601e47469ddeec30fbdfda44c83"
1125 | integrity sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==
1126 | dependencies:
1127 | "@socket.io/component-emitter" "~3.1.0"
1128 | debug "~4.3.1"
1129 |
1130 | socket.io@*, socket.io@^4.7.2:
1131 | version "4.7.2"
1132 | resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-4.7.2.tgz#22557d76c3f3ca48f82e73d68b7add36a22df002"
1133 | integrity sha512-bvKVS29/I5fl2FGLNHuXlQaUH/BlzX1IN6S+NKLNZpBsPZIDH+90eQmCs2Railn4YUiww4SzUedJ6+uzwFnKLw==
1134 | dependencies:
1135 | accepts "~1.3.4"
1136 | base64id "~2.0.0"
1137 | cors "~2.8.5"
1138 | debug "~4.3.2"
1139 | engine.io "~6.5.2"
1140 | socket.io-adapter "~2.5.2"
1141 | socket.io-parser "~4.2.4"
1142 |
1143 | socks@^2.7.1:
1144 | version "2.7.1"
1145 | resolved "https://registry.yarnpkg.com/socks/-/socks-2.7.1.tgz#d8e651247178fde79c0663043e07240196857d55"
1146 | integrity sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==
1147 | dependencies:
1148 | ip "^2.0.0"
1149 | smart-buffer "^4.2.0"
1150 |
1151 | sparse-bitfield@^3.0.3:
1152 | version "3.0.3"
1153 | resolved "https://registry.yarnpkg.com/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz#ff4ae6e68656056ba4b3e792ab3334d38273ca11"
1154 | integrity sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==
1155 | dependencies:
1156 | memory-pager "^1.0.2"
1157 |
1158 | statuses@2.0.1:
1159 | version "2.0.1"
1160 | resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63"
1161 | integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==
1162 |
1163 | supports-color@^5.5.0:
1164 | version "5.5.0"
1165 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
1166 | integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
1167 | dependencies:
1168 | has-flag "^3.0.0"
1169 |
1170 | to-regex-range@^5.0.1:
1171 | version "5.0.1"
1172 | resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
1173 | integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==
1174 | dependencies:
1175 | is-number "^7.0.0"
1176 |
1177 | toidentifier@1.0.1:
1178 | version "1.0.1"
1179 | resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35"
1180 | integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==
1181 |
1182 | touch@^3.1.0:
1183 | version "3.1.0"
1184 | resolved "https://registry.yarnpkg.com/touch/-/touch-3.1.0.tgz#fe365f5f75ec9ed4e56825e0bb76d24ab74af83b"
1185 | integrity sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==
1186 | dependencies:
1187 | nopt "~1.0.10"
1188 |
1189 | tr46@^3.0.0:
1190 | version "3.0.0"
1191 | resolved "https://registry.yarnpkg.com/tr46/-/tr46-3.0.0.tgz#555c4e297a950617e8eeddef633c87d4d9d6cbf9"
1192 | integrity sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==
1193 | dependencies:
1194 | punycode "^2.1.1"
1195 |
1196 | ts-node@^10.9.1:
1197 | version "10.9.1"
1198 | resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.1.tgz#e73de9102958af9e1f0b168a6ff320e25adcff4b"
1199 | integrity sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==
1200 | dependencies:
1201 | "@cspotcode/source-map-support" "^0.8.0"
1202 | "@tsconfig/node10" "^1.0.7"
1203 | "@tsconfig/node12" "^1.0.7"
1204 | "@tsconfig/node14" "^1.0.0"
1205 | "@tsconfig/node16" "^1.0.2"
1206 | acorn "^8.4.1"
1207 | acorn-walk "^8.1.1"
1208 | arg "^4.1.0"
1209 | create-require "^1.1.0"
1210 | diff "^4.0.1"
1211 | make-error "^1.1.1"
1212 | v8-compile-cache-lib "^3.0.1"
1213 | yn "3.1.1"
1214 |
1215 | tslib@^2.5.3:
1216 | version "2.5.3"
1217 | resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.3.tgz#24944ba2d990940e6e982c4bea147aba80209913"
1218 | integrity sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==
1219 |
1220 | tsscmp@1.0.6:
1221 | version "1.0.6"
1222 | resolved "https://registry.yarnpkg.com/tsscmp/-/tsscmp-1.0.6.tgz#85b99583ac3589ec4bfef825b5000aa911d605eb"
1223 | integrity sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==
1224 |
1225 | type-is@^1.6.12, type-is@~1.6.18:
1226 | version "1.6.18"
1227 | resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131"
1228 | integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==
1229 | dependencies:
1230 | media-typer "0.3.0"
1231 | mime-types "~2.1.24"
1232 |
1233 | typescript@^5.1.3:
1234 | version "5.1.3"
1235 | resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.1.3.tgz#8d84219244a6b40b6fb2b33cc1c062f715b9e826"
1236 | integrity sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw==
1237 |
1238 | uid-safe@~2.1.5:
1239 | version "2.1.5"
1240 | resolved "https://registry.yarnpkg.com/uid-safe/-/uid-safe-2.1.5.tgz#2b3d5c7240e8fc2e58f8aa269e5ee49c0857bd3a"
1241 | integrity sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==
1242 | dependencies:
1243 | random-bytes "~1.0.0"
1244 |
1245 | uid2@0.0.x:
1246 | version "0.0.4"
1247 | resolved "https://registry.yarnpkg.com/uid2/-/uid2-0.0.4.tgz#033f3b1d5d32505f5ce5f888b9f3b667123c0a44"
1248 | integrity sha512-IevTus0SbGwQzYh3+fRsAMTVVPOoIVufzacXcHPmdlle1jUpq7BRL+mw3dgeLanvGZdwwbWhRV6XrcFNdBmjWA==
1249 |
1250 | undefsafe@^2.0.5:
1251 | version "2.0.5"
1252 | resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c"
1253 | integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==
1254 |
1255 | unpipe@1.0.0, unpipe@~1.0.0:
1256 | version "1.0.0"
1257 | resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
1258 | integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==
1259 |
1260 | utils-merge@1.0.1, utils-merge@1.x.x:
1261 | version "1.0.1"
1262 | resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
1263 | integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==
1264 |
1265 | uuid@^9.0.0:
1266 | version "9.0.0"
1267 | resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.0.tgz#592f550650024a38ceb0c562f2f6aa435761efb5"
1268 | integrity sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==
1269 |
1270 | v8-compile-cache-lib@^3.0.1:
1271 | version "3.0.1"
1272 | resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf"
1273 | integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==
1274 |
1275 | vary@^1, vary@~1.1.2:
1276 | version "1.1.2"
1277 | resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
1278 | integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==
1279 |
1280 | webidl-conversions@^7.0.0:
1281 | version "7.0.0"
1282 | resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a"
1283 | integrity sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==
1284 |
1285 | whatwg-url@^11.0.0:
1286 | version "11.0.0"
1287 | resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-11.0.0.tgz#0a849eebb5faf2119b901bb76fd795c2848d4018"
1288 | integrity sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==
1289 | dependencies:
1290 | tr46 "^3.0.0"
1291 | webidl-conversions "^7.0.0"
1292 |
1293 | ws@~8.11.0:
1294 | version "8.11.0"
1295 | resolved "https://registry.yarnpkg.com/ws/-/ws-8.11.0.tgz#6a0d36b8edfd9f96d8b25683db2f8d7de6e8e143"
1296 | integrity sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==
1297 |
1298 | xss-clean@^0.1.4:
1299 | version "0.1.4"
1300 | resolved "https://registry.yarnpkg.com/xss-clean/-/xss-clean-0.1.4.tgz#592b7f59cc52d39d4b8c397787243817639fb73d"
1301 | integrity sha512-4hArTFHYxrifK9VXOu/zFvwjTXVjKByPi6woUHb1IqxlX0Z4xtFBRjOhTKuYV/uE1VswbYsIh5vUEYp7MmoISQ==
1302 | dependencies:
1303 | xss-filters "1.2.7"
1304 |
1305 | xss-filters@1.2.7:
1306 | version "1.2.7"
1307 | resolved "https://registry.yarnpkg.com/xss-filters/-/xss-filters-1.2.7.tgz#59fa1de201f36f2f3470dcac5f58ccc2830b0a9a"
1308 | integrity sha512-KzcmYT/f+YzcYrYRqw6mXxd25BEZCxBQnf+uXTopQDIhrmiaLwO+f+yLsIvvNlPhYvgff8g3igqrBxYh9k8NbQ==
1309 |
1310 | yallist@^4.0.0:
1311 | version "4.0.0"
1312 | resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
1313 | integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
1314 |
1315 | yn@3.1.1:
1316 | version "3.1.1"
1317 | resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50"
1318 | integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==
1319 |
--------------------------------------------------------------------------------