diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..2949e75 --- /dev/null +++ b/.env.example @@ -0,0 +1,23 @@ +# Supabase Configuration +SUPABASE_URL=your_supabase_project_url +SUPABASE_ANON_KEY=your_supabase_anon_key +SUPABASE_SERVICE_ROLE_KEY=your_supabase_service_role_key + +# Notion Configuration +NOTION_TOKEN=your_notion_integration_token +NOTION_DATABASE_COMPANIES=your_notion_companies_database_id +NOTION_DATABASE_NOVELS=your_notion_novels_database_id +NOTION_DATABASE_CREDIT_MEMOS=your_notion_credit_memos_database_id + +# Google Drive Configuration +GOOGLE_CLIENT_ID=your_google_client_id +GOOGLE_CLIENT_SECRET=your_google_client_secret +GOOGLE_REDIRECT_URI=http://localhost:3000/auth/google/callback +GOOGLE_DRIVE_FOLDER_ID=your_google_drive_folder_id + +# JWT Configuration +JWT_SECRET=your_jwt_secret_key + +# Application Configuration +NODE_ENV=development +PORT=3000 \ No newline at end of file diff --git a/SETUP.md b/SETUP.md new file mode 100644 index 0000000..0724423 --- /dev/null +++ b/SETUP.md @@ -0,0 +1,228 @@ +# Workspace Setup Guide + +This guide will help you set up the Credit Analysis & Novel Planning Workspace with cloud integrations. + +## Prerequisites + +- Node.js (version 14 or higher) +- npm or yarn +- Supabase account +- Notion account (optional) +- Google Cloud Platform account (optional) + +## Installation + +1. Clone the repository and install dependencies: +```bash +npm install +``` + +2. Copy the environment configuration: +```bash +cp .env.example .env +``` + +## Supabase Setup + +1. **Create a Supabase Project**: + - Go to [supabase.com](https://supabase.com) + - Create a new project + - Note your project URL and API keys + +2. **Configure Environment Variables**: + ```bash + SUPABASE_URL=https://your-project.supabase.co + SUPABASE_ANON_KEY=your_anon_key_here + SUPABASE_SERVICE_ROLE_KEY=your_service_role_key_here + ``` + +3. **Set up Database Schema**: + - Go to your Supabase dashboard + - Navigate to the SQL Editor + - Copy and paste the contents of `supabase/schema.sql` + - Run the script to create all tables and policies + +4. **Enable Authentication**: + - In Supabase dashboard, go to Authentication > Settings + - Configure your site URL (e.g., `http://localhost:3000`) + - Enable email authentication + +## Notion Integration (Optional) + +1. **Create Notion Integration**: + - Go to [notion.so/my-integrations](https://www.notion.so/my-integrations) + - Create a new integration + - Copy the integration token + +2. **Create Notion Databases**: + Create three databases in your Notion workspace: + + **Companies Database**: + - Name (Title) + - Industry (Text) + - Status (Select: Active, Inactive) + + **Novels Database**: + - Title (Title) + - Description (Text) + - POV Style (Select: dual_alternating, single, multiple) + - Tense (Select: past, present) + - Target Chapters (Number) + - Target Beats (Number) + - Status (Select: Planning, Writing, Editing, Complete) + + **Credit Memos Database**: + - Title (Title) + - Company (Text) + - Memo Type (Select: annual_review, refinancing, new_deal, amendment) + - Content (Text) + - Status (Select: Draft, Review, Final) + +3. **Share Databases with Integration**: + - For each database, click "Share" and invite your integration + +4. **Configure Environment Variables**: + ```bash + NOTION_TOKEN=your_notion_integration_token + NOTION_DATABASE_COMPANIES=your_companies_database_id + NOTION_DATABASE_NOVELS=your_novels_database_id + NOTION_DATABASE_CREDIT_MEMOS=your_credit_memos_database_id + ``` + +## Google Drive Integration (Optional) + +1. **Create Google Cloud Project**: + - Go to [Google Cloud Console](https://console.cloud.google.com) + - Create a new project or select existing one + +2. **Enable Google Drive API**: + - Go to APIs & Services > Library + - Search for "Google Drive API" and enable it + +3. **Create OAuth2 Credentials**: + - Go to APIs & Services > Credentials + - Click "Create Credentials" > "OAuth 2.0 Client IDs" + - Application type: Web application + - Add authorized redirect URI: `http://localhost:3000/auth/google/callback` + +4. **Configure Environment Variables**: + ```bash + GOOGLE_CLIENT_ID=your_google_client_id + GOOGLE_CLIENT_SECRET=your_google_client_secret + GOOGLE_REDIRECT_URI=http://localhost:3000/auth/google/callback + GOOGLE_DRIVE_FOLDER_ID=your_main_folder_id + ``` + +5. **Create Main Folder** (Optional): + - Create a folder in Google Drive for your workspace files + - Get the folder ID from the URL and set it as `GOOGLE_DRIVE_FOLDER_ID` + +## JWT Configuration + +Set a secure JWT secret: +```bash +JWT_SECRET=your_super_secret_jwt_key_change_this_in_production +``` + +## Running the Application + +1. Start the server: +```bash +npm start +``` + +2. Open your browser and navigate to `http://localhost:3000` + +3. Create an account or sign in + +4. Configure integrations in your profile settings + +## Features + +### Authentication +- User registration and login +- JWT-based session management +- Profile management + +### Credit Analysis +- Company management with Notion sync +- Financial document upload with Google Drive backup +- Credit memo generation with external sync +- OCR processing simulation + +### Novel Planning +- Novel project management +- Chapter organization +- Story beats tracking +- Manuscript backup to Google Drive + +### Cloud Integrations +- **Supabase**: Primary database with real-time features +- **Notion**: Sync companies, novels, and credit memos +- **Google Drive**: Backup financial documents and manuscripts +- Sync status tracking and manual sync triggers + +## Development + +### Database Schema +The application uses Supabase PostgreSQL with Row Level Security (RLS) enabled. Key tables: +- `profiles`: User profiles and integration settings +- `companies`: Trading company information +- `financial_data`: Uploaded documents and OCR data +- `credit_memos`: Generated credit analysis memos +- `novels`: Novel project details +- `chapters`: Individual chapters with content +- `story_beats`: Detailed story beats +- `sync_logs`: Track sync operations with external services + +### API Endpoints +- Authentication: `/auth/*` +- Companies: `/api/companies` +- Financial data: `/api/upload-financial` +- Credit memos: `/api/credit-memos` +- Novels: `/api/novels` +- Chapters: `/api/chapters` +- Story beats: `/api/beats` +- Sync operations: `/api/sync/*` + +## Troubleshooting + +### Common Issues + +1. **Database Connection Error**: + - Verify Supabase URL and keys + - Check if schema is properly set up + +2. **Authentication Issues**: + - Ensure JWT secret is set + - Verify Supabase auth settings + +3. **Google Drive Upload Fails**: + - Check OAuth credentials + - Verify redirect URI matches exactly + - Ensure user has connected Google Drive + +4. **Notion Sync Fails**: + - Verify integration token + - Check database IDs + - Ensure databases are shared with integration + +### Logs +Check server logs for detailed error messages. All sync operations are logged to the `sync_logs` table. + +## Security Notes + +- Never commit `.env` file to version control +- Use strong JWT secrets in production +- Enable HTTPS in production +- Review Supabase RLS policies +- Limit Google OAuth scopes to minimum required + +## Production Deployment + +1. Set `NODE_ENV=production` +2. Use secure JWT secrets +3. Configure proper CORS settings +4. Set up SSL/TLS certificates +5. Update OAuth redirect URIs for production domain +6. Review and test all security policies \ No newline at end of file diff --git a/config/googleDrive.js b/config/googleDrive.js new file mode 100644 index 0000000..5a022b1 --- /dev/null +++ b/config/googleDrive.js @@ -0,0 +1,216 @@ +const { google } = require('googleapis'); +const fs = require('fs'); +const path = require('path'); +require('dotenv').config(); + +// OAuth2 client setup +const oauth2Client = new google.auth.OAuth2( + process.env.GOOGLE_CLIENT_ID, + process.env.GOOGLE_CLIENT_SECRET, + process.env.GOOGLE_REDIRECT_URI +); + +// Google Drive API instance +const drive = google.drive({ version: 'v3', auth: oauth2Client }); + +const googleDriveHelpers = { + // Set credentials from tokens + setCredentials(tokens) { + oauth2Client.setCredentials(tokens); + }, + + // Generate OAuth URL for authentication + getAuthUrl() { + const scopes = [ + 'https://www.googleapis.com/auth/drive.file', + 'https://www.googleapis.com/auth/drive.metadata.readonly' + ]; + + return oauth2Client.generateAuthUrl({ + access_type: 'offline', + scope: scopes, + }); + }, + + // Exchange authorization code for tokens + async getTokens(code) { + const { tokens } = await oauth2Client.getToken(code); + return tokens; + }, + + // Upload file to Google Drive + async uploadFile(filePath, fileName, mimeType, folderId = null) { + try { + const fileMetadata = { + name: fileName, + parents: folderId ? [folderId] : [process.env.GOOGLE_DRIVE_FOLDER_ID] + }; + + const media = { + mimeType: mimeType, + body: fs.createReadStream(filePath), + }; + + const response = await drive.files.create({ + resource: fileMetadata, + media: media, + fields: 'id, name, webViewLink, webContentLink', + }); + + return response.data; + } catch (error) { + console.error('Error uploading file to Google Drive:', error); + throw error; + } + }, + + // Create folder in Google Drive + async createFolder(folderName, parentFolderId = null) { + try { + const fileMetadata = { + name: folderName, + mimeType: 'application/vnd.google-apps.folder', + parents: parentFolderId ? [parentFolderId] : [process.env.GOOGLE_DRIVE_FOLDER_ID] + }; + + const response = await drive.files.create({ + resource: fileMetadata, + fields: 'id, name', + }); + + return response.data; + } catch (error) { + console.error('Error creating folder in Google Drive:', error); + throw error; + } + }, + + // List files in a folder + async listFiles(folderId = null) { + try { + const query = folderId + ? `'${folderId}' in parents and trashed=false` + : `'${process.env.GOOGLE_DRIVE_FOLDER_ID}' in parents and trashed=false`; + + const response = await drive.files.list({ + q: query, + fields: 'files(id, name, mimeType, createdTime, webViewLink)', + orderBy: 'createdTime desc' + }); + + return response.data.files; + } catch (error) { + console.error('Error listing files from Google Drive:', error); + throw error; + } + }, + + // Upload financial document with metadata + async uploadFinancialDocument(filePath, originalName, companyName, documentType) { + try { + // Create company folder if it doesn't exist + const companyFolderName = `${companyName}_Financial_Documents`; + let companyFolderId; + + // Check if company folder exists + const existingFolders = await this.listFiles(); + const existingFolder = existingFolders.find(file => + file.name === companyFolderName && + file.mimeType === 'application/vnd.google-apps.folder' + ); + + if (existingFolder) { + companyFolderId = existingFolder.id; + } else { + const newFolder = await this.createFolder(companyFolderName); + companyFolderId = newFolder.id; + } + + // Upload file with timestamp + const timestamp = new Date().toISOString().split('T')[0]; + const fileName = `${timestamp}_${documentType}_${originalName}`; + const mimeType = this.getMimeType(originalName); + + const uploadedFile = await this.uploadFile(filePath, fileName, mimeType, companyFolderId); + + return { + ...uploadedFile, + folderId: companyFolderId, + companyName, + documentType + }; + } catch (error) { + console.error('Error uploading financial document:', error); + throw error; + } + }, + + // Upload novel manuscript backup + async uploadNovelBackup(novelTitle, content, format = 'txt') { + try { + // Create novels folder if it doesn't exist + const novelsFolderName = 'Novel_Manuscripts'; + let novelsFolderId; + + const existingFolders = await this.listFiles(); + const existingFolder = existingFolders.find(file => + file.name === novelsFolderName && + file.mimeType === 'application/vnd.google-apps.folder' + ); + + if (existingFolder) { + novelsFolderId = existingFolder.id; + } else { + const newFolder = await this.createFolder(novelsFolderName); + novelsFolderId = newFolder.id; + } + + // Create temporary file + const timestamp = new Date().toISOString().split('T')[0]; + const fileName = `${novelTitle}_backup_${timestamp}.${format}`; + const tempFilePath = path.join(__dirname, '..', 'temp', fileName); + + // Ensure temp directory exists + const tempDir = path.dirname(tempFilePath); + if (!fs.existsSync(tempDir)) { + fs.mkdirSync(tempDir, { recursive: true }); + } + + // Write content to temp file + fs.writeFileSync(tempFilePath, content); + + // Upload to Google Drive + const mimeType = format === 'txt' ? 'text/plain' : 'application/vnd.google-apps.document'; + const uploadedFile = await this.uploadFile(tempFilePath, fileName, mimeType, novelsFolderId); + + // Clean up temp file + fs.unlinkSync(tempFilePath); + + return uploadedFile; + } catch (error) { + console.error('Error uploading novel backup:', error); + throw error; + } + }, + + // Helper to determine MIME type + getMimeType(filename) { + const ext = path.extname(filename).toLowerCase(); + const mimeTypes = { + '.pdf': 'application/pdf', + '.jpg': 'image/jpeg', + '.jpeg': 'image/jpeg', + '.png': 'image/png', + '.txt': 'text/plain', + '.doc': 'application/msword', + '.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' + }; + return mimeTypes[ext] || 'application/octet-stream'; + } +}; + +module.exports = { + oauth2Client, + drive, + googleDriveHelpers +}; \ No newline at end of file diff --git a/config/notion.js b/config/notion.js new file mode 100644 index 0000000..b1f95a9 --- /dev/null +++ b/config/notion.js @@ -0,0 +1,192 @@ +const { Client } = require('@notionhq/client'); +require('dotenv').config(); + +const notion = new Client({ + auth: process.env.NOTION_TOKEN, +}); + +const databases = { + companies: process.env.NOTION_DATABASE_COMPANIES, + novels: process.env.NOTION_DATABASE_NOVELS, + creditMemos: process.env.NOTION_DATABASE_CREDIT_MEMOS +}; + +// Helper functions for Notion integration +const notionHelpers = { + // Create a company page in Notion + async createCompany(name, industry) { + try { + const response = await notion.pages.create({ + parent: { database_id: databases.companies }, + properties: { + 'Name': { + title: [ + { + text: { + content: name, + }, + }, + ], + }, + 'Industry': { + rich_text: [ + { + text: { + content: industry || '', + }, + }, + ], + }, + 'Status': { + select: { + name: 'Active' + } + } + }, + }); + return response; + } catch (error) { + console.error('Error creating company in Notion:', error); + throw error; + } + }, + + // Create a novel project page in Notion + async createNovel(title, description, povStyle, tense, targetChapters, targetBeats) { + try { + const response = await notion.pages.create({ + parent: { database_id: databases.novels }, + properties: { + 'Title': { + title: [ + { + text: { + content: title, + }, + }, + ], + }, + 'Description': { + rich_text: [ + { + text: { + content: description || '', + }, + }, + ], + }, + 'POV Style': { + select: { + name: povStyle + } + }, + 'Tense': { + select: { + name: tense + } + }, + 'Target Chapters': { + number: targetChapters + }, + 'Target Beats': { + number: targetBeats + }, + 'Status': { + select: { + name: 'Planning' + } + } + }, + }); + return response; + } catch (error) { + console.error('Error creating novel in Notion:', error); + throw error; + } + }, + + // Create a credit memo page in Notion + async createCreditMemo(companyName, memoType, title, content, financialMetrics) { + try { + const response = await notion.pages.create({ + parent: { database_id: databases.creditMemos }, + properties: { + 'Title': { + title: [ + { + text: { + content: title, + }, + }, + ], + }, + 'Company': { + rich_text: [ + { + text: { + content: companyName, + }, + }, + ], + }, + 'Memo Type': { + select: { + name: memoType + } + }, + 'Content': { + rich_text: [ + { + text: { + content: content.substring(0, 2000), // Notion has limits + }, + }, + ], + }, + 'Status': { + select: { + name: 'Draft' + } + } + }, + }); + return response; + } catch (error) { + console.error('Error creating credit memo in Notion:', error); + throw error; + } + }, + + // Sync data from Supabase to Notion + async syncToNotion(type, data) { + switch (type) { + case 'company': + return await this.createCompany(data.name, data.industry); + case 'novel': + return await this.createNovel( + data.title, + data.description, + data.pov_style, + data.tense, + data.target_chapters, + data.target_beats + ); + case 'credit_memo': + return await this.createCreditMemo( + data.company_name, + data.memo_type, + data.title, + data.content, + data.financial_metrics + ); + default: + throw new Error(`Unknown sync type: ${type}`); + } + } +}; + +module.exports = { + notion, + databases, + notionHelpers +}; \ No newline at end of file diff --git a/config/supabase.js b/config/supabase.js new file mode 100644 index 0000000..cb4748a --- /dev/null +++ b/config/supabase.js @@ -0,0 +1,17 @@ +const { createClient } = require('@supabase/supabase-js'); +require('dotenv').config(); + +const supabaseUrl = process.env.SUPABASE_URL; +const supabaseKey = process.env.SUPABASE_ANON_KEY; +const supabaseServiceKey = process.env.SUPABASE_SERVICE_ROLE_KEY; + +// Client for frontend operations +const supabase = createClient(supabaseUrl, supabaseKey); + +// Admin client for server-side operations +const supabaseAdmin = createClient(supabaseUrl, supabaseServiceKey); + +module.exports = { + supabase, + supabaseAdmin +}; \ No newline at end of file diff --git a/middleware/auth.js b/middleware/auth.js new file mode 100644 index 0000000..b393ad9 --- /dev/null +++ b/middleware/auth.js @@ -0,0 +1,52 @@ +const jwt = require('jsonwebtoken'); +const { supabaseAdmin } = require('../config/supabase'); + +// Middleware to verify JWT token from Supabase +const authenticateToken = async (req, res, next) => { + const authHeader = req.headers['authorization']; + const token = authHeader && authHeader.split(' ')[1]; + + if (!token) { + return res.status(401).json({ error: 'Access token required' }); + } + + try { + // Verify the token with Supabase + const { data: { user }, error } = await supabaseAdmin.auth.getUser(token); + + if (error || !user) { + return res.status(403).json({ error: 'Invalid or expired token' }); + } + + req.user = user; + next(); + } catch (error) { + console.error('Auth middleware error:', error); + return res.status(403).json({ error: 'Token verification failed' }); + } +}; + +// Middleware to optionally authenticate (for public endpoints that can benefit from user context) +const optionalAuth = async (req, res, next) => { + const authHeader = req.headers['authorization']; + const token = authHeader && authHeader.split(' ')[1]; + + if (!token) { + req.user = null; + return next(); + } + + try { + const { data: { user }, error } = await supabaseAdmin.auth.getUser(token); + req.user = error ? null : user; + } catch (error) { + req.user = null; + } + + next(); +}; + +module.exports = { + authenticateToken, + optionalAuth +}; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index fc93824..220cf6b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,10 +9,18 @@ "version": "1.0.0", "license": "MIT", "dependencies": { + "@notionhq/client": "^2.2.14", + "@supabase/supabase-js": "^2.38.4", + "bcryptjs": "^2.4.3", "body-parser": "^1.20.2", + "cors": "^2.8.5", + "dotenv": "^16.3.1", "express": "^4.18.2", "fs": "^0.0.1-security", + "googleapis": "^128.0.0", + "jsonwebtoken": "^9.0.2", "multer": "^1.4.5-lts.1", + "multer-google-storage": "^1.3.0", "path": "^0.12.7", "sqlite3": "^5.1.6" } @@ -24,6 +32,92 @@ "license": "MIT", "optional": true }, + "node_modules/@google-cloud/common": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-0.17.0.tgz", + "integrity": "sha512-HRZLSU762E6HaKoGfJGa8W95yRjb9rY7LePhjaHK9ILAnFacMuUGVamDbTHu1csZomm1g3tZTtXfX/aAhtie/Q==", + "license": "Apache-2.0", + "dependencies": { + "array-uniq": "^1.0.3", + "arrify": "^1.0.1", + "concat-stream": "^1.6.0", + "create-error-class": "^3.0.2", + "duplexify": "^3.5.0", + "ent": "^2.2.0", + "extend": "^3.0.1", + "google-auto-auth": "^0.10.0", + "is": "^3.2.0", + "log-driver": "1.2.7", + "methmeth": "^1.1.0", + "modelo": "^4.2.0", + "request": "^2.79.0", + "retry-request": "^3.0.0", + "split-array-stream": "^1.0.0", + "stream-events": "^1.0.1", + "string-format-obj": "^1.1.0", + "through2": "^2.0.3" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/@google-cloud/storage": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-1.7.0.tgz", + "integrity": "sha512-QaAxzCkbhspwajoaEnT0GcnQcpjPRcBrHYuQsXtD05BtOJgVnHCLXSsfUiRdU0nVpK+Thp7+sTkQ0fvk5PanKg==", + "license": "Apache-2.0", + "dependencies": { + "@google-cloud/common": "^0.17.0", + "arrify": "^1.0.0", + "async": "^2.0.1", + "compressible": "^2.0.12", + "concat-stream": "^1.5.0", + "create-error-class": "^3.0.2", + "duplexify": "^3.5.0", + "extend": "^3.0.0", + "gcs-resumable-upload": "^0.10.2", + "hash-stream-validation": "^0.2.1", + "is": "^3.0.1", + "mime": "^2.2.0", + "mime-types": "^2.0.8", + "once": "^1.3.1", + "pumpify": "^1.5.1", + "request": "^2.85.0", + "safe-buffer": "^5.1.1", + "snakeize": "^0.1.0", + "stream-events": "^1.0.1", + "through2": "^2.0.0", + "xdg-basedir": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@google-cloud/storage/node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/@notionhq/client": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@notionhq/client/-/client-2.3.0.tgz", + "integrity": "sha512-l7WqTCpQqC+HibkB9chghONQTYcxNQT0/rOJemBfmuKQRTu2vuV8B3yA395iKaUdDo7HI+0KvQaz9687Xskzkw==", + "license": "MIT", + "dependencies": { + "@types/node-fetch": "^2.5.10", + "node-fetch": "^2.6.1" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@npmcli/fs": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.1.tgz", @@ -63,6 +157,80 @@ "node": ">=10" } }, + "node_modules/@supabase/auth-js": { + "version": "2.72.0", + "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.72.0.tgz", + "integrity": "sha512-4+bnUrtTDK1YD0/FCx2YtMiQH5FGu9Jlf4IQi5kcqRwRwqp2ey39V61nHNdH86jm3DIzz0aZKiWfTW8qXk1swQ==", + "license": "MIT", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/functions-js": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.5.0.tgz", + "integrity": "sha512-SXBx6Jvp+MOBekeKFu+G11YLYPeVeGQl23eYyAG9+Ro0pQ1aIP0UZNIBxHKNHqxzR0L0n6gysNr2KT3841NATw==", + "license": "MIT", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/node-fetch": { + "version": "2.6.15", + "resolved": "https://registry.npmjs.org/@supabase/node-fetch/-/node-fetch-2.6.15.tgz", + "integrity": "sha512-1ibVeYUacxWYi9i0cf5efil6adJ9WRyZBLivgjs+AUpewx1F3xPi7gLgaASI2SmIQxPoCEjAsLAzKPgMJVgOUQ==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + } + }, + "node_modules/@supabase/postgrest-js": { + "version": "1.21.4", + "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-1.21.4.tgz", + "integrity": "sha512-TxZCIjxk6/dP9abAi89VQbWWMBbybpGWyvmIzTd79OeravM13OjR/YEYeyUOPcM1C3QyvXkvPZhUfItvmhY1IQ==", + "license": "MIT", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/realtime-js": { + "version": "2.15.5", + "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.15.5.tgz", + "integrity": "sha512-/Rs5Vqu9jejRD8ZeuaWXebdkH+J7V6VySbCZ/zQM93Ta5y3mAmocjioa/nzlB6qvFmyylUgKVS1KpE212t30OA==", + "license": "MIT", + "dependencies": { + "@supabase/node-fetch": "^2.6.13", + "@types/phoenix": "^1.6.6", + "@types/ws": "^8.18.1", + "ws": "^8.18.2" + } + }, + "node_modules/@supabase/storage-js": { + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.12.2.tgz", + "integrity": "sha512-SiySHxi3q7gia7NBYpsYRu8gyI0NhFwSORMxbZIxJ/zAVkN6QpwDRan158CJ+UdzD4WB/rQMAGRqIJQP+7ccAQ==", + "license": "MIT", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/supabase-js": { + "version": "2.58.0", + "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.58.0.tgz", + "integrity": "sha512-Tm1RmQpoAKdQr4/8wiayGti/no+If7RtveVZjHR8zbO7hhQjmPW2Ok5ZBPf1MGkt5c+9R85AVMsTfSaqAP1sUg==", + "license": "MIT", + "dependencies": { + "@supabase/auth-js": "2.72.0", + "@supabase/functions-js": "2.5.0", + "@supabase/node-fetch": "2.6.15", + "@supabase/postgrest-js": "1.21.4", + "@supabase/realtime-js": "2.15.5", + "@supabase/storage-js": "2.12.2" + } + }, "node_modules/@tootallnate/once": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", @@ -73,6 +241,179 @@ "node": ">= 6" } }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/caseless": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.5.tgz", + "integrity": "sha512-hWtVTC2q7hc7xZ/RLbxapMvDMgUnDvKvMOpKal4DrMyfGBUfB1oKaZlIRr6mJL+If3bAP6sV/QneGzF6tJjZDg==", + "license": "MIT" + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/express": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.23.tgz", + "integrity": "sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==", + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.19.6", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz", + "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/google-cloud__storage": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/@types/google-cloud__storage/-/google-cloud__storage-1.7.2.tgz", + "integrity": "sha512-RaQJ7+Ht20MRYJu7mgKBpbVNZIPneztKIl/DUKacRC6A8mXRsJfgDdPA7indHmJGIgm+hzUTj44+A3RyuuYZhg==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/request": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "license": "MIT" + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.5.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.5.2.tgz", + "integrity": "sha512-FYxk1I7wPv3K2XBaoyH2cTnocQEu8AOZ60hPbsyukMPLv5/5qr7V1i8PLHdl6Zf87I+xZXFvPCXYjiTFq+YSDQ==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.12.0" + } + }, + "node_modules/@types/node-fetch": { + "version": "2.6.13", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.13.tgz", + "integrity": "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "form-data": "^4.0.4" + } + }, + "node_modules/@types/phoenix": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.6.tgz", + "integrity": "sha512-PIzZZlEppgrpoT2QgbnDU+MMzuR6BbCjllj0bM70lWoejMeNJAxCchxnv7J3XFkI8MpygtRpzXrIlmWUBclP5A==", + "license": "MIT" + }, + "node_modules/@types/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "license": "MIT" + }, + "node_modules/@types/request": { + "version": "2.48.13", + "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.13.tgz", + "integrity": "sha512-FGJ6udDNUCjd19pp0Q3iTiDkwhYup7J8hpMW9c4k53NrccQFFWKRho6hvtPPEhnXWKvukfwAlB6DbDz4yhH5Gg==", + "license": "MIT", + "dependencies": { + "@types/caseless": "*", + "@types/node": "*", + "@types/tough-cookie": "*", + "form-data": "^2.5.5" + } + }, + "node_modules/@types/request/node_modules/form-data": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.5.tgz", + "integrity": "sha512-jqdObeR2rxZZbPSGL+3VckHMYtu+f9//KXBsVny6JSX/pa38Fy+bGjuG8eW/H6USNQWhLi8Num++cU2yOCNz4A==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.35", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/@types/send": { + "version": "0.17.5", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.5.tgz", + "integrity": "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==", + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.8", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.8.tgz", + "integrity": "sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==", + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, + "node_modules/@types/tough-cookie": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", + "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", + "license": "MIT" + }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -80,6 +421,18 @@ "license": "ISC", "optional": true }, + "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==", + "license": "MIT", + "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", @@ -158,6 +511,22 @@ "node": ">=8" } }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -217,6 +586,83 @@ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "license": "MIT" }, + "node_modules/array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "license": "MIT", + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, + "node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "license": "MIT", + "dependencies": { + "lodash": "^4.17.14" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", + "license": "Apache-2.0", + "engines": { + "node": "*" + } + }, + "node_modules/aws4": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz", + "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.18.1.tgz", + "integrity": "sha512-0BfJq4NSfQXd+SkFdrvFbG7addhYSBA2mQwISr46pD6E5iqkWg02RAs8vyTT/j0RTnoYmeXauBuSv1qKwR179g==", + "deprecated": "Critical security vulnerability fixed in v0.21.1. For more information, see https://github.com/axios/axios/pull/3410", + "license": "MIT", + "dependencies": { + "follow-redirects": "1.5.10", + "is-buffer": "^2.0.2" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -244,6 +690,30 @@ ], "license": "MIT" }, + "node_modules/bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", + "license": "BSD-3-Clause", + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, + "node_modules/bcryptjs": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", + "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==", + "license": "MIT" + }, + "node_modules/bignumber.js": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", + "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/bindings": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", @@ -337,6 +807,12 @@ "ieee754": "^1.1.13" } }, + "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==", + "license": "BSD-3-Clause" + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -435,6 +911,24 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/capture-stack-trace": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.2.tgz", + "integrity": "sha512-X/WM2UQs6VMHUtjUDnZTRI+i1crWteJySFzr9UpGoQa4WQffXVTTXuekjl7TjZRlcF2XfjgITT0HxZ9RnxeT0w==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", + "license": "Apache-2.0" + }, "node_modules/chownr": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", @@ -464,6 +958,30 @@ "color-support": "bin.js" } }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "license": "MIT", + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -486,6 +1004,23 @@ "typedarray": "^0.0.6" } }, + "node_modules/configstore": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-3.1.5.tgz", + "integrity": "sha512-nlOhI4+fdzoK5xmJ+NY+1gZK56bwEaWZr8fYuXohZ9Vkc1o3a4T/R3M+yE/w7x/ZVJ1zF8c+oaOvF0dztdUgmA==", + "license": "BSD-2-Clause", + "dependencies": { + "dot-prop": "^4.2.1", + "graceful-fs": "^4.1.2", + "make-dir": "^1.0.0", + "unique-string": "^1.0.0", + "write-file-atomic": "^2.0.0", + "xdg-basedir": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/console-control-strings": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", @@ -535,6 +1070,52 @@ "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", "license": "MIT" }, + "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==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/create-error-class": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/create-error-class/-/create-error-class-3.0.2.tgz", + "integrity": "sha512-gYTKKexFO3kh200H1Nit76sRwRtOY32vQd3jpAQKpLtZqyNsSQNfI4N7o3eP2wUjV35pTWKRYqFUDBvUha/Pkw==", + "license": "MIT", + "dependencies": { + "capture-stack-trace": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/crypto-random-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz", + "integrity": "sha512-GsVpkFPlycH7/fRR7Dhcmnoii54gV1nz7y4CWyeFS14N+JVBBhY+r8amRHE4BwSYal7BPTDp8isvAlCxyFt3Hg==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -568,6 +1149,15 @@ "node": ">=4.0.0" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/delegates": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", @@ -603,8 +1193,76 @@ "node": ">=8" } }, - "node_modules/dunder-proto": { - "version": "1.0.1", + "node_modules/dicer": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.2.5.tgz", + "integrity": "sha512-FDvbtnq7dzlPz0wyYlOExifDEZcu8h+rErEXgfxqmLfRfC/kJidEFh4+effJRO3P0xmfqyPbSMG0LveNRfTKVg==", + "dependencies": { + "readable-stream": "1.1.x", + "streamsearch": "0.1.2" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/dicer/node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", + "license": "MIT" + }, + "node_modules/dicer/node_modules/readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "node_modules/dicer/node_modules/streamsearch": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", + "integrity": "sha512-jos8u++JKm0ARcSUTAZXOVC0mSox7Bhn6sBgty73P1f3JGf7yG2clTbBNHUdde/kdvP2FESam+vM6l8jBrNxHA==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/dicer/node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==", + "license": "MIT" + }, + "node_modules/dot-prop": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.1.tgz", + "integrity": "sha512-l0p4+mIuJIua0mhxGoh4a+iNL9bmeK5DvnSVQa6T0OhrVmaEa1XScX5Etc673FePCJOArq/4Pa2cLGODUWTPOQ==", + "license": "MIT", + "dependencies": { + "is-obj": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", "license": "MIT", @@ -617,6 +1275,37 @@ "node": ">= 0.4" } }, + "node_modules/duplexify": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", + "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + } + }, + "node_modules/ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", + "license": "MIT", + "dependencies": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.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==", + "license": "Apache-2.0", + "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", @@ -671,6 +1360,21 @@ "once": "^1.4.0" } }, + "node_modules/ent": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.2.tgz", + "integrity": "sha512-kKvD1tO6BM+oK9HzCPpUdRb4vKFQY/FPTFmurMvh6LlN68VMrdj77w8yp51/kDbpkFOS9J8w5W6zIzgM2H8/hw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "punycode": "^1.4.1", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/env-paths": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", @@ -718,6 +1422,36 @@ "node": ">= 0.4" } }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", + "license": "MIT" + }, + "node_modules/es6-promisify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "integrity": "sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ==", + "license": "MIT", + "dependencies": { + "es6-promise": "^4.0.3" + } + }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -733,6 +1467,15 @@ "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==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/expand-template": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", @@ -788,6 +1531,33 @@ "url": "https://opencollective.com/express" } }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", + "engines": [ + "node >=0.6.0" + ], + "license": "MIT" + }, + "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==", + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "license": "MIT" + }, "node_modules/file-uri-to-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", @@ -812,6 +1582,52 @@ "node": ">= 0.8" } }, + "node_modules/follow-redirects": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", + "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", + "license": "MIT", + "dependencies": { + "debug": "=3.1.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/follow-redirects/node_modules/debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", + "license": "Apache-2.0", + "engines": { + "node": "*" + } + }, + "node_modules/form-data": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -891,6 +1707,98 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, + "node_modules/gaxios": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz", + "integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==", + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.9", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/gaxios/node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/gaxios/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/gaxios/node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/gaxios/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==", + "license": "MIT" + }, + "node_modules/gcp-metadata": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.1.tgz", + "integrity": "sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A==", + "license": "Apache-2.0", + "dependencies": { + "gaxios": "^6.1.1", + "google-logging-utils": "^0.0.2", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/gcs-resumable-upload": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/gcs-resumable-upload/-/gcs-resumable-upload-0.10.2.tgz", + "integrity": "sha512-sXonJFdrLHhIsE+uOtKrmlP/jwnks2GYPrhZaVGnDpggOXMXgSy06xY/G045TdMd22VubAsrk1YWgrjGn5Fexw==", + "deprecated": "gcs-resumable-upload is deprecated. Support will end on 11/01/2023", + "license": "MIT", + "dependencies": { + "configstore": "^3.1.2", + "google-auto-auth": "^0.10.0", + "pumpify": "^1.4.0", + "request": "^2.85.0", + "stream-events": "^1.0.3" + }, + "bin": { + "gcs-upload": "build/src/cli.js" + } + }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -928,6 +1836,15 @@ "node": ">= 0.4" } }, + "node_modules/getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0" + } + }, "node_modules/github-from-package": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", @@ -942,18 +1859,270 @@ "license": "ISC", "optional": true, "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/google-auth-library": { + "version": "9.15.1", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.15.1.tgz", + "integrity": "sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng==", + "license": "Apache-2.0", + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^6.1.1", + "gcp-metadata": "^6.1.0", + "gtoken": "^7.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/google-auto-auth": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/google-auto-auth/-/google-auto-auth-0.10.1.tgz", + "integrity": "sha512-iIqSbY7Ypd32mnHGbYctp80vZzXoDlvI9gEfvtl3kmyy5HzOcrZCIGCBdSlIzRsg7nHpQiHE3Zl6Ycur6TSodQ==", + "license": "MIT", + "dependencies": { + "async": "^2.3.0", + "gcp-metadata": "^0.6.1", + "google-auth-library": "^1.3.1", + "request": "^2.79.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/google-auto-auth/node_modules/agent-base": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", + "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", + "license": "MIT", + "dependencies": { + "es6-promisify": "^5.0.0" + }, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/google-auto-auth/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/google-auto-auth/node_modules/gaxios": { + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-1.8.4.tgz", + "integrity": "sha512-BoENMnu1Gav18HcpV9IleMPZ9exM+AvUjrAOV4Mzs/vfz2Lu/ABv451iEXByKiMPn2M140uul1txXCg83sAENw==", + "license": "Apache-2.0", + "dependencies": { + "abort-controller": "^3.0.0", + "extend": "^3.0.2", + "https-proxy-agent": "^2.2.1", + "node-fetch": "^2.3.0" + } + }, + "node_modules/google-auto-auth/node_modules/gcp-metadata": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-0.6.3.tgz", + "integrity": "sha512-MSmczZctbz91AxCvqp9GHBoZOSbJKAICV7Ow/AIWSJZRrRchUd5NL1b2P4OfP+4m490BEUPhhARfpHdqCxuCvg==", + "license": "MIT", + "dependencies": { + "axios": "^0.18.0", + "extend": "^3.0.1", + "retry-axios": "0.3.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/google-auto-auth/node_modules/google-auth-library": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-1.6.1.tgz", + "integrity": "sha512-jYiWC8NA9n9OtQM7ANn0Tk464do9yhKEtaJ72pKcaBiEwn4LwcGYIYOfwtfsSm3aur/ed3tlSxbmg24IAT6gAg==", + "license": "Apache-2.0", + "dependencies": { + "axios": "^0.18.0", + "gcp-metadata": "^0.6.3", + "gtoken": "^2.3.0", + "jws": "^3.1.5", + "lodash.isstring": "^4.0.1", + "lru-cache": "^4.1.3", + "retry-axios": "^0.3.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/google-auto-auth/node_modules/gtoken": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-2.3.3.tgz", + "integrity": "sha512-EaB49bu/TCoNeQjhCYKI/CurooBKkGxIqFHsWABW0b25fobBYVTMe84A8EBVVZhl8emiUdNypil9huMOTmyAnw==", + "license": "MIT", + "dependencies": { + "gaxios": "^1.0.4", + "google-p12-pem": "^1.0.0", + "jws": "^3.1.5", + "mime": "^2.2.0", + "pify": "^4.0.0" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/google-auto-auth/node_modules/https-proxy-agent": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", + "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", + "license": "MIT", + "dependencies": { + "agent-base": "^4.3.0", + "debug": "^3.1.0" + }, + "engines": { + "node": ">= 4.5.0" + } + }, + "node_modules/google-auto-auth/node_modules/jwa": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz", + "integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/google-auto-auth/node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "license": "MIT", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/google-auto-auth/node_modules/lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "license": "ISC", + "dependencies": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "node_modules/google-auto-auth/node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/google-auto-auth/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==", + "license": "MIT" + }, + "node_modules/google-auto-auth/node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/google-auto-auth/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==", + "license": "ISC" + }, + "node_modules/google-logging-utils": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-0.0.2.tgz", + "integrity": "sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/google-p12-pem": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-1.0.5.tgz", + "integrity": "sha512-50rTrqYPTPPwlu9TNl/HkJbBENEpbRzTOVLFJ4YWM86njZgXHFy+FP+tLRSd9m132Li9Dqi27Z3KIWDEv5y+EA==", + "deprecated": "Package is no longer maintained", + "license": "MIT", + "dependencies": { + "node-forge": "^0.10.0", + "pify": "^4.0.0" + }, + "bin": { + "gp12-pem": "build/src/bin/gp12-pem.js" + } + }, + "node_modules/google-p12-pem/node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/googleapis": { + "version": "128.0.0", + "resolved": "https://registry.npmjs.org/googleapis/-/googleapis-128.0.0.tgz", + "integrity": "sha512-+sLtVYNazcxaSD84N6rihVX4QiGoqRdnlz2SwmQQkadF31XonDfy4ufk3maMg27+FiySrH0rd7V8p+YJG6cknA==", + "license": "Apache-2.0", + "dependencies": { + "google-auth-library": "^9.0.0", + "googleapis-common": "^7.0.0" }, "engines": { - "node": "*" + "node": ">=14.0.0" + } + }, + "node_modules/googleapis-common": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/googleapis-common/-/googleapis-common-7.2.0.tgz", + "integrity": "sha512-/fhDZEJZvOV3X5jmD+fKxMqma5q2Q9nZNSF3kn1F18tpxmA86BcTxAGBQdM0N89Z3bEaIs+HVznSmFJEAmMTjA==", + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "gaxios": "^6.0.3", + "google-auth-library": "^9.7.0", + "qs": "^6.7.0", + "url-template": "^2.0.8", + "uuid": "^9.0.0" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "engines": { + "node": ">=14.0.0" } }, "node_modules/gopd": { @@ -972,8 +2141,43 @@ "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/gtoken": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz", + "integrity": "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==", + "license": "MIT", + "dependencies": { + "gaxios": "^6.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==", "license": "ISC", - "optional": true + "engines": { + "node": ">=4" + } + }, + "node_modules/har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "deprecated": "this library is no longer supported", + "license": "MIT", + "dependencies": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + }, + "engines": { + "node": ">=6" + } }, "node_modules/has-symbols": { "version": "1.1.0", @@ -987,6 +2191,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-unicode": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", @@ -994,6 +2213,12 @@ "license": "ISC", "optional": true }, + "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==", + "license": "MIT" + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -1069,6 +2294,21 @@ "license": "MIT", "optional": true }, + "node_modules/http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + }, + "engines": { + "node": ">=0.8", + "npm": ">=1.3.7" + } + }, "node_modules/https-proxy-agent": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", @@ -1155,7 +2395,6 @@ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "license": "MIT", - "optional": true, "engines": { "node": ">=0.8.19" } @@ -1220,6 +2459,38 @@ "node": ">= 0.10" } }, + "node_modules/is": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/is/-/is-3.3.2.tgz", + "integrity": "sha512-a2xr4E3s1PjDS8ORcGgXpWx6V+liNs+O3JRD2mb9aeugD7rtkkZ0zgLdYgw0tWsKhsdiezGYptSiMlVazCBTuQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-buffer": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "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", @@ -1237,6 +2508,57 @@ "license": "MIT", "optional": true }, + "node_modules/is-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "license": "MIT", + "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==", + "license": "MIT" + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "license": "MIT" + }, "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -1250,6 +2572,187 @@ "license": "ISC", "optional": true }, + "node_modules/isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", + "license": "MIT" + }, + "node_modules/jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", + "license": "MIT" + }, + "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==", + "license": "MIT", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", + "license": "(AFL-2.1 OR BSD-3-Clause)" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "license": "MIT" + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "license": "ISC" + }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "license": "MIT", + "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": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jsonwebtoken/node_modules/jwa": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz", + "integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==", + "license": "MIT", + "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==", + "license": "MIT", + "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==", + "license": "MIT" + }, + "node_modules/jsprim": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", + "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", + "license": "MIT", + "dependencies": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.4.0", + "verror": "1.10.0" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, + "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==", + "license": "MIT" + }, + "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==", + "license": "MIT" + }, + "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==", + "license": "MIT" + }, + "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==", + "license": "MIT" + }, + "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==", + "license": "MIT" + }, + "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==", + "license": "MIT" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, + "node_modules/log-driver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/log-driver/-/log-driver-1.2.7.tgz", + "integrity": "sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg==", + "license": "ISC", + "engines": { + "node": ">=0.8.6" + } + }, "node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -1263,6 +2766,18 @@ "node": ">=10" } }, + "node_modules/make-dir": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "license": "MIT", + "dependencies": { + "pify": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/make-fetch-happen": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz", @@ -1318,6 +2833,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/methmeth": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/methmeth/-/methmeth-1.1.0.tgz", + "integrity": "sha512-+txcYSooM3rPYuoDD0K2b4tUteHrxgQ5J3RFQQ9wdONrXg90Uwal331fYyrHzCV3mbPy/8A82UmKyZPUITs5lg==", + "license": "MIT" + }, "node_modules/methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", @@ -1495,41 +3016,129 @@ "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", "license": "MIT", "dependencies": { - "minimist": "^1.2.6" + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "license": "MIT" + }, + "node_modules/modelo": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/modelo/-/modelo-4.2.3.tgz", + "integrity": "sha512-9DITV2YEMcw7XojdfvGl3gDD8J9QjZTJ7ZOUuSAkP+F3T6rDbzMJuPktxptsdHYEvZcmXrCD3LMOhdSAEq6zKA==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/multer": { + "version": "1.4.5-lts.2", + "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.2.tgz", + "integrity": "sha512-VzGiVigcG9zUAoCNU+xShztrlr1auZOlurXynNvO9GiWD1/mTBbUljOKY+qMeazBqXgRnjzeEgJI/wyjJUHg9A==", + "deprecated": "Multer 1.x is impacted by a number of vulnerabilities, which have been patched in 2.x. You should upgrade to the latest 2.x version.", + "license": "MIT", + "dependencies": { + "append-field": "^1.0.0", + "busboy": "^1.0.0", + "concat-stream": "^1.5.2", + "mkdirp": "^0.5.4", + "object-assign": "^4.1.1", + "type-is": "^1.6.4", + "xtend": "^4.0.0" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/multer-google-storage": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/multer-google-storage/-/multer-google-storage-1.3.0.tgz", + "integrity": "sha512-TBnFtdK3ADAWyfT/jYQ5Lz42AM83YuOw+HauczR5d2Yoa8RN2/T/kUJPfdOAYLKLvvP7dErdwN2fFBDiErSooQ==", + "license": "MIT", + "dependencies": { + "@google-cloud/storage": "^1.1.1", + "@types/express": "^4.16.1", + "@types/google-cloud__storage": "^1.1.1", + "multer": "^1.3.0", + "uuid": "^3.1.0" + } + }, + "node_modules/multer-google-storage/node_modules/busboy": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-0.2.14.tgz", + "integrity": "sha512-InWFDomvlkEj+xWLBfU3AvnbVYqeTWmQopiW0tWWEy5yehYm2YkGEc59sUmw/4ty5Zj/b0WHGs1LgecuBSBGrg==", + "dependencies": { + "dicer": "0.2.5", + "readable-stream": "1.1.x" }, - "bin": { - "mkdirp": "bin/cmd.js" + "engines": { + "node": ">=0.8.0" } }, - "node_modules/mkdirp-classic": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", - "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", - "license": "MIT" - }, - "node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "node_modules/multer-google-storage/node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", "license": "MIT" }, - "node_modules/multer": { - "version": "1.4.5-lts.2", - "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.2.tgz", - "integrity": "sha512-VzGiVigcG9zUAoCNU+xShztrlr1auZOlurXynNvO9GiWD1/mTBbUljOKY+qMeazBqXgRnjzeEgJI/wyjJUHg9A==", - "deprecated": "Multer 1.x is impacted by a number of vulnerabilities, which have been patched in 2.x. You should upgrade to the latest 2.x version.", + "node_modules/multer-google-storage/node_modules/multer": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.4.tgz", + "integrity": "sha512-2wY2+xD4udX612aMqMcB8Ws2Voq6NIUPEtD1be6m411T4uDH/VtL9i//xvcyFlTVfRdaBsk7hV5tgrGQqhuBiw==", + "deprecated": "Multer 1.x is affected by CVE-2022-24434. This is fixed in v1.4.4-lts.1 which drops support for versions of Node.js before 6. Please upgrade to at least Node.js 6 and version 1.4.4-lts.1 of Multer. If you need support for older versions of Node.js, we are open to accepting patches that would fix the CVE on the main 1.x release line, whilst maintaining compatibility with Node.js 0.10.", "license": "MIT", "dependencies": { "append-field": "^1.0.0", - "busboy": "^1.0.0", + "busboy": "^0.2.11", "concat-stream": "^1.5.2", "mkdirp": "^0.5.4", "object-assign": "^4.1.1", + "on-finished": "^2.3.0", "type-is": "^1.6.4", "xtend": "^4.0.0" }, "engines": { - "node": ">= 6.0.0" + "node": ">= 0.10.0" + } + }, + "node_modules/multer-google-storage/node_modules/readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "node_modules/multer-google-storage/node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==", + "license": "MIT" + }, + "node_modules/multer-google-storage/node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "license": "MIT", + "bin": { + "uuid": "bin/uuid" } }, "node_modules/napi-build-utils": { @@ -1565,6 +3174,35 @@ "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", "license": "MIT" }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "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": "0.10.0", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", + "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==", + "license": "(BSD-3-Clause OR GPL-2.0)", + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/node-gyp": { "version": "8.4.1", "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-8.4.1.tgz", @@ -1623,6 +3261,15 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, + "node_modules/oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "license": "Apache-2.0", + "engines": { + "node": "*" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -1716,6 +3363,21 @@ "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", "license": "MIT" }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", + "license": "MIT" + }, + "node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/prebuild-install": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", @@ -1791,6 +3453,33 @@ "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==", + "license": "ISC" + }, + "node_modules/psl": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", + "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "funding": { + "url": "https://github.com/sponsors/lupomontero" + } + }, + "node_modules/psl/node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/pump": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", @@ -1801,6 +3490,33 @@ "once": "^1.3.1" } }, + "node_modules/pumpify": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", + "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", + "license": "MIT", + "dependencies": { + "duplexify": "^3.6.0", + "inherits": "^2.0.3", + "pump": "^2.0.0" + } + }, + "node_modules/pumpify/node_modules/pump": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", + "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", + "license": "MIT" + }, "node_modules/qs": { "version": "6.13.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", @@ -1876,6 +3592,71 @@ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "license": "MIT" }, + "node_modules/request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", + "license": "Apache-2.0", + "dependencies": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/request/node_modules/form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/request/node_modules/qs": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", + "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/request/node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "license": "MIT", + "bin": { + "uuid": "bin/uuid" + } + }, "node_modules/retry": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", @@ -1886,6 +3667,28 @@ "node": ">= 4" } }, + "node_modules/retry-axios": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/retry-axios/-/retry-axios-0.3.2.tgz", + "integrity": "sha512-jp4YlI0qyDFfXiXGhkCOliBN1G7fRH03Nqy8YdShzGqbY5/9S2x/IR6C88ls2DFkbWuL3ASkP7QD3pVrNpPgwQ==", + "license": "Apache-2.0", + "peerDependencies": { + "axios": "*" + } + }, + "node_modules/retry-request": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-3.3.2.tgz", + "integrity": "sha512-WIiGp37XXDC6e7ku3LFoi7LCL/Gs9luGeeqvbPRb+Zl6OQMw4RCRfSaW+aLfE6lhz1R941UavE6Svl3Dm5xGIQ==", + "license": "MIT", + "dependencies": { + "request": "^2.81.0", + "through2": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -1923,6 +3726,23 @@ ], "license": "MIT" }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -2084,8 +3904,7 @@ "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "license": "ISC", - "optional": true + "license": "ISC" }, "node_modules/simple-concat": { "version": "1.0.1", @@ -2143,6 +3962,12 @@ "npm": ">= 3.0.0" } }, + "node_modules/snakeize": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/snakeize/-/snakeize-0.1.0.tgz", + "integrity": "sha512-ot3bb6pQt6IVq5G/JQ640ceSYTPtriVrwNyfoUw1LmQQGzPMAGxE5F+ded2UwSUCyf2PW1fFAYUnVEX21PWbpQ==", + "license": "MIT" + }, "node_modules/socks": { "version": "2.8.7", "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", @@ -2198,6 +4023,16 @@ "license": "MIT", "optional": true }, + "node_modules/split-array-stream": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/split-array-stream/-/split-array-stream-1.0.3.tgz", + "integrity": "sha512-yGY35QmZFzZkWZ0eHE06RPBi63umym8m+pdtuC/dlO1ADhdKSfCj0uNn87BYCXBBDFxyTq4oTw0BgLYT0K5z/A==", + "license": "MIT", + "dependencies": { + "async": "^2.4.0", + "is-stream-ended": "^0.1.0" + } + }, "node_modules/sqlite3": { "version": "5.1.7", "resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-5.1.7.tgz", @@ -2222,6 +4057,31 @@ } } }, + "node_modules/sshpk": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", + "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==", + "license": "MIT", + "dependencies": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + }, + "bin": { + "sshpk-conv": "bin/sshpk-conv", + "sshpk-sign": "bin/sshpk-sign", + "sshpk-verify": "bin/sshpk-verify" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/ssri": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", @@ -2244,6 +4104,21 @@ "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==", + "license": "MIT", + "dependencies": { + "stubs": "^3.0.0" + } + }, + "node_modules/stream-shift": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz", + "integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==", + "license": "MIT" + }, "node_modules/streamsearch": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", @@ -2267,6 +4142,12 @@ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "license": "MIT" }, + "node_modules/string-format-obj": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string-format-obj/-/string-format-obj-1.1.1.tgz", + "integrity": "sha512-Mm+sROy+pHJmx0P/0Bs1uxIX6UhGJGj6xDGQZ5zh9v/SZRmLGevp+p0VJxV7lirrkAmQ2mvva/gHKpnF/pTb+Q==", + "license": "MIT" + }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -2304,6 +4185,12 @@ "node": ">=0.10.0" } }, + "node_modules/stubs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz", + "integrity": "sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==", + "license": "MIT" + }, "node_modules/tar": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", @@ -2390,6 +4277,16 @@ "node": ">=10" } }, + "node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "license": "MIT", + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -2399,6 +4296,34 @@ "node": ">=0.6" } }, + "node_modules/tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "license": "BSD-3-Clause", + "dependencies": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tough-cookie/node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, "node_modules/tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -2411,6 +4336,12 @@ "node": "*" } }, + "node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", + "license": "Unlicense" + }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -2430,6 +4361,12 @@ "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", "license": "MIT" }, + "node_modules/undici-types": { + "version": "7.12.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.12.0.tgz", + "integrity": "sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ==", + "license": "MIT" + }, "node_modules/unique-filename": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", @@ -2450,6 +4387,18 @@ "imurmurhash": "^0.1.4" } }, + "node_modules/unique-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-1.0.0.tgz", + "integrity": "sha512-ODgiYu03y5g76A1I9Gt0/chLCzQjvzDy7DsZGsLOE/1MrF6wriEskSncj1+/C58Xk/kPZDppSctDybCwOSaGAg==", + "license": "MIT", + "dependencies": { + "crypto-random-string": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -2459,6 +4408,30 @@ "node": ">= 0.8" } }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/uri-js/node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/url-template": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz", + "integrity": "sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw==", + "license": "BSD" + }, "node_modules/util": { "version": "0.10.4", "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", @@ -2489,6 +4462,19 @@ "node": ">= 0.4.0" } }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -2498,6 +4484,42 @@ "node": ">= 0.8" } }, + "node_modules/verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", + "engines": [ + "node >=0.6.0" + ], + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "node_modules/verror/node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", + "license": "MIT" + }, + "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==", + "license": "BSD-2-Clause" + }, + "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==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -2530,6 +4552,47 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" }, + "node_modules/write-file-atomic": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz", + "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", + "license": "ISC", + "dependencies": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.2" + } + }, + "node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xdg-basedir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-3.0.0.tgz", + "integrity": "sha512-1Dly4xqlulvPD3fZUQJLY+FUIeqN3N2MM3uqe4rCJftAvOjFa3jFGfctOgluGx4ahPbUCsZkmJILiP0Vi4T6lQ==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", diff --git a/package.json b/package.json index 3a686b9..6d2748a 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,14 @@ "multer": "^1.4.5-lts.1", "body-parser": "^1.20.2", "path": "^0.12.7", - "fs": "^0.0.1-security" + "fs": "^0.0.1-security", + "@supabase/supabase-js": "^2.38.4", + "@notionhq/client": "^2.2.14", + "googleapis": "^128.0.0", + "dotenv": "^16.3.1", + "cors": "^2.8.5", + "jsonwebtoken": "^9.0.2", + "bcryptjs": "^2.4.3", + "multer-google-storage": "^1.3.0" } } \ No newline at end of file diff --git a/public/auth.js b/public/auth.js new file mode 100644 index 0000000..9945d34 --- /dev/null +++ b/public/auth.js @@ -0,0 +1,334 @@ +// Authentication state management +let currentUser = null; +let authToken = null; + +// Check for existing session on page load +document.addEventListener('DOMContentLoaded', function() { + checkAuthStatus(); +}); + +async function checkAuthStatus() { + const token = localStorage.getItem('authToken'); + if (token) { + authToken = token; + try { + const response = await fetch('/api/profile', { + headers: { + 'Authorization': `Bearer ${token}` + } + }); + + if (response.ok) { + currentUser = await response.json(); + showAuthenticatedUI(); + return; + } + } catch (error) { + console.error('Auth check failed:', error); + } + } + + showUnauthenticatedUI(); +} + +function showUnauthenticatedUI() { + const authModal = document.getElementById('auth-modal'); + if (authModal) { + authModal.style.display = 'block'; + } + + // Hide main content + const mainContent = document.querySelector('.container > header, .section'); + if (mainContent) { + document.querySelectorAll('.section, .container > header').forEach(el => { + el.style.display = 'none'; + }); + } +} + +function showAuthenticatedUI() { + const authModal = document.getElementById('auth-modal'); + if (authModal) { + authModal.style.display = 'none'; + } + + // Show main content + document.querySelectorAll('.section, .container > header').forEach(el => { + el.style.display = 'block'; + }); + + // Update user info in header + updateUserInfo(); + + // Load user data + loadUserData(); +} + +function updateUserInfo() { + const userInfo = document.getElementById('user-info'); + if (userInfo && currentUser) { + userInfo.innerHTML = ` + Welcome, ${currentUser.full_name || currentUser.email} + + + `; + + document.getElementById('profile-btn').addEventListener('click', showProfileModal); + document.getElementById('logout-btn').addEventListener('click', logout); + } +} + +async function signup(email, password, fullName) { + try { + const response = await fetch('/auth/signup', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ email, password, fullName }) + }); + + const data = await response.json(); + + if (response.ok) { + showSuccess('Account created successfully! Please sign in.'); + showSignInForm(); + } else { + throw new Error(data.error || 'Signup failed'); + } + } catch (error) { + showError('Signup failed: ' + error.message); + } +} + +async function signin(email, password) { + try { + const response = await fetch('/auth/signin', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ email, password }) + }); + + const data = await response.json(); + + if (response.ok) { + authToken = data.session.access_token; + currentUser = data.user; + localStorage.setItem('authToken', authToken); + showAuthenticatedUI(); + showSuccess('Successfully signed in!'); + } else { + throw new Error(data.error || 'Sign in failed'); + } + } catch (error) { + showError('Sign in failed: ' + error.message); + } +} + +async function logout() { + try { + if (authToken) { + await fetch('/auth/signout', { + method: 'POST', + headers: { + 'Authorization': `Bearer ${authToken}` + } + }); + } + } catch (error) { + console.error('Logout error:', error); + } finally { + authToken = null; + currentUser = null; + localStorage.removeItem('authToken'); + showUnauthenticatedUI(); + showSuccess('Successfully logged out'); + } +} + +// Google Drive Integration +async function connectGoogleDrive() { + try { + const response = await fetch('/auth/google', { + headers: { + 'Authorization': `Bearer ${authToken}` + } + }); + + const data = await response.json(); + + if (response.ok) { + window.open(data.authUrl, 'google-auth', 'width=500,height=600'); + + // Listen for auth completion + const checkAuth = setInterval(() => { + if (window.location.search.includes('google_auth=success')) { + clearInterval(checkAuth); + showSuccess('Google Drive connected successfully!'); + updateIntegrationStatus(); + } else if (window.location.search.includes('google_auth=error')) { + clearInterval(checkAuth); + showError('Google Drive connection failed'); + } + }, 1000); + } + } catch (error) { + showError('Failed to connect Google Drive: ' + error.message); + } +} + +// Profile management +function showProfileModal() { + const modal = document.getElementById('profile-modal'); + if (modal) { + modal.style.display = 'block'; + + // Populate form with current user data + document.getElementById('profile-full-name').value = currentUser.full_name || ''; + document.getElementById('profile-notion-workspace').value = currentUser.notion_workspace_id || ''; + + updateIntegrationStatus(); + } +} + +function hideProfileModal() { + const modal = document.getElementById('profile-modal'); + if (modal) { + modal.style.display = 'none'; + } +} + +async function updateProfile() { + try { + const fullName = document.getElementById('profile-full-name').value; + const notionWorkspaceId = document.getElementById('profile-notion-workspace').value; + + const response = await fetch('/api/profile', { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${authToken}` + }, + body: JSON.stringify({ + full_name: fullName, + notion_workspace_id: notionWorkspaceId + }) + }); + + const data = await response.json(); + + if (response.ok) { + currentUser = data; + updateUserInfo(); + hideProfileModal(); + showSuccess('Profile updated successfully!'); + } else { + throw new Error(data.error || 'Profile update failed'); + } + } catch (error) { + showError('Profile update failed: ' + error.message); + } +} + +async function updateIntegrationStatus() { + const googleStatus = document.getElementById('google-drive-status'); + const notionStatus = document.getElementById('notion-status'); + + if (currentUser?.google_tokens) { + googleStatus.innerHTML = '✓ Connected'; + } else { + googleStatus.innerHTML = 'Not connected'; + } + + if (currentUser?.notion_workspace_id) { + notionStatus.innerHTML = '✓ Configured'; + } else { + notionStatus.innerHTML = 'Not configured'; + } +} + +// UI form handlers +function showSignInForm() { + document.getElementById('signup-form').style.display = 'none'; + document.getElementById('signin-form').style.display = 'block'; + document.getElementById('auth-title').textContent = 'Sign In'; + document.getElementById('auth-toggle').innerHTML = 'Need an account? Sign up'; +} + +function showSignUpForm() { + document.getElementById('signin-form').style.display = 'none'; + document.getElementById('signup-form').style.display = 'block'; + document.getElementById('auth-title').textContent = 'Sign Up'; + document.getElementById('auth-toggle').innerHTML = 'Already have an account? Sign in'; +} + +// Form event handlers +document.addEventListener('DOMContentLoaded', function() { + // Auth forms + const signupForm = document.getElementById('signup-form'); + if (signupForm) { + signupForm.addEventListener('submit', function(e) { + e.preventDefault(); + const email = document.getElementById('signup-email').value; + const password = document.getElementById('signup-password').value; + const fullName = document.getElementById('signup-name').value; + signup(email, password, fullName); + }); + } + + const signinForm = document.getElementById('signin-form'); + if (signinForm) { + signinForm.addEventListener('submit', function(e) { + e.preventDefault(); + const email = document.getElementById('signin-email').value; + const password = document.getElementById('signin-password').value; + signin(email, password); + }); + } + + // Profile form + const profileForm = document.getElementById('profile-form'); + if (profileForm) { + profileForm.addEventListener('submit', function(e) { + e.preventDefault(); + updateProfile(); + }); + } + + // Google Drive connect button + const googleConnectBtn = document.getElementById('connect-google-drive'); + if (googleConnectBtn) { + googleConnectBtn.addEventListener('click', connectGoogleDrive); + } + + // Profile modal close + const profileCloseBtn = document.getElementById('profile-close'); + if (profileCloseBtn) { + profileCloseBtn.addEventListener('click', hideProfileModal); + } +}); + +// Helper function to make authenticated requests +async function authenticatedFetch(url, options = {}) { + if (!authToken) { + throw new Error('Not authenticated'); + } + + const headers = { + ...options.headers, + 'Authorization': `Bearer ${authToken}` + }; + + return fetch(url, { ...options, headers }); +} + +// Load user-specific data +async function loadUserData() { + if (currentUser) { + // Load companies and novels with authentication + loadCompanies(); + loadNovels(); + } +} \ No newline at end of file diff --git a/public/index.html b/public/index.html index 4e35965..33e930b 100644 --- a/public/index.html +++ b/public/index.html @@ -10,6 +10,7 @@

Workspace

+
+ + + + + + + \ No newline at end of file diff --git a/public/script.js b/public/script.js index aa37199..57e4fb7 100644 --- a/public/script.js +++ b/public/script.js @@ -41,7 +41,7 @@ function switchTab(tab) { // Credit Analysis Functions async function loadCompanies() { try { - const response = await fetch('/api/companies'); + const response = await authenticatedFetch('/api/companies'); companies = await response.json(); renderCompanies(); updateCompanySelects(); @@ -57,6 +57,9 @@ function renderCompanies() {

${company.name}

Industry: ${company.industry || 'Not specified'}

Added: ${new Date(company.created_at).toLocaleDateString()}

+ ${company.notion_page_id ? '📝 Notion' : ''} + ${company.google_drive_folder_id ? '📁 Drive' : ''} + `).join(''); } @@ -76,18 +79,20 @@ async function handleCompanySubmit(e) { const industry = document.getElementById('company-industry').value; try { - const response = await fetch('/api/companies', { + const response = await authenticatedFetch('/api/companies', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name, industry }) }); if (response.ok) { - showSuccess('Company added successfully!'); + const result = await response.json(); + showSuccess(result.message || 'Company added successfully!'); document.getElementById('company-form').reset(); loadCompanies(); } else { - throw new Error('Failed to add company'); + const error = await response.json(); + throw new Error(error.error || 'Failed to add company'); } } catch (error) { showError('Error adding company: ' + error.message); @@ -102,16 +107,21 @@ async function handleFileUpload(e) { formData.append('document', document.getElementById('financial-document').files[0]); try { - const response = await fetch('/api/upload-financial', { + const response = await authenticatedFetch('/api/upload-financial', { method: 'POST', body: formData }); if (response.ok) { - showSuccess('Financial document uploaded and processed successfully!'); + const result = await response.json(); + const message = result.google_drive_backup ? + 'Financial document uploaded, processed, and backed up to Google Drive!' : + 'Financial document uploaded and processed successfully!'; + showSuccess(message); document.getElementById('upload-form').reset(); } else { - throw new Error('Failed to upload document'); + const error = await response.json(); + throw new Error(error.error || 'Failed to upload document'); } } catch (error) { showError('Error uploading document: ' + error.message); @@ -129,17 +139,19 @@ async function handleMemoSubmit(e) { }; try { - const response = await fetch('/api/credit-memos', { + const response = await authenticatedFetch('/api/credit-memos', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(memoData) }); if (response.ok) { - showSuccess('Credit memo created successfully!'); + const result = await response.json(); + showSuccess(result.message || 'Credit memo created successfully!'); document.getElementById('memo-form').reset(); } else { - throw new Error('Failed to create credit memo'); + const error = await response.json(); + throw new Error(error.error || 'Failed to create credit memo'); } } catch (error) { showError('Error creating credit memo: ' + error.message); @@ -149,7 +161,7 @@ async function handleMemoSubmit(e) { // Novel Planning Functions async function loadNovels() { try { - const response = await fetch('/api/novels'); + const response = await authenticatedFetch('/api/novels'); novels = await response.json(); renderNovels(); } catch (error) { @@ -165,7 +177,10 @@ function renderNovels() {

${novel.description || 'No description'}

POV: ${novel.pov_style} | Tense: ${novel.tense}

Target: ${novel.target_chapters} chapters, ${novel.target_beats} beats

- + ${novel.notion_page_id ? '📝 Notion' : ''} + ${novel.google_drive_folder_id ? '📁 Drive' : ''} + + `).join(''); } @@ -191,7 +206,7 @@ async function selectNovel(novelId) { async function loadChapters(novelId) { try { - const response = await fetch(`/api/novels/${novelId}/chapters`); + const response = await authenticatedFetch(`/api/novels/${novelId}/chapters`); const chapters = await response.json(); renderChapters(chapters); updateChapterSelect(chapters); @@ -207,6 +222,8 @@ function renderChapters(chapters) {

Chapter ${chapter.chapter_number}: ${chapter.title}

POV: ${chapter.pov_character}

${chapter.summary || 'No summary'}

+ ${chapter.google_drive_backup_id ? '📁 Backed up' : ''} + ${chapter.word_count > 0 ? `

Word count: ${chapter.word_count}

` : ''} `).join(''); } @@ -219,7 +236,7 @@ function updateChapterSelect(chapters) { async function loadBeats(novelId) { try { - const response = await fetch(`/api/novels/${novelId}/beats`); + const response = await authenticatedFetch(`/api/novels/${novelId}/beats`); const beats = await response.json(); renderBeats(beats); } catch (error) { @@ -250,18 +267,20 @@ async function handleNovelSubmit(e) { }; try { - const response = await fetch('/api/novels', { + const response = await authenticatedFetch('/api/novels', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(novelData) }); if (response.ok) { - showSuccess('Novel project created successfully!'); + const result = await response.json(); + showSuccess(result.message || 'Novel project created successfully!'); document.getElementById('novel-form').reset(); loadNovels(); } else { - throw new Error('Failed to create novel project'); + const error = await response.json(); + throw new Error(error.error || 'Failed to create novel project'); } } catch (error) { showError('Error creating novel project: ' + error.message); @@ -279,18 +298,20 @@ async function handleChapterSubmit(e) { }; try { - const response = await fetch('/api/chapters', { + const response = await authenticatedFetch('/api/chapters', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(chapterData) }); if (response.ok) { - showSuccess('Chapter added successfully!'); + const result = await response.json(); + showSuccess(result.message || 'Chapter added successfully!'); document.getElementById('chapter-form').reset(); loadChapters(currentNovelId); } else { - throw new Error('Failed to add chapter'); + const error = await response.json(); + throw new Error(error.error || 'Failed to add chapter'); } } catch (error) { showError('Error adding chapter: ' + error.message); @@ -309,7 +330,7 @@ async function handleBeatSubmit(e) { }; try { - const response = await fetch('/api/beats', { + const response = await authenticatedFetch('/api/beats', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(beatData) @@ -359,4 +380,82 @@ function parseJSON(str) { } catch { return {}; } +} + +// Sync status functions +async function showSyncStatus(entityType, entityId) { + try { + const response = await authenticatedFetch(`/api/sync-status/${entityType}/${entityId}`); + const status = await response.json(); + + let statusHtml = `

Sync Status for ${entityType}

`; + + Object.keys(status).forEach(service => { + const serviceStatus = status[service]; + const statusIcon = serviceStatus.status === 'success' ? '✅' : + serviceStatus.status === 'failed' ? '❌' : '⏳'; + + statusHtml += ` +
+ ${service}: ${statusIcon} ${serviceStatus.status} + ${serviceStatus.error_message ? `
Error: ${serviceStatus.error_message}` : ''} +
Last updated: ${new Date(serviceStatus.created_at).toLocaleString()} +
+ `; + }); + + statusHtml += ``; + statusHtml += ``; + + showModal('Sync Status', statusHtml); + } catch (error) { + showError('Failed to load sync status: ' + error.message); + } +} + +async function manualSync(entityType, entityId) { + try { + const response = await authenticatedFetch(`/api/sync/${entityType}/${entityId}`, { + method: 'POST' + }); + + const result = await response.json(); + + if (response.ok) { + showSuccess(result.message || 'Manual sync completed'); + setTimeout(() => showSyncStatus(entityType, entityId), 1000); // Refresh status + } else { + throw new Error(result.error || 'Manual sync failed'); + } + } catch (error) { + showError('Manual sync failed: ' + error.message); + } +} + +function showModal(title, content) { + const modal = document.getElementById('sync-modal') || createSyncModal(); + document.getElementById('sync-modal-title').textContent = title; + document.getElementById('sync-modal-content').innerHTML = content; + modal.style.display = 'block'; +} + +function hideSyncStatus() { + const modal = document.getElementById('sync-modal'); + if (modal) { + modal.style.display = 'none'; + } +} + +function createSyncModal() { + const modal = document.createElement('div'); + modal.id = 'sync-modal'; + modal.className = 'modal'; + modal.innerHTML = ` + + `; + document.body.appendChild(modal); + return modal; } \ No newline at end of file diff --git a/public/styles.css b/public/styles.css index 7285898..14252fc 100644 --- a/public/styles.css +++ b/public/styles.css @@ -25,9 +25,36 @@ header { border-radius: 5px; } +header { + display: flex; + justify-content: space-between; + align-items: center; + flex-wrap: wrap; +} + header h1 { - margin-bottom: 1rem; - text-align: center; + margin-bottom: 0; +} + +#user-info { + display: flex; + align-items: center; + gap: 1rem; + color: white; +} + +.profile-button, .logout-button { + background: #34495e; + color: white; + border: none; + padding: 0.5rem 1rem; + border-radius: 4px; + cursor: pointer; + font-size: 0.9rem; +} + +.profile-button:hover, .logout-button:hover { + background: #4a6278; } nav { @@ -193,6 +220,168 @@ button:disabled { font-size: 0.9rem; } +/* Modal styles */ +.modal { + display: none; + position: fixed; + z-index: 1000; + left: 0; + top: 0; + width: 100%; + height: 100%; + background-color: rgba(0,0,0,0.5); +} + +.modal-content { + background-color: white; + margin: 5% auto; + padding: 2rem; + border-radius: 8px; + width: 90%; + max-width: 500px; + position: relative; + max-height: 80vh; + overflow-y: auto; +} + +.modal-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 1.5rem; +} + +.close-btn { + background: none; + border: none; + font-size: 1.5rem; + cursor: pointer; + color: #7f8c8d; +} + +.close-btn:hover { + color: #2c3e50; +} + +.form-group { + margin-bottom: 1rem; +} + +.form-group label { + display: block; + margin-bottom: 0.5rem; + font-weight: bold; + color: #2c3e50; +} + +.form-group small { + display: block; + margin-top: 0.25rem; + color: #7f8c8d; + font-size: 0.8rem; +} + +.integrations-section { + margin-top: 2rem; + padding-top: 1.5rem; + border-top: 2px solid #ecf0f1; +} + +.integrations-section h3 { + margin-bottom: 1rem; + color: #2c3e50; +} + +.integration-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 1rem; + margin-bottom: 1rem; + background: #f8f9fa; + border-radius: 6px; + flex-wrap: wrap; + gap: 0.5rem; +} + +.status-connected { + color: #27ae60; + font-weight: bold; +} + +.status-disconnected { + color: #e74c3c; + font-weight: bold; +} + +.modal-actions { + display: flex; + gap: 1rem; + justify-content: flex-end; + margin-top: 2rem; + padding-top: 1rem; + border-top: 2px solid #ecf0f1; +} + +/* Sync badges */ +.sync-badge { + display: inline-block; + padding: 0.25rem 0.5rem; + border-radius: 12px; + font-size: 0.7rem; + font-weight: bold; + margin: 0.25rem 0.25rem 0 0; +} + +.sync-badge.notion { + background: #f7f6f3; + color: #37352f; + border: 1px solid #e9e9e7; +} + +.sync-badge.drive { + background: #e8f0fe; + color: #1a73e8; + border: 1px solid #dadce0; +} + +.sync-btn { + background: #95a5a6; + color: white; + border: none; + padding: 0.25rem 0.5rem; + border-radius: 4px; + font-size: 0.8rem; + cursor: pointer; + margin-left: 0.5rem; +} + +.sync-btn:hover { + background: #7f8c8d; +} + +.sync-status-item { + padding: 1rem; + margin-bottom: 1rem; + background: #f8f9fa; + border-radius: 6px; + border-left: 4px solid #3498db; +} + +#auth-toggle { + text-align: center; + margin-top: 1rem; +} + +#auth-toggle a { + color: #3498db; + text-decoration: none; +} + +#auth-toggle a:hover { + text-decoration: underline; +} + @media (max-width: 768px) { .container { padding: 10px; @@ -209,4 +398,28 @@ button:disabled { form { gap: 0.5rem; } + + .modal-content { + width: 95%; + margin: 2% auto; + padding: 1rem; + } + + .integration-item { + flex-direction: column; + align-items: flex-start; + } + + .modal-actions { + flex-direction: column; + } + + header { + flex-direction: column; + gap: 1rem; + } + + #user-info { + justify-content: center; + } } \ No newline at end of file diff --git a/server.js b/server.js index 3e60877..c6f58bd 100644 --- a/server.js +++ b/server.js @@ -4,11 +4,21 @@ const multer = require('multer'); const bodyParser = require('body-parser'); const path = require('path'); const fs = require('fs'); +const cors = require('cors'); +require('dotenv').config(); + +// Import configurations and services +const { supabase, supabaseAdmin } = require('./config/supabase'); +const { notion, notionHelpers } = require('./config/notion'); +const { oauth2Client, googleDriveHelpers } = require('./config/googleDrive'); +const { authenticateToken, optionalAuth } = require('./middleware/auth'); +const syncService = require('./services/syncService'); const app = express(); const PORT = process.env.PORT || 3000; // Middleware +app.use(cors()); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(express.static('public')); @@ -101,124 +111,469 @@ app.get('/', (req, res) => { res.sendFile(path.join(__dirname, 'public', 'index.html')); }); +// Authentication Routes +app.post('/auth/signup', async (req, res) => { + try { + const { email, password, fullName } = req.body; + + const { data, error } = await supabaseAdmin.auth.admin.createUser({ + email, + password, + user_metadata: { full_name: fullName }, + email_confirm: true + }); + + if (error) throw error; + + res.json({ user: data.user, message: 'User created successfully' }); + } catch (error) { + res.status(400).json({ error: error.message }); + } +}); + +app.post('/auth/signin', async (req, res) => { + try { + const { email, password } = req.body; + + const { data, error } = await supabase.auth.signInWithPassword({ + email, + password + }); + + if (error) throw error; + + res.json({ user: data.user, session: data.session }); + } catch (error) { + res.status(400).json({ error: error.message }); + } +}); + +app.post('/auth/signout', authenticateToken, async (req, res) => { + try { + const { error } = await supabase.auth.signOut(); + if (error) throw error; + + res.json({ message: 'Signed out successfully' }); + } catch (error) { + res.status(400).json({ error: error.message }); + } +}); + +// Google OAuth Routes +app.get('/auth/google', (req, res) => { + const authUrl = googleDriveHelpers.getAuthUrl(); + res.json({ authUrl }); +}); + +app.get('/auth/google/callback', authenticateToken, async (req, res) => { + try { + const { code } = req.query; + const tokens = await googleDriveHelpers.getTokens(code); + + // Store tokens in user profile + const { error } = await supabaseAdmin + .from('profiles') + .update({ google_tokens: tokens }) + .eq('id', req.user.id); + + if (error) throw error; + + res.redirect('/?google_auth=success'); + } catch (error) { + res.redirect('/?google_auth=error'); + } +}); + +// User profile routes +app.get('/api/profile', authenticateToken, async (req, res) => { + try { + const { data, error } = await supabaseAdmin + .from('profiles') + .select('*') + .eq('id', req.user.id) + .single(); + + if (error) throw error; + res.json(data); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +app.put('/api/profile', authenticateToken, async (req, res) => { + try { + const { full_name, notion_workspace_id } = req.body; + + const { data, error } = await supabaseAdmin + .from('profiles') + .update({ + full_name, + notion_workspace_id, + updated_at: new Date().toISOString() + }) + .eq('id', req.user.id) + .select() + .single(); + + if (error) throw error; + res.json(data); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + // Credit Analysis Routes -app.get('/api/companies', (req, res) => { - db.all('SELECT * FROM companies ORDER BY name', (err, rows) => { - if (err) { - res.status(500).json({ error: err.message }); - } else { - res.json(rows); - } - }); +app.get('/api/companies', authenticateToken, async (req, res) => { + try { + const { data, error } = await supabaseAdmin + .from('companies') + .select('*') + .eq('user_id', req.user.id) + .order('name'); + + if (error) throw error; + res.json(data); + } catch (error) { + res.status(500).json({ error: error.message }); + } }); -app.post('/api/companies', (req, res) => { - const { name, industry } = req.body; - db.run('INSERT INTO companies (name, industry) VALUES (?, ?)', [name, industry], function(err) { - if (err) { - res.status(500).json({ error: err.message }); - } else { - res.json({ id: this.lastID, name, industry }); - } - }); +app.post('/api/companies', authenticateToken, async (req, res) => { + try { + const { name, industry } = req.body; + + const { data, error } = await supabaseAdmin + .from('companies') + .insert({ + name, + industry, + user_id: req.user.id + }) + .select() + .single(); + + if (error) throw error; + + // Sync to external services + const syncResults = await syncService.syncCompany(req.user.id, data); + + res.json({ + ...data, + sync_status: syncResults, + message: 'Company created and syncing to external services' + }); + } catch (error) { + res.status(500).json({ error: error.message }); + } }); -app.post('/api/upload-financial', upload.single('document'), (req, res) => { - const { company_id, document_type } = req.body; - const file_path = req.file.path; - - // Basic OCR simulation - in a real implementation, you'd use tesseract.js or similar - const extracted_data = `Extracted financial data from ${req.file.originalname}`; - - db.run('INSERT INTO financial_data (company_id, document_type, file_path, extracted_data) VALUES (?, ?, ?, ?)', - [company_id, document_type, file_path, extracted_data], function(err) { - if (err) { - res.status(500).json({ error: err.message }); - } else { - res.json({ id: this.lastID, message: 'Financial document uploaded and processed' }); +app.post('/api/upload-financial', authenticateToken, upload.single('document'), async (req, res) => { + try { + const { company_id, document_type } = req.body; + const file_path = req.file.path; + + // Basic OCR simulation - in a real implementation, you'd use tesseract.js or similar + const extracted_data = `Extracted financial data from ${req.file.originalname}`; + + // Get company info for Google Drive upload + const { data: company, error: companyError } = await supabaseAdmin + .from('companies') + .select('name') + .eq('id', company_id) + .eq('user_id', req.user.id) + .single(); + + if (companyError) throw companyError; + + let googleDriveResult = null; + + // Upload to Google Drive if user has connected their account + try { + const { data: profile } = await supabaseAdmin + .from('profiles') + .select('google_tokens') + .eq('id', req.user.id) + .single(); + + if (profile?.google_tokens) { + googleDriveHelpers.setCredentials(profile.google_tokens); + googleDriveResult = await googleDriveHelpers.uploadFinancialDocument( + file_path, + req.file.originalname, + company.name, + document_type + ); + } + } catch (driveError) { + console.error('Google Drive upload failed:', driveError); + // Continue without Google Drive upload } - }); + + // Store in Supabase + const { data, error } = await supabaseAdmin + .from('financial_data') + .insert({ + company_id, + user_id: req.user.id, + document_type, + original_filename: req.file.originalname, + file_path, + google_drive_file_id: googleDriveResult?.id, + google_drive_link: googleDriveResult?.webViewLink, + extracted_data + }) + .select() + .single(); + + if (error) throw error; + + res.json({ + ...data, + google_drive_backup: googleDriveResult ? true : false, + message: 'Financial document uploaded and processed' + }); + } catch (error) { + res.status(500).json({ error: error.message }); + } }); -app.post('/api/credit-memos', (req, res) => { - const { company_id, memo_type, title, content, financial_metrics } = req.body; - db.run('INSERT INTO credit_memos (company_id, memo_type, title, content, financial_metrics) VALUES (?, ?, ?, ?, ?)', - [company_id, memo_type, title, content, JSON.stringify(financial_metrics)], function(err) { - if (err) { - res.status(500).json({ error: err.message }); - } else { - res.json({ id: this.lastID, message: 'Credit memo created' }); - } - }); +app.post('/api/credit-memos', authenticateToken, async (req, res) => { + try { + const { company_id, memo_type, title, content, financial_metrics } = req.body; + + // Get company name for Notion sync + const { data: company, error: companyError } = await supabaseAdmin + .from('companies') + .select('name') + .eq('id', company_id) + .eq('user_id', req.user.id) + .single(); + + if (companyError) throw companyError; + + const { data, error } = await supabaseAdmin + .from('credit_memos') + .insert({ + company_id, + user_id: req.user.id, + memo_type, + title, + content, + financial_metrics: typeof financial_metrics === 'string' ? JSON.parse(financial_metrics) : financial_metrics + }) + .select() + .single(); + + if (error) throw error; + + // Sync to external services + const syncResults = await syncService.syncCreditMemo(req.user.id, data, company.name); + + res.json({ + ...data, + sync_status: syncResults, + message: 'Credit memo created and syncing to external services' + }); + } catch (error) { + res.status(500).json({ error: error.message }); + } }); // Novel Planning Routes -app.get('/api/novels', (req, res) => { - db.all('SELECT * FROM novels ORDER BY created_at DESC', (err, rows) => { - if (err) { - res.status(500).json({ error: err.message }); - } else { - res.json(rows); - } - }); +app.get('/api/novels', authenticateToken, async (req, res) => { + try { + const { data, error } = await supabaseAdmin + .from('novels') + .select('*') + .eq('user_id', req.user.id) + .order('created_at', { ascending: false }); + + if (error) throw error; + res.json(data); + } catch (error) { + res.status(500).json({ error: error.message }); + } }); -app.post('/api/novels', (req, res) => { - const { title, description, pov_style, tense, target_chapters, target_beats } = req.body; - db.run('INSERT INTO novels (title, description, pov_style, tense, target_chapters, target_beats) VALUES (?, ?, ?, ?, ?, ?)', - [title, description, pov_style || 'dual_alternating', tense || 'past', target_chapters || 25, target_beats || 250], function(err) { - if (err) { - res.status(500).json({ error: err.message }); - } else { - res.json({ id: this.lastID, title, description }); - } - }); +app.post('/api/novels', authenticateToken, async (req, res) => { + try { + const { title, description, pov_style, tense, target_chapters, target_beats } = req.body; + + const { data, error } = await supabaseAdmin + .from('novels') + .insert({ + title, + description, + pov_style: pov_style || 'dual_alternating', + tense: tense || 'past', + target_chapters: target_chapters || 25, + target_beats: target_beats || 250, + user_id: req.user.id + }) + .select() + .single(); + + if (error) throw error; + + // Sync to external services + const syncResults = await syncService.syncNovel(req.user.id, data); + + res.json({ + ...data, + sync_status: syncResults, + message: 'Novel created and syncing to external services' + }); + } catch (error) { + res.status(500).json({ error: error.message }); + } }); -app.get('/api/novels/:id/chapters', (req, res) => { - const novel_id = req.params.id; - db.all('SELECT * FROM chapters WHERE novel_id = ? ORDER BY chapter_number', [novel_id], (err, rows) => { - if (err) { - res.status(500).json({ error: err.message }); - } else { - res.json(rows); - } - }); +app.get('/api/novels/:id/chapters', authenticateToken, async (req, res) => { + try { + const novel_id = req.params.id; + + const { data, error } = await supabaseAdmin + .from('chapters') + .select('*') + .eq('novel_id', novel_id) + .eq('user_id', req.user.id) + .order('chapter_number'); + + if (error) throw error; + res.json(data); + } catch (error) { + res.status(500).json({ error: error.message }); + } }); -app.post('/api/chapters', (req, res) => { - const { novel_id, chapter_number, title, pov_character, summary } = req.body; - db.run('INSERT INTO chapters (novel_id, chapter_number, title, pov_character, summary) VALUES (?, ?, ?, ?, ?)', - [novel_id, chapter_number, title, pov_character, summary], function(err) { - if (err) { - res.status(500).json({ error: err.message }); - } else { - res.json({ id: this.lastID, novel_id, chapter_number, title }); +app.post('/api/chapters', authenticateToken, async (req, res) => { + try { + const { novel_id, chapter_number, title, pov_character, summary, content } = req.body; + + const { data, error } = await supabaseAdmin + .from('chapters') + .insert({ + novel_id, + chapter_number, + title, + pov_character, + summary, + content, + user_id: req.user.id + }) + .select() + .single(); + + if (error) throw error; + + // Backup to Google Drive if content is provided + if (content) { + try { + const { data: novel } = await supabaseAdmin + .from('novels') + .select('title') + .eq('id', novel_id) + .single(); + + await syncService.backupChapter(req.user.id, data, novel.title); + } catch (backupError) { + console.error('Chapter backup failed:', backupError); + } } - }); + + res.json({ + ...data, + message: 'Chapter created' + (content ? ' and backed up to Google Drive' : '') + }); + } catch (error) { + res.status(500).json({ error: error.message }); + } }); -app.get('/api/novels/:id/beats', (req, res) => { - const novel_id = req.params.id; - db.all('SELECT * FROM story_beats WHERE novel_id = ? ORDER BY beat_number', [novel_id], (err, rows) => { - if (err) { - res.status(500).json({ error: err.message }); - } else { - res.json(rows); - } - }); +app.get('/api/novels/:id/beats', authenticateToken, async (req, res) => { + try { + const novel_id = req.params.id; + + const { data, error } = await supabaseAdmin + .from('story_beats') + .select('*') + .eq('novel_id', novel_id) + .eq('user_id', req.user.id) + .order('beat_number'); + + if (error) throw error; + res.json(data); + } catch (error) { + res.status(500).json({ error: error.message }); + } }); -app.post('/api/beats', (req, res) => { - const { novel_id, chapter_id, beat_number, description, beat_type, pov_character } = req.body; - db.run('INSERT INTO story_beats (novel_id, chapter_id, beat_number, description, beat_type, pov_character) VALUES (?, ?, ?, ?, ?, ?)', - [novel_id, chapter_id, beat_number, description, beat_type, pov_character], function(err) { - if (err) { - res.status(500).json({ error: err.message }); - } else { - res.json({ id: this.lastID, novel_id, beat_number, description }); +app.post('/api/beats', authenticateToken, async (req, res) => { + try { + const { novel_id, chapter_id, beat_number, description, beat_type, pov_character } = req.body; + + const { data, error } = await supabaseAdmin + .from('story_beats') + .insert({ + novel_id, + chapter_id, + beat_number, + description, + beat_type, + pov_character, + user_id: req.user.id + }) + .select() + .single(); + + if (error) throw error; + res.json(data); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// Sync status endpoint +app.get('/api/sync-status/:entity_type/:entity_id', authenticateToken, async (req, res) => { + try { + const { entity_type, entity_id } = req.params; + const status = await syncService.getSyncStatus(req.user.id, entity_type, entity_id); + res.json(status); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// Manual sync trigger +app.post('/api/sync/:entity_type/:entity_id', authenticateToken, async (req, res) => { + try { + const { entity_type, entity_id } = req.params; + let syncResults = {}; + + if (entity_type === 'company') { + const { data: company } = await supabaseAdmin + .from('companies') + .select('*') + .eq('id', entity_id) + .eq('user_id', req.user.id) + .single(); + + syncResults = await syncService.syncCompany(req.user.id, company, 'update'); + } else if (entity_type === 'novel') { + const { data: novel } = await supabaseAdmin + .from('novels') + .select('*') + .eq('id', entity_id) + .eq('user_id', req.user.id) + .single(); + + syncResults = await syncService.syncNovel(req.user.id, novel, 'update'); } - }); + + res.json({ sync_results: syncResults, message: 'Manual sync completed' }); + } catch (error) { + res.status(500).json({ error: error.message }); + } }); // Start server diff --git a/services/syncService.js b/services/syncService.js new file mode 100644 index 0000000..c4b8248 --- /dev/null +++ b/services/syncService.js @@ -0,0 +1,254 @@ +const { supabaseAdmin } = require('../config/supabase'); +const { notionHelpers } = require('../config/notion'); +const { googleDriveHelpers } = require('../config/googleDrive'); + +class SyncService { + constructor() { + this.services = ['notion', 'google_drive']; + } + + // Log sync operation + async logSync(userId, entityType, entityId, service, action, status, externalId = null, errorMessage = null) { + try { + const { error } = await supabaseAdmin + .from('sync_logs') + .insert({ + user_id: userId, + entity_type: entityType, + entity_id: entityId, + service: service, + action: action, + status: status, + external_id: externalId, + error_message: errorMessage + }); + + if (error) { + console.error('Error logging sync:', error); + } + } catch (error) { + console.error('Error in logSync:', error); + } + } + + // Sync company to external services + async syncCompany(userId, companyData, action = 'create') { + const results = {}; + + // Sync to Notion + try { + await this.logSync(userId, 'company', companyData.id, 'notion', action, 'pending'); + + const notionResult = await notionHelpers.createCompany(companyData.name, companyData.industry); + + // Update company with Notion page ID + await supabaseAdmin + .from('companies') + .update({ notion_page_id: notionResult.id }) + .eq('id', companyData.id); + + await this.logSync(userId, 'company', companyData.id, 'notion', action, 'success', notionResult.id); + results.notion = { success: true, id: notionResult.id }; + } catch (error) { + await this.logSync(userId, 'company', companyData.id, 'notion', action, 'failed', null, error.message); + results.notion = { success: false, error: error.message }; + } + + // Create Google Drive folder for company documents + try { + await this.logSync(userId, 'company', companyData.id, 'google_drive', action, 'pending'); + + // Set user's Google tokens if available + const { data: profile } = await supabaseAdmin + .from('profiles') + .select('google_tokens') + .eq('id', userId) + .single(); + + if (profile?.google_tokens) { + googleDriveHelpers.setCredentials(profile.google_tokens); + + const folderName = `${companyData.name}_Financial_Documents`; + const driveFolder = await googleDriveHelpers.createFolder(folderName); + + // Update company with Google Drive folder ID + await supabaseAdmin + .from('companies') + .update({ google_drive_folder_id: driveFolder.id }) + .eq('id', companyData.id); + + await this.logSync(userId, 'company', companyData.id, 'google_drive', action, 'success', driveFolder.id); + results.google_drive = { success: true, id: driveFolder.id }; + } else { + results.google_drive = { success: false, error: 'Google tokens not available' }; + } + } catch (error) { + await this.logSync(userId, 'company', companyData.id, 'google_drive', action, 'failed', null, error.message); + results.google_drive = { success: false, error: error.message }; + } + + return results; + } + + // Sync novel to external services + async syncNovel(userId, novelData, action = 'create') { + const results = {}; + + // Sync to Notion + try { + await this.logSync(userId, 'novel', novelData.id, 'notion', action, 'pending'); + + const notionResult = await notionHelpers.createNovel( + novelData.title, + novelData.description, + novelData.pov_style, + novelData.tense, + novelData.target_chapters, + novelData.target_beats + ); + + // Update novel with Notion page ID + await supabaseAdmin + .from('novels') + .update({ notion_page_id: notionResult.id }) + .eq('id', novelData.id); + + await this.logSync(userId, 'novel', novelData.id, 'notion', action, 'success', notionResult.id); + results.notion = { success: true, id: notionResult.id }; + } catch (error) { + await this.logSync(userId, 'novel', novelData.id, 'notion', action, 'failed', null, error.message); + results.notion = { success: false, error: error.message }; + } + + // Create Google Drive folder for novel manuscripts + try { + await this.logSync(userId, 'novel', novelData.id, 'google_drive', action, 'pending'); + + const { data: profile } = await supabaseAdmin + .from('profiles') + .select('google_tokens') + .eq('id', userId) + .single(); + + if (profile?.google_tokens) { + googleDriveHelpers.setCredentials(profile.google_tokens); + + const folderName = `${novelData.title}_Manuscripts`; + const driveFolder = await googleDriveHelpers.createFolder(folderName); + + // Update novel with Google Drive folder ID + await supabaseAdmin + .from('novels') + .update({ google_drive_folder_id: driveFolder.id }) + .eq('id', novelData.id); + + await this.logSync(userId, 'novel', novelData.id, 'google_drive', action, 'success', driveFolder.id); + results.google_drive = { success: true, id: driveFolder.id }; + } else { + results.google_drive = { success: false, error: 'Google tokens not available' }; + } + } catch (error) { + await this.logSync(userId, 'novel', novelData.id, 'google_drive', action, 'failed', null, error.message); + results.google_drive = { success: false, error: error.message }; + } + + return results; + } + + // Sync credit memo to external services + async syncCreditMemo(userId, memoData, companyName, action = 'create') { + const results = {}; + + // Sync to Notion + try { + await this.logSync(userId, 'credit_memo', memoData.id, 'notion', action, 'pending'); + + const notionResult = await notionHelpers.createCreditMemo( + companyName, + memoData.memo_type, + memoData.title, + memoData.content, + memoData.financial_metrics + ); + + // Update memo with Notion page ID + await supabaseAdmin + .from('credit_memos') + .update({ notion_page_id: notionResult.id }) + .eq('id', memoData.id); + + await this.logSync(userId, 'credit_memo', memoData.id, 'notion', action, 'success', notionResult.id); + results.notion = { success: true, id: notionResult.id }; + } catch (error) { + await this.logSync(userId, 'credit_memo', memoData.id, 'notion', action, 'failed', null, error.message); + results.notion = { success: false, error: error.message }; + } + + return results; + } + + // Backup chapter content to Google Drive + async backupChapter(userId, chapterData, novelTitle) { + try { + const { data: profile } = await supabaseAdmin + .from('profiles') + .select('google_tokens') + .eq('id', userId) + .single(); + + if (!profile?.google_tokens) { + throw new Error('Google tokens not available'); + } + + googleDriveHelpers.setCredentials(profile.google_tokens); + + const content = `Chapter ${chapterData.chapter_number}: ${chapterData.title || 'Untitled'}\n\n${chapterData.content || chapterData.summary || 'No content yet'}`; + + const backupResult = await googleDriveHelpers.uploadNovelBackup( + `${novelTitle}_Chapter_${chapterData.chapter_number}`, + content + ); + + // Update chapter with backup ID + await supabaseAdmin + .from('chapters') + .update({ google_drive_backup_id: backupResult.id }) + .eq('id', chapterData.id); + + return { success: true, id: backupResult.id }; + } catch (error) { + console.error('Error backing up chapter:', error); + return { success: false, error: error.message }; + } + } + + // Get sync status for an entity + async getSyncStatus(userId, entityType, entityId) { + try { + const { data, error } = await supabaseAdmin + .from('sync_logs') + .select('service, action, status, external_id, error_message, created_at') + .eq('user_id', userId) + .eq('entity_type', entityType) + .eq('entity_id', entityId) + .order('created_at', { ascending: false }); + + if (error) throw error; + + // Group by service to get latest status + const statusByService = {}; + data.forEach(log => { + if (!statusByService[log.service]) { + statusByService[log.service] = log; + } + }); + + return statusByService; + } catch (error) { + console.error('Error getting sync status:', error); + return {}; + } + } +} + +module.exports = new SyncService(); \ No newline at end of file diff --git a/supabase/schema.sql b/supabase/schema.sql new file mode 100644 index 0000000..83ae0fd --- /dev/null +++ b/supabase/schema.sql @@ -0,0 +1,239 @@ +-- Enable RLS (Row Level Security) +ALTER DEFAULT PRIVILEGES REVOKE EXECUTE ON FUNCTIONS FROM PUBLIC; + +-- Create custom types +CREATE TYPE memo_type AS ENUM ('annual_review', 'refinancing', 'new_deal', 'amendment'); +CREATE TYPE document_type AS ENUM ('financial_statement', 'balance_sheet', 'cash_flow', 'income_statement'); +CREATE TYPE pov_style AS ENUM ('dual_alternating', 'single', 'multiple'); +CREATE TYPE tense_type AS ENUM ('past', 'present'); +CREATE TYPE beat_type AS ENUM ('setup', 'inciting_incident', 'rising_action', 'climax', 'falling_action', 'resolution'); + +-- Users table (extends Supabase auth.users) +CREATE TABLE public.profiles ( + id UUID REFERENCES auth.users(id) ON DELETE CASCADE PRIMARY KEY, + email TEXT, + full_name TEXT, + avatar_url TEXT, + google_tokens JSONB, + notion_workspace_id TEXT, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() +); + +-- Companies table +CREATE TABLE public.companies ( + id UUID DEFAULT gen_random_uuid() PRIMARY KEY, + user_id UUID REFERENCES public.profiles(id) ON DELETE CASCADE NOT NULL, + name TEXT NOT NULL, + industry TEXT, + notion_page_id TEXT, + google_drive_folder_id TEXT, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() +); + +-- Financial data table +CREATE TABLE public.financial_data ( + id UUID DEFAULT gen_random_uuid() PRIMARY KEY, + company_id UUID REFERENCES public.companies(id) ON DELETE CASCADE NOT NULL, + user_id UUID REFERENCES public.profiles(id) ON DELETE CASCADE NOT NULL, + document_type document_type NOT NULL, + original_filename TEXT, + file_path TEXT, + google_drive_file_id TEXT, + google_drive_link TEXT, + extracted_data TEXT, + ocr_confidence DECIMAL(5,2), + upload_date TIMESTAMP WITH TIME ZONE DEFAULT NOW() +); + +-- Credit memos table +CREATE TABLE public.credit_memos ( + id UUID DEFAULT gen_random_uuid() PRIMARY KEY, + company_id UUID REFERENCES public.companies(id) ON DELETE CASCADE NOT NULL, + user_id UUID REFERENCES public.profiles(id) ON DELETE CASCADE NOT NULL, + memo_type memo_type NOT NULL, + title TEXT NOT NULL, + content TEXT NOT NULL, + financial_metrics JSONB, + notion_page_id TEXT, + google_drive_backup_id TEXT, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() +); + +-- Novels table +CREATE TABLE public.novels ( + id UUID DEFAULT gen_random_uuid() PRIMARY KEY, + user_id UUID REFERENCES public.profiles(id) ON DELETE CASCADE NOT NULL, + title TEXT NOT NULL, + description TEXT, + pov_style pov_style DEFAULT 'dual_alternating', + tense tense_type DEFAULT 'past', + target_chapters INTEGER DEFAULT 25, + target_beats INTEGER DEFAULT 250, + current_word_count INTEGER DEFAULT 0, + notion_page_id TEXT, + google_drive_folder_id TEXT, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() +); + +-- Chapters table +CREATE TABLE public.chapters ( + id UUID DEFAULT gen_random_uuid() PRIMARY KEY, + novel_id UUID REFERENCES public.novels(id) ON DELETE CASCADE NOT NULL, + user_id UUID REFERENCES public.profiles(id) ON DELETE CASCADE NOT NULL, + chapter_number INTEGER NOT NULL, + title TEXT, + pov_character TEXT, + summary TEXT, + content TEXT, + word_count INTEGER DEFAULT 0, + google_drive_backup_id TEXT, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + UNIQUE(novel_id, chapter_number) +); + +-- Story beats table +CREATE TABLE public.story_beats ( + id UUID DEFAULT gen_random_uuid() PRIMARY KEY, + novel_id UUID REFERENCES public.novels(id) ON DELETE CASCADE NOT NULL, + chapter_id UUID REFERENCES public.chapters(id) ON DELETE CASCADE, + user_id UUID REFERENCES public.profiles(id) ON DELETE CASCADE NOT NULL, + beat_number INTEGER NOT NULL, + description TEXT NOT NULL, + beat_type beat_type, + pov_character TEXT, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() +); + +-- Sync logs table (track sync status with external services) +CREATE TABLE public.sync_logs ( + id UUID DEFAULT gen_random_uuid() PRIMARY KEY, + user_id UUID REFERENCES public.profiles(id) ON DELETE CASCADE NOT NULL, + entity_type TEXT NOT NULL, -- 'company', 'novel', 'credit_memo', etc. + entity_id UUID NOT NULL, + service TEXT NOT NULL, -- 'notion', 'google_drive' + action TEXT NOT NULL, -- 'create', 'update', 'delete' + status TEXT NOT NULL, -- 'pending', 'success', 'failed' + error_message TEXT, + external_id TEXT, -- ID in external service + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() +); + +-- Enable Row Level Security +ALTER TABLE public.profiles ENABLE ROW LEVEL SECURITY; +ALTER TABLE public.companies ENABLE ROW LEVEL SECURITY; +ALTER TABLE public.financial_data ENABLE ROW LEVEL SECURITY; +ALTER TABLE public.credit_memos ENABLE ROW LEVEL SECURITY; +ALTER TABLE public.novels ENABLE ROW LEVEL SECURITY; +ALTER TABLE public.chapters ENABLE ROW LEVEL SECURITY; +ALTER TABLE public.story_beats ENABLE ROW LEVEL SECURITY; +ALTER TABLE public.sync_logs ENABLE ROW LEVEL SECURITY; + +-- RLS Policies +-- Profiles +CREATE POLICY "Users can view own profile" ON public.profiles + FOR SELECT USING (auth.uid() = id); + +CREATE POLICY "Users can update own profile" ON public.profiles + FOR UPDATE USING (auth.uid() = id); + +CREATE POLICY "Users can insert own profile" ON public.profiles + FOR INSERT WITH CHECK (auth.uid() = id); + +-- Companies +CREATE POLICY "Users can manage own companies" ON public.companies + FOR ALL USING (auth.uid() = user_id); + +-- Financial Data +CREATE POLICY "Users can manage own financial data" ON public.financial_data + FOR ALL USING (auth.uid() = user_id); + +-- Credit Memos +CREATE POLICY "Users can manage own credit memos" ON public.credit_memos + FOR ALL USING (auth.uid() = user_id); + +-- Novels +CREATE POLICY "Users can manage own novels" ON public.novels + FOR ALL USING (auth.uid() = user_id); + +-- Chapters +CREATE POLICY "Users can manage own chapters" ON public.chapters + FOR ALL USING (auth.uid() = user_id); + +-- Story Beats +CREATE POLICY "Users can manage own story beats" ON public.story_beats + FOR ALL USING (auth.uid() = user_id); + +-- Sync Logs +CREATE POLICY "Users can manage own sync logs" ON public.sync_logs + FOR ALL USING (auth.uid() = user_id); + +-- Functions and Triggers +-- Update updated_at timestamp +CREATE OR REPLACE FUNCTION public.handle_updated_at() +RETURNS TRIGGER AS $$ +BEGIN + NEW.updated_at = NOW(); + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +-- Apply updated_at triggers +CREATE TRIGGER handle_profiles_updated_at + BEFORE UPDATE ON public.profiles + FOR EACH ROW EXECUTE FUNCTION public.handle_updated_at(); + +CREATE TRIGGER handle_companies_updated_at + BEFORE UPDATE ON public.companies + FOR EACH ROW EXECUTE FUNCTION public.handle_updated_at(); + +CREATE TRIGGER handle_credit_memos_updated_at + BEFORE UPDATE ON public.credit_memos + FOR EACH ROW EXECUTE FUNCTION public.handle_updated_at(); + +CREATE TRIGGER handle_novels_updated_at + BEFORE UPDATE ON public.novels + FOR EACH ROW EXECUTE FUNCTION public.handle_updated_at(); + +CREATE TRIGGER handle_chapters_updated_at + BEFORE UPDATE ON public.chapters + FOR EACH ROW EXECUTE FUNCTION public.handle_updated_at(); + +CREATE TRIGGER handle_story_beats_updated_at + BEFORE UPDATE ON public.story_beats + FOR EACH ROW EXECUTE FUNCTION public.handle_updated_at(); + +-- Function to handle new user signup +CREATE OR REPLACE FUNCTION public.handle_new_user() +RETURNS TRIGGER AS $$ +BEGIN + INSERT INTO public.profiles (id, email, full_name) + VALUES (NEW.id, NEW.email, NEW.raw_user_meta_data->>'full_name'); + RETURN NEW; +END; +$$ LANGUAGE plpgsql SECURITY DEFINER; + +-- Trigger for new user signup +CREATE TRIGGER on_auth_user_created + AFTER INSERT ON auth.users + FOR EACH ROW EXECUTE FUNCTION public.handle_new_user(); + +-- Indexes for performance +CREATE INDEX idx_companies_user_id ON public.companies(user_id); +CREATE INDEX idx_financial_data_company_id ON public.financial_data(company_id); +CREATE INDEX idx_financial_data_user_id ON public.financial_data(user_id); +CREATE INDEX idx_credit_memos_company_id ON public.credit_memos(company_id); +CREATE INDEX idx_credit_memos_user_id ON public.credit_memos(user_id); +CREATE INDEX idx_novels_user_id ON public.novels(user_id); +CREATE INDEX idx_chapters_novel_id ON public.chapters(novel_id); +CREATE INDEX idx_chapters_user_id ON public.chapters(user_id); +CREATE INDEX idx_story_beats_novel_id ON public.story_beats(novel_id); +CREATE INDEX idx_story_beats_chapter_id ON public.story_beats(chapter_id); +CREATE INDEX idx_story_beats_user_id ON public.story_beats(user_id); +CREATE INDEX idx_sync_logs_user_id ON public.sync_logs(user_id); +CREATE INDEX idx_sync_logs_entity ON public.sync_logs(entity_type, entity_id); \ No newline at end of file