diff --git a/.gitignore b/.gitignore index 96fab4fe..63cf0569 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,8 @@ build dist +secrets/ + # Debug npm-debug.log* yarn-debug.log* diff --git a/platforms/blabsy-api/src/controllers/CommentController.ts b/platforms/blabsy-api/src/controllers/CommentController.ts deleted file mode 100644 index d3e21ed8..00000000 --- a/platforms/blabsy-api/src/controllers/CommentController.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { Request, Response } from "express"; -import { CommentService } from "../services/CommentService"; - -export class CommentController { - private commentService: CommentService; - - constructor() { - this.commentService = new CommentService(); - } - - createComment = async (req: Request, res: Response) => { - try { - const { blabId, text } = req.body; - const userId = req.user?.id; - - if (!userId) { - return res.status(401).json({ error: "Unauthorized" }); - } - - const reply = await this.commentService.createComment(blabId, userId, text); - res.status(201).json(reply); - } catch (error) { - console.error("Error creating reply:", error); - res.status(500).json({ error: "Internal server error" }); - } - }; - - getPostComments = async (req: Request, res: Response) => { - try { - const { blabId } = req.params; - const replies = await this.commentService.getPostComments(blabId); - res.json(replies); - } catch (error) { - console.error("Error fetching replies:", error); - res.status(500).json({ error: "Internal server error" }); - } - }; - - updateComment = async (req: Request, res: Response) => { - try { - const { id } = req.params; - const { text } = req.body; - const userId = req.user?.id; - - if (!userId) { - return res.status(401).json({ error: "Unauthorized" }); - } - - const reply = await this.commentService.getCommentById(id); - - if (!reply) { - return res.status(404).json({ error: "Reply not found" }); - } - - if (reply.creator.id !== userId) { - return res.status(403).json({ error: "Forbidden" }); - } - - const updatedReply = await this.commentService.updateComment(id, text); - res.json(updatedReply); - } catch (error) { - console.error("Error updating reply:", error); - res.status(500).json({ error: "Internal server error" }); - } - }; - - deleteComment = async (req: Request, res: Response) => { - try { - const { id } = req.params; - const userId = req.user?.id; - - if (!userId) { - return res.status(401).json({ error: "Unauthorized" }); - } - - const reply = await this.commentService.getCommentById(id); - - if (!reply) { - return res.status(404).json({ error: "Reply not found" }); - } - - if (reply.creator.id !== userId) { - return res.status(403).json({ error: "Forbidden" }); - } - - await this.commentService.deleteComment(id); - res.status(204).send(); - } catch (error) { - console.error("Error deleting reply:", error); - res.status(500).json({ error: "Internal server error" }); - } - }; -} \ No newline at end of file diff --git a/platforms/blabsy-api/src/controllers/MessageController.ts b/platforms/blabsy-api/src/controllers/MessageController.ts deleted file mode 100644 index 1b071183..00000000 --- a/platforms/blabsy-api/src/controllers/MessageController.ts +++ /dev/null @@ -1,340 +0,0 @@ -import { Request, Response } from "express"; -import { ChatService } from "../services/ChatService"; -import { verifyToken } from "../utils/jwt"; -import { AppDataSource } from "../database/data-source"; -import { User } from "../database/entities/User"; - -export class MessageController { - private chatService = new ChatService(); - - // Chat Operations - createChat = async (req: Request, res: Response) => { - try { - const { name, participantIds } = req.body; - const userId = req.user?.id; - - if (!userId) { - return res.status(401).json({ error: "Unauthorized" }); - } - - // Ensure the creator is included in participants - const allParticipants = [ - ...new Set([userId, ...(participantIds || [])]), - ]; - const chat = await this.chatService.createChat( - name, - allParticipants, - ); - res.status(201).json(chat); - } catch (error) { - console.error("Error creating chat:", error); - res.status(500).json({ error: "Internal server error" }); - } - }; - - getChat = async (req: Request, res: Response) => { - try { - const { chatId } = req.params; - const userId = req.user?.id; - const page = parseInt(req.query.page as string) || 1; - const limit = parseInt(req.query.limit as string) || 20; - - if (!userId) { - return res.status(401).json({ error: "Unauthorized" }); - } - - const chat = await this.chatService.getChatById(chatId); - if (!chat) { - return res.status(404).json({ error: "Chat not found" }); - } - - // Verify user is a participant - if (!chat.users.some((user: User) => user.id === userId)) { - return res - .status(403) - .json({ error: "Not a participant in this chat" }); - } - - // Get messages for the chat - const messages = await this.chatService.getChatMessages( - chatId, - userId, - page, - limit, - ); - - res.json({ - ...chat, - messages: messages.messages, - messagesTotal: messages.total, - messagesPage: messages.page, - messagesTotalPages: messages.totalPages, - }); - } catch (error) { - console.error("Error fetching chat:", error); - res.status(500).json({ error: "Internal server error" }); - } - }; - - getUserChats = async (req: Request, res: Response) => { - try { - const userId = req.user?.id; - const page = parseInt(req.query.page as string) || 1; - const limit = parseInt(req.query.limit as string) || 10; - - if (!userId) { - return res.status(401).json({ error: "Unauthorized" }); - } - - const result = await this.chatService.getUserChats( - userId, - page, - limit, - ); - - // Transform the response to include only necessary data - const transformedChats = result.chats.map((chat) => ({ - id: chat.id, - chatName: chat.chatName, - users: chat.users.map((user: User) => ({ - id: user.id, - username: user.username, - displayName: user.displayName, - profilePictureUrl: user.profilePictureUrl, - })), - latestMessage: chat.latestMessage, - updatedAt: chat.updatedAt, - })); - - res.json({ - ...result, - chats: transformedChats, - }); - } catch (error) { - console.error("Error fetching user chats:", error); - res.status(500).json({ error: "Internal server error" }); - } - }; - - // Chat Participant Operations - addParticipants = async (req: Request, res: Response) => { - try { - const { chatId } = req.params; - const { participantIds } = req.body; - const userId = req.user?.id; - - if (!userId) { - return res.status(401).json({ error: "Unauthorized" }); - } - - const chat = await this.chatService.addParticipants( - chatId, - participantIds, - ); - res.json(chat); - } catch (error) { - console.error("Error adding participants:", error); - res.status(500).json({ error: "Internal server error" }); - } - }; - - removeParticipant = async (req: Request, res: Response) => { - try { - const { chatId, userId } = req.params; - const currentUserId = req.user?.id; - - if (!currentUserId) { - return res.status(401).json({ error: "Unauthorized" }); - } - - const chat = await this.chatService.removeParticipant( - chatId, - userId, - ); - res.json(chat); - } catch (error) { - console.error("Error removing participant:", error); - res.status(500).json({ error: "Internal server error" }); - } - }; - - // Message Operations (as sub-resources of Chat) - createMessage = async (req: Request, res: Response) => { - try { - const { chatId } = req.params; - const { text } = req.body; - const userId = req.user?.id; - - if (!userId) { - return res.status(401).json({ error: "Unauthorized" }); - } - console.log("asdfasd"); - - const message = await this.chatService.sendMessage( - chatId, - userId, - text, - ); - res.status(201).json(message); - } catch (error) { - console.error("Error sending message:", error); - res.status(500).json({ error: "Internal server error" }); - } - }; - - getMessages = async (req: Request, res: Response) => { - try { - const { chatId } = req.params; - const userId = req.user?.id; - const page = parseInt(req.query.page as string) || 1; - const limit = parseInt(req.query.limit as string) || 20; - - if (!userId) { - return res.status(401).json({ error: "Unauthorized" }); - } - - const result = await this.chatService.getChatMessages( - chatId, - userId, - page, - limit, - ); - res.json(result); - } catch (error) { - console.error("Error fetching messages:", error); - res.status(500).json({ error: "Internal server error" }); - } - }; - - markAsRead = async (req: Request, res: Response) => { - try { - const { chatId } = req.params; - const userId = req.user?.id; - - if (!userId) { - return res.status(401).json({ error: "Unauthorized" }); - } - - await this.chatService.markMessagesAsRead(chatId, userId); - res.status(204).send(); - } catch (error) { - console.error("Error marking messages as read:", error); - res.status(500).json({ error: "Internal server error" }); - } - }; - - deleteMessage = async (req: Request, res: Response) => { - try { - const { chatId, messageId } = req.params; - const userId = req.user?.id; - - if (!userId) { - return res.status(401).json({ error: "Unauthorized" }); - } - - await this.chatService.deleteMessage(messageId, userId); - res.status(204).send(); - } catch (error) { - console.error("Error deleting message:", error); - res.status(500).json({ error: "Internal server error" }); - } - }; - - getUnreadCount = async (req: Request, res: Response) => { - try { - const { chatId } = req.params; - const userId = req.user?.id; - - if (!userId) { - return res.status(401).json({ error: "Unauthorized" }); - } - - const count = await this.chatService.getUnreadMessageCount( - chatId, - userId, - ); - res.json({ count }); - } catch (error) { - console.error("Error getting unread count:", error); - res.status(500).json({ error: "Internal server error" }); - } - }; - - // SSE route for chat events - getChatEvents = async (req: Request, res: Response) => { - try { - const { chatId } = req.params; - const token = req.query.token; - if (!token) - return res.status(400).json({ error: "token is required" }); - - const decoded = verifyToken(token as string) as { userId: string }; - - if (!decoded?.userId) { - return res.status(401).json({ error: "Invalid token" }); - } - - const userRepository = AppDataSource.getRepository(User); - const user = await userRepository.findOneBy({ id: decoded.userId }); - if (!user) return res.status(401).json({ error: "Invalid token" }); - const userId = user.id; - - // Verify user is a participant - const chat = await this.chatService.getChatById(chatId); - if (!chat) { - return res.status(404).json({ error: "Chat not found" }); - } - - if (!chat.users.some((user: User) => user.id === userId)) { - return res - .status(403) - .json({ error: "Not a participant in this chat" }); - } - - // Set SSE headers - res.writeHead(200, { - "Content-Type": "text/event-stream", - "Cache-Control": "no-cache", - Connection: "keep-alive", - }); - - const page = parseInt(req.query.page as string) || 1; - const limit = parseInt(req.query.limit as string) || 2000; - - if (!userId) { - return res.status(401).json({ error: "Unauthorized" }); - } - - // Get messages for the chat - const messages = await this.chatService.getChatMessages( - chatId, - userId, - page, - limit, - ); - - // Send initial connection message - res.write(`data: ${JSON.stringify(messages.messages)}\n\n`); - - // Create event listener for this chat - const eventEmitter = this.chatService.getEventEmitter(); - const eventName = `chat:${chatId}`; - - const messageHandler = (data: any) => { - res.write(`data: ${JSON.stringify(data)}\n\n`); - }; - - // Add event listener - eventEmitter.on(eventName, messageHandler); - - // Handle client disconnect - req.on("close", () => { - eventEmitter.off(eventName, messageHandler); - res.end(); - }); - } catch (error) { - console.error("Error setting up chat events:", error); - res.status(500).json({ error: "Internal server error" }); - } - }; -} diff --git a/platforms/blabsy-api/src/controllers/PostController.ts b/platforms/blabsy-api/src/controllers/PostController.ts deleted file mode 100644 index 40f0f592..00000000 --- a/platforms/blabsy-api/src/controllers/PostController.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { Request, Response } from "express"; -import { PostService } from "../services/PostService"; - -export class PostController { - private postService: PostService; - - constructor() { - this.postService = new PostService(); - } - - getFeed = async (req: Request, res: Response) => { - try { - const userId = req.user?.id; - if (!userId) { - return res.status(401).json({ error: "Unauthorized" }); - } - - const page = parseInt(req.query.page as string) || 1; - const limit = parseInt(req.query.limit as string) || 10; - - const feed = await this.postService.getFollowingFeed(userId, page, limit); - res.json(feed); - } catch (error) { - console.error("Error fetching feed:", error); - res.status(500).json({ error: "Internal server error" }); - } - }; - - createPost = async (req: Request, res: Response) => { - try { - const userId = req.user?.id; - if (!userId) { - return res.status(401).json({ error: "Unauthorized" }); - } - - const { content, images, hashtags } = req.body; - const blab = await this.postService.createPost(userId, { - content, - images, - hashtags, - }); - - res.status(201).json(blab); - } catch (error) { - console.error("Error creating blab:", error); - res.status(500).json({ error: "Internal server error" }); - } - }; - - toggleLike = async (req: Request, res: Response) => { - try { - const { id: blabId } = req.params; - const userId = req.user?.id; - - if (!userId) { - return res.status(401).json({ error: "Unauthorized" }); - } - - const blab = await this.postService.toggleLike(blabId, userId); - res.json(blab); - } catch (error) { - console.error("Error toggling like:", error); - res.status(500).json({ error: "Internal server error" }); - } - }; -} diff --git a/platforms/blabsy-api/src/controllers/UserController.ts b/platforms/blabsy-api/src/controllers/UserController.ts deleted file mode 100644 index 6b6be1fe..00000000 --- a/platforms/blabsy-api/src/controllers/UserController.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { Request, Response } from "express"; -import { UserService } from "../services/UserService"; - -export class UserController { - private userService: UserService; - - constructor() { - this.userService = new UserService(); - } - - currentUser = async (req: Request, res: Response) => { - res.json(req.user); - }; - - getProfileById = async (req: Request, res: Response) => { - try { - const { id } = req.params; - - if (!id) { - return res.status(400).json({ error: "User ID is required" }); - } - - const profile = await this.userService.getProfileById(id); - if (!profile) { - return res.status(404).json({ error: "User not found" }); - } - - res.json(profile); - } catch (error) { - console.error("Error fetching user profile:", error); - res.status(500).json({ error: "Internal server error" }); - } - }; - - search = async (req: Request, res: Response) => { - try { - const { q } = req.query; - - if (!q || typeof q !== "string") { - return res - .status(400) - .json({ error: "Search query is required" }); - } - - const users = await this.userService.searchUsers(q); - res.json(users); - } catch (error) { - console.error("Error searching users:", error); - res.status(500).json({ error: "Internal server error" }); - } - }; - - follow = async (req: Request, res: Response) => { - try { - const followerId = req.user?.id; - const { id: followingId } = req.params; - - if (!followerId || !followingId) { - return res - .status(400) - .json({ error: "Missing required fields" }); - } - - const updatedUser = await this.userService.followUser( - followerId, - followingId, - ); - res.json(updatedUser); - } catch (error) { - console.error("Error following user:", error); - res.status(500).json({ error: "Internal server error" }); - } - }; - - updateProfile = async (req: Request, res: Response) => { - try { - const userId = req.user?.id; - const { username, profilePictureUrl, displayName } = req.body; - - if (!userId) { - return res.status(401).json({ error: "Unauthorized" }); - } - - const updatedUser = await this.userService.updateProfile(userId, { - username, - profilePictureUrl, - displayName, - }); - - res.json(updatedUser); - } catch (error) { - console.error("Error updating user profile:", error); - res.status(500).json({ error: "Internal server error" }); - } - }; -} diff --git a/platforms/blabsy-api/src/database/data-source.ts b/platforms/blabsy-api/src/database/data-source.ts deleted file mode 100644 index 4ab7d3dc..00000000 --- a/platforms/blabsy-api/src/database/data-source.ts +++ /dev/null @@ -1,22 +0,0 @@ -import "reflect-metadata"; -import { DataSource } from "typeorm"; -import { config } from "dotenv"; -import { User } from "./entities/User"; -import path from "path"; -import { Chat } from "./entities/Chat"; -import { MessageReadStatus } from "./entities/MessageReadStatus"; -import { Blab } from "./entities/Blab"; -import { Reply } from "./entities/Reply"; -import { Text } from "./entities/Text"; - -config({ path: path.resolve(__dirname, "../../../../.env") }); - -export const AppDataSource = new DataSource({ - type: "postgres", - url: process.env.BLABSY_DATABASE_URL, - synchronize: false, - logging: process.env.NODE_ENV === "development", - entities: [User, Blab, Reply, Text, Chat, MessageReadStatus], - migrations: ["src/database/migrations/*.ts"], - subscribers: [], -}); diff --git a/platforms/blabsy-api/src/database/entities/Blab.ts b/platforms/blabsy-api/src/database/entities/Blab.ts deleted file mode 100644 index c05e0c75..00000000 --- a/platforms/blabsy-api/src/database/entities/Blab.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, ManyToOne, ManyToMany, JoinTable, OneToMany } from "typeorm"; -import { User } from "./User"; -import { Reply } from "./Reply"; - -@Entity("blabs") -export class Blab{ - @PrimaryGeneratedColumn("uuid") - id!: string; - - @ManyToOne(() => User, (user: User) => user.blabs) - author!: User; - - @Column({ type: "text" }) - content!: string; // was content - - @Column("simple-array", { nullable: true }) - images!: string[]; - - @OneToMany(() => Reply, (comment: Reply) => comment.blab) - replies!: Reply[]; - - @ManyToMany(() => User) - @JoinTable({ - name: "blab_likes", - joinColumn: { name: "blab_id", referencedColumnName: "id" }, - inverseJoinColumn: { name: "user_id", referencedColumnName: "id" } - }) - likedBy!: User[]; // was likes - - @Column("simple-array", { nullable: true }) - hashtags!: string[]; // was tags - - @CreateDateColumn() - createdAt!: Date; - - @UpdateDateColumn() - updatedAt!: Date; - - @Column({ default: false }) - isArchived!: boolean; // was isDeleted -} \ No newline at end of file diff --git a/platforms/blabsy-api/src/database/entities/Chat.ts b/platforms/blabsy-api/src/database/entities/Chat.ts deleted file mode 100644 index 890db645..00000000 --- a/platforms/blabsy-api/src/database/entities/Chat.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { - Entity, - CreateDateColumn, - UpdateDateColumn, - PrimaryGeneratedColumn, - Column, - OneToMany, - ManyToMany, - JoinTable, -} from "typeorm"; -import { User } from "./User"; -import { Text } from "./Text"; - -@Entity() -export class Chat { - @PrimaryGeneratedColumn("uuid") - id!: string; - - @Column({ nullable: true }) - chatName!: string; - - @OneToMany(() => Text, (e) => e.chat) - texts!: Text[]; - - @ManyToMany(() => User) - @JoinTable({ - name: "chat_participants", - joinColumn: { name: "chat_id", referencedColumnName: "id" }, - inverseJoinColumn: { name: "user_id", referencedColumnName: "id" } - }) - users!: User[]; - - @CreateDateColumn() - createdAt!: Date; - - @UpdateDateColumn() - updatedAt!: Date; -} diff --git a/platforms/blabsy-api/src/database/entities/MessageReadStatus.ts b/platforms/blabsy-api/src/database/entities/MessageReadStatus.ts deleted file mode 100644 index 42fea035..00000000 --- a/platforms/blabsy-api/src/database/entities/MessageReadStatus.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { - Entity, - PrimaryGeneratedColumn, - Column, - CreateDateColumn, - UpdateDateColumn, - ManyToOne, -} from "typeorm"; -import { Text } from "./Text"; -import { User } from "./User"; - -@Entity("message_read_status") -export class MessageReadStatus { - @PrimaryGeneratedColumn("uuid") - id!: string; - - @ManyToOne(() => Text) - text!: Text; - - @ManyToOne(() => User) - user!: User; - - @Column({ default: false }) - isRead!: boolean; - - @CreateDateColumn() - createdAt!: Date; - - @UpdateDateColumn() - updatedAt!: Date; -} \ No newline at end of file diff --git a/platforms/blabsy-api/src/database/entities/Reply.ts b/platforms/blabsy-api/src/database/entities/Reply.ts deleted file mode 100644 index d8557259..00000000 --- a/platforms/blabsy-api/src/database/entities/Reply.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, ManyToOne, ManyToMany, JoinTable } from "typeorm"; -import { User } from "./User"; -import { Blab } from "./Blab"; - -@Entity("replies") -export class Reply { - @PrimaryGeneratedColumn("uuid") - id!: string; - - @ManyToOne(() => User, (user: User) => user.replies) - creator!: User; - - @ManyToOne(() => Blab, (post: Blab) => post.replies) - blab!: Blab; - - @Column("text") - text!: string; - - @ManyToMany(() => User) - @JoinTable({ - name: "reply_likes", - joinColumn: { name: "replyt_id", referencedColumnName: "id" }, - inverseJoinColumn: { name: "user_id", referencedColumnName: "id" } - }) - likedBy!: User[]; - - @CreateDateColumn() - createdAt!: Date; - - @UpdateDateColumn() - updatedAt!: Date; - - @Column({ default: false }) - isArchived!: boolean; -} \ No newline at end of file diff --git a/platforms/blabsy-api/src/database/entities/Text.ts b/platforms/blabsy-api/src/database/entities/Text.ts deleted file mode 100644 index 31c51bad..00000000 --- a/platforms/blabsy-api/src/database/entities/Text.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { - Entity, - PrimaryGeneratedColumn, - Column, - CreateDateColumn, - UpdateDateColumn, - ManyToOne, - OneToMany, -} from "typeorm"; -import { User } from "./User"; -import { Chat } from "./Chat"; -import { MessageReadStatus } from "./MessageReadStatus"; - -@Entity("texts") -export class Text { - @PrimaryGeneratedColumn("uuid") - id!: string; - - @ManyToOne(() => User) - author!: User; - - @Column({ type: "text" }) - content!: string; - - @ManyToOne(() => Chat, (e) => e.texts) - chat!: Chat; - - @OneToMany(() => MessageReadStatus, (status) => status.text) - readStatuses!: MessageReadStatus[]; - - @CreateDateColumn() - createdAt!: Date; - - @UpdateDateColumn() - updatedAt!: Date; - - @Column({ default: false }) - isArchived!: boolean; -} diff --git a/platforms/blabsy-api/src/database/entities/User.ts b/platforms/blabsy-api/src/database/entities/User.ts deleted file mode 100644 index 5bc065fd..00000000 --- a/platforms/blabsy-api/src/database/entities/User.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { - Entity, - PrimaryGeneratedColumn, - Column, - CreateDateColumn, - UpdateDateColumn, - OneToMany, - ManyToMany, - JoinTable, -} from "typeorm"; -import { Blab} from "./Blab"; -import { Reply} from "./Reply"; -import { Chat } from "./Chat"; - -@Entity("users") -export class User { - @PrimaryGeneratedColumn("uuid") - id!: string; - - @Column({ nullable: true }) - username!: string; - - @Column({ nullable: true }) - displayName!: string; - - @Column({ nullable: true }) - bio!: string; - - @Column({ nullable: true }) - profilePictureUrl!: string; - - @Column({ nullable: true }) - bannerUrl!: string; - - @Column({ nullable: true }) - ename!: string; - - @Column({ default: false }) - isVerified!: boolean; - - @Column({ default: false }) - isPrivate!: boolean; - - @OneToMany(() => Blab, (post: Blab) => post.author) - blabs!: Blab[]; - - @OneToMany(() => Reply, (reply: Reply) => reply.creator) - replies!: Reply[]; - - @ManyToMany(() => User) - @JoinTable({ - name: "user_followers", - joinColumn: { name: "user_id", referencedColumnName: "id" }, - inverseJoinColumn: { name: "follower_id", referencedColumnName: "id" }, - }) - followers!: User[]; - - @ManyToMany(() => User) - @JoinTable({ - name: "user_following", - joinColumn: { name: "user_id", referencedColumnName: "id" }, - inverseJoinColumn: { name: "following_id", referencedColumnName: "id" }, - }) - following!: User[]; - - @ManyToMany(() => Chat, (chat) => chat.users) - chats!: Chat[]; - - @CreateDateColumn() - createdAt!: Date; - - @UpdateDateColumn() - updatedAt!: Date; - - @Column({ default: false }) - isArchived!: boolean; -} diff --git a/platforms/blabsy-api/src/database/migrations/1749294377768-migration.ts b/platforms/blabsy-api/src/database/migrations/1749294377768-migration.ts deleted file mode 100644 index 974790d4..00000000 --- a/platforms/blabsy-api/src/database/migrations/1749294377768-migration.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { MigrationInterface, QueryRunner } from "typeorm"; - -export class Migration1749294377768 implements MigrationInterface { - name = 'Migration1749294377768' - - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query(`CREATE TABLE "replies" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "text" text NOT NULL, "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "isArchived" boolean NOT NULL DEFAULT false, "creatorId" uuid, "blabId" uuid, CONSTRAINT "PK_08f619ebe431e27e9d206bea132" PRIMARY KEY ("id"))`); - await queryRunner.query(`CREATE TABLE "blabs" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "content" text NOT NULL, "images" text, "hashtags" text, "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "isArchived" boolean NOT NULL DEFAULT false, "authorId" uuid, CONSTRAINT "PK_b0d95cd60d167bef0a53b13a83c" PRIMARY KEY ("id"))`); - await queryRunner.query(`CREATE TABLE "message_read_status" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "isRead" boolean NOT NULL DEFAULT false, "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "textId" uuid, "userId" uuid, CONSTRAINT "PK_258e8d92b4e212a121dc10a74d3" PRIMARY KEY ("id"))`); - await queryRunner.query(`CREATE TABLE "texts" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "content" text NOT NULL, "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "isArchived" boolean NOT NULL DEFAULT false, "authorId" uuid, "chatId" uuid, CONSTRAINT "PK_ce044efbc0a1872f20feca7e19f" PRIMARY KEY ("id"))`); - await queryRunner.query(`CREATE TABLE "chat" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "chatName" character varying, "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), CONSTRAINT "PK_9d0b2ba74336710fd31154738a5" PRIMARY KEY ("id"))`); - await queryRunner.query(`CREATE TABLE "users" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "username" character varying, "displayName" character varying, "bio" character varying, "profilePictureUrl" character varying, "bannerUrl" character varying, "ename" character varying, "isVerified" boolean NOT NULL DEFAULT false, "isPrivate" boolean NOT NULL DEFAULT false, "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "isArchived" boolean NOT NULL DEFAULT false, CONSTRAINT "PK_a3ffb1c0c8416b9fc6f907b7433" PRIMARY KEY ("id"))`); - await queryRunner.query(`CREATE TABLE "reply_likes" ("replyt_id" uuid NOT NULL, "user_id" uuid NOT NULL, CONSTRAINT "PK_49ea2d0de64487d96abeb821fc3" PRIMARY KEY ("replyt_id", "user_id"))`); - await queryRunner.query(`CREATE INDEX "IDX_60cd31853a064c673a3d3324dc" ON "reply_likes" ("replyt_id") `); - await queryRunner.query(`CREATE INDEX "IDX_ab114098af787728a1d33dd0d2" ON "reply_likes" ("user_id") `); - await queryRunner.query(`CREATE TABLE "blab_likes" ("blab_id" uuid NOT NULL, "user_id" uuid NOT NULL, CONSTRAINT "PK_71e1e125898ad7b8f98ba53a90e" PRIMARY KEY ("blab_id", "user_id"))`); - await queryRunner.query(`CREATE INDEX "IDX_8a0152105b2e6a0c279c384dad" ON "blab_likes" ("blab_id") `); - await queryRunner.query(`CREATE INDEX "IDX_6a02fb4b2afef6af04f030a1c5" ON "blab_likes" ("user_id") `); - await queryRunner.query(`CREATE TABLE "chat_participants" ("chat_id" uuid NOT NULL, "user_id" uuid NOT NULL, CONSTRAINT "PK_36c99e4a017767179cc49d0ac74" PRIMARY KEY ("chat_id", "user_id"))`); - await queryRunner.query(`CREATE INDEX "IDX_9946d299e9ccfbee23aa40c554" ON "chat_participants" ("chat_id") `); - await queryRunner.query(`CREATE INDEX "IDX_b4129b3e21906ca57b503a1d83" ON "chat_participants" ("user_id") `); - await queryRunner.query(`CREATE TABLE "user_followers" ("user_id" uuid NOT NULL, "follower_id" uuid NOT NULL, CONSTRAINT "PK_d7b47e785d7dbc74b2f22f30045" PRIMARY KEY ("user_id", "follower_id"))`); - await queryRunner.query(`CREATE INDEX "IDX_a59d62cda8101214445e295cdc" ON "user_followers" ("user_id") `); - await queryRunner.query(`CREATE INDEX "IDX_da722d93356ae3119d6be40d98" ON "user_followers" ("follower_id") `); - await queryRunner.query(`CREATE TABLE "user_following" ("user_id" uuid NOT NULL, "following_id" uuid NOT NULL, CONSTRAINT "PK_5d7e9a83ee6f9b806d569068a30" PRIMARY KEY ("user_id", "following_id"))`); - await queryRunner.query(`CREATE INDEX "IDX_a28a2c27629ac06a41720d01c3" ON "user_following" ("user_id") `); - await queryRunner.query(`CREATE INDEX "IDX_94e1183284db3e697031eb7775" ON "user_following" ("following_id") `); - await queryRunner.query(`ALTER TABLE "replies" ADD CONSTRAINT "FK_34408818aba710d6ea7bb40358a" FOREIGN KEY ("creatorId") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); - await queryRunner.query(`ALTER TABLE "replies" ADD CONSTRAINT "FK_3a74da2d3059288882a69be43fa" FOREIGN KEY ("blabId") REFERENCES "blabs"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); - await queryRunner.query(`ALTER TABLE "blabs" ADD CONSTRAINT "FK_ea5969bad99c59d4f0af17d4692" FOREIGN KEY ("authorId") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); - await queryRunner.query(`ALTER TABLE "message_read_status" ADD CONSTRAINT "FK_f8aaa4a571e96838b1e15d5ff63" FOREIGN KEY ("textId") REFERENCES "texts"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); - await queryRunner.query(`ALTER TABLE "message_read_status" ADD CONSTRAINT "FK_00956f27e567b20ea63956a94da" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); - await queryRunner.query(`ALTER TABLE "texts" ADD CONSTRAINT "FK_16d5fc6d4a731bbd3703793a49c" FOREIGN KEY ("authorId") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); - await queryRunner.query(`ALTER TABLE "texts" ADD CONSTRAINT "FK_fc650300b13333cbe5ae5fac281" FOREIGN KEY ("chatId") REFERENCES "chat"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); - await queryRunner.query(`ALTER TABLE "reply_likes" ADD CONSTRAINT "FK_60cd31853a064c673a3d3324dc6" FOREIGN KEY ("replyt_id") REFERENCES "replies"("id") ON DELETE CASCADE ON UPDATE CASCADE`); - await queryRunner.query(`ALTER TABLE "reply_likes" ADD CONSTRAINT "FK_ab114098af787728a1d33dd0d25" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE`); - await queryRunner.query(`ALTER TABLE "blab_likes" ADD CONSTRAINT "FK_8a0152105b2e6a0c279c384dad3" FOREIGN KEY ("blab_id") REFERENCES "blabs"("id") ON DELETE CASCADE ON UPDATE CASCADE`); - await queryRunner.query(`ALTER TABLE "blab_likes" ADD CONSTRAINT "FK_6a02fb4b2afef6af04f030a1c59" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE`); - await queryRunner.query(`ALTER TABLE "chat_participants" ADD CONSTRAINT "FK_9946d299e9ccfbee23aa40c5545" FOREIGN KEY ("chat_id") REFERENCES "chat"("id") ON DELETE CASCADE ON UPDATE CASCADE`); - await queryRunner.query(`ALTER TABLE "chat_participants" ADD CONSTRAINT "FK_b4129b3e21906ca57b503a1d834" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE`); - await queryRunner.query(`ALTER TABLE "user_followers" ADD CONSTRAINT "FK_a59d62cda8101214445e295cdc8" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE`); - await queryRunner.query(`ALTER TABLE "user_followers" ADD CONSTRAINT "FK_da722d93356ae3119d6be40d988" FOREIGN KEY ("follower_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE`); - await queryRunner.query(`ALTER TABLE "user_following" ADD CONSTRAINT "FK_a28a2c27629ac06a41720d01c30" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE`); - await queryRunner.query(`ALTER TABLE "user_following" ADD CONSTRAINT "FK_94e1183284db3e697031eb7775d" FOREIGN KEY ("following_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE`); - } - - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE "user_following" DROP CONSTRAINT "FK_94e1183284db3e697031eb7775d"`); - await queryRunner.query(`ALTER TABLE "user_following" DROP CONSTRAINT "FK_a28a2c27629ac06a41720d01c30"`); - await queryRunner.query(`ALTER TABLE "user_followers" DROP CONSTRAINT "FK_da722d93356ae3119d6be40d988"`); - await queryRunner.query(`ALTER TABLE "user_followers" DROP CONSTRAINT "FK_a59d62cda8101214445e295cdc8"`); - await queryRunner.query(`ALTER TABLE "chat_participants" DROP CONSTRAINT "FK_b4129b3e21906ca57b503a1d834"`); - await queryRunner.query(`ALTER TABLE "chat_participants" DROP CONSTRAINT "FK_9946d299e9ccfbee23aa40c5545"`); - await queryRunner.query(`ALTER TABLE "blab_likes" DROP CONSTRAINT "FK_6a02fb4b2afef6af04f030a1c59"`); - await queryRunner.query(`ALTER TABLE "blab_likes" DROP CONSTRAINT "FK_8a0152105b2e6a0c279c384dad3"`); - await queryRunner.query(`ALTER TABLE "reply_likes" DROP CONSTRAINT "FK_ab114098af787728a1d33dd0d25"`); - await queryRunner.query(`ALTER TABLE "reply_likes" DROP CONSTRAINT "FK_60cd31853a064c673a3d3324dc6"`); - await queryRunner.query(`ALTER TABLE "texts" DROP CONSTRAINT "FK_fc650300b13333cbe5ae5fac281"`); - await queryRunner.query(`ALTER TABLE "texts" DROP CONSTRAINT "FK_16d5fc6d4a731bbd3703793a49c"`); - await queryRunner.query(`ALTER TABLE "message_read_status" DROP CONSTRAINT "FK_00956f27e567b20ea63956a94da"`); - await queryRunner.query(`ALTER TABLE "message_read_status" DROP CONSTRAINT "FK_f8aaa4a571e96838b1e15d5ff63"`); - await queryRunner.query(`ALTER TABLE "blabs" DROP CONSTRAINT "FK_ea5969bad99c59d4f0af17d4692"`); - await queryRunner.query(`ALTER TABLE "replies" DROP CONSTRAINT "FK_3a74da2d3059288882a69be43fa"`); - await queryRunner.query(`ALTER TABLE "replies" DROP CONSTRAINT "FK_34408818aba710d6ea7bb40358a"`); - await queryRunner.query(`DROP INDEX "public"."IDX_94e1183284db3e697031eb7775"`); - await queryRunner.query(`DROP INDEX "public"."IDX_a28a2c27629ac06a41720d01c3"`); - await queryRunner.query(`DROP TABLE "user_following"`); - await queryRunner.query(`DROP INDEX "public"."IDX_da722d93356ae3119d6be40d98"`); - await queryRunner.query(`DROP INDEX "public"."IDX_a59d62cda8101214445e295cdc"`); - await queryRunner.query(`DROP TABLE "user_followers"`); - await queryRunner.query(`DROP INDEX "public"."IDX_b4129b3e21906ca57b503a1d83"`); - await queryRunner.query(`DROP INDEX "public"."IDX_9946d299e9ccfbee23aa40c554"`); - await queryRunner.query(`DROP TABLE "chat_participants"`); - await queryRunner.query(`DROP INDEX "public"."IDX_6a02fb4b2afef6af04f030a1c5"`); - await queryRunner.query(`DROP INDEX "public"."IDX_8a0152105b2e6a0c279c384dad"`); - await queryRunner.query(`DROP TABLE "blab_likes"`); - await queryRunner.query(`DROP INDEX "public"."IDX_ab114098af787728a1d33dd0d2"`); - await queryRunner.query(`DROP INDEX "public"."IDX_60cd31853a064c673a3d3324dc"`); - await queryRunner.query(`DROP TABLE "reply_likes"`); - await queryRunner.query(`DROP TABLE "users"`); - await queryRunner.query(`DROP TABLE "chat"`); - await queryRunner.query(`DROP TABLE "texts"`); - await queryRunner.query(`DROP TABLE "message_read_status"`); - await queryRunner.query(`DROP TABLE "blabs"`); - await queryRunner.query(`DROP TABLE "replies"`); - } - -} diff --git a/platforms/blabsy-api/src/index.ts b/platforms/blabsy-api/src/index.ts deleted file mode 100644 index f083feff..00000000 --- a/platforms/blabsy-api/src/index.ts +++ /dev/null @@ -1,122 +0,0 @@ -import "reflect-metadata"; -import express from "express"; -import cors from "cors"; -import { config } from "dotenv"; -import { AppDataSource } from "./database/data-source"; -import { PostController } from "./controllers/PostController"; -import path from "path"; -import { AuthController } from "./controllers/AuthController"; -import { CommentController } from "./controllers/CommentController"; -import { MessageController } from "./controllers/MessageController"; -import { authMiddleware, authGuard } from "./middleware/auth"; -import { UserController } from "./controllers/UserController"; - -config({ path: path.resolve(__dirname, "../../../.env") }); - -const app = express(); -const port = process.env.PORT || 3000; - -// Middleware -app.use( - cors({ - origin: "*", - methods: ["GET", "POST", "OPTIONS", "PATCH", "DELETE"], - allowedHeaders: ["Content-Type", "Authorization"], - credentials: true, - }), -); -app.use(express.json({ limit: "50mb" })); -app.use(express.urlencoded({ limit: "50mb", extended: true })); - -// Initialize database connection -AppDataSource.initialize() - .then(() => { - console.log("Database connection established"); - }) - .catch((error) => { - console.error("Error connecting to database:", error); - process.exit(1); - }); - -// Controllers -const postController = new PostController(); -const authController = new AuthController(); -const commentController = new CommentController(); -const messageController = new MessageController(); -const userController = new UserController(); - -// Public routes (no auth required) -app.get("/api/auth/offer", authController.getOffer); -app.post("/api/auth", authController.login); -app.get("/api/auth/sessions/:id", authController.sseStream); -app.get("/api/chats/:chatId/events", messageController.getChatEvents); - -// Protected routes (auth required) -app.use(authMiddleware); // Apply auth middleware to all routes below - -// Blab routes -app.get("/api/blabs/feed", authGuard, postController.getFeed); -app.post("/api/blabs", authGuard, postController.createPost); -app.post("/api/blabs/:id/like", authGuard, postController.toggleLike); - -// Reply routes -app.post("/api/replies", authGuard, commentController.createComment); -app.get( - "/api/blabs/:blabId/replies", - authGuard, - commentController.getPostComments, -); -app.put("/api/replies/:id", authGuard, commentController.updateComment); -app.delete("/api/replies/:id", authGuard, commentController.deleteComment); - -// Chat routes -app.post("/api/chats", authGuard, messageController.createChat); -app.get("/api/chats", authGuard, messageController.getUserChats); -app.get("/api/chats/:chatId", authGuard, messageController.getChat); - -// Chat participant routes -app.post( - "/api/chats/:chatId/users", - authGuard, - messageController.addParticipants, -); -app.delete( - "/api/chats/:chatId/users/:userId", - authGuard, - messageController.removeParticipant, -); - -// Chat message routes -app.post( - "/api/chats/:chatId/texts", - authGuard, - messageController.createMessage, -); -app.get("/api/chats/:chatId/texts", authGuard, messageController.getMessages); -app.delete( - "/api/chats/:chatId/texts/:textId", - authGuard, - messageController.deleteMessage, -); -app.post( - "/api/chats/:chatId/texts/read", - authGuard, - messageController.markAsRead, -); -app.get( - "/api/chats/:chatId/texts/unread", - authGuard, - messageController.getUnreadCount, -); - -// User routes -app.get("/api/users", userController.currentUser); -app.get("/api/users/search", userController.search); -app.post("/api/users/:id/follow", authGuard, userController.follow); -app.get("/api/users/:id", authGuard, userController.getProfileById); -app.patch("/api/users", authGuard, userController.updateProfile); - -// Start server -app.listen(port, () => { - console.log(`Server running on port ${port}`); -}); diff --git a/platforms/blabsy-api/src/middleware/auth.ts b/platforms/blabsy-api/src/middleware/auth.ts deleted file mode 100644 index fdace1d2..00000000 --- a/platforms/blabsy-api/src/middleware/auth.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { Request, Response, NextFunction } from "express"; -import { AppDataSource } from "../database/data-source"; -import { User } from "../database/entities/User"; -import { verifyToken } from "../utils/jwt"; - -export const authMiddleware = async ( - req: Request, - res: Response, - next: NextFunction, -) => { - try { - const authHeader = req.headers.authorization; - if (!authHeader?.startsWith("Bearer ")) { - return res.status(401).json({ error: "No token provided" }); - } - - const token = authHeader.split(" ")[1]; - const decoded = verifyToken(token) as { userId: string }; - - if (!decoded?.userId) { - return res.status(401).json({ error: "Invalid token" }); - } - - const userRepository = AppDataSource.getRepository(User); - const user = await userRepository.findOneBy({ id: decoded.userId }); - - if (!user) { - return res.status(401).json({ error: "User not found" }); - } - - req.user = user; - console.log("user", user.ename); - next(); - } catch (error) { - console.error("Auth middleware error:", error); - res.status(401).json({ error: "Invalid token" }); - } -}; - -export const authGuard = (req: Request, res: Response, next: NextFunction) => { - if (!req.user) { - return res.status(401).json({ error: "Authentication required" }); - } - next(); -}; - diff --git a/platforms/blabsy-api/src/services/ChatService.ts b/platforms/blabsy-api/src/services/ChatService.ts deleted file mode 100644 index 8839e20b..00000000 --- a/platforms/blabsy-api/src/services/ChatService.ts +++ /dev/null @@ -1,303 +0,0 @@ -import { AppDataSource } from "../database/data-source"; -import { Chat } from "../database/entities/Chat"; -import { User } from "../database/entities/User"; -import { MessageReadStatus } from "../database/entities/MessageReadStatus"; -import { In } from "typeorm"; -import { EventEmitter } from "events"; -import { Text } from "../database/entities/Text"; - -export class ChatService { - private chatRepository = AppDataSource.getRepository(Chat); - private textRepository = AppDataSource.getRepository(Text); - private userRepository = AppDataSource.getRepository(User); - private messageReadStatusRepository = AppDataSource.getRepository(MessageReadStatus); - private eventEmitter = new EventEmitter(); - - // Event emitter getter - getEventEmitter(): EventEmitter { - return this.eventEmitter; - } - - // Chat CRUD Operations - async createChat( - name?: string, - participantIds: string[] = [], - ): Promise { - const participants = await this.userRepository.findBy({ - id: In(participantIds), - }); - if (participants.length !== participantIds.length) { - throw new Error("One or more participants not found"); - } - - const chat = this.chatRepository.create({ - chatName: name || undefined, - users: participants, - }); - return await this.chatRepository.save(chat); - } - - async getChatById(id: string): Promise { - return await this.chatRepository.findOne({ - where: { id }, - relations: [ - "texts", - "texts.author", - "texts.readStatuses", - "users", - ], - }); - } - - async updateChat(id: string, name: string): Promise { - const chat = await this.getChatById(id); - if (!chat) { - throw new Error("Chat not found"); - } - chat.chatName = name; - return await this.chatRepository.save(chat); - } - - async deleteChat(id: string): Promise { - const chat = await this.getChatById(id); - if (!chat) { - throw new Error("Chat not found"); - } - await this.chatRepository.softDelete(id); - } - - // Participant Operations - async addParticipants( - chatId: string, - participantIds: string[], - ): Promise { - const chat = await this.getChatById(chatId); - if (!chat) { - throw new Error("Chat not found"); - } - - const newParticipants = await this.userRepository.findBy({ - id: In(participantIds), - }); - if (newParticipants.length !== participantIds.length) { - throw new Error("One or more participants not found"); - } - - chat.users = [...chat.users, ...newParticipants]; - return await this.chatRepository.save(chat); - } - - async removeParticipant(chatId: string, userId: string): Promise { - const chat = await this.getChatById(chatId); - if (!chat) { - throw new Error("Chat not found"); - } - - chat.users = chat.users.filter((p) => p.id !== userId); - return await this.chatRepository.save(chat); - } - - // Message Operations - async sendMessage( - chatId: string, - senderId: string, - text: string, - ): Promise { - const chat = await this.getChatById(chatId); - if (!chat) { - throw new Error("Chat not found"); - } - - const sender = await this.userRepository.findOneBy({ id: senderId }); - if (!sender) { - throw new Error("Sender not found"); - } - - // Verify sender is a participant - if (!chat.users.some((p) => p.id === senderId)) { - throw new Error("Sender is not a participant in this chat"); - } - - const message = this.textRepository.create({ - content: text, - author: sender, - chat, - }); - - const savedMessage = await this.textRepository.save(message); - - // Create read status entries for all participants except sender - const readStatuses = chat.users - .filter((p) => p.id !== senderId) - .map((user) => - this.messageReadStatusRepository.create({ - text: savedMessage, - user, - isRead: false, - }), - ); - - await this.messageReadStatusRepository.save(readStatuses); - - // Emit new message event - this.eventEmitter.emit(`chat:${chatId}`, [savedMessage]); - - return savedMessage; - } - - async getChatMessages( - chatId: string, - userId: string, - page: number = 1, - limit: number = 20, - ): Promise<{ - messages: Text[]; - total: number; - page: number; - totalPages: number; - }> { - const [messages, total] = await this.textRepository.findAndCount({ - where: { chat: { id: chatId } }, - relations: ["author", "readStatuses", "readStatuses.user"], - order: { createdAt: "ASC" }, - skip: (page - 1) * limit, - take: limit, - }); - - return { - messages, - total, - page, - totalPages: Math.ceil(total / limit), - }; - } - - async markMessagesAsRead(chatId: string, userId: string): Promise { - const chat = await this.getChatById(chatId); - if (!chat) { - throw new Error("Chat not found"); - } - - // Verify user is a participant - if (!chat.users.some((p) => p.id === userId)) { - throw new Error("User is not a participant in this chat"); - } - - // First get all message IDs for this chat that were sent by other users - const messageIds = await this.textRepository - .createQueryBuilder("text") - .select("text.id") - .where("text.chat.id = :chatId", { chatId }) - .andWhere("text.author.id != :userId", { userId }) // Only messages not sent by the user - .getMany(); - - if (messageIds.length === 0) { - return; // No messages to mark as read - } - - // Then update the read status for these messages - await this.messageReadStatusRepository - .createQueryBuilder() - .update(MessageReadStatus) - .set({ isRead: true }) - .where("text.id IN (:...messageIds)", { messageIds: messageIds.map(m => m.id) }) - .andWhere("user.id = :userId", { userId }) - .andWhere("isRead = :isRead", { isRead: false }) - .execute(); - } - - async deleteMessage(messageId: string, userId: string): Promise { - const message = await this.textRepository.findOne({ - where: { id: messageId }, - relations: ["author"], - }); - - if (!message) { - throw new Error("Message not found"); - } - - if (message.author.id !== userId) { - throw new Error("Unauthorized to delete this message"); - } - - await this.textRepository.softDelete(messageId); - } - - // Additional Utility Methods - async getUserChats( - userId: string, - page: number = 1, - limit: number = 10, - ): Promise<{ - chats: (Chat & { latestMessage?: { content: string; isRead: boolean } })[]; - total: number; - page: number; - totalPages: number; - }> { - // First, get the chat IDs that the user is part of - const [chatIds, total] = await this.chatRepository - .createQueryBuilder("chat") - .select(["chat.id", "chat.updatedAt"]) - .innerJoin("chat.users", "users") - .where("users.id = :userId", { userId }) - .orderBy("chat.updatedAt", "DESC") - .skip((page - 1) * limit) - .take(limit) - .getManyAndCount(); - - // Then, load the full chat data with all relations - const chats = await this.chatRepository.find({ - where: { id: In(chatIds.map((chat) => chat.id)) }, - relations: [ - "users", - "texts", - "texts.author", - "texts.readStatuses", - "texts.readStatuses.user", - ], - order: { updatedAt: "DESC" }, - }); - - // For each chat, get the latest message and its read status - const chatsWithLatestMessage = await Promise.all( - chats.map(async (chat) => { - const latestMessage = chat.texts[chat.texts.length - 1]; - if (!latestMessage) { - return { ...chat, latestMessage: undefined }; - } - - const readStatus = latestMessage.readStatuses.find( - (status) => status.user.id === userId - ); - - return { - ...chat, - latestMessage: { - content: latestMessage.content, - isRead: readStatus?.isRead ?? false, - }, - }; - }) - ); - - return { - chats: chatsWithLatestMessage, - total, - page, - totalPages: Math.ceil(total / limit), - }; - } - - async getUnreadMessageCount( - chatId: string, - userId: string, - ): Promise { - return await this.messageReadStatusRepository.count({ - where: { - text: { chat: { id: chatId } }, - user: { id: userId }, - isRead: false, - }, - }); - } -} diff --git a/platforms/blabsy-api/src/services/CommentService.ts b/platforms/blabsy-api/src/services/CommentService.ts deleted file mode 100644 index 935db20b..00000000 --- a/platforms/blabsy-api/src/services/CommentService.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { AppDataSource } from "../database/data-source"; -import { Reply } from "../database/entities/Reply"; -import { Blab } from "../database/entities/Blab"; - -export class CommentService { - private replyRepository = AppDataSource.getRepository(Reply); - private blabRepository = AppDataSource.getRepository(Blab); - - async createComment(blabId: string, authorId: string, text: string): Promise { - const blab = await this.blabRepository.findOneBy({ id: blabId }); - if (!blab) { - throw new Error('Blab not found'); - } - - const reply = this.replyRepository.create({ - text, - creator: { id: authorId }, - blab: { id: blabId } - }); - - return await this.replyRepository.save(reply); - } - - async getPostComments(blabId: string): Promise { - return await this.replyRepository.find({ - where: { blab: { id: blabId } }, - relations: ['creator'], - order: { createdAt: 'DESC' } - }); - } - - async getCommentById(id: string): Promise { - return await this.replyRepository.findOne({ - where: { id }, - relations: ['creator'] - }); - } - - async updateComment(id: string, text: string): Promise { - const reply = await this.getCommentById(id); - if (!reply) { - throw new Error('Reply not found'); - } - - reply.text = text; - return await this.replyRepository.save(reply); - } - - async deleteComment(id: string): Promise { - const reply = await this.getCommentById(id); - if (!reply) { - throw new Error('Reply not found'); - } - - await this.replyRepository.softDelete(id); - } -} \ No newline at end of file diff --git a/platforms/blabsy-api/src/services/PostService.ts b/platforms/blabsy-api/src/services/PostService.ts deleted file mode 100644 index d93881f6..00000000 --- a/platforms/blabsy-api/src/services/PostService.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { AppDataSource } from "../database/data-source"; -import { Blab } from "../database/entities/Blab"; -import { User } from "../database/entities/User"; -import { In } from "typeorm"; - -interface CreateBlabData { - content: string; - images?: string[]; - hashtags?: string[]; -} - -export class PostService { - private blabRepository = AppDataSource.getRepository(Blab); - private userRepository = AppDataSource.getRepository(User); - - async getFollowingFeed(userId: string, page: number, limit: number) { - const user = await this.userRepository.findOne({ - where: { id: userId }, - relations: ["following"], - }); - - if (!user) { - throw new Error("User not found"); - } - - const followingIds = user.following.map((f: User) => f.id); - const authorIds = [...followingIds, userId]; - - const [blabs, total] = await this.blabRepository.findAndCount({ - where: { - author: { id: In(authorIds) }, - isArchived: false, - }, - relations: ["author", "likedBy", "replies", "replies.creator"], - order: { - createdAt: "DESC", - }, - skip: (page - 1) * limit, - take: limit, - }); - - return { - blabs, - total, - page, - totalPages: Math.ceil(total / limit), - }; - } - - async createPost(userId: string, data: CreateBlabData) { - const user = await this.userRepository.findOneBy({ id: userId }); - if (!user) { - throw new Error("User not found"); - } - - const blab = this.blabRepository.create({ - author: user, - content: data.content, - images: data.images || [], - hashtags: data.hashtags || [], - likedBy: [], - }); - - return await this.blabRepository.save(blab); - } - - async toggleLike(blabId: string, userId: string): Promise { - const blab = await this.blabRepository.findOne({ - where: { id: blabId }, - relations: ["likedBy"], - }); - - if (!blab) { - throw new Error("Blab not found"); - } - - const user = await this.userRepository.findOneBy({ id: userId }); - if (!user) { - throw new Error("User not found"); - } - - const isLiked = blab.likedBy.some((u) => u.id === userId); - - if (isLiked) { - blab.likedBy = blab.likedBy.filter((u) => u.id !== userId); - } else { - blab.likedBy.push(user); - } - - return await this.blabRepository.save(blab); - } -} diff --git a/platforms/blabsy-api/src/services/UserService.ts b/platforms/blabsy-api/src/services/UserService.ts deleted file mode 100644 index 4e2a93a0..00000000 --- a/platforms/blabsy-api/src/services/UserService.ts +++ /dev/null @@ -1,139 +0,0 @@ -import { AppDataSource } from "../database/data-source"; -import { User } from "../database/entities/User"; -import { Blab } from "../database/entities/Blab"; -import { signToken } from "../utils/jwt"; -import { Like } from "typeorm"; - -export class UserService { - private userRepository = AppDataSource.getRepository(User); - private blabRepository = AppDataSource.getRepository(Blab); - - async createBlankUser(ename: string): Promise { - const user = this.userRepository.create({ - ename, - isVerified: false, - isPrivate: false, - isArchived: false, - }); - - return await this.userRepository.save(user); - } - - async findOrCreateUser(ename: string): Promise<{ user: User; token: string }> { - let user = await this.userRepository.findOne({ - where: { ename }, - }); - - if (!user) { - user = await this.createBlankUser(ename); - } - - const token = signToken({ userId: user.id }); - return { user, token }; - } - - async findById(id: string): Promise { - return await this.userRepository.findOneBy({ id }); - } - - searchUsers = async (query: string) => { - const searchQuery = query.toLowerCase(); - - return this.userRepository.find({ - where: [ - { username: Like(`%${searchQuery}%`) }, - { ename: Like(`%${searchQuery}%`) }, - ], - select: { - id: true, - username: true, - displayName: true, - bio: true, - profilePictureUrl: true, - isVerified: true, - }, - take: 10, - }); - }; - - followUser = async (followerId: string, followingId: string) => { - const follower = await this.userRepository.findOne({ - where: { id: followerId }, - relations: ["following"], - }); - - const following = await this.userRepository.findOne({ - where: { id: followingId }, - }); - - if (!follower || !following) { - throw new Error("User not found"); - } - - if (!follower.following) { - follower.following = []; - } - - if (follower.following.some((user) => user.id === followingId)) { - return follower; - } - - follower.following.push(following); - return await this.userRepository.save(follower); - }; - - async getProfileById(userId: string) { - const user = await this.userRepository.findOne({ - where: { id: userId }, - select: { - id: true, - username: true, - displayName: true, - profilePictureUrl: true, - followers: true, - following: true, - bio: true, - }, - }); - - if (!user) return null; - - const blabs = await this.blabRepository.find({ - where: { author: { id: userId } }, - relations: ["author"], - order: { createdAt: "DESC" }, - }); - - return { - ...user, - totalBlabs: blabs.length, - blabs: blabs.map((blab) => ({ - id: blab.id, - avatar: blab.author.profilePictureUrl, - userId: blab.author.id, - username: blab.author.username, - imgUris: blab.images, - caption: blab.content, - time: blab.createdAt, - count: { - likes: blab.likedBy, - replies: blab.replies, - }, - })), - }; - } - - async updateProfile(userId: string, data: { username?: string; profilePictureUrl?: string; displayName?: string }): Promise { - const user = await this.userRepository.findOneBy({ id: userId }); - if (!user) { - throw new Error("User not found"); - } - - if (data.username !== undefined) user.username = data.username; - if (data.profilePictureUrl !== undefined) user.profilePictureUrl = data.profilePictureUrl; - if (data.displayName !== undefined) user.displayName = data.displayName; - - return await this.userRepository.save(user); - } -} - diff --git a/platforms/blabsy-api/src/types/express.d.ts b/platforms/blabsy-api/src/types/express.d.ts deleted file mode 100644 index 8ad6ec48..00000000 --- a/platforms/blabsy-api/src/types/express.d.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { User } from "../database/entities/User"; - -declare global { - namespace Express { - interface Request { - user?: User; - } - } -} \ No newline at end of file diff --git a/platforms/blabsy-api/src/utils/jwt.ts b/platforms/blabsy-api/src/utils/jwt.ts deleted file mode 100644 index 4cebfb90..00000000 --- a/platforms/blabsy-api/src/utils/jwt.ts +++ /dev/null @@ -1,15 +0,0 @@ -import jwt from 'jsonwebtoken'; - -const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key'; - -export const signToken = (payload: any): string => { - return jwt.sign(payload, JWT_SECRET, { expiresIn: '7d' }); -}; - -export const verifyToken = (token: string): any => { - try { - return jwt.verify(token, JWT_SECRET); - } catch (error) { - throw new Error('Invalid token'); - } -}; \ No newline at end of file diff --git a/platforms/blabsy-api/package.json b/platforms/blabsy-w3ds-auth-api/package.json similarity index 95% rename from platforms/blabsy-api/package.json rename to platforms/blabsy-w3ds-auth-api/package.json index 8a1fe477..e8abf137 100644 --- a/platforms/blabsy-api/package.json +++ b/platforms/blabsy-w3ds-auth-api/package.json @@ -1,5 +1,5 @@ { - "name": "piqtique-api", + "name": "blabsy-w3ds-auth-api", "version": "1.0.0", "description": "Piqtique Social Media Platform API", "main": "src/index.ts", @@ -18,6 +18,7 @@ "dotenv": "^16.4.5", "eventsource-polyfill": "^0.9.6", "express": "^4.18.2", + "firebase-admin": "^13.4.0", "jsonwebtoken": "^9.0.2", "pg": "^8.11.3", "reflect-metadata": "^0.2.1", diff --git a/platforms/blabsy-api/src/controllers/AuthController.ts b/platforms/blabsy-w3ds-auth-api/src/controllers/AuthController.ts similarity index 74% rename from platforms/blabsy-api/src/controllers/AuthController.ts rename to platforms/blabsy-w3ds-auth-api/src/controllers/AuthController.ts index ce886e51..f398c850 100644 --- a/platforms/blabsy-api/src/controllers/AuthController.ts +++ b/platforms/blabsy-w3ds-auth-api/src/controllers/AuthController.ts @@ -1,13 +1,12 @@ import { Request, Response } from "express"; import { v4 as uuidv4 } from "uuid"; -import { UserService } from "../services/UserService"; import { EventEmitter } from "events"; +import { applicationDefault, initializeApp } from "firebase-admin/app"; +import { auth } from "firebase-admin"; export class AuthController { - private userService: UserService; private eventEmitter: EventEmitter; constructor() { - this.userService = new UserService(); this.eventEmitter = new EventEmitter(); } @@ -44,7 +43,7 @@ export class AuthController { getOffer = async (req: Request, res: Response) => { const url = new URL( "/api/auth", - process.env.PUBLIC_BLABSY_BASE_URL, + process.env.PUBLIC_BLABSY_BASE_URL ).toString(); const session = uuidv4(); const offer = `w3ds://auth?redirect=${url}&session=${session}&platform=blabsy`; @@ -58,20 +57,13 @@ export class AuthController { if (!ename) { return res.status(400).json({ error: "ename is required" }); } + initializeApp({ + credential: applicationDefault(), + }); + const token = await auth().createCustomToken(ename); + console.log(token); - const { user, token } = - await this.userService.findOrCreateUser(ename); - - const data = { - user: { - id: user.id, - ename: user.ename, - isVerified: user.isVerified, - isPrivate: user.isPrivate, - }, - token, - }; - this.eventEmitter.emit(session, data); + this.eventEmitter.emit(session, { token }); res.status(200).send(); } catch (error) { console.error("Error during login:", error); diff --git a/platforms/blabsy-w3ds-auth-api/src/index.ts b/platforms/blabsy-w3ds-auth-api/src/index.ts new file mode 100644 index 00000000..9eb37819 --- /dev/null +++ b/platforms/blabsy-w3ds-auth-api/src/index.ts @@ -0,0 +1,32 @@ +import "reflect-metadata"; +import express from "express"; +import cors from "cors"; +import { config } from "dotenv"; +import path from "path"; +import { AuthController } from "./controllers/AuthController"; + +config({ path: path.resolve(__dirname, "../../../.env") }); + +const app = express(); +const port = process.env.PORT || 3000; + +app.use( + cors({ + origin: "*", + methods: ["GET", "POST", "OPTIONS", "PATCH", "DELETE"], + allowedHeaders: ["Content-Type", "Authorization"], + credentials: true, + }) +); +app.use(express.json({ limit: "50mb" })); +app.use(express.urlencoded({ limit: "50mb", extended: true })); + +const authController = new AuthController(); + +app.get("/api/auth/offer", authController.getOffer); +app.post("/api/auth", authController.login); +app.get("/api/auth/sessions/:id", authController.sseStream); + +app.listen(port, () => { + console.log(`Server running on port ${port}`); +}); diff --git a/platforms/blabsy-api/tsconfig.json b/platforms/blabsy-w3ds-auth-api/tsconfig.json similarity index 100% rename from platforms/blabsy-api/tsconfig.json rename to platforms/blabsy-w3ds-auth-api/tsconfig.json diff --git a/platforms/blabsy/.env.development b/platforms/blabsy/.env.development new file mode 100644 index 00000000..9aaca90b --- /dev/null +++ b/platforms/blabsy/.env.development @@ -0,0 +1,18 @@ +# Dev URL +NEXT_PUBLIC_URL=http://localhost +NEXT_PUBLIC_BASE_URL=http://192.168.0.226:4444 + +# Emulator +NEXT_PUBLIC_USE_EMULATOR=false + +# Firebase +NEXT_PUBLIC_API_KEY=AIzaSyBa59njuCotm34gRGuGd6_HOCmE0-sbF4A +NEXT_PUBLIC_AUTH_DOMAIN=blabsy-msf.firebaseapp.com +NEXT_PUBLIC_PROJECT_ID=blabsy-msf +NEXT_PUBLIC_STORAGE_BUCKET=blabsy-msf.firebasestorage.app +NEXT_PUBLIC_MESSAGING_SENDER_ID=876614847689 +NEXT_PUBLIC_APP_ID=1:876614847689:web:c90a9a6dbb2d0b87c2d118 +NEXT_PUBLIC_MEASUREMENT_ID=G-8Q1TEDQYT3 + +GOOGLE_APPLICATION_CREDENTIALS="/Users/mrl/Projects/metastate/platforms/blabsy/secrets/firebase-secrets.json" + diff --git a/platforms/blabsy/.env.production b/platforms/blabsy/.env.production new file mode 100644 index 00000000..ca10eb4d --- /dev/null +++ b/platforms/blabsy/.env.production @@ -0,0 +1,2 @@ +# Preview URL +NEXT_PUBLIC_URL=https://$NEXT_PUBLIC_VERCEL_URL diff --git a/platforms/blabsy/.eslintignore b/platforms/blabsy/.eslintignore new file mode 100644 index 00000000..67766db7 --- /dev/null +++ b/platforms/blabsy/.eslintignore @@ -0,0 +1,9 @@ +# next config +next.config.js + +# tailwind config +tailwind.config.js +postcss.config.js + +# jest config +jest.config.js diff --git a/platforms/blabsy/.eslintrc.json b/platforms/blabsy/.eslintrc.json new file mode 100644 index 00000000..88d31759 --- /dev/null +++ b/platforms/blabsy/.eslintrc.json @@ -0,0 +1,76 @@ +{ + "parser": "@typescript-eslint/parser", + "parserOptions": { + "project": "tsconfig.json" + }, + "plugins": ["@typescript-eslint"], + "extends": [ + "eslint:recommended", + "plugin:import/recommended", + "plugin:import/typescript", + "plugin:@typescript-eslint/recommended", + "plugin:@typescript-eslint/recommended-requiring-type-checking", + "next/core-web-vitals" + ], + "settings": { + "import/resolver": { + "typescript": true, + "node": true + } + }, + "rules": { + "semi": ["error", "always"], + "curly": ["warn", "multi"], + "quotes": ["error", "single", { "avoidEscape": true }], + "jsx-quotes": ["error", "prefer-single"], + "linebreak-style": ["error", "unix"], + "no-console": "warn", + "comma-dangle": ["error", "never"], + "no-unused-expressions": "error", + "no-constant-binary-expression": "error", + "import/order": [ + "warn", + { + "pathGroups": [ + { + "pattern": "*.scss", + "group": "builtin", + "position": "before", + "patternOptions": { "matchBase": true } + }, + { + "pattern": "@lib/**", + "group": "external", + "position": "after" + }, + { + "pattern": "@components/**", + "group": "external", + "position": "after" + } + ], + "warnOnUnassignedImports": true, + "pathGroupsExcludedImportTypes": ["type"], + "groups": [ + "builtin", + "external", + "internal", + "parent", + "sibling", + "index", + "object", + "type" + ] + } + ], + "@typescript-eslint/no-misused-promises": [ + "error", + { + "checksVoidReturn": { "attributes": false } + } + ], + "@typescript-eslint/consistent-type-imports": "warn", + "@typescript-eslint/prefer-nullish-coalescing": "warn", + "@typescript-eslint/explicit-function-return-type": "warn" + } +} diff --git a/platforms/blabsy/.firebaserc b/platforms/blabsy/.firebaserc new file mode 100644 index 00000000..d4339af8 --- /dev/null +++ b/platforms/blabsy/.firebaserc @@ -0,0 +1,5 @@ +{ + "projects": { + "default": "twitter-clone-ccrsxx" + } +} diff --git a/platforms/blabsy/.github/assets/presentation.png b/platforms/blabsy/.github/assets/presentation.png new file mode 100644 index 00000000..b925cff8 Binary files /dev/null and b/platforms/blabsy/.github/assets/presentation.png differ diff --git a/platforms/blabsy/.github/workflows/deployment.yaml b/platforms/blabsy/.github/workflows/deployment.yaml new file mode 100644 index 00000000..ad359cf0 --- /dev/null +++ b/platforms/blabsy/.github/workflows/deployment.yaml @@ -0,0 +1,48 @@ +name: Deploy 🚀 + +on: + push: + branches: ['main'] + pull_request: + branches: ['main'] + +jobs: + prettier: + name: 🧪 Prettier + runs-on: ubuntu-latest + steps: + - name: ⬇️ Checkout repo + uses: actions/checkout@v3 + + - name: 📥 Download deps + run: npm ci + + - name: 🔍 Format + run: npm run format + + eslint: + name: ✅ ESLint + runs-on: ubuntu-latest + steps: + - name: ⬇️ Checkout repo + uses: actions/checkout@v3 + + - name: 📥 Download deps + run: npm ci + + - name: 🪄 Lint + run: npm run lint + + jest: + name: 🃏 Jest + runs-on: ubuntu-latest + steps: + - name: ⬇️ Checkout repo + uses: actions/checkout@v3 + + - name: 📥 Download deps + run: npm ci + + # ! uncomment this after you add test + # - name: 🔬 Test + # run: npm run test:ci diff --git a/platforms/blabsy/.gitignore b/platforms/blabsy/.gitignore index c5dabf31..e423cfd8 100644 --- a/platforms/blabsy/.gitignore +++ b/platforms/blabsy/.gitignore @@ -1,25 +1,40 @@ -node_modules +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. -# Output -.output -.vercel -.netlify -.wrangler -/.svelte-kit +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production /build -# OS +# misc .DS_Store -Thumbs.db +*.pem -# Env -.env -.env.* -!.env.example -!.env.test +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# local env files +.env*.local + +# vercel +.vercel -# Vite -vite.config.js.timestamp-* -vite.config.ts.timestamp-* +# typescript +*.tsbuildinfo +next-env.d.ts -*storybook.log +# python +/.mypy_cache +*.py diff --git a/platforms/blabsy/.npmrc b/platforms/blabsy/.npmrc deleted file mode 100644 index b6f27f13..00000000 --- a/platforms/blabsy/.npmrc +++ /dev/null @@ -1 +0,0 @@ -engine-strict=true diff --git a/platforms/blabsy/.prettierignore b/platforms/blabsy/.prettierignore index 6562bcbb..01fc65b1 100644 --- a/platforms/blabsy/.prettierignore +++ b/platforms/blabsy/.prettierignore @@ -1,6 +1,17 @@ -# Package Managers -package-lock.json -pnpm-lock.yaml -yarn.lock -bun.lock -bun.lockb +# testing +/coverage + +# next.js +/.next/ +/.vercel/ +/out/ + +# production +/build + +# compiled js functions +/functions/lib/ + +# python +*.py +.mypy_cache/ diff --git a/platforms/blabsy/.prettierrc b/platforms/blabsy/.prettierrc deleted file mode 100644 index 7ebb855b..00000000 --- a/platforms/blabsy/.prettierrc +++ /dev/null @@ -1,15 +0,0 @@ -{ - "useTabs": true, - "singleQuote": true, - "trailingComma": "none", - "printWidth": 100, - "plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"], - "overrides": [ - { - "files": "*.svelte", - "options": { - "parser": "svelte" - } - } - ] -} diff --git a/platforms/blabsy/.prettierrc.json b/platforms/blabsy/.prettierrc.json new file mode 100644 index 00000000..f7fe5497 --- /dev/null +++ b/platforms/blabsy/.prettierrc.json @@ -0,0 +1,5 @@ +{ + "singleQuote": true, + "jsxSingleQuote": true, + "trailingComma": "none" +} diff --git a/platforms/blabsy/.storybook/main.ts b/platforms/blabsy/.storybook/main.ts deleted file mode 100644 index e5a31a17..00000000 --- a/platforms/blabsy/.storybook/main.ts +++ /dev/null @@ -1,24 +0,0 @@ -import type { StorybookConfig } from '@storybook/sveltekit'; - -import { join, dirname } from 'path'; - -/** - * This function is used to resolve the absolute path of a package. - * It is needed in projects that use Yarn PnP or are set up within a monorepo. - */ -function getAbsolutePath(value: string): string { - return dirname(require.resolve(join(value, 'package.json'))); -} -const config: StorybookConfig = { - stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|ts|svelte)'], - addons: [ - getAbsolutePath('@storybook/addon-essentials'), - getAbsolutePath('@storybook/addon-svelte-csf'), - getAbsolutePath('@chromatic-com/storybook') - ], - framework: { - name: getAbsolutePath('@storybook/sveltekit'), - options: {} - } -}; -export default config; diff --git a/platforms/blabsy/.storybook/preview.ts b/platforms/blabsy/.storybook/preview.ts deleted file mode 100644 index 94b3fe76..00000000 --- a/platforms/blabsy/.storybook/preview.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type { Preview } from '@storybook/svelte'; -import '../src/app.css'; - -const preview: Preview = { - parameters: { - controls: { - matchers: { - color: /(background|color)$/i, - date: /Date$/i - } - } - } -}; - -export default preview; diff --git a/platforms/blabsy/LICENSE b/platforms/blabsy/LICENSE new file mode 100644 index 00000000..b47b9f70 --- /dev/null +++ b/platforms/blabsy/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 ccrsxx + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/platforms/blabsy/README.md b/platforms/blabsy/README.md index b5b29507..43f08875 100644 --- a/platforms/blabsy/README.md +++ b/platforms/blabsy/README.md @@ -1,38 +1,124 @@ -# sv +
-Everything you need to build a Svelte project, powered by [`sv`](https://github.com/sveltejs/cli). +![](/.github/assets/presentation.png) -## Creating a project +

+ Twitter clone built in Next.js + TypeScript + Tailwind CSS using Cloud Firestore and Storage +

-If you're seeing this, you've probably already done this step. Congrats! +## Preview 🎬 -```bash -# create a new project in the current directory -npx sv create +https://user-images.githubusercontent.com/55032197/201472767-9db0177a-79b5-4913-8666-1744102b0ad7.mp4 -# create a new project in my-app -npx sv create my-app -``` +## Features ✨ -## Developing +- Authentication with Firebase Authentication +- Strongly typed React components with TypeScript +- Users can add tweets, like, retweet, and reply +- Users can delete tweets, add a tweet to bookmarks, and pin their tweet +- Users can add images and GIFs to tweet +- Users can follow and unfollow other users +- Users can see their and other followers and the following list +- Users can see all users and the trending list +- Realtime update likes, retweets, and user profile +- Realtime trending data from Twitter API +- User can edit their profile +- Responsive design for mobile, tablet, and desktop +- Users can customize the site color scheme and color background +- All images uploads are stored on Firebase Cloud Storage -Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server: +## Tech 🛠 -```bash -npm run dev +- [Next.js](https://nextjs.org) +- [TypeScript](https://www.typescriptlang.org) +- [Tailwind CSS](https://tailwindcss.com) +- [Firebase](https://firebase.google.com) +- [SWR](https://swr.vercel.app) +- [Headless UI](https://headlessui.com) +- [React Hot Toast](https://react-hot-toast.com) +- [Framer Motion](https://framer.com) -# or start the server and open the app in a new browser tab -npm run dev -- --open -``` +## Development 💻 -## Building +Here are the steps to run the project locally. -To create a production version of your app: +1. Clone the repository -```bash -npm run build -``` + ```bash + git clone https://github.com/ccrsxx/twitter-clone.git + ``` -You can preview the production build with `npm run preview`. +1. Install dependencies -> To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment. + ```bash + npm i + ``` + +1. Create a Firebase project and select the web app + +1. Add your Firebase config to `.env.development`. Note that `NEXT_PUBLIC_MEASUREMENT_ID` is optional + +1. Make sure you have enabled the following Firebase services: + + - Authentication. Enable the Google sign-in method. + - Cloud Firestore. Create a database and set its location to your nearest region. + - Cloud Storage. Create a storage bucket. + +1. Install Firebase CLI globally + + ```bash + npm i -g firebase-tools + ``` + +1. Log in to Firebase + + ```bash + firebase login + ``` + +1. Get your project ID + + ```bash + firebase projects:list + ``` + +1. Select your project ID + + ```bash + firebase use your-project-id + ``` + +1. At this point, you have two choices. Either run this project using the Firebase on the cloud or locally using emulator. + + 1. Using the Firebase Cloud Backend: + + 1. Deploy Firestore rules, Firestore indexes, and Cloud Storage rules + + ```bash + firebase deploy --except functions + ``` + + 1. Run the project + + ```bash + npm run dev + ``` + + 1. Using Firebase Local Emulator: + + 1. Install [Java JDK version 11 or higher](https://jdk.java.net/) before proceeding. This is required to run the emulators. + + 1. Set the environment variable `NEXT_PUBLIC_USE_EMULATOR` to `true` in `.env.development`. This will make the app use the emulators instead of the cloud backend. + + 1. At this point, you can run the following command to have a fully functional Twitter clone running locally: + + ```bash + npm run dev:emulators + ``` + +> **_Note_**: When you deploy Firestore indexes rules, it might take a few minutes to complete. So before the indexes are enabled, you will get an error when you fetch the data from Firestore.

You can check the status of your Firestore indexes with the link below, replace `your-project-id` with your project ID: https://console.firebase.google.com/u/0/project/your-project-id/firestore/indexes + +Optional: + +- If you want to get trending data from Twitter API, you need to create a Twitter developer account and get your API keys. Then add your API keys to `.env.development`. I hope Elon Musk doesn't make this API paid 😅. +- If you want to make the user stats synced with the deleted tweets, you need to enable the Cloud Functions for Firebase. Then deploy the Cloud Functions. diff --git a/platforms/blabsy/eslint.config.js b/platforms/blabsy/eslint.config.js deleted file mode 100644 index 44868546..00000000 --- a/platforms/blabsy/eslint.config.js +++ /dev/null @@ -1,42 +0,0 @@ -import prettier from 'eslint-config-prettier'; -import js from '@eslint/js'; -import { includeIgnoreFile } from '@eslint/compat'; -import svelte from 'eslint-plugin-svelte'; -import globals from 'globals'; -import { fileURLToPath } from 'node:url'; -import ts from 'typescript-eslint'; -import svelteConfig from './svelte.config.js'; - -const gitignorePath = fileURLToPath(new URL('./.gitignore', import.meta.url)); - -export default ts.config( - includeIgnoreFile(gitignorePath), - js.configs.recommended, - ...ts.configs.recommended, - ...svelte.configs.recommended, - prettier, - ...svelte.configs.prettier, - { - languageOptions: { - globals: { ...globals.browser } - }, - rules: {}, - overrides: [ - { - files: ['*.svelte'], - rules: { 'no-undef': 'off' } - } - ] - }, - { - files: ['**/*.svelte', '**/*.svelte.ts', '**/*.svelte.js'], - languageOptions: { - parserOptions: { - projectService: true, - extraFileExtensions: ['.svelte'], - parser: ts.parser, - svelteConfig - } - } - } -); diff --git a/platforms/blabsy/firebase.json b/platforms/blabsy/firebase.json new file mode 100644 index 00000000..ae9b1d30 --- /dev/null +++ b/platforms/blabsy/firebase.json @@ -0,0 +1,37 @@ +{ + "firestore": { + "rules": "firestore.rules", + "indexes": "firestore.indexes.json" + }, + "functions": { + "ignore": [ + "node_modules", + ".git", + "firebase-debug.log", + "firebase-debug.*.log" + ], + "predeploy": "npm --prefix \"$RESOURCE_DIR\" run build", + "source": "functions" + }, + "storage": { + "rules": "storage.rules" + }, + "emulators": { + "auth": { + "port": 9099 + }, + "functions": { + "port": 5001 + }, + "firestore": { + "port": 8080 + }, + "storage": { + "port": 9199 + }, + "ui": { + "enabled": true + }, + "singleProjectMode": true + } +} diff --git a/platforms/blabsy/firestore.indexes.json b/platforms/blabsy/firestore.indexes.json new file mode 100644 index 00000000..13af2901 --- /dev/null +++ b/platforms/blabsy/firestore.indexes.json @@ -0,0 +1,112 @@ +{ + "indexes": [ + { + "collectionGroup": "tweets", + "queryScope": "COLLECTION", + "fields": [ + { + "fieldPath": "createdBy", + "order": "ASCENDING" + }, + { + "fieldPath": "createdAt", + "order": "DESCENDING" + } + ] + }, + { + "collectionGroup": "tweets", + "queryScope": "COLLECTION", + "fields": [ + { + "fieldPath": "createdBy", + "order": "ASCENDING" + }, + { + "fieldPath": "images", + "order": "ASCENDING" + } + ] + }, + { + "collectionGroup": "tweets", + "queryScope": "COLLECTION", + "fields": [ + { + "fieldPath": "parent", + "order": "ASCENDING" + }, + { + "fieldPath": "createdAt", + "order": "DESCENDING" + } + ] + }, + { + "collectionGroup": "tweets", + "queryScope": "COLLECTION", + "fields": [ + { + "fieldPath": "parent.id", + "order": "ASCENDING" + }, + { + "fieldPath": "createdAt", + "order": "DESCENDING" + } + ] + }, + { + "collectionGroup": "tweets", + "queryScope": "COLLECTION", + "fields": [ + { + "fieldPath": "userLikes", + "arrayConfig": "CONTAINS" + }, + { + "fieldPath": "createdAt", + "order": "DESCENDING" + } + ] + }, + { + "collectionGroup": "tweets", + "queryScope": "COLLECTION", + "fields": [ + { + "fieldPath": "userRetweets", + "arrayConfig": "CONTAINS" + }, + { + "fieldPath": "createdBy", + "order": "ASCENDING" + } + ] + } + ], + "fieldOverrides": [ + { + "collectionGroup": "bookmarks", + "fieldPath": "id", + "indexes": [ + { + "order": "ASCENDING", + "queryScope": "COLLECTION" + }, + { + "order": "DESCENDING", + "queryScope": "COLLECTION" + }, + { + "arrayConfig": "CONTAINS", + "queryScope": "COLLECTION" + }, + { + "order": "ASCENDING", + "queryScope": "COLLECTION_GROUP" + } + ] + } + ] +} diff --git a/platforms/blabsy/firestore.rules b/platforms/blabsy/firestore.rules new file mode 100644 index 00000000..ebc09b8c --- /dev/null +++ b/platforms/blabsy/firestore.rules @@ -0,0 +1,57 @@ +rules_version = '2'; +service cloud.firestore { + match /databases/{database}/documents { + function isAdmin() { + return request.auth.uid == 'Twt0A27bx9YcG4vu3RTsR7ifJzf2'; + } + + function isAuthorized(userId) { + return request.auth != null && (userId == request.auth.uid || isAdmin()); + } + + function isValidText(text) { + return text is string || text == null; + } + + function isValidImages(images) { + return (images is list && images.size() <= 4) || images == null; + } + + function isChatParticipant(chatId) { + return request.auth != null && + exists(/databases/$(database)/documents/chats/$(chatId)) && + request.auth.uid in get(/databases/$(database)/documents/chats/$(chatId)).data.participants; + } + + match /tweets/{tweetId} { + allow read, update: if request.auth != null; + allow create: if isAuthorized(request.resource.data.createdBy) + && isValidText(request.resource.data.text) + && isValidImages(request.resource.data.images); + allow delete: if isAuthorized(resource.data.createdBy); + } + + match /users/{document=**} { + allow read, write: if request.auth != null; + } + + match /chats/{chatId} { + allow read: if request.auth != null; + allow create: if request.auth != null && request.auth.uid in request.resource.data.participants; + allow update: if request.auth != null && request.auth.uid in resource.data.participants; + allow delete: if request.auth != null && request.auth.uid in resource.data.participants; + } + + match /chats/{chatId}/messages/{messageId} { + allow read: if request.auth != null; + allow create: if request.auth != null && + request.auth.uid in get(/databases/$(database)/documents/chats/$(chatId)).data.participants && + request.auth.uid == request.resource.data.senderId; + allow update: if request.auth != null && + request.auth.uid in get(/databases/$(database)/documents/chats/$(chatId)).data.participants; + allow delete: if request.auth != null && + request.auth.uid in get(/databases/$(database)/documents/chats/$(chatId)).data.participants && + request.auth.uid == resource.data.senderId; + } + } +} diff --git a/platforms/blabsy/functions/.gitignore b/platforms/blabsy/functions/.gitignore new file mode 100644 index 00000000..911edff5 --- /dev/null +++ b/platforms/blabsy/functions/.gitignore @@ -0,0 +1,12 @@ +# Compiled JavaScript files +lib/**/*.js +lib/**/*.js.map + +# TypeScript v1 declaration files +typings/ + +# Node.js dependency directory +node_modules/ + +# Env +.env* diff --git a/platforms/blabsy/functions/package-lock.json b/platforms/blabsy/functions/package-lock.json new file mode 100644 index 00000000..6754922c --- /dev/null +++ b/platforms/blabsy/functions/package-lock.json @@ -0,0 +1,4260 @@ +{ + "name": "functions", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "functions", + "dependencies": { + "firebase-admin": "^10.2.0", + "firebase-functions": "^4.0.1", + "nodemailer": "^6.8.0" + }, + "devDependencies": { + "@types/nodemailer": "^6.4.6", + "typescript": "^4.6.4" + }, + "engines": { + "node": "16" + } + }, + "node_modules/@fastify/busboy": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-1.1.0.tgz", + "integrity": "sha512-Fv854f94v0CzIDllbY3i/0NJPNBRNLDawf3BTYVGCe9VrIIs3Wi7AFx24F9NzCxdf0wyx/x0Q9kEVnvDOPnlxA==", + "dependencies": { + "text-decoding": "^1.0.0" + }, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/@firebase/app-types": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.7.0.tgz", + "integrity": "sha512-6fbHQwDv2jp/v6bXhBw2eSRbNBpxHcd1NBF864UksSMVIqIyri9qpJB1Mn6sGZE+bnDsSQBC5j2TbMxYsJQkQg==" + }, + "node_modules/@firebase/auth-interop-types": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.1.6.tgz", + "integrity": "sha512-etIi92fW3CctsmR9e3sYM3Uqnoq861M0Id9mdOPF6PWIg38BXL5k4upCNBggGUpLIS0H1grMOvy/wn1xymwe2g==", + "peerDependencies": { + "@firebase/app-types": "0.x", + "@firebase/util": "1.x" + } + }, + "node_modules/@firebase/component": { + "version": "0.5.17", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.5.17.tgz", + "integrity": "sha512-mTM5CBSIlmI+i76qU4+DhuExnWtzcPS3cVgObA3VAjliPPr3GrUlTaaa8KBGfxsD27juQxMsYA0TvCR5X+GQ3Q==", + "dependencies": { + "@firebase/util": "1.6.3", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/database": { + "version": "0.13.6", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.13.6.tgz", + "integrity": "sha512-5IZIBw2LT50Z8mwmKYmdX37p+Gg2HgeJsrruZmRyOSVgbfoY4Pg87n1uFx6qWqDmfL6HwQgwcrrQfVIXE3C5SA==", + "dependencies": { + "@firebase/auth-interop-types": "0.1.6", + "@firebase/component": "0.5.17", + "@firebase/logger": "0.3.3", + "@firebase/util": "1.6.3", + "faye-websocket": "0.11.4", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/database-compat": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-0.2.6.tgz", + "integrity": "sha512-Ls1BAODaiDYgeJljrIgSuC7JkFIY/HNhhNYebzZSoGQU62RuvnaO3Qgp2EH6h2LzHyRnycNadfh1suROtPaUIA==", + "dependencies": { + "@firebase/component": "0.5.17", + "@firebase/database": "0.13.6", + "@firebase/database-types": "0.9.13", + "@firebase/logger": "0.3.3", + "@firebase/util": "1.6.3", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/database-types": { + "version": "0.9.13", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.9.13.tgz", + "integrity": "sha512-dIJ1zGe3EHMhwcvukTOPzYlFYFIG1Et5Znl7s7y/ZTN2/toARRNnsv1qCKvqevIMYKvIrRsYOYfOXDS8l1YIJA==", + "dependencies": { + "@firebase/app-types": "0.7.0", + "@firebase/util": "1.6.3" + } + }, + "node_modules/@firebase/logger": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.3.3.tgz", + "integrity": "sha512-POTJl07jOKTOevLXrTvJD/VZ0M6PnJXflbAh5J9VGkmtXPXNG6MdZ9fmRgqYhXKTaDId6AQenQ262uwgpdtO0Q==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/util": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.6.3.tgz", + "integrity": "sha512-FujteO6Zjv6v8A4HS+t7c+PjU0Kaxj+rOnka0BsI/twUaCC9t8EQPmXpWZdk7XfszfahJn2pqsflUWUhtUkRlg==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@google-cloud/firestore": { + "version": "4.15.1", + "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-4.15.1.tgz", + "integrity": "sha512-2PWsCkEF1W02QbghSeRsNdYKN1qavrHBP3m72gPDMHQSYrGULOaTi7fSJquQmAtc4iPVB2/x6h80rdLHTATQtA==", + "optional": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "functional-red-black-tree": "^1.0.1", + "google-gax": "^2.24.1", + "protobufjs": "^6.8.6" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@google-cloud/paginator": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-3.0.7.tgz", + "integrity": "sha512-jJNutk0arIQhmpUUQJPJErsojqo834KcyB6X7a1mxuic8i1tKXxde8E69IZxNZawRIlZdIK2QY4WALvlK5MzYQ==", + "optional": true, + "dependencies": { + "arrify": "^2.0.0", + "extend": "^3.0.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@google-cloud/projectify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-2.1.1.tgz", + "integrity": "sha512-+rssMZHnlh0twl122gXY4/aCrk0G1acBqkHFfYddtsqpYXGxA29nj9V5V9SfC+GyOG00l650f6lG9KL+EpFEWQ==", + "optional": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/@google-cloud/promisify": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-2.0.4.tgz", + "integrity": "sha512-j8yRSSqswWi1QqUGKVEKOG03Q7qOoZP6/h2zN2YO+F5h2+DHU0bSrHCK9Y7lo2DI9fBd8qGAw795sf+3Jva4yA==", + "optional": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/@google-cloud/storage": { + "version": "5.20.5", + "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-5.20.5.tgz", + "integrity": "sha512-lOs/dCyveVF8TkVFnFSF7IGd0CJrTm91qiK6JLu+Z8qiT+7Ag0RyVhxZIWkhiACqwABo7kSHDm8FdH8p2wxSSw==", + "optional": true, + "dependencies": { + "@google-cloud/paginator": "^3.0.7", + "@google-cloud/projectify": "^2.0.0", + "@google-cloud/promisify": "^2.0.0", + "abort-controller": "^3.0.0", + "arrify": "^2.0.0", + "async-retry": "^1.3.3", + "compressible": "^2.0.12", + "configstore": "^5.0.0", + "duplexify": "^4.0.0", + "ent": "^2.2.0", + "extend": "^3.0.2", + "gaxios": "^4.0.0", + "google-auth-library": "^7.14.1", + "hash-stream-validation": "^0.2.2", + "mime": "^3.0.0", + "mime-types": "^2.0.8", + "p-limit": "^3.0.1", + "pumpify": "^2.0.0", + "retry-request": "^4.2.2", + "stream-events": "^1.0.4", + "teeny-request": "^7.1.3", + "uuid": "^8.0.0", + "xdg-basedir": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@grpc/grpc-js": { + "version": "1.6.12", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.6.12.tgz", + "integrity": "sha512-JmvQ03OTSpVd9JTlj/K3IWHSz4Gk/JMLUTtW7Zb0KvO1LcOYGATh5cNuRYzCAeDR3O8wq+q8FZe97eO9MBrkUw==", + "optional": true, + "dependencies": { + "@grpc/proto-loader": "^0.7.0", + "@types/node": ">=12.12.47" + }, + "engines": { + "node": "^8.13.0 || >=10.10.0" + } + }, + "node_modules/@grpc/grpc-js/node_modules/@grpc/proto-loader": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.3.tgz", + "integrity": "sha512-5dAvoZwna2Py3Ef96Ux9jIkp3iZ62TUsV00p3wVBPNX5K178UbNi8Q7gQVqwXT1Yq9RejIGG9G2IPEo93T6RcA==", + "optional": true, + "dependencies": { + "@types/long": "^4.0.1", + "lodash.camelcase": "^4.3.0", + "long": "^4.0.0", + "protobufjs": "^7.0.0", + "yargs": "^16.2.0" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@grpc/grpc-js/node_modules/protobufjs": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.1.2.tgz", + "integrity": "sha512-4ZPTPkXCdel3+L81yw3dG6+Kq3umdWKh7Dc7GW/CpNk4SX3hK58iPCWeCyhVTDrbkNeKrYNZ7EojM5WDaEWTLQ==", + "hasInstallScript": true, + "optional": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@grpc/grpc-js/node_modules/protobufjs/node_modules/long": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.0.tgz", + "integrity": "sha512-9RTUNjK60eJbx3uz+TEGF7fUr29ZDxR5QzXcyDpeSfeH28S9ycINflOgOlppit5U+4kNTe83KQnMEerw7GmE8w==", + "optional": true + }, + "node_modules/@grpc/proto-loader": { + "version": "0.6.13", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.6.13.tgz", + "integrity": "sha512-FjxPYDRTn6Ec3V0arm1FtSpmP6V50wuph2yILpyvTKzjc76oDdoihXqM1DzOW5ubvCC8GivfCnNtfaRE8myJ7g==", + "optional": true, + "dependencies": { + "@types/long": "^4.0.1", + "lodash.camelcase": "^4.3.0", + "long": "^4.0.0", + "protobufjs": "^6.11.3", + "yargs": "^16.2.0" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@panva/asn1.js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@panva/asn1.js/-/asn1.js-1.0.0.tgz", + "integrity": "sha512-UdkG3mLEqXgnlKsWanWcgb6dOjUzJ+XC5f+aWw30qrtjxeNUSfKX1cd5FBzOaXQumoe9nIqeZUvrRJS03HCCtw==", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "optional": true + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "optional": true + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "optional": true + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "optional": true + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "optional": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "optional": true + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", + "optional": true + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "optional": true + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "optional": true + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", + "optional": true + }, + "node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "optional": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", + "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.35", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", + "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/cors": { + "version": "2.8.12", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz", + "integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==" + }, + "node_modules/@types/express": { + "version": "4.17.3", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.3.tgz", + "integrity": "sha512-I8cGRJj3pyOLs/HndoP+25vOqhqWkAZsWMEmq1qXy/b/M3ppufecUwaK2/TVDVxcV61/iSdhykUjQQ2DLSrTdg==", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.17.31", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.31.tgz", + "integrity": "sha512-DxMhY+NAsTwMMFHBTtJFNp5qiHKJ7TeqOo23zVEM9alT1Ml27Q3xcTH0xwxn7Q0BbMcVEJOs/7aQtUWupUQN3Q==", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*" + } + }, + "node_modules/@types/jsonwebtoken": { + "version": "8.5.9", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-8.5.9.tgz", + "integrity": "sha512-272FMnFGzAVMGtu9tkr29hRL6bZj4Zs1KZNeHLnKqAvp06tAIcarTMwOh8/8bz4FmKRcMxZhZNeUAQsNLoiPhg==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/long": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", + "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==", + "optional": true + }, + "node_modules/@types/mime": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz", + "integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==" + }, + "node_modules/@types/node": { + "version": "18.7.19", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.19.tgz", + "integrity": "sha512-Sq1itGUKUX1ap7GgZlrzdBydjbsJL/NSQt/4wkAxUJ7/OS5c2WkoN6WSpWc2Yc5wtKMZOUA0VCs/j2XJadN3HA==" + }, + "node_modules/@types/nodemailer": { + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.6.tgz", + "integrity": "sha512-pD6fL5GQtUKvD2WnPmg5bC2e8kWCAPDwMPmHe/ohQbW+Dy0EcHgZ2oCSuPlWNqk74LS5BVMig1SymQbFMPPK3w==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/qs": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==" + }, + "node_modules/@types/range-parser": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", + "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" + }, + "node_modules/@types/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-z5xyF6uh8CbjAu9760KDKsH2FcDxZ2tFCsA4HIMWE6IkiYMXfVoa+4f9KX+FN0ZLsaMw1WNG2ETLA6N+/YA+cg==", + "dependencies": { + "@types/mime": "*", + "@types/node": "*" + } + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "optional": true, + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "optional": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/agent-base/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "optional": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/agent-base/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "optional": true + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "optional": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, + "node_modules/arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/async-retry": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz", + "integrity": "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==", + "optional": true, + "dependencies": { + "retry": "0.13.1" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "optional": true + }, + "node_modules/bignumber.js": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.0.tgz", + "integrity": "sha512-4LwHK4nfDOraBCtst+wOWIHbu1vhvAPJK8g8nROd4iuc3PSEjWif/qwbkh8jwCJz6yDBvtU4KPynETgrfh7y3A==", + "optional": true, + "engines": { + "node": "*" + } + }, + "node_modules/body-parser": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz", + "integrity": "sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.10.3", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "optional": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "optional": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "optional": true + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "optional": true, + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/configstore": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", + "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==", + "optional": true, + "dependencies": { + "dot-prop": "^5.2.0", + "graceful-fs": "^4.1.2", + "make-dir": "^3.0.0", + "unique-string": "^2.0.0", + "write-file-atomic": "^3.0.0", + "xdg-basedir": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/crypto-random-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", + "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/dot-prop": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", + "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", + "optional": true, + "dependencies": { + "is-obj": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/duplexify": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.2.tgz", + "integrity": "sha512-fz3OjcNCHmRP12MJoZMPglx8m4rrFP8rovnk4vT8Fs+aonZoCwGg10dSsQsfP/E62eZcPTMSMP6686fu9Qlqtw==", + "optional": true, + "dependencies": { + "end-of-stream": "^1.4.1", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1", + "stream-shift": "^1.0.0" + } + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "optional": true + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "optional": true, + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/ent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", + "integrity": "sha512-GHrMyVZQWvTIdDtpiEXdHZnFQKzeO09apj8Cbl4pKWy4i0Oprcq17usfDt5aO63swf0JOeMWjWQE/LzgSRuWpA==", + "optional": true + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/express": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.1.tgz", + "integrity": "sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q==", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.0", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.5.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.10.3", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "optional": true + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "optional": true + }, + "node_modules/fast-text-encoding": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.6.tgz", + "integrity": "sha512-VhXlQgj9ioXCqGstD37E/HBeqEGV/qOD/kmbVG8h5xKBYvM1L3lR1Zn4555cQ8GkYbJa8aJSipLPndE1k6zK2w==", + "optional": true + }, + "node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/firebase-admin": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-10.3.0.tgz", + "integrity": "sha512-A0wgMLEjyVyUE+heyMJYqHRkPVjpebhOYsa47RHdrTM4ltApcx8Tn86sUmjqxlfh09gNnILAm7a8q5+FmgBYpg==", + "dependencies": { + "@fastify/busboy": "^1.1.0", + "@firebase/database-compat": "^0.2.0", + "@firebase/database-types": "^0.9.7", + "@types/node": ">=12.12.47", + "jsonwebtoken": "^8.5.1", + "jwks-rsa": "^2.0.2", + "node-forge": "^1.3.1", + "uuid": "^8.3.2" + }, + "engines": { + "node": ">=12.7.0" + }, + "optionalDependencies": { + "@google-cloud/firestore": "^4.15.1", + "@google-cloud/storage": "^5.18.3" + } + }, + "node_modules/firebase-functions": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/firebase-functions/-/firebase-functions-4.0.1.tgz", + "integrity": "sha512-U0dOqGPShLi0g3jUlZ3aZlVTPFO9cREJfIxMJIlfRz/vNbYoKdIVdI7OAS9RKPcqz99zxkN/A8Ro4kjI+ytT8A==", + "dependencies": { + "@types/cors": "^2.8.5", + "@types/express": "4.17.3", + "cors": "^2.8.5", + "express": "^4.17.1", + "node-fetch": "^2.6.7" + }, + "bin": { + "firebase-functions": "lib/bin/firebase-functions.js" + }, + "engines": { + "node": ">=14.10.0" + }, + "peerDependencies": { + "firebase-admin": "^10.0.0 || ^11.0.0" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "node_modules/functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", + "optional": true + }, + "node_modules/gaxios": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-4.3.3.tgz", + "integrity": "sha512-gSaYYIO1Y3wUtdfHmjDUZ8LWaxJQpiavzbF5Kq53akSzvmVg0RfyOcFDbO1KJ/KCGRFz2qG+lS81F0nkr7cRJA==", + "optional": true, + "dependencies": { + "abort-controller": "^3.0.0", + "extend": "^3.0.2", + "https-proxy-agent": "^5.0.0", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.7" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/gcp-metadata": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-4.3.1.tgz", + "integrity": "sha512-x850LS5N7V1F3UcV7PoupzGsyD6iVwTVvsh3tbXfkctZnBnjW5yu5z1/3k3SehF7TyoTIe78rJs02GMMy+LF+A==", + "optional": true, + "dependencies": { + "gaxios": "^4.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "optional": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", + "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==", + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/google-auth-library": { + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-7.14.1.tgz", + "integrity": "sha512-5Rk7iLNDFhFeBYc3s8l1CqzbEBcdhwR193RlD4vSNFajIcINKI8W8P0JLmBpwymHqqWbX34pJDQu39cSy/6RsA==", + "optional": true, + "dependencies": { + "arrify": "^2.0.0", + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "fast-text-encoding": "^1.0.0", + "gaxios": "^4.0.0", + "gcp-metadata": "^4.2.0", + "gtoken": "^5.0.4", + "jws": "^4.0.0", + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/google-gax": { + "version": "2.30.5", + "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-2.30.5.tgz", + "integrity": "sha512-Jey13YrAN2hfpozHzbtrwEfEHdStJh1GwaQ2+Akh1k0Tv/EuNVSuBtHZoKSBm5wBMvNsxTsEIZ/152NrYyZgxQ==", + "optional": true, + "dependencies": { + "@grpc/grpc-js": "~1.6.0", + "@grpc/proto-loader": "^0.6.12", + "@types/long": "^4.0.0", + "abort-controller": "^3.0.0", + "duplexify": "^4.0.0", + "fast-text-encoding": "^1.0.3", + "google-auth-library": "^7.14.0", + "is-stream-ended": "^0.1.4", + "node-fetch": "^2.6.1", + "object-hash": "^3.0.0", + "proto3-json-serializer": "^0.1.8", + "protobufjs": "6.11.3", + "retry-request": "^4.0.0" + }, + "bin": { + "compileProtos": "build/tools/compileProtos.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/google-p12-pem": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.1.4.tgz", + "integrity": "sha512-HHuHmkLgwjdmVRngf5+gSmpkyaRI6QmOg77J8tkNBHhNEI62sGHyw4/+UkgyZEI7h84NbWprXDJ+sa3xOYFvTg==", + "optional": true, + "dependencies": { + "node-forge": "^1.3.1" + }, + "bin": { + "gp12-pem": "build/src/bin/gp12-pem.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "optional": true + }, + "node_modules/gtoken": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-5.3.2.tgz", + "integrity": "sha512-gkvEKREW7dXWF8NV8pVrKfW7WqReAmjjkMBh6lNCCGOM4ucS0r0YyXXl0r/9Yj8wcW/32ISkfc8h5mPTDbtifQ==", + "optional": true, + "dependencies": { + "gaxios": "^4.0.0", + "google-p12-pem": "^3.1.3", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hash-stream-validation": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/hash-stream-validation/-/hash-stream-validation-0.2.4.tgz", + "integrity": "sha512-Gjzu0Xn7IagXVkSu9cSFuK1fqzwtLwFhNhVL8IFJijRNMgUttFbBSIAzKuSIrsFMO1+g1RlsoN49zPIbwPDMGQ==", + "optional": true + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-parser-js": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", + "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==" + }, + "node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "optional": true, + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/http-proxy-agent/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "optional": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/http-proxy-agent/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "optional": true + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "optional": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/https-proxy-agent/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "optional": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/https-proxy-agent/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "optional": true + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "optional": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "optional": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-stream-ended": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-stream-ended/-/is-stream-ended-0.1.4.tgz", + "integrity": "sha512-xj0XPvmr7bQFTvirqnFr50o0hQIh6ZItDqloxt5aJrR4NQsYeSsyFQERYGCAzfindAcnKjINnwEEgLx4IqVzQw==", + "optional": true + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "optional": true + }, + "node_modules/jose": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/jose/-/jose-2.0.6.tgz", + "integrity": "sha512-FVoPY7SflDodE4lknJmbAHSUjLCzE2H1F6MS0RYKMQ8SR+lNccpMf8R4eqkNYyyUjR5qZReOzZo5C5YiHOCjjg==", + "dependencies": { + "@panva/asn1.js": "^1.0.0" + }, + "engines": { + "node": ">=10.13.0 < 13 || >=13.7.0" + }, + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "optional": true, + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, + "node_modules/jsonwebtoken": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", + "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=4", + "npm": ">=1.4.28" + } + }, + "node_modules/jsonwebtoken/node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jsonwebtoken/node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jsonwebtoken/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "optional": true, + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jwks-rsa": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-2.1.4.tgz", + "integrity": "sha512-mpArfgPkUpX11lNtGxsF/szkasUcbWHGplZl/uFvFO2NuMHmt0dQXIihh0rkPU2yQd5niQtuUHbXnG/WKiXF6Q==", + "dependencies": { + "@types/express": "^4.17.13", + "@types/jsonwebtoken": "^8.5.8", + "debug": "^4.3.4", + "jose": "^2.0.5", + "limiter": "^1.1.5", + "lru-memoizer": "^2.1.4" + }, + "engines": { + "node": ">=10 < 13 || >=14" + } + }, + "node_modules/jwks-rsa/node_modules/@types/express": { + "version": "4.17.14", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.14.tgz", + "integrity": "sha512-TEbt+vaPFQ+xpxFLFssxUDXj5cWCxZJjIcB7Yg0k0GMHGtgtQgpvx/MUQUeAkNbA9AAGrwkAsoeItdTgS7FMyg==", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.18", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/jwks-rsa/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/jwks-rsa/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "optional": true, + "dependencies": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/limiter": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz", + "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==" + }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "optional": true + }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==" + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" + }, + "node_modules/long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", + "optional": true + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/lru-memoizer": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-2.1.4.tgz", + "integrity": "sha512-IXAq50s4qwrOBrXJklY+KhgZF+5y98PDaNo0gi/v2KQBFLyWr+JyFvijZXkGKjQj/h9c0OwoE+JZbwUXce76hQ==", + "dependencies": { + "lodash.clonedeep": "^4.5.0", + "lru-cache": "~4.0.0" + } + }, + "node_modules/lru-memoizer/node_modules/lru-cache": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.0.2.tgz", + "integrity": "sha512-uQw9OqphAGiZhkuPlpFGmdTU2tEuhxTourM/19qGJrxBPHAr/f8BT1a0i/lOclESnGatdJG/UCkP9kZB/Lh1iw==", + "dependencies": { + "pseudomap": "^1.0.1", + "yallist": "^2.0.0" + } + }, + "node_modules/lru-memoizer/node_modules/yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==" + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "optional": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "optional": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "optional": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "engines": { + "node": ">= 6.13.0" + } + }, + "node_modules/nodemailer": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.8.0.tgz", + "integrity": "sha512-EjYvSmHzekz6VNkNd12aUqAco+bOkRe3Of5jVhltqKhEsjw/y0PYPJfp83+s9Wzh1dspYAkUW/YNQ350NATbSQ==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "optional": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/object-inspect": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", + "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "optional": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "optional": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + }, + "node_modules/proto3-json-serializer": { + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-0.1.9.tgz", + "integrity": "sha512-A60IisqvnuI45qNRygJjrnNjX2TMdQGMY+57tR3nul3ZgO2zXkR9OGR8AXxJhkqx84g0FTnrfi3D5fWMSdANdQ==", + "optional": true, + "dependencies": { + "protobufjs": "^6.11.2" + } + }, + "node_modules/protobufjs": { + "version": "6.11.3", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz", + "integrity": "sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg==", + "hasInstallScript": true, + "optional": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/long": "^4.0.1", + "@types/node": ">=13.7.0", + "long": "^4.0.0" + }, + "bin": { + "pbjs": "bin/pbjs", + "pbts": "bin/pbts" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==" + }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "optional": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/pumpify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-2.0.1.tgz", + "integrity": "sha512-m7KOje7jZxrmutanlkS1daj1dS6z6BgslzOXmcSEpIlCxM3VJH7lG5QLeck/6hgF6F4crFf01UtQmNsJfweTAw==", + "optional": true, + "dependencies": { + "duplexify": "^4.1.1", + "inherits": "^2.0.3", + "pump": "^3.0.0" + } + }, + "node_modules/qs": { + "version": "6.10.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", + "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "optional": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "optional": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/retry-request": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-4.2.2.tgz", + "integrity": "sha512-xA93uxUD/rogV7BV59agW/JHPGXeREMWiZc9jhcwY4YdZ7QOtC7qbomYg0n4wyk2lJhggjvKvhNX8wln/Aldhg==", + "optional": true, + "dependencies": { + "debug": "^4.1.1", + "extend": "^3.0.2" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/retry-request/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "optional": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/retry-request/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "optional": true + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "optional": true + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/stream-events": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz", + "integrity": "sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==", + "optional": true, + "dependencies": { + "stubs": "^3.0.0" + } + }, + "node_modules/stream-shift": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", + "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", + "optional": true + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "optional": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "optional": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "optional": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/stubs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz", + "integrity": "sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==", + "optional": true + }, + "node_modules/teeny-request": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-7.2.0.tgz", + "integrity": "sha512-SyY0pek1zWsi0LRVAALem+avzMLc33MKW/JLLakdP4s9+D7+jHcy5x6P+h94g2QNZsAqQNfX5lsbd3WSeJXrrw==", + "optional": true, + "dependencies": { + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "node-fetch": "^2.6.1", + "stream-events": "^1.0.5", + "uuid": "^8.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/text-decoding": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-decoding/-/text-decoding-1.0.0.tgz", + "integrity": "sha512-/0TJD42KDnVwKmDK6jj3xP7E2MG7SHAOG4tyTgyUCRPdHwvkquYNLEQltmdMa3owq3TkddCVcTsoctJI8VQNKA==" + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "optional": true, + "dependencies": { + "is-typedarray": "^1.0.0" + } + }, + "node_modules/typescript": { + "version": "4.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.3.tgz", + "integrity": "sha512-goMHfm00nWPa8UvR/CPSvykqf6dVV8x/dp0c5mFTMTIu0u0FlGWRioyy7Nn0PGAdHxpJZnuO/ut+PpQ8UiHAig==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/unique-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", + "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", + "optional": true, + "dependencies": { + "crypto-random-string": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "optional": true + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "dependencies": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "optional": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "optional": true + }, + "node_modules/write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "optional": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "node_modules/xdg-basedir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", + "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "optional": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "optional": true + }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "optional": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "optional": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + }, + "dependencies": { + "@fastify/busboy": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-1.1.0.tgz", + "integrity": "sha512-Fv854f94v0CzIDllbY3i/0NJPNBRNLDawf3BTYVGCe9VrIIs3Wi7AFx24F9NzCxdf0wyx/x0Q9kEVnvDOPnlxA==", + "requires": { + "text-decoding": "^1.0.0" + } + }, + "@firebase/app-types": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.7.0.tgz", + "integrity": "sha512-6fbHQwDv2jp/v6bXhBw2eSRbNBpxHcd1NBF864UksSMVIqIyri9qpJB1Mn6sGZE+bnDsSQBC5j2TbMxYsJQkQg==" + }, + "@firebase/auth-interop-types": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.1.6.tgz", + "integrity": "sha512-etIi92fW3CctsmR9e3sYM3Uqnoq861M0Id9mdOPF6PWIg38BXL5k4upCNBggGUpLIS0H1grMOvy/wn1xymwe2g==", + "requires": {} + }, + "@firebase/component": { + "version": "0.5.17", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.5.17.tgz", + "integrity": "sha512-mTM5CBSIlmI+i76qU4+DhuExnWtzcPS3cVgObA3VAjliPPr3GrUlTaaa8KBGfxsD27juQxMsYA0TvCR5X+GQ3Q==", + "requires": { + "@firebase/util": "1.6.3", + "tslib": "^2.1.0" + } + }, + "@firebase/database": { + "version": "0.13.6", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.13.6.tgz", + "integrity": "sha512-5IZIBw2LT50Z8mwmKYmdX37p+Gg2HgeJsrruZmRyOSVgbfoY4Pg87n1uFx6qWqDmfL6HwQgwcrrQfVIXE3C5SA==", + "requires": { + "@firebase/auth-interop-types": "0.1.6", + "@firebase/component": "0.5.17", + "@firebase/logger": "0.3.3", + "@firebase/util": "1.6.3", + "faye-websocket": "0.11.4", + "tslib": "^2.1.0" + } + }, + "@firebase/database-compat": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-0.2.6.tgz", + "integrity": "sha512-Ls1BAODaiDYgeJljrIgSuC7JkFIY/HNhhNYebzZSoGQU62RuvnaO3Qgp2EH6h2LzHyRnycNadfh1suROtPaUIA==", + "requires": { + "@firebase/component": "0.5.17", + "@firebase/database": "0.13.6", + "@firebase/database-types": "0.9.13", + "@firebase/logger": "0.3.3", + "@firebase/util": "1.6.3", + "tslib": "^2.1.0" + } + }, + "@firebase/database-types": { + "version": "0.9.13", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.9.13.tgz", + "integrity": "sha512-dIJ1zGe3EHMhwcvukTOPzYlFYFIG1Et5Znl7s7y/ZTN2/toARRNnsv1qCKvqevIMYKvIrRsYOYfOXDS8l1YIJA==", + "requires": { + "@firebase/app-types": "0.7.0", + "@firebase/util": "1.6.3" + } + }, + "@firebase/logger": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.3.3.tgz", + "integrity": "sha512-POTJl07jOKTOevLXrTvJD/VZ0M6PnJXflbAh5J9VGkmtXPXNG6MdZ9fmRgqYhXKTaDId6AQenQ262uwgpdtO0Q==", + "requires": { + "tslib": "^2.1.0" + } + }, + "@firebase/util": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.6.3.tgz", + "integrity": "sha512-FujteO6Zjv6v8A4HS+t7c+PjU0Kaxj+rOnka0BsI/twUaCC9t8EQPmXpWZdk7XfszfahJn2pqsflUWUhtUkRlg==", + "requires": { + "tslib": "^2.1.0" + } + }, + "@google-cloud/firestore": { + "version": "4.15.1", + "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-4.15.1.tgz", + "integrity": "sha512-2PWsCkEF1W02QbghSeRsNdYKN1qavrHBP3m72gPDMHQSYrGULOaTi7fSJquQmAtc4iPVB2/x6h80rdLHTATQtA==", + "optional": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "functional-red-black-tree": "^1.0.1", + "google-gax": "^2.24.1", + "protobufjs": "^6.8.6" + } + }, + "@google-cloud/paginator": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-3.0.7.tgz", + "integrity": "sha512-jJNutk0arIQhmpUUQJPJErsojqo834KcyB6X7a1mxuic8i1tKXxde8E69IZxNZawRIlZdIK2QY4WALvlK5MzYQ==", + "optional": true, + "requires": { + "arrify": "^2.0.0", + "extend": "^3.0.2" + } + }, + "@google-cloud/projectify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-2.1.1.tgz", + "integrity": "sha512-+rssMZHnlh0twl122gXY4/aCrk0G1acBqkHFfYddtsqpYXGxA29nj9V5V9SfC+GyOG00l650f6lG9KL+EpFEWQ==", + "optional": true + }, + "@google-cloud/promisify": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-2.0.4.tgz", + "integrity": "sha512-j8yRSSqswWi1QqUGKVEKOG03Q7qOoZP6/h2zN2YO+F5h2+DHU0bSrHCK9Y7lo2DI9fBd8qGAw795sf+3Jva4yA==", + "optional": true + }, + "@google-cloud/storage": { + "version": "5.20.5", + "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-5.20.5.tgz", + "integrity": "sha512-lOs/dCyveVF8TkVFnFSF7IGd0CJrTm91qiK6JLu+Z8qiT+7Ag0RyVhxZIWkhiACqwABo7kSHDm8FdH8p2wxSSw==", + "optional": true, + "requires": { + "@google-cloud/paginator": "^3.0.7", + "@google-cloud/projectify": "^2.0.0", + "@google-cloud/promisify": "^2.0.0", + "abort-controller": "^3.0.0", + "arrify": "^2.0.0", + "async-retry": "^1.3.3", + "compressible": "^2.0.12", + "configstore": "^5.0.0", + "duplexify": "^4.0.0", + "ent": "^2.2.0", + "extend": "^3.0.2", + "gaxios": "^4.0.0", + "google-auth-library": "^7.14.1", + "hash-stream-validation": "^0.2.2", + "mime": "^3.0.0", + "mime-types": "^2.0.8", + "p-limit": "^3.0.1", + "pumpify": "^2.0.0", + "retry-request": "^4.2.2", + "stream-events": "^1.0.4", + "teeny-request": "^7.1.3", + "uuid": "^8.0.0", + "xdg-basedir": "^4.0.0" + } + }, + "@grpc/grpc-js": { + "version": "1.6.12", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.6.12.tgz", + "integrity": "sha512-JmvQ03OTSpVd9JTlj/K3IWHSz4Gk/JMLUTtW7Zb0KvO1LcOYGATh5cNuRYzCAeDR3O8wq+q8FZe97eO9MBrkUw==", + "optional": true, + "requires": { + "@grpc/proto-loader": "^0.7.0", + "@types/node": ">=12.12.47" + }, + "dependencies": { + "@grpc/proto-loader": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.3.tgz", + "integrity": "sha512-5dAvoZwna2Py3Ef96Ux9jIkp3iZ62TUsV00p3wVBPNX5K178UbNi8Q7gQVqwXT1Yq9RejIGG9G2IPEo93T6RcA==", + "optional": true, + "requires": { + "@types/long": "^4.0.1", + "lodash.camelcase": "^4.3.0", + "long": "^4.0.0", + "protobufjs": "^7.0.0", + "yargs": "^16.2.0" + } + }, + "protobufjs": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.1.2.tgz", + "integrity": "sha512-4ZPTPkXCdel3+L81yw3dG6+Kq3umdWKh7Dc7GW/CpNk4SX3hK58iPCWeCyhVTDrbkNeKrYNZ7EojM5WDaEWTLQ==", + "optional": true, + "requires": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "dependencies": { + "long": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.0.tgz", + "integrity": "sha512-9RTUNjK60eJbx3uz+TEGF7fUr29ZDxR5QzXcyDpeSfeH28S9ycINflOgOlppit5U+4kNTe83KQnMEerw7GmE8w==", + "optional": true + } + } + } + } + }, + "@grpc/proto-loader": { + "version": "0.6.13", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.6.13.tgz", + "integrity": "sha512-FjxPYDRTn6Ec3V0arm1FtSpmP6V50wuph2yILpyvTKzjc76oDdoihXqM1DzOW5ubvCC8GivfCnNtfaRE8myJ7g==", + "optional": true, + "requires": { + "@types/long": "^4.0.1", + "lodash.camelcase": "^4.3.0", + "long": "^4.0.0", + "protobufjs": "^6.11.3", + "yargs": "^16.2.0" + } + }, + "@panva/asn1.js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@panva/asn1.js/-/asn1.js-1.0.0.tgz", + "integrity": "sha512-UdkG3mLEqXgnlKsWanWcgb6dOjUzJ+XC5f+aWw30qrtjxeNUSfKX1cd5FBzOaXQumoe9nIqeZUvrRJS03HCCtw==" + }, + "@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "optional": true + }, + "@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "optional": true + }, + "@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "optional": true + }, + "@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "optional": true + }, + "@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "optional": true, + "requires": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "optional": true + }, + "@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", + "optional": true + }, + "@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "optional": true + }, + "@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "optional": true + }, + "@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", + "optional": true + }, + "@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "optional": true + }, + "@types/body-parser": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", + "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", + "requires": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "@types/connect": { + "version": "3.4.35", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", + "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", + "requires": { + "@types/node": "*" + } + }, + "@types/cors": { + "version": "2.8.12", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz", + "integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==" + }, + "@types/express": { + "version": "4.17.3", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.3.tgz", + "integrity": "sha512-I8cGRJj3pyOLs/HndoP+25vOqhqWkAZsWMEmq1qXy/b/M3ppufecUwaK2/TVDVxcV61/iSdhykUjQQ2DLSrTdg==", + "requires": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "*", + "@types/serve-static": "*" + } + }, + "@types/express-serve-static-core": { + "version": "4.17.31", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.31.tgz", + "integrity": "sha512-DxMhY+NAsTwMMFHBTtJFNp5qiHKJ7TeqOo23zVEM9alT1Ml27Q3xcTH0xwxn7Q0BbMcVEJOs/7aQtUWupUQN3Q==", + "requires": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*" + } + }, + "@types/jsonwebtoken": { + "version": "8.5.9", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-8.5.9.tgz", + "integrity": "sha512-272FMnFGzAVMGtu9tkr29hRL6bZj4Zs1KZNeHLnKqAvp06tAIcarTMwOh8/8bz4FmKRcMxZhZNeUAQsNLoiPhg==", + "requires": { + "@types/node": "*" + } + }, + "@types/long": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", + "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==", + "optional": true + }, + "@types/mime": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz", + "integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==" + }, + "@types/node": { + "version": "18.7.19", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.19.tgz", + "integrity": "sha512-Sq1itGUKUX1ap7GgZlrzdBydjbsJL/NSQt/4wkAxUJ7/OS5c2WkoN6WSpWc2Yc5wtKMZOUA0VCs/j2XJadN3HA==" + }, + "@types/nodemailer": { + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.6.tgz", + "integrity": "sha512-pD6fL5GQtUKvD2WnPmg5bC2e8kWCAPDwMPmHe/ohQbW+Dy0EcHgZ2oCSuPlWNqk74LS5BVMig1SymQbFMPPK3w==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/qs": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==" + }, + "@types/range-parser": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", + "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" + }, + "@types/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-z5xyF6uh8CbjAu9760KDKsH2FcDxZ2tFCsA4HIMWE6IkiYMXfVoa+4f9KX+FN0ZLsaMw1WNG2ETLA6N+/YA+cg==", + "requires": { + "@types/mime": "*", + "@types/node": "*" + } + }, + "abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "optional": true, + "requires": { + "event-target-shim": "^5.0.0" + } + }, + "accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "requires": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + } + }, + "agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "optional": true, + "requires": { + "debug": "4" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "optional": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "optional": true + } + } + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "optional": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "optional": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, + "arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", + "optional": true + }, + "async-retry": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz", + "integrity": "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==", + "optional": true, + "requires": { + "retry": "0.13.1" + } + }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "optional": true + }, + "bignumber.js": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.0.tgz", + "integrity": "sha512-4LwHK4nfDOraBCtst+wOWIHbu1vhvAPJK8g8nROd4iuc3PSEjWif/qwbkh8jwCJz6yDBvtU4KPynETgrfh7y3A==", + "optional": true + }, + "body-parser": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz", + "integrity": "sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==", + "requires": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.10.3", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + } + }, + "buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + }, + "bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" + }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "optional": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "optional": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "optional": true + }, + "compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "optional": true, + "requires": { + "mime-db": ">= 1.43.0 < 2" + } + }, + "configstore": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", + "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==", + "optional": true, + "requires": { + "dot-prop": "^5.2.0", + "graceful-fs": "^4.1.2", + "make-dir": "^3.0.0", + "unique-string": "^2.0.0", + "write-file-atomic": "^3.0.0", + "xdg-basedir": "^4.0.0" + } + }, + "content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "requires": { + "safe-buffer": "5.2.1" + } + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + }, + "cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==" + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, + "cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "requires": { + "object-assign": "^4", + "vary": "^1" + } + }, + "crypto-random-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", + "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", + "optional": true + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + }, + "destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" + }, + "dot-prop": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", + "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", + "optional": true, + "requires": { + "is-obj": "^2.0.0" + } + }, + "duplexify": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.2.tgz", + "integrity": "sha512-fz3OjcNCHmRP12MJoZMPglx8m4rrFP8rovnk4vT8Fs+aonZoCwGg10dSsQsfP/E62eZcPTMSMP6686fu9Qlqtw==", + "optional": true, + "requires": { + "end-of-stream": "^1.4.1", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1", + "stream-shift": "^1.0.0" + } + }, + "ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "optional": true + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "optional": true, + "requires": { + "once": "^1.4.0" + } + }, + "ent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", + "integrity": "sha512-GHrMyVZQWvTIdDtpiEXdHZnFQKzeO09apj8Cbl4pKWy4i0Oprcq17usfDt5aO63swf0JOeMWjWQE/LzgSRuWpA==", + "optional": true + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "optional": true + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" + }, + "event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "optional": true + }, + "express": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.1.tgz", + "integrity": "sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q==", + "requires": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.0", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.5.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.10.3", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "optional": true + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "optional": true + }, + "fast-text-encoding": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.6.tgz", + "integrity": "sha512-VhXlQgj9ioXCqGstD37E/HBeqEGV/qOD/kmbVG8h5xKBYvM1L3lR1Zn4555cQ8GkYbJa8aJSipLPndE1k6zK2w==", + "optional": true + }, + "faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "requires": { + "websocket-driver": ">=0.5.1" + } + }, + "finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + } + }, + "firebase-admin": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-10.3.0.tgz", + "integrity": "sha512-A0wgMLEjyVyUE+heyMJYqHRkPVjpebhOYsa47RHdrTM4ltApcx8Tn86sUmjqxlfh09gNnILAm7a8q5+FmgBYpg==", + "requires": { + "@fastify/busboy": "^1.1.0", + "@firebase/database-compat": "^0.2.0", + "@firebase/database-types": "^0.9.7", + "@google-cloud/firestore": "^4.15.1", + "@google-cloud/storage": "^5.18.3", + "@types/node": ">=12.12.47", + "jsonwebtoken": "^8.5.1", + "jwks-rsa": "^2.0.2", + "node-forge": "^1.3.1", + "uuid": "^8.3.2" + } + }, + "firebase-functions": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/firebase-functions/-/firebase-functions-4.0.1.tgz", + "integrity": "sha512-U0dOqGPShLi0g3jUlZ3aZlVTPFO9cREJfIxMJIlfRz/vNbYoKdIVdI7OAS9RKPcqz99zxkN/A8Ro4kjI+ytT8A==", + "requires": { + "@types/cors": "^2.8.5", + "@types/express": "4.17.3", + "cors": "^2.8.5", + "express": "^4.17.1", + "node-fetch": "^2.6.7" + } + }, + "forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", + "optional": true + }, + "gaxios": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-4.3.3.tgz", + "integrity": "sha512-gSaYYIO1Y3wUtdfHmjDUZ8LWaxJQpiavzbF5Kq53akSzvmVg0RfyOcFDbO1KJ/KCGRFz2qG+lS81F0nkr7cRJA==", + "optional": true, + "requires": { + "abort-controller": "^3.0.0", + "extend": "^3.0.2", + "https-proxy-agent": "^5.0.0", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.7" + } + }, + "gcp-metadata": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-4.3.1.tgz", + "integrity": "sha512-x850LS5N7V1F3UcV7PoupzGsyD6iVwTVvsh3tbXfkctZnBnjW5yu5z1/3k3SehF7TyoTIe78rJs02GMMy+LF+A==", + "optional": true, + "requires": { + "gaxios": "^4.0.0", + "json-bigint": "^1.0.0" + } + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "optional": true + }, + "get-intrinsic": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", + "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==", + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + } + }, + "google-auth-library": { + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-7.14.1.tgz", + "integrity": "sha512-5Rk7iLNDFhFeBYc3s8l1CqzbEBcdhwR193RlD4vSNFajIcINKI8W8P0JLmBpwymHqqWbX34pJDQu39cSy/6RsA==", + "optional": true, + "requires": { + "arrify": "^2.0.0", + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "fast-text-encoding": "^1.0.0", + "gaxios": "^4.0.0", + "gcp-metadata": "^4.2.0", + "gtoken": "^5.0.4", + "jws": "^4.0.0", + "lru-cache": "^6.0.0" + } + }, + "google-gax": { + "version": "2.30.5", + "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-2.30.5.tgz", + "integrity": "sha512-Jey13YrAN2hfpozHzbtrwEfEHdStJh1GwaQ2+Akh1k0Tv/EuNVSuBtHZoKSBm5wBMvNsxTsEIZ/152NrYyZgxQ==", + "optional": true, + "requires": { + "@grpc/grpc-js": "~1.6.0", + "@grpc/proto-loader": "^0.6.12", + "@types/long": "^4.0.0", + "abort-controller": "^3.0.0", + "duplexify": "^4.0.0", + "fast-text-encoding": "^1.0.3", + "google-auth-library": "^7.14.0", + "is-stream-ended": "^0.1.4", + "node-fetch": "^2.6.1", + "object-hash": "^3.0.0", + "proto3-json-serializer": "^0.1.8", + "protobufjs": "6.11.3", + "retry-request": "^4.0.0" + } + }, + "google-p12-pem": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.1.4.tgz", + "integrity": "sha512-HHuHmkLgwjdmVRngf5+gSmpkyaRI6QmOg77J8tkNBHhNEI62sGHyw4/+UkgyZEI7h84NbWprXDJ+sa3xOYFvTg==", + "optional": true, + "requires": { + "node-forge": "^1.3.1" + } + }, + "graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "optional": true + }, + "gtoken": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-5.3.2.tgz", + "integrity": "sha512-gkvEKREW7dXWF8NV8pVrKfW7WqReAmjjkMBh6lNCCGOM4ucS0r0YyXXl0r/9Yj8wcW/32ISkfc8h5mPTDbtifQ==", + "optional": true, + "requires": { + "gaxios": "^4.0.0", + "google-p12-pem": "^3.1.3", + "jws": "^4.0.0" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" + }, + "hash-stream-validation": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/hash-stream-validation/-/hash-stream-validation-0.2.4.tgz", + "integrity": "sha512-Gjzu0Xn7IagXVkSu9cSFuK1fqzwtLwFhNhVL8IFJijRNMgUttFbBSIAzKuSIrsFMO1+g1RlsoN49zPIbwPDMGQ==", + "optional": true + }, + "http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "requires": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + } + }, + "http-parser-js": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", + "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==" + }, + "http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "optional": true, + "requires": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "optional": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "optional": true + } + } + }, + "https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "optional": true, + "requires": { + "agent-base": "6", + "debug": "4" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "optional": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "optional": true + } + } + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "optional": true + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "optional": true + }, + "is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "optional": true + }, + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "optional": true + }, + "is-stream-ended": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-stream-ended/-/is-stream-ended-0.1.4.tgz", + "integrity": "sha512-xj0XPvmr7bQFTvirqnFr50o0hQIh6ZItDqloxt5aJrR4NQsYeSsyFQERYGCAzfindAcnKjINnwEEgLx4IqVzQw==", + "optional": true + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "optional": true + }, + "jose": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/jose/-/jose-2.0.6.tgz", + "integrity": "sha512-FVoPY7SflDodE4lknJmbAHSUjLCzE2H1F6MS0RYKMQ8SR+lNccpMf8R4eqkNYyyUjR5qZReOzZo5C5YiHOCjjg==", + "requires": { + "@panva/asn1.js": "^1.0.0" + } + }, + "json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "optional": true, + "requires": { + "bignumber.js": "^9.0.0" + } + }, + "jsonwebtoken": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", + "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "requires": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^5.6.0" + }, + "dependencies": { + "jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "requires": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + } + } + }, + "jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "optional": true, + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jwks-rsa": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-2.1.4.tgz", + "integrity": "sha512-mpArfgPkUpX11lNtGxsF/szkasUcbWHGplZl/uFvFO2NuMHmt0dQXIihh0rkPU2yQd5niQtuUHbXnG/WKiXF6Q==", + "requires": { + "@types/express": "^4.17.13", + "@types/jsonwebtoken": "^8.5.8", + "debug": "^4.3.4", + "jose": "^2.0.5", + "limiter": "^1.1.5", + "lru-memoizer": "^2.1.4" + }, + "dependencies": { + "@types/express": { + "version": "4.17.14", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.14.tgz", + "integrity": "sha512-TEbt+vaPFQ+xpxFLFssxUDXj5cWCxZJjIcB7Yg0k0GMHGtgtQgpvx/MUQUeAkNbA9AAGrwkAsoeItdTgS7FMyg==", + "requires": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.18", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "optional": true, + "requires": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, + "limiter": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz", + "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==" + }, + "lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "optional": true + }, + "lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==" + }, + "lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" + }, + "lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" + }, + "lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" + }, + "lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==" + }, + "lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" + }, + "lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" + }, + "lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" + }, + "long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", + "optional": true + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "optional": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "lru-memoizer": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-2.1.4.tgz", + "integrity": "sha512-IXAq50s4qwrOBrXJklY+KhgZF+5y98PDaNo0gi/v2KQBFLyWr+JyFvijZXkGKjQj/h9c0OwoE+JZbwUXce76hQ==", + "requires": { + "lodash.clonedeep": "^4.5.0", + "lru-cache": "~4.0.0" + }, + "dependencies": { + "lru-cache": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.0.2.tgz", + "integrity": "sha512-uQw9OqphAGiZhkuPlpFGmdTU2tEuhxTourM/19qGJrxBPHAr/f8BT1a0i/lOclESnGatdJG/UCkP9kZB/Lh1iw==", + "requires": { + "pseudomap": "^1.0.1", + "yallist": "^2.0.0" + } + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==" + } + } + }, + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "optional": true, + "requires": { + "semver": "^6.0.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "optional": true + } + } + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==" + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==" + }, + "mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "optional": true + }, + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "requires": { + "mime-db": "1.52.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" + }, + "node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "requires": { + "whatwg-url": "^5.0.0" + } + }, + "node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==" + }, + "nodemailer": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.8.0.tgz", + "integrity": "sha512-EjYvSmHzekz6VNkNd12aUqAco+bOkRe3Of5jVhltqKhEsjw/y0PYPJfp83+s9Wzh1dspYAkUW/YNQ350NATbSQ==" + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" + }, + "object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "optional": true + }, + "object-inspect": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", + "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==" + }, + "on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "requires": { + "ee-first": "1.1.1" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "optional": true, + "requires": { + "wrappy": "1" + } + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "optional": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + }, + "proto3-json-serializer": { + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-0.1.9.tgz", + "integrity": "sha512-A60IisqvnuI45qNRygJjrnNjX2TMdQGMY+57tR3nul3ZgO2zXkR9OGR8AXxJhkqx84g0FTnrfi3D5fWMSdANdQ==", + "optional": true, + "requires": { + "protobufjs": "^6.11.2" + } + }, + "protobufjs": { + "version": "6.11.3", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz", + "integrity": "sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg==", + "optional": true, + "requires": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/long": "^4.0.1", + "@types/node": ">=13.7.0", + "long": "^4.0.0" + } + }, + "proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "requires": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + } + }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==" + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "optional": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "pumpify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-2.0.1.tgz", + "integrity": "sha512-m7KOje7jZxrmutanlkS1daj1dS6z6BgslzOXmcSEpIlCxM3VJH7lG5QLeck/6hgF6F4crFf01UtQmNsJfweTAw==", + "optional": true, + "requires": { + "duplexify": "^4.1.1", + "inherits": "^2.0.3", + "pump": "^3.0.0" + } + }, + "qs": { + "version": "6.10.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", + "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", + "requires": { + "side-channel": "^1.0.4" + } + }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" + }, + "raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "requires": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + } + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "optional": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "optional": true + }, + "retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "optional": true + }, + "retry-request": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-4.2.2.tgz", + "integrity": "sha512-xA93uxUD/rogV7BV59agW/JHPGXeREMWiZc9jhcwY4YdZ7QOtC7qbomYg0n4wyk2lJhggjvKvhNX8wln/Aldhg==", + "optional": true, + "requires": { + "debug": "^4.1.1", + "extend": "^3.0.2" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "optional": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "optional": true + } + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + }, + "send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "requires": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "dependencies": { + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + } + } + }, + "serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + } + }, + "setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, + "signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "optional": true + }, + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" + }, + "stream-events": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz", + "integrity": "sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==", + "optional": true, + "requires": { + "stubs": "^3.0.0" + } + }, + "stream-shift": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", + "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", + "optional": true + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "optional": true, + "requires": { + "safe-buffer": "~5.2.0" + } + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "optional": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "optional": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "stubs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz", + "integrity": "sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==", + "optional": true + }, + "teeny-request": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-7.2.0.tgz", + "integrity": "sha512-SyY0pek1zWsi0LRVAALem+avzMLc33MKW/JLLakdP4s9+D7+jHcy5x6P+h94g2QNZsAqQNfX5lsbd3WSeJXrrw==", + "optional": true, + "requires": { + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "node-fetch": "^2.6.1", + "stream-events": "^1.0.5", + "uuid": "^8.0.0" + } + }, + "text-decoding": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-decoding/-/text-decoding-1.0.0.tgz", + "integrity": "sha512-/0TJD42KDnVwKmDK6jj3xP7E2MG7SHAOG4tyTgyUCRPdHwvkquYNLEQltmdMa3owq3TkddCVcTsoctJI8VQNKA==" + }, + "toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" + }, + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, + "typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "optional": true, + "requires": { + "is-typedarray": "^1.0.0" + } + }, + "typescript": { + "version": "4.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.3.tgz", + "integrity": "sha512-goMHfm00nWPa8UvR/CPSvykqf6dVV8x/dp0c5mFTMTIu0u0FlGWRioyy7Nn0PGAdHxpJZnuO/ut+PpQ8UiHAig==", + "dev": true + }, + "unique-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", + "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", + "optional": true, + "requires": { + "crypto-random-string": "^2.0.0" + } + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==" + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "optional": true + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==" + }, + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" + }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "requires": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + } + }, + "websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==" + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "optional": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "optional": true + }, + "write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "optional": true, + "requires": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "xdg-basedir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", + "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", + "optional": true + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "optional": true + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "optional": true + }, + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "optional": true, + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + } + }, + "yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "optional": true + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "optional": true + } + } +} diff --git a/platforms/blabsy/functions/package.json b/platforms/blabsy/functions/package.json new file mode 100644 index 00000000..5ab9cb1f --- /dev/null +++ b/platforms/blabsy/functions/package.json @@ -0,0 +1,26 @@ +{ + "name": "functions", + "scripts": { + "build": "tsc", + "build:watch": "tsc --watch", + "serve": "npm run build && firebase emulators:start --only functions", + "shell": "npm run build && firebase functions:shell", + "start": "npm run shell", + "deploy": "firebase deploy --only functions", + "logs": "firebase functions:log" + }, + "engines": { + "node": "16" + }, + "main": "lib/index.js", + "dependencies": { + "firebase-admin": "^10.2.0", + "firebase-functions": "^4.0.1", + "nodemailer": "^6.8.0" + }, + "devDependencies": { + "@types/nodemailer": "^6.4.6", + "typescript": "^4.6.4" + }, + "private": true +} diff --git a/platforms/blabsy/functions/src/index.ts b/platforms/blabsy/functions/src/index.ts new file mode 100644 index 00000000..d084f0bc --- /dev/null +++ b/platforms/blabsy/functions/src/index.ts @@ -0,0 +1,6 @@ +import * as admin from 'firebase-admin'; + +admin.initializeApp(); + +export * from './normalize-stats'; +export * from './notify-email'; diff --git a/platforms/blabsy/functions/src/lib/env.ts b/platforms/blabsy/functions/src/lib/env.ts new file mode 100644 index 00000000..371e641e --- /dev/null +++ b/platforms/blabsy/functions/src/lib/env.ts @@ -0,0 +1,7 @@ +import { defineString } from 'firebase-functions/params'; + +const EMAIL_API = defineString('EMAIL_API'); +const EMAIL_API_PASSWORD = defineString('EMAIL_API_PASSWORD'); +const TARGET_EMAIL = defineString('TARGET_EMAIL'); + +export { EMAIL_API, EMAIL_API_PASSWORD, TARGET_EMAIL }; diff --git a/platforms/blabsy/functions/src/lib/utils.ts b/platforms/blabsy/functions/src/lib/utils.ts new file mode 100644 index 00000000..73cdcf68 --- /dev/null +++ b/platforms/blabsy/functions/src/lib/utils.ts @@ -0,0 +1,6 @@ +import * as functions from 'firebase-functions'; + +const regionalFunctions = functions.region('asia-southeast2'); + +export { firestore } from 'firebase-admin'; +export { functions, regionalFunctions }; diff --git a/platforms/blabsy/functions/src/normalize-stats.ts b/platforms/blabsy/functions/src/normalize-stats.ts new file mode 100644 index 00000000..1c1c9540 --- /dev/null +++ b/platforms/blabsy/functions/src/normalize-stats.ts @@ -0,0 +1,49 @@ +import { functions, firestore, regionalFunctions } from './lib/utils'; +import { tweetConverter, bookmarkConverter } from './types'; +import type { Tweet } from './types'; + +export const normalizeStats = regionalFunctions.firestore + .document('tweets/{tweetId}') + .onDelete(async (snapshot): Promise => { + const tweetId = snapshot.id; + const tweetData = snapshot.data() as Tweet; + + functions.logger.info(`Normalizing stats from tweet ${tweetId}`); + + const { userRetweets, userLikes } = tweetData; + + const usersStatsToDelete = new Set([...userRetweets, ...userLikes]); + + const batch = firestore().batch(); + + usersStatsToDelete.forEach((userId) => { + functions.logger.info(`Deleting stats from ${userId}`); + + const userStatsRef = firestore() + .doc(`users/${userId}/stats/stats`) + .withConverter(tweetConverter); + + batch.update(userStatsRef, { + tweets: firestore.FieldValue.arrayRemove(tweetId), + likes: firestore.FieldValue.arrayRemove(tweetId) + }); + }); + + const bookmarksQuery = firestore() + .collectionGroup('bookmarks') + .where('id', '==', tweetId) + .withConverter(bookmarkConverter); + + const docsSnap = await bookmarksQuery.get(); + + functions.logger.info(`Deleting ${docsSnap.size} bookmarks`); + + docsSnap.docs.forEach(({ id, ref }) => { + functions.logger.info(`Deleting bookmark ${id}`); + batch.delete(ref); + }); + + await batch.commit(); + + functions.logger.info(`Normalizing stats for tweet ${tweetId} is done`); + }); diff --git a/platforms/blabsy/functions/src/notify-email.ts b/platforms/blabsy/functions/src/notify-email.ts new file mode 100644 index 00000000..ff7b67b2 --- /dev/null +++ b/platforms/blabsy/functions/src/notify-email.ts @@ -0,0 +1,45 @@ +import { createTransport } from 'nodemailer'; +import { firestore, functions, regionalFunctions } from './lib/utils'; +import { EMAIL_API, EMAIL_API_PASSWORD, TARGET_EMAIL } from './lib/env'; +import type { Tweet, User } from './types'; + +export const notifyEmail = regionalFunctions.firestore + .document('tweets/{tweetId}') + .onCreate(async (snapshot): Promise => { + functions.logger.info('Sending notification email.'); + + const { text, createdBy, images, parent } = snapshot.data() as Tweet; + + const imagesLength = images?.length ?? 0; + + const { name, username } = ( + await firestore().doc(`users/${createdBy}`).get() + ).data() as User; + + const client = createTransport({ + service: 'Gmail', + auth: { + user: EMAIL_API.value(), + pass: EMAIL_API_PASSWORD.value() + } + }); + + const tweetLink = `https://twitter-clone-ccrsxx.vercel.app/tweet/${snapshot.id}`; + + const emailHeader = `New Tweet${ + parent ? ' reply' : '' + } from ${name} (@${username})`; + + const emailText = `${text ?? 'No text provided'}${ + images ? ` (${imagesLength} image${imagesLength > 1 ? 's' : ''})` : '' + }\n\nLink to Tweet: ${tweetLink}\n\n- Firebase Function.`; + + await client.sendMail({ + from: EMAIL_API.value(), + to: TARGET_EMAIL.value(), + subject: emailHeader, + text: emailText + }); + + functions.logger.info('Notification email sent.'); + }); diff --git a/platforms/blabsy/functions/src/types/bookmark.ts b/platforms/blabsy/functions/src/types/bookmark.ts new file mode 100644 index 00000000..2ee59739 --- /dev/null +++ b/platforms/blabsy/functions/src/types/bookmark.ts @@ -0,0 +1,19 @@ +import type { + Timestamp, + FirestoreDataConverter +} from 'firebase-admin/firestore'; + +type Bookmark = { + id: string; + createdAt: Timestamp; +}; + +export const bookmarkConverter: FirestoreDataConverter = { + toFirestore(bookmark) { + return { ...bookmark }; + }, + fromFirestore(snapshot) { + const data = snapshot.data(); + return { ...data } as Bookmark; + } +}; diff --git a/platforms/blabsy/functions/src/types/index.ts b/platforms/blabsy/functions/src/types/index.ts new file mode 100644 index 00000000..74334aca --- /dev/null +++ b/platforms/blabsy/functions/src/types/index.ts @@ -0,0 +1,3 @@ +export * from './user'; +export * from './tweet'; +export * from './bookmark'; diff --git a/platforms/blabsy/functions/src/types/theme.ts b/platforms/blabsy/functions/src/types/theme.ts new file mode 100644 index 00000000..4030e671 --- /dev/null +++ b/platforms/blabsy/functions/src/types/theme.ts @@ -0,0 +1,2 @@ +export type Theme = 'light' | 'dim' | 'dark'; +export type Accent = 'blue' | 'yellow' | 'pink' | 'purple' | 'orange' | 'green'; diff --git a/platforms/blabsy/functions/src/types/tweet.ts b/platforms/blabsy/functions/src/types/tweet.ts new file mode 100644 index 00000000..548ea667 --- /dev/null +++ b/platforms/blabsy/functions/src/types/tweet.ts @@ -0,0 +1,36 @@ +import type { + Timestamp, + FirestoreDataConverter +} from 'firebase-admin/firestore'; + +export type ImageData = { + src: string; + alt: string; +}; + +export type ImagesPreview = (ImageData & { + id: number; +})[]; + +export type Tweet = { + text: string | null; + images: ImagesPreview | null; + parent: { id: string; username: string } | null; + userLikes: string[]; + createdBy: string; + createdAt: Timestamp; + updatedAt: Timestamp | null; + userReplies: number; + userRetweets: string[]; +}; + +export const tweetConverter: FirestoreDataConverter = { + toFirestore(tweet) { + return { ...tweet }; + }, + fromFirestore(snapshot) { + const data = snapshot.data(); + + return { ...data } as Tweet; + } +}; diff --git a/platforms/blabsy/functions/src/types/user.ts b/platforms/blabsy/functions/src/types/user.ts new file mode 100644 index 00000000..ccdb69c6 --- /dev/null +++ b/platforms/blabsy/functions/src/types/user.ts @@ -0,0 +1,40 @@ +import type { Theme, Accent } from './theme'; +import type { Timestamp, FirestoreDataConverter } from 'firebase/firestore'; + +export type User = { + id: string; + bio: string | null; + name: string; + theme: Theme | null; + accent: Accent | null; + website: string | null; + location: string | null; + username: string; + photoURL: string; + verified: boolean; + following: string[]; + followers: string[]; + createdAt: Timestamp; + updatedAt: Timestamp | null; + totalTweets: number; + totalPhotos: number; + pinnedTweet: string | null; + coverPhotoURL: string | null; +}; + +export type EditableData = Extract< + keyof User, + 'bio' | 'name' | 'website' | 'photoURL' | 'location' | 'coverPhotoURL' +>; + +export type EditableUserData = Pick; + +export const userConverter: FirestoreDataConverter = { + toFirestore(user) { + return { ...user }; + }, + fromFirestore(snapshot, options) { + const data = snapshot.data(options); + return { ...data } as User; + } +}; diff --git a/platforms/blabsy/functions/tsconfig.json b/platforms/blabsy/functions/tsconfig.json new file mode 100644 index 00000000..a9ed863a --- /dev/null +++ b/platforms/blabsy/functions/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "module": "commonjs", + "noImplicitReturns": true, + "noUnusedLocals": true, + "outDir": "lib", + "sourceMap": true, + "strict": true, + "target": "es2017" + }, + "compileOnSave": true, + "include": ["src"] +} diff --git a/platforms/blabsy/jest.config.js b/platforms/blabsy/jest.config.js new file mode 100644 index 00000000..98b63893 --- /dev/null +++ b/platforms/blabsy/jest.config.js @@ -0,0 +1,25 @@ +// jest.config.js +const nextJest = require('next/jest'); + +const createJestConfig = nextJest({ + // Provide the path to your Next.js app to load next.config.js and .env files in your test environment + dir: './' +}); + +// Add any custom config to be passed to Jest +const customJestConfig = { + // Add more setup options before each test is run + // setupFilesAfterEnv: ['/jest.setup.js'], + // if using TypeScript with a baseUrl set to the root directory then you need the below for alias' to work + modulePaths: ['/src'], + testEnvironment: 'jest-environment-jsdom', + // Math aliases too instead of just baseUrl + moduleNameMapper: { + '^@components(.*)$': '/src/components$1', + '^@lib(.*)$': '/src/lib$1', + '^@styles(.*)$': '/src/styles$1' + } +}; + +// createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async +module.exports = createJestConfig(customJestConfig); diff --git a/platforms/blabsy/messages/en.json b/platforms/blabsy/messages/en.json deleted file mode 100644 index 37a98944..00000000 --- a/platforms/blabsy/messages/en.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "$schema": "https://inlang.com/schema/inlang-message-format", - "hello_world": "Hello, {name} from en!" -} diff --git a/platforms/blabsy/messages/es.json b/platforms/blabsy/messages/es.json deleted file mode 100644 index 176345c1..00000000 --- a/platforms/blabsy/messages/es.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "$schema": "https://inlang.com/schema/inlang-message-format", - "hello_world": "Hello, {name} from es!" -} diff --git a/platforms/blabsy/next.config.js b/platforms/blabsy/next.config.js new file mode 100644 index 00000000..ba5a8585 --- /dev/null +++ b/platforms/blabsy/next.config.js @@ -0,0 +1,10 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = { + reactStrictMode: true, + swcMinify: true, + images: { + unoptimized: true + } +}; + +module.exports = nextConfig; diff --git a/platforms/blabsy/package.json b/platforms/blabsy/package.json index f35eb2d6..5d37e2b7 100644 --- a/platforms/blabsy/package.json +++ b/platforms/blabsy/package.json @@ -1,60 +1,63 @@ { - "name": "metagram", - "private": true, - "version": "0.0.1", - "type": "module", - "scripts": { - "dev": "vite dev", - "build": "vite build", - "preview": "vite preview", - "prepare": "svelte-kit sync || echo ''", - "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", - "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", - "format": "prettier --write .", - "lint": "prettier --check . && eslint .", - "storybook": "storybook dev -p 6006", - "build-storybook": "storybook build" - }, - "devDependencies": { - "@chromatic-com/storybook": "^3", - "@eslint/compat": "^1.2.5", - "@eslint/js": "^9.18.0", - "@hugeicons/core-free-icons": "^1.0.13", - "@hugeicons/svelte": "^1.0.2", - "@storybook/addon-essentials": "^8.6.12", - "@storybook/addon-svelte-csf": "^5.0.0-next.0", - "@storybook/blocks": "^8.6.12", - "@storybook/svelte": "^8.6.12", - "@storybook/sveltekit": "^8.6.12", - "@storybook/test": "^8.6.12", - "@sveltejs/adapter-static": "^3.0.8", - "@sveltejs/kit": "^2.16.0", - "@sveltejs/vite-plugin-svelte": "^5.0.0", - "@tailwindcss/vite": "^4.0.0", - "clsx": "^2.1.1", - "cupertino-pane": "^1.4.22", - "eslint": "^9.18.0", - "eslint-config-prettier": "^10.0.1", - "eslint-plugin-svelte": "^3.0.0", - "globals": "^16.0.0", - "prettier": "^3.4.2", - "prettier-plugin-svelte": "^3.3.3", - "prettier-plugin-tailwindcss": "^0.6.11", - "storybook": "^8.6.12", - "svelte": "^5.0.0", - "svelte-check": "^4.0.0", - "svelte-gestures": "^5.1.3", - "tailwindcss": "^4.0.0", - "typescript": "^5.0.0", - "typescript-eslint": "^8.20.0", - "vite": "^6.2.6" - }, - "dependencies": { - "-": "^0.0.1", - "D": "^1.0.0", - "axios": "^1.6.7", - "moment": "^2.30.1", - "svelte-qrcode": "^1.0.1", - "tailwind-merge": "^3.0.2" - } + "name": "blabsy", + "version": "1.0.0", + "private": true, + "scripts": { + "emulators": "firebase emulators:start --only firestore,auth,storage,functions", + "dev": "next dev -p 80", + "dev:emulators": "concurrently npm:dev npm:emulators", + "build": "next build", + "start": "next start", + "format": "prettier --check .", + "lint": "next lint", + "test": "jest --watch", + "test:ci": "jest --ci" + }, + "dependencies": { + "@headlessui/react": "^1.7.2", + "@heroicons/react": "^2.0.11", + "axios": "^1.6.7", + "clsx": "^1.2.1", + "date-fns": "^4.1.0", + "firebase": "^9.9.4", + "firebase-admin": "^13.4.0", + "framer-motion": "^7.2.1", + "next": "^12.3.0", + "react": "18.2.0", + "react-dom": "18.2.0", + "react-hot-toast": "^2.3.0", + "react-qr-code": "^2.0.15", + "react-textarea-autosize": "^8.3.4", + "swr": "^1.3.0", + "uuid": "^11.1.0" + }, + "devDependencies": { + "@testing-library/jest-dom": "^5.16.4", + "@testing-library/react": "^13.3.0", + "@testing-library/user-event": "^13.5.0", + "@types/node": "18.6.4", + "@types/react": "18.0.16", + "@types/react-dom": "18.0.6", + "@typescript-eslint/eslint-plugin": "^5.32.0", + "@typescript-eslint/parser": "^5.32.0", + "autoprefixer": "^10.4.8", + "concurrently": "^8.2.1", + "eslint": "8.21.0", + "eslint-config-next": "12.2.4", + "eslint-import-resolver-typescript": "^3.4.0", + "eslint-plugin-import": "^2.26.0", + "husky": "^8.0.1", + "jest": "^28.1.3", + "jest-environment-jsdom": "^28.1.3", + "lint-staged": "^13.0.3", + "postcss": "^8.4.16", + "prettier": "^2.7.1", + "prettier-plugin-tailwindcss": "^0.1.13", + "sass": "^1.54.4", + "tailwindcss": "^3.2.4", + "typescript": "4.7.4" + }, + "lint-staged": { + "**/*": "prettier --write --ignore-unknown" + } } diff --git a/platforms/blabsy/postcss.config.js b/platforms/blabsy/postcss.config.js new file mode 100644 index 00000000..5cbc2c7d --- /dev/null +++ b/platforms/blabsy/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {} + } +}; diff --git a/platforms/blabsy/project.inlang/.gitignore b/platforms/blabsy/project.inlang/.gitignore deleted file mode 100644 index 5e465967..00000000 --- a/platforms/blabsy/project.inlang/.gitignore +++ /dev/null @@ -1 +0,0 @@ -cache \ No newline at end of file diff --git a/platforms/blabsy/project.inlang/project_id b/platforms/blabsy/project.inlang/project_id deleted file mode 100644 index 66ac0986..00000000 --- a/platforms/blabsy/project.inlang/project_id +++ /dev/null @@ -1 +0,0 @@ -1lExt3FnvpxOpPeeZE \ No newline at end of file diff --git a/platforms/blabsy/project.inlang/settings.json b/platforms/blabsy/project.inlang/settings.json deleted file mode 100644 index dc73166c..00000000 --- a/platforms/blabsy/project.inlang/settings.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "$schema": "https://inlang.com/schema/project-settings", - "modules": [ - "https://cdn.jsdelivr.net/npm/@inlang/plugin-message-format@4/dist/index.js", - "https://cdn.jsdelivr.net/npm/@inlang/plugin-m-function-matcher@2/dist/index.js" - ], - "plugin.inlang.messageFormat": { - "pathPattern": "../messages/{locale}.json" - }, - "baseLocale": "en", - "locales": ["en", "es"] -} diff --git a/platforms/blabsy/public/404.png b/platforms/blabsy/public/404.png new file mode 100644 index 00000000..f3f2859c Binary files /dev/null and b/platforms/blabsy/public/404.png differ diff --git a/platforms/blabsy/public/assets/no-bookmarks.png b/platforms/blabsy/public/assets/no-bookmarks.png new file mode 100644 index 00000000..aca4ce73 Binary files /dev/null and b/platforms/blabsy/public/assets/no-bookmarks.png differ diff --git a/platforms/blabsy/public/assets/no-followers.png b/platforms/blabsy/public/assets/no-followers.png new file mode 100644 index 00000000..b0433d94 Binary files /dev/null and b/platforms/blabsy/public/assets/no-followers.png differ diff --git a/platforms/blabsy/public/assets/no-likes.png b/platforms/blabsy/public/assets/no-likes.png new file mode 100644 index 00000000..c01e640c Binary files /dev/null and b/platforms/blabsy/public/assets/no-likes.png differ diff --git a/platforms/blabsy/public/assets/no-media.png b/platforms/blabsy/public/assets/no-media.png new file mode 100644 index 00000000..af06c77d Binary files /dev/null and b/platforms/blabsy/public/assets/no-media.png differ diff --git a/platforms/blabsy/public/assets/no-retweets.png b/platforms/blabsy/public/assets/no-retweets.png new file mode 100644 index 00000000..f906811b Binary files /dev/null and b/platforms/blabsy/public/assets/no-retweets.png differ diff --git a/platforms/blabsy/public/assets/twitter-avatar.jpg b/platforms/blabsy/public/assets/twitter-avatar.jpg new file mode 100644 index 00000000..df7a72b1 Binary files /dev/null and b/platforms/blabsy/public/assets/twitter-avatar.jpg differ diff --git a/platforms/blabsy/public/assets/twitter-banner.png b/platforms/blabsy/public/assets/twitter-banner.png new file mode 100644 index 00000000..bca76e34 Binary files /dev/null and b/platforms/blabsy/public/assets/twitter-banner.png differ diff --git a/platforms/blabsy/public/favicon.ico b/platforms/blabsy/public/favicon.ico new file mode 100644 index 00000000..42b00fa2 Binary files /dev/null and b/platforms/blabsy/public/favicon.ico differ diff --git a/platforms/blabsy/public/fonts/chirp-bold-web.woff b/platforms/blabsy/public/fonts/chirp-bold-web.woff new file mode 100644 index 00000000..08f1b904 Binary files /dev/null and b/platforms/blabsy/public/fonts/chirp-bold-web.woff differ diff --git a/platforms/blabsy/public/fonts/chirp-bold-web.woff2 b/platforms/blabsy/public/fonts/chirp-bold-web.woff2 new file mode 100644 index 00000000..86052455 Binary files /dev/null and b/platforms/blabsy/public/fonts/chirp-bold-web.woff2 differ diff --git a/platforms/blabsy/public/fonts/chirp-extended-heavy-web.woff b/platforms/blabsy/public/fonts/chirp-extended-heavy-web.woff new file mode 100644 index 00000000..bb39eb57 Binary files /dev/null and b/platforms/blabsy/public/fonts/chirp-extended-heavy-web.woff differ diff --git a/platforms/blabsy/public/fonts/chirp-extended-heavy-web.woff2 b/platforms/blabsy/public/fonts/chirp-extended-heavy-web.woff2 new file mode 100644 index 00000000..97fe3b9a Binary files /dev/null and b/platforms/blabsy/public/fonts/chirp-extended-heavy-web.woff2 differ diff --git a/platforms/blabsy/public/fonts/chirp-heavy-web.woff b/platforms/blabsy/public/fonts/chirp-heavy-web.woff new file mode 100644 index 00000000..f175777a Binary files /dev/null and b/platforms/blabsy/public/fonts/chirp-heavy-web.woff differ diff --git a/platforms/blabsy/public/fonts/chirp-heavy-web.woff2 b/platforms/blabsy/public/fonts/chirp-heavy-web.woff2 new file mode 100644 index 00000000..fd84a814 Binary files /dev/null and b/platforms/blabsy/public/fonts/chirp-heavy-web.woff2 differ diff --git a/platforms/blabsy/public/fonts/chirp-medium-web.woff b/platforms/blabsy/public/fonts/chirp-medium-web.woff new file mode 100644 index 00000000..a3758655 Binary files /dev/null and b/platforms/blabsy/public/fonts/chirp-medium-web.woff differ diff --git a/platforms/blabsy/public/fonts/chirp-medium-web.woff2 b/platforms/blabsy/public/fonts/chirp-medium-web.woff2 new file mode 100644 index 00000000..c3b03364 Binary files /dev/null and b/platforms/blabsy/public/fonts/chirp-medium-web.woff2 differ diff --git a/platforms/blabsy/public/fonts/chirp-regular-web.woff b/platforms/blabsy/public/fonts/chirp-regular-web.woff new file mode 100644 index 00000000..39324469 Binary files /dev/null and b/platforms/blabsy/public/fonts/chirp-regular-web.woff differ diff --git a/platforms/blabsy/public/fonts/chirp-regular-web.woff2 b/platforms/blabsy/public/fonts/chirp-regular-web.woff2 new file mode 100644 index 00000000..53145a22 Binary files /dev/null and b/platforms/blabsy/public/fonts/chirp-regular-web.woff2 differ diff --git a/platforms/blabsy/public/home.png b/platforms/blabsy/public/home.png new file mode 100644 index 00000000..053c92fd Binary files /dev/null and b/platforms/blabsy/public/home.png differ diff --git a/platforms/blabsy/public/logo192.png b/platforms/blabsy/public/logo192.png new file mode 100644 index 00000000..511fbbcb Binary files /dev/null and b/platforms/blabsy/public/logo192.png differ diff --git a/platforms/blabsy/public/logo512.png b/platforms/blabsy/public/logo512.png new file mode 100644 index 00000000..24beb904 Binary files /dev/null and b/platforms/blabsy/public/logo512.png differ diff --git a/platforms/blabsy/public/site.webmanifest b/platforms/blabsy/public/site.webmanifest new file mode 100644 index 00000000..3e937a46 --- /dev/null +++ b/platforms/blabsy/public/site.webmanifest @@ -0,0 +1,22 @@ +{ + "name": "Next.js Template", + "short_name": "Next.js Template", + "description": "Generated by create next app.", + "display": "standalone", + "start_url": "/", + "theme_color": "#fff", + "background_color": "#000000", + "orientation": "portrait", + "icons": [ + { + "src": "/logo192.png", + "type": "image/png", + "sizes": "192x192" + }, + { + "src": "/logo512.png", + "type": "image/png", + "sizes": "512x512" + } + ] +} diff --git a/platforms/blabsy/src/app.css b/platforms/blabsy/src/app.css deleted file mode 100644 index 4579296e..00000000 --- a/platforms/blabsy/src/app.css +++ /dev/null @@ -1,169 +0,0 @@ -@import 'tailwindcss'; - -@font-face { - font-family: 'Geist', sans-serif; - font-style: normal; - font-weight: 700; - font-display: swap; - src: url('/fonts/Geist-VariableFont_wght.ttf') format('truetype'); -} - -body { - font-family: 'Geist', sans-serif; - padding-top: env(safe-area-inset-top); - padding-bottom: env(safe-area-inset-bottom); - padding-left: env(safe-area-inset-left); - padding-right: env(safe-area-inset-right); - background-color: white; -} - -@layer base { - h1 { - @apply font-geist text-xl/[1] font-semibold; - } - h2 { - @apply font-geist text-lg/[1] font-medium; - } - h3 { - @apply font-geist text-base/[1] font-normal; - } - p { - @apply font-geist text-[15px]/[1] font-normal; - } - .small { - @apply font-geist text-sm/[1] font-normal; - } - .subtext { - @apply font-geist text-xs/[1] font-normal; - } - .hide-scrollbar { - -ms-overflow-style: none; - scrollbar-width: none; - } - - .hide-scrollbar::-webkit-scrollbar { - display: none; - } -} - -@theme { - /* fonts */ - --font-geist: 'Geist', sans-serif; - - /* colors */ - --color-black: #1f1f1f; - --color-black-800: #4c4c4c; - --color-black-600: #797979; - --color-black-400: #a5a5a5; - --color-black-200: #d2d2d2; - - --color-grey: #f5f5f5; - --color-red: #ff5255; - - --color-gray-200: #eaecf0; - - --color-brand-burnt-orange: #da4a11; - --color-brand-burnt-orange-100: #f8dbcf; - --color-brand-burnt-orange-200: #f3c3b0; - --color-brand-burnt-orange-300: #eca488; - --color-brand-burnt-orange-400: #e68660; - --color-brand-burnt-orange-500: #e06839; - --color-brand-burnt-orange-600: #91310b; - --color-brand-burnt-orange-700: #6d2509; - --color-brand-burnt-orange-800: #491906; - --color-brand-burnt-orange-900: #2c0f03; - - --color-brand-gradient: linear-gradient( - 91.82deg, - #4d44ef -36.17%, - #f35b5b 57.95%, - #f7a428 152.07% - ); -} - -/* Ensure background remains correct during transitions */ -:root[data-transition]::view-transition-group(root), -:root[data-transition]::view-transition-old(root), -:root[data-transition]::view-transition-new(root) { - background-color: white !important; /* Default to white */ -} - -/* Prevent flickering */ -:root[data-transition]::view-transition-old(root), -:root[data-transition]::view-transition-new(root) { - contain: paint; - will-change: transform, opacity; -} - -/* Slide-in from the right without fade */ -@keyframes slide-from-right { - from { - transform: translateX(100%); /* Start from the right */ - opacity: 1; /* Ensure fully visible */ - } - to { - transform: translateX(0); /* Move to original position */ - opacity: 1; - } -} - -/* Slide-out to the right without fade */ -@keyframes slide-to-right { - from { - transform: translateX(0); /* Start at original position */ - opacity: 1; - } - to { - transform: translateX(100%); /* Move to the right */ - opacity: 1; - } -} - -/* Slide-in from the left without fade */ -@keyframes slide-from-left { - from { - transform: translateX(-100%); /* Start from the left */ - opacity: 1; - } - to { - transform: translateX(0); /* Move to original position */ - opacity: 1; - } -} - -/* Slide-out to the left without fade */ -@keyframes slide-to-left { - from { - transform: translateX(0); /* Start at original position */ - opacity: 1; - } - to { - transform: translateX(-100%); /* Move to the left */ - opacity: 1; - } -} - -@keyframes fade-out { - from { - opacity: 1; - } - to { - opacity: 0; - } -} - -:root[data-transition]::view-transition-old(root) { - animation: 400ms ease-out both fade-out; -} - -:root[data-transition='right']::view-transition-new(root) { - animation: 200ms cubic-bezier(0.4, 0, 0.2, 1) both slide-from-right; - position: relative; - z-index: 1; -} - -:root[data-transition='left']::view-transition-new(root) { - animation: 200ms cubic-bezier(0.4, 0, 0.2, 1) both slide-from-left; - position: relative; - z-index: 1; -} diff --git a/platforms/blabsy/src/app.d.ts b/platforms/blabsy/src/app.d.ts deleted file mode 100644 index da08e6da..00000000 --- a/platforms/blabsy/src/app.d.ts +++ /dev/null @@ -1,13 +0,0 @@ -// See https://svelte.dev/docs/kit/types#app.d.ts -// for information about these interfaces -declare global { - namespace App { - // interface Error {} - // interface Locals {} - // interface PageData {} - // interface PageState {} - // interface Platform {} - } -} - -export {}; diff --git a/platforms/blabsy/src/app.html b/platforms/blabsy/src/app.html deleted file mode 100644 index 77a5ff52..00000000 --- a/platforms/blabsy/src/app.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - %sveltekit.head% - - -
%sveltekit.body%
- - diff --git a/platforms/blabsy/src/components/aside/aside-footer.tsx b/platforms/blabsy/src/components/aside/aside-footer.tsx new file mode 100644 index 00000000..93a40a33 --- /dev/null +++ b/platforms/blabsy/src/components/aside/aside-footer.tsx @@ -0,0 +1,34 @@ +const footerLinks = [ + ['Terms of Service', 'https://twitter.com/tos'], + ['Privacy Policy', 'https://twitter.com/privacy'], + ['Cookie Policy', 'https://support.twitter.com/articles/20170514'], + ['Accessibility', 'https://help.twitter.com/resources/accessibility'], + [ + 'Ads Info', + 'https://business.twitter.com/en/help/troubleshooting/how-twitter-ads-work.html' + ] +] as const; + +export function AsideFooter(): JSX.Element { + return ( +
+ +

© 2022 Twitter, Inc.

+
+ ); +} diff --git a/platforms/blabsy/src/components/aside/aside-trends.tsx b/platforms/blabsy/src/components/aside/aside-trends.tsx new file mode 100644 index 00000000..99b5d726 --- /dev/null +++ b/platforms/blabsy/src/components/aside/aside-trends.tsx @@ -0,0 +1,99 @@ +import Link from 'next/link'; +import cn from 'clsx'; +import { motion } from 'framer-motion'; +import { formatNumber } from '@lib/date'; +import { preventBubbling } from '@lib/utils'; +import { useTrends } from '@lib/api/trends'; +import { Error } from '@components/ui/error'; +import { HeroIcon } from '@components/ui/hero-icon'; +import { Button } from '@components/ui/button'; +import { ToolTip } from '@components/ui/tooltip'; +import { Loading } from '@components/ui/loading'; +import type { MotionProps } from 'framer-motion'; + +export const variants: MotionProps = { + initial: { opacity: 0 }, + animate: { opacity: 1 }, + transition: { duration: 0.8 } +}; + +type AsideTrendsProps = { + inTrendsPage?: boolean; +}; + +export function AsideTrends({ inTrendsPage }: AsideTrendsProps): JSX.Element { + const { data, loading } = useTrends(1, inTrendsPage ? 100 : 10, { + refreshInterval: 30000 + }); + + const { trends, location } = data ?? {}; + + return ( +
+ {loading ? ( + + ) : trends ? ( + + {!inTrendsPage && ( +

Trends for you

+ )} + {trends.map(({ name, query, tweet_volume, url }) => ( + + +
+ +
+

+ Trending{' '} + {location === 'Worldwide' + ? 'Worldwide' + : `in ${location as string}`} +

+

{name}

+

+ {formatNumber(tweet_volume)} tweets +

+
+ + ))} + {!inTrendsPage && ( + + + Show more + + + )} +
+ ) : ( + + )} +
+ ); +} diff --git a/platforms/blabsy/src/components/aside/aside.tsx b/platforms/blabsy/src/components/aside/aside.tsx new file mode 100644 index 00000000..63b0c1a3 --- /dev/null +++ b/platforms/blabsy/src/components/aside/aside.tsx @@ -0,0 +1,22 @@ +import { useWindow } from '@lib/context/window-context'; +import { SearchBar } from './search-bar'; +import { AsideFooter } from './aside-footer'; +import type { ReactNode } from 'react'; + +type AsideProps = { + children: ReactNode; +}; + +export function Aside({ children }: AsideProps): JSX.Element | null { + const { width } = useWindow(); + + if (width < 1024) return null; + + return ( + + ); +} diff --git a/platforms/blabsy/src/components/aside/search-bar.tsx b/platforms/blabsy/src/components/aside/search-bar.tsx new file mode 100644 index 00000000..0b2132d4 --- /dev/null +++ b/platforms/blabsy/src/components/aside/search-bar.tsx @@ -0,0 +1,76 @@ +import { useState, useRef } from 'react'; +import { useRouter } from 'next/router'; +import cn from 'clsx'; +import { HeroIcon } from '@components/ui/hero-icon'; +import { Button } from '@components/ui/button'; +import type { ChangeEvent, FormEvent, KeyboardEvent } from 'react'; + +export function SearchBar(): JSX.Element { + const [inputValue, setInputValue] = useState(''); + + const { push } = useRouter(); + + const inputRef = useRef(null); + + const handleChange = ({ + target: { value } + }: ChangeEvent): void => setInputValue(value); + + const handleSubmit = (e: FormEvent): void => { + e.preventDefault(); + if (inputValue) void push(`/search?q=${inputValue}`); + }; + + const clearInputValue = (focus?: boolean) => (): void => { + if (focus) inputRef.current?.focus(); + else inputRef.current?.blur(); + + setInputValue(''); + }; + + const handleEscape = ({ key }: KeyboardEvent): void => { + if (key === 'Escape') clearInputValue()(); + }; + + return ( +
+ +
+ ); +} diff --git a/platforms/blabsy/src/components/aside/suggestions.tsx b/platforms/blabsy/src/components/aside/suggestions.tsx new file mode 100644 index 00000000..f5383218 --- /dev/null +++ b/platforms/blabsy/src/components/aside/suggestions.tsx @@ -0,0 +1,63 @@ +import Link from 'next/link'; +import { motion } from 'framer-motion'; +import { + doc, + limit, + query, + where, + orderBy, + documentId +} from 'firebase/firestore'; +import { useAuth } from '@lib/context/auth-context'; +import { useCollection } from '@lib/hooks/useCollection'; +import { useDocument } from '@lib/hooks/useDocument'; +import { usersCollection } from '@lib/firebase/collections'; +import { UserCard } from '@components/user/user-card'; +import { Loading } from '@components/ui/loading'; +import { Error } from '@components/ui/error'; +import { variants } from './aside-trends'; + +export function Suggestions(): JSX.Element { + const { randomSeed } = useAuth(); + + const { data: adminData, loading: adminLoading } = useDocument( + doc(usersCollection, 'Twt0A27bx9YcG4vu3RTsR7ifJzf2'), + { allowNull: true } + ); + + const { data: suggestionsData, loading: suggestionsLoading } = useCollection( + query( + usersCollection, + where(documentId(), '>=', randomSeed), + orderBy(documentId()), + limit(2) + ), + { allowNull: true } + ); + + return ( +
+ {adminLoading || suggestionsLoading ? ( + + ) : suggestionsData ? ( + +

Who to follow

+ {adminData && } + {suggestionsData?.map((userData) => ( + + ))} + + + Show more + + +
+ ) : ( + + )} +
+ ); +} diff --git a/platforms/blabsy/src/components/chat/chat-list.tsx b/platforms/blabsy/src/components/chat/chat-list.tsx new file mode 100644 index 00000000..1ebd543d --- /dev/null +++ b/platforms/blabsy/src/components/chat/chat-list.tsx @@ -0,0 +1,195 @@ +import { useChat } from '@lib/context/chat-context'; +import { useAuth } from '@lib/context/auth-context'; +import { formatDistanceToNow } from 'date-fns'; +import type { Chat } from '@lib/types/chat'; +import { Loading } from '@components/ui/loading'; +import { UserIcon } from '@heroicons/react/24/outline'; +import { useEffect, useState } from 'react'; +import { doc, getDoc } from 'firebase/firestore'; +import { db } from '@lib/firebase/app'; +import { usersCollection } from '@lib/firebase/collections'; +import type { User } from '@lib/types/user'; +import Image from 'next/image'; + +type ParticipantData = { + [key: string]: User; +}; + +export function ChatList(): JSX.Element { + const { chats, currentChat, setCurrentChat, loading } = useChat(); + const { user } = useAuth(); + const [participantData, setParticipantData] = useState({}); + + useEffect(() => { + if (!chats || !user) return; + + const fetchParticipantData = async (): Promise => { + const newParticipantData: ParticipantData = {}; + + for (const chat of chats) { + const otherParticipantId = chat.participants.find(p => p !== user.id); + if (otherParticipantId && !participantData[otherParticipantId]) { + const userDoc = await getDoc(doc(db, 'users', otherParticipantId)); + if (userDoc.exists()) { + newParticipantData[otherParticipantId] = userDoc.data() as User; + } + } + } + + if (Object.keys(newParticipantData).length > 0) { + setParticipantData(prev => ({ ...prev, ...newParticipantData })); + } + }; + + void fetchParticipantData(); + }, [chats, user, participantData]); + + + + if (loading) { + console.log('ChatList: Loading state'); + return ; + } + + if (!chats?.length) { + console.log('ChatList: No chats found'); + return ( +
+

+ No chats yet +

+
+ ); + } + + return ( +
+ {chats.map((chat) => { + const otherParticipant = chat.participants.find( + (p) => p !== user?.id + ); + const participant = otherParticipant ? participantData[otherParticipant] : null; + + + return ( + + ); + })} +
+ ); +} + +type ChatListItemProps = { + chat: Chat; + isSelected: boolean; + currentUserId?: string; + onClick: () => void; +}; + +function ChatListItem({ + chat, + isSelected, + currentUserId, + onClick +}: ChatListItemProps): JSX.Element { + const otherParticipants = chat.participants.filter( + (id) => id !== currentUserId + ); + + return ( + + ); +} \ No newline at end of file diff --git a/platforms/blabsy/src/components/chat/chat-window.tsx b/platforms/blabsy/src/components/chat/chat-window.tsx new file mode 100644 index 00000000..d6665d58 --- /dev/null +++ b/platforms/blabsy/src/components/chat/chat-window.tsx @@ -0,0 +1,241 @@ +import { useEffect, useRef, useState } from 'react'; +import { useChat } from '@lib/context/chat-context'; +import { useAuth } from '@lib/context/auth-context'; +import { formatDistanceToNow } from 'date-fns'; +import type { Message } from '@lib/types/message'; +import { UserIcon, PaperAirplaneIcon } from '@heroicons/react/24/outline'; +import Image from 'next/image'; +import { doc, getDoc } from 'firebase/firestore'; +import { db } from '@lib/firebase/app'; +import type { User } from '@lib/types/user'; +import { Loading } from '@components/ui/loading'; + +function MessageItem({ + message, + isOwnMessage, + showTime = true +}: { + message: Message; + isOwnMessage: boolean; + showTime?: boolean; +}): JSX.Element { + return ( +
+
+

{message.text}

+ {showTime && message.createdAt?.toDate && ( +

+ {formatDistanceToNow(message.createdAt.toDate(), { + addSuffix: true + })} +

+ )} +
+
+ ); +} + +export function ChatWindow(): JSX.Element { + const { currentChat, messages, sendNewMessage, markAsRead, loading } = + useChat(); + const { user } = useAuth(); + const [messageText, setMessageText] = useState(''); + const messagesEndRef = useRef(null); + const [otherUser, setOtherUser] = useState(null); + const [isLoading, setIsLoading] = useState(false); + + + + const otherParticipant = currentChat?.participants.find( + (p) => p !== user?.id + ); + + useEffect(() => { + if (!otherParticipant) { + return; + } + + const fetchUserData = async (): Promise => { + try { + const userDoc = await getDoc( + doc(db, 'users', otherParticipant) + ); + if (userDoc.exists()) { + setOtherUser(userDoc.data() as User); + } else { + } + } catch (error) { + } + }; + + void fetchUserData(); + }, [otherParticipant]); + + useEffect(() => { + if (currentChat) { + setIsLoading(true); + // Simulate loading time for messages + const timer = setTimeout(() => { + setIsLoading(false); + }, 500); + return () => clearTimeout(timer); + } + }, [currentChat]); + + useEffect(() => { + if (messagesEndRef.current) { + messagesEndRef.current.scrollIntoView({ behavior: 'smooth' }); + } + }, [messages]); + + useEffect(() => { + if (!currentChat || !user) { + return; + } + + const unreadMessages = messages?.filter( + (message) => + message.senderId !== user.id && + !message.readBy.includes(user.id) + ); + + + if (unreadMessages?.length) { + void Promise.all( + unreadMessages.map((message) => markAsRead(message.id)) + ); + } + }, [currentChat, messages, user, markAsRead]); + + const handleSubmit = async (e: React.FormEvent): Promise => { + e.preventDefault(); + if (!messageText.trim()) return; + + try { + await sendNewMessage(messageText); + setMessageText(''); + } catch (error) { + } + }; + + return ( +
+ {currentChat ? ( + <> +
+
+ {otherUser?.photoURL ? ( + { + ) : ( + + )} +
+
+

+ {currentChat.type === 'direct' + ? otherUser?.name || + otherUser?.username || + otherParticipant + : currentChat.name} +

+

+ {currentChat.type === 'direct' + ? 'Direct Message' + : `${currentChat.participants.length} participants`} +

+
+
+
+ {isLoading ? ( +
+ +
+ ) : messages?.length ? ( +
+ {[...messages] + .reverse() + .map((message, index, reversedMessages) => { + const isOwnMessage = + message.senderId === user?.id; + const nextMessage = + reversedMessages[index + 1]; + const showTime = + !nextMessage || + nextMessage.senderId !== + message.senderId; + + return ( + + ); + })} +
+
+ ) : ( +
+

+ No messages yet +

+
+ )} +
+
+
+ setMessageText(e.target.value)} + placeholder='Type a message...' + className='flex-1 rounded-full border border-gray-200 bg-gray-100 px-4 py-2 outline-none transition-colors focus:border-primary dark:border-gray-800 dark:bg-gray-900 dark:focus:border-primary' + /> + +
+
+ + ) : ( +
+

+ Select a chat to start messaging +

+
+ )} +
+ ); +} diff --git a/platforms/blabsy/src/components/chat/chat.tsx b/platforms/blabsy/src/components/chat/chat.tsx new file mode 100644 index 00000000..b94426d3 --- /dev/null +++ b/platforms/blabsy/src/components/chat/chat.tsx @@ -0,0 +1,17 @@ +import { ChatList } from './chat-list'; +import { ChatWindow } from './chat-window'; + +export function Chat(): JSX.Element { + return ( +
+
+
+ +
+
+ +
+
+
+ ); +} \ No newline at end of file diff --git a/platforms/blabsy/src/components/common/app-head.tsx b/platforms/blabsy/src/components/common/app-head.tsx new file mode 100644 index 00000000..9905ca68 --- /dev/null +++ b/platforms/blabsy/src/components/common/app-head.tsx @@ -0,0 +1,14 @@ +import Head from 'next/head'; + +export function AppHead(): JSX.Element { + return ( + + Twitter + + + + + + + ); +} diff --git a/platforms/blabsy/src/components/common/placeholder.tsx b/platforms/blabsy/src/components/common/placeholder.tsx new file mode 100644 index 00000000..d30a229c --- /dev/null +++ b/platforms/blabsy/src/components/common/placeholder.tsx @@ -0,0 +1,20 @@ +import { CustomIcon } from '@components/ui/custom-icon'; +import { SEO } from './seo'; + +export function Placeholder(): JSX.Element { + return ( +
+ + + + +
+ ); +} diff --git a/platforms/blabsy/src/components/common/seo.tsx b/platforms/blabsy/src/components/common/seo.tsx new file mode 100644 index 00000000..91e57a19 --- /dev/null +++ b/platforms/blabsy/src/components/common/seo.tsx @@ -0,0 +1,31 @@ +import { useRouter } from 'next/router'; +import Head from 'next/head'; +import { siteURL } from '@lib/env'; + +type MainLayoutProps = { + title: string; + image?: string; + description?: string; +}; + +export function SEO({ + title, + image, + description +}: MainLayoutProps): JSX.Element { + const { asPath } = useRouter(); + + return ( + + {title} + + {description && } + {description && } + {image && } + + + ); +} diff --git a/platforms/blabsy/src/components/home/main-container.tsx b/platforms/blabsy/src/components/home/main-container.tsx new file mode 100644 index 00000000..de195d30 --- /dev/null +++ b/platforms/blabsy/src/components/home/main-container.tsx @@ -0,0 +1,24 @@ +import cn from 'clsx'; +import type { ReactNode } from 'react'; + +type MainContainerProps = { + children: ReactNode; + className?: string; +}; + +export function MainContainer({ + children, + className +}: MainContainerProps): JSX.Element { + return ( +
+ {children} +
+ ); +} diff --git a/platforms/blabsy/src/components/home/main-header.tsx b/platforms/blabsy/src/components/home/main-header.tsx new file mode 100644 index 00000000..f8f93cfa --- /dev/null +++ b/platforms/blabsy/src/components/home/main-header.tsx @@ -0,0 +1,64 @@ +import cn from 'clsx'; +import { Button } from '@components/ui/button'; +import { HeroIcon } from '@components/ui/hero-icon'; +import { ToolTip } from '@components/ui/tooltip'; +import { MobileSidebar } from '@components/sidebar/mobile-sidebar'; +import type { ReactNode } from 'react'; +import type { IconName } from '@components/ui/hero-icon'; + +type HomeHeaderProps = { + tip?: string; + title?: string; + children?: ReactNode; + iconName?: IconName; + className?: string; + disableSticky?: boolean; + useActionButton?: boolean; + useMobileSidebar?: boolean; + action?: () => void; +}; + +export function MainHeader({ + tip, + title, + children, + iconName, + className, + disableSticky, + useActionButton, + useMobileSidebar, + action +}: HomeHeaderProps): JSX.Element { + return ( +
+ {useActionButton && ( + + )} + {title && ( +
+ {useMobileSidebar && } +

+ {title} +

+
+ )} + {children} +
+ ); +} diff --git a/platforms/blabsy/src/components/home/update-username.tsx b/platforms/blabsy/src/components/home/update-username.tsx new file mode 100644 index 00000000..a65e05a1 --- /dev/null +++ b/platforms/blabsy/src/components/home/update-username.tsx @@ -0,0 +1,135 @@ +/* eslint-disable react-hooks/exhaustive-deps */ + +import { useState, useEffect } from 'react'; +import { toast } from 'react-hot-toast'; +import { checkUsernameAvailability, updateUsername } from '@lib/firebase/utils'; +import { useAuth } from '@lib/context/auth-context'; +import { useModal } from '@lib/hooks/useModal'; +import { isValidUsername } from '@lib/validation'; +import { sleep } from '@lib/utils'; +import { Button } from '@components/ui/button'; +import { HeroIcon } from '@components/ui/hero-icon'; +import { ToolTip } from '@components/ui/tooltip'; +import { Modal } from '@components/modal/modal'; +import { UsernameModal } from '@components/modal/username-modal'; +import { InputField } from '@components/input/input-field'; +import type { FormEvent, ChangeEvent } from 'react'; + +export function UpdateUsername(): JSX.Element { + const [alreadySet, setAlreadySet] = useState(false); + const [available, setAvailable] = useState(false); + const [loading, setLoading] = useState(false); + const [visited, setVisited] = useState(false); + const [searching, setSearching] = useState(false); + const [inputValue, setInputValue] = useState(''); + const [errorMessage, setErrorMessage] = useState(''); + + const { user } = useAuth(); + const { open, openModal, closeModal } = useModal(); + + useEffect(() => { + const checkAvailability = async (value: string): Promise => { + setSearching(true); + + const empty = await checkUsernameAvailability(value); + + if (empty) setAvailable(true); + else { + setAvailable(false); + setErrorMessage('This username has been taken. Please choose another.'); + } + + setSearching(false); + }; + + if (!visited && inputValue.length > 0) setVisited(true); + + if (visited) { + if (errorMessage) setErrorMessage(''); + + const error = isValidUsername(user?.username as string, inputValue); + + if (error) { + setAvailable(false); + setErrorMessage(error); + } else void checkAvailability(inputValue); + } + }, [inputValue]); + + useEffect(() => { + if (!user?.updatedAt) openModal(); + else setAlreadySet(true); + }, []); + + const changeUsername = async ( + e: FormEvent + ): Promise => { + e.preventDefault(); + + if (!available) return; + + if (searching) return; + + setLoading(true); + + await sleep(500); + + await updateUsername(user?.id as string, inputValue); + + closeModal(); + + setLoading(false); + + setInputValue(''); + setVisited(false); + setAvailable(false); + + toast.success('Username updated successfully'); + }; + + const cancelUpdateUsername = (): void => { + closeModal(); + + if (!alreadySet) void updateUsername(user?.id as string); + }; + + const handleChange = ({ + target: { value } + }: ChangeEvent): void => + setInputValue(value); + + return ( + <> + + + + + + + + ); +} diff --git a/platforms/blabsy/src/components/input/image-preview.tsx b/platforms/blabsy/src/components/input/image-preview.tsx new file mode 100644 index 00000000..3d204e9b --- /dev/null +++ b/platforms/blabsy/src/components/input/image-preview.tsx @@ -0,0 +1,198 @@ +import { useEffect, useRef, useState } from 'react'; +import { AnimatePresence, motion } from 'framer-motion'; +import cn from 'clsx'; +import { useModal } from '@lib/hooks/useModal'; +import { preventBubbling } from '@lib/utils'; +import { ImageModal } from '@components/modal/image-modal'; +import { Modal } from '@components/modal/modal'; +import { NextImage } from '@components/ui/next-image'; +import { Button } from '@components/ui/button'; +import { HeroIcon } from '@components/ui/hero-icon'; +import { ToolTip } from '@components/ui/tooltip'; +import type { MotionProps } from 'framer-motion'; +import type { ImagesPreview, ImageData } from '@lib/types/file'; + +type ImagePreviewProps = { + tweet?: boolean; + viewTweet?: boolean; + previewCount: number; + imagesPreview: ImagesPreview; + removeImage?: (targetId: string) => () => void; +}; + +const variants: MotionProps = { + initial: { opacity: 0, scale: 0.5 }, + animate: { + opacity: 1, + scale: 1, + transition: { duration: 0.3 } + }, + exit: { opacity: 0, scale: 0.5 }, + transition: { type: 'spring', duration: 0.5 } +}; + +type PostImageBorderRadius = Record; + +const postImageBorderRadius: Readonly = { + 1: ['rounded-2xl'], + 2: ['rounded-tl-2xl rounded-bl-2xl', 'rounded-tr-2xl rounded-br-2xl'], + 3: ['rounded-tl-2xl rounded-bl-2xl', 'rounded-tr-2xl', 'rounded-br-2xl'], + 4: ['rounded-tl-2xl', 'rounded-tr-2xl', 'rounded-bl-2xl', 'rounded-br-2xl'] +}; + +export function ImagePreview({ + tweet, + viewTweet, + previewCount, + imagesPreview, + removeImage +}: ImagePreviewProps): JSX.Element { + const [selectedIndex, setSelectedIndex] = useState(0); + const [selectedImage, setSelectedImage] = useState(null); + + const videoRef = useRef(null); + + const { open, openModal, closeModal } = useModal(); + + useEffect(() => { + const imageData = imagesPreview[selectedIndex]; + setSelectedImage(imageData); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [selectedIndex]); + + const handleVideoStop = (): void => { + if (videoRef.current) videoRef.current.pause(); + }; + + const handleSelectedImage = (index: number, isVideo?: boolean) => () => { + if (isVideo) handleVideoStop(); + + setSelectedIndex(index); + openModal(); + }; + + const handleNextIndex = (type: 'prev' | 'next') => () => { + const nextIndex = + type === 'prev' + ? selectedIndex === 0 + ? previewCount - 1 + : selectedIndex - 1 + : selectedIndex === previewCount - 1 + ? 0 + : selectedIndex + 1; + + setSelectedIndex(nextIndex); + }; + + const isTweet = tweet ?? viewTweet; + + return ( +
+ + + + + {imagesPreview.map(({ id, src, alt }, index) => { + const isVideo = imagesPreview[index].type?.includes('video'); + + return ( + + {isVideo ? ( + <> + + + ); + })} + +
+ ); +} diff --git a/platforms/blabsy/src/components/input/input-accent-radio.tsx b/platforms/blabsy/src/components/input/input-accent-radio.tsx new file mode 100644 index 00000000..1239c0fc --- /dev/null +++ b/platforms/blabsy/src/components/input/input-accent-radio.tsx @@ -0,0 +1,57 @@ +import cn from 'clsx'; +import { useTheme } from '@lib/context/theme-context'; +import { HeroIcon } from '@components/ui/hero-icon'; +import type { Accent } from '@lib/types/theme'; + +type InputAccentRadioProps = { + type: Accent; +}; + +type InputAccentData = Record; + +const InputColors: Readonly = { + yellow: + 'bg-accent-yellow hover:ring-accent-yellow/10 active:ring-accent-yellow/20', + blue: 'bg-accent-blue hover:ring-accent-blue/10 active:ring-accent-blue/20', + pink: 'bg-accent-pink hover:ring-accent-pink/10 active:ring-accent-pink/20', + purple: + 'bg-accent-purple hover:ring-accent-purple/10 active:ring-accent-purple/20', + orange: + 'bg-accent-orange hover:ring-accent-orange/10 active:ring-accent-orange/20', + green: + 'bg-accent-green hover:ring-accent-green/10 active:ring-accent-green/20' +}; + +export function InputAccentRadio({ type }: InputAccentRadioProps): JSX.Element { + const { accent, changeAccent } = useTheme(); + + const bgColor = InputColors[type]; + const isChecked = type === accent; + + return ( + + ); +} diff --git a/platforms/blabsy/src/components/input/input-field.tsx b/platforms/blabsy/src/components/input/input-field.tsx new file mode 100644 index 00000000..4627fe32 --- /dev/null +++ b/platforms/blabsy/src/components/input/input-field.tsx @@ -0,0 +1,101 @@ +import cn from 'clsx'; +import type { User, EditableData } from '@lib/types/user'; +import type { KeyboardEvent, ChangeEvent } from 'react'; + +export type InputFieldProps = { + label: string; + inputId: EditableData | Extract; + inputValue: string | null; + inputLimit?: number; + useTextArea?: boolean; + errorMessage?: string; + handleChange: ( + e: ChangeEvent + ) => void; + handleKeyboardShortcut?: ({ + key, + ctrlKey + }: KeyboardEvent) => void; +}; + +export function InputField({ + label, + inputId, + inputValue, + inputLimit, + useTextArea, + errorMessage, + handleChange, + handleKeyboardShortcut +}: InputFieldProps): JSX.Element { + const slicedInputValue = inputValue?.slice(0, inputLimit) ?? ''; + + const inputLength = slicedInputValue.length; + const isHittingInputLimit = inputLimit && inputLength > inputLimit; + + return ( +
+
+ {useTextArea ? ( +