update 9/20/25

This commit is contained in:
Ra
2025-09-20 21:44:02 -07:00
parent c713c2ed5e
commit b41f489fe6
58 changed files with 11529 additions and 2769 deletions

View File

@@ -1,23 +1,12 @@
const { onRequest } = require("firebase-functions/v2/https");
const { setGlobalOptions, logger } = require("firebase-functions/v2");
const admin = require("firebase-admin");
const { VertexAI } = require('@google-cloud/vertexai');
const Stripe = require("stripe");
const { executeQuery, executeTransaction } = require('./database');
// Set global options for all functions to use us-central1 region
setGlobalOptions({ region: "us-central1" });
// const serviceAccount = require("./auditly-consulting-firebase-adminsdk-fbsvc-e4b51ef5cf.json");
const serviceAccount = require("./auditly-c0027-firebase-adminsdk-fbsvc-1db7c58141.json")
admin.initializeApp({
credential: admin.credential.cert(serviceAccount)
});
//region Interface Clients
const db = admin.firestore();
// Initialize Vertex AI with your project ID
// This automatically uses IAM authentication from the service account
const vertexAI = new VertexAI({
@@ -441,59 +430,78 @@ const RESPONSE_FORMAT_COMPANY = {
//endregion Constants
//region Helper Functions
const validateAuthAndGetContext = async (req, res) => {
const authHeader = req.headers.authorization;
async function validateAuthAndGetContext(req, res) {
// Set CORS headers for all requests
res.setHeader('Access-Control-Allow-Origin', '*')
.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS')
.setHeader('Access-Control-Allow-Headers', 'Authorization, Content-Type');
if (req.method == "OPTIONS") {
res.headers['Access-Control-Allow-Origin'] = '*';
res.headers['Access-Control-Allow-Methods'] = 'GET, POST, OPTIONS';
res.headers['Access-Control-Allow-Headers'] = 'Authorization, Content-Type';
if (req.method === "OPTIONS") {
res.status(204).send('');
return false;
return null;
}
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
throw new Error('Missing or invalid authorization header');
res.status(401).json({ error: 'Missing or invalid authorization header' });
return null;
}
const token = authHeader.substring(7);
// Validate token format (should start with 'session_')
if (!token.startsWith('session_')) {
throw new Error('Invalid token format');
// Validate token format more thoroughly
if (!token.startsWith('session_') || token.length < 20) {
res.status(401).json({ error: 'Invalid token format' });
return null;
}
// Look up token in Firestore
const tokenDoc = await db.collection("authTokens").doc(token).get();
try {
// Look up token in PostgreSQL
const tokenRows = await executeQuery(
'SELECT * FROM auth_tokens WHERE token = $1 AND is_active = true',
[token]
);
if (!tokenDoc.exists) {
throw new Error('Token not found');
if (tokenRows.length === 0) {
res.status(401).json({ error: 'Token not found' });
return null;
}
const tokenData = tokenRows[0];
if (Date.now() > tokenData.expires_at) {
res.status(401).json({ error: 'Token has expired' });
return null;
}
// Update last used timestamp (don't await to avoid blocking)
executeQuery(
'UPDATE auth_tokens SET last_used_at = $1 WHERE token = $2',
[Date.now(), token]
).catch(error => {
console.warn('Failed to update token lastUsedAt:', error);
});
// Get user's organizations
const userOrgsRows = await executeQuery(
'SELECT organization_id FROM user_organizations WHERE user_id = $1',
[tokenData.user_id]
);
const orgIds = userOrgsRows.map(row => row.organization_id);
return {
userId: tokenData.user_id,
orgIds: orgIds,
orgId: orgIds[0] || null,
token: token
};
} catch (error) {
console.error('Auth validation error:', error);
res.status(500).json({ error: 'Authentication validation failed' });
return null;
}
const tokenData = tokenDoc.data();
if (!tokenData.isActive) {
throw new Error('Token is inactive');
}
if (Date.now() > tokenData.expiresAt) {
throw new Error('Token has expired');
}
// Update last used timestamp
await tokenDoc.ref.update({ lastUsedAt: Date.now() });
// Get user's organizations
const userOrgsSnapshot = await db.collection("users").doc(tokenData.userId).collection("organizations").get();
const orgIds = userOrgsSnapshot.docs.map(doc => doc.id);
return {
userId: tokenData.userId,
orgIds: orgIds,
// For backward compatibility, use first org as default
orgId: orgIds[0] || null,
token: token
};
};
const generateOTP = () => {
@@ -640,9 +648,18 @@ const verifyUserAuthorization = async (userId, orgId) => {
}
try {
// Check if user exists in the organization's employees collection
const employeeDoc = await db.collection("orgs").doc(orgId).collection("employees").doc(userId).get();
return employeeDoc.exists;
// Check if user exists in the organization's employees or as an organization member
const employeeRows = await executeQuery(
'SELECT id FROM employees WHERE organization_id = $1 AND (id = $2 OR user_id = $2)',
[orgId, userId]
);
const userOrgRows = await executeQuery(
'SELECT user_id FROM user_organizations WHERE user_id = $1 AND organization_id = $2',
[userId, orgId]
);
return employeeRows.length > 0 || userOrgRows.length > 0;
} catch (error) {
console.error("Authorization check error:", error);
return false;
@@ -667,14 +684,19 @@ exports.sendOTP = onRequest({cors: true}, async (req, res) => {
const otp = generateOTP();
const expiresAt = Date.now() + (5 * 60 * 1000); // 5 minutes expiry
// Store OTP in Firestore
await db.collection("otps").doc(email).set({
otp,
expiresAt,
attempts: 0,
inviteCode: inviteCode || null,
createdAt: Date.now(),
});
// Store OTP in PostgreSQL
await executeQuery(
`INSERT INTO otps (email, otp, expires_at, attempts, invite_code, created_at)
VALUES ($1, $2, $3, $4, $5, $6)
ON CONFLICT (email)
DO UPDATE SET
otp = EXCLUDED.otp,
expires_at = EXCLUDED.expires_at,
attempts = 0,
invite_code = EXCLUDED.invite_code,
created_at = EXCLUDED.created_at`,
[email, otp, expiresAt, 0, inviteCode || null, Date.now()]
);
// In production, send actual email
console.log(`📧 OTP for ${email}: ${otp} (expires in 5 minutes)`);
@@ -705,56 +727,59 @@ exports.verifyOTP = onRequest({cors: true}, async (req, res) => {
}
try {
// Retrieve OTP document
const otpDoc = await db.collection("otps").doc(email).get();
// Retrieve OTP from PostgreSQL
const otpRows = await executeQuery(
'SELECT * FROM otps WHERE email = $1',
[email]
);
if (!otpDoc.exists) {
if (otpRows.length === 0) {
return res.status(400).json({ error: "Invalid verification code" });
}
const otpData = otpDoc.data();
const otpData = otpRows[0];
// Check if OTP is expired
if (Date.now() > otpData.expiresAt) {
await otpDoc.ref.delete();
if (Date.now() > otpData.expires_at) {
await executeQuery('DELETE FROM otps WHERE email = $1', [email]);
return res.status(400).json({ error: "Verification code has expired" });
}
// Check if too many attempts
if (otpData.attempts >= 5) {
await otpDoc.ref.delete();
await executeQuery('DELETE FROM otps WHERE email = $1', [email]);
return res.status(400).json({ error: "Too many failed attempts" });
}
// Verify OTP
if (otpData.otp !== otp) {
await otpDoc.ref.update({
attempts: (otpData.attempts || 0) + 1,
});
await executeQuery(
'UPDATE otps SET attempts = $1 WHERE email = $2',
[(otpData.attempts || 0) + 1, email]
);
return res.status(400).json({ error: "Invalid verification code" });
}
// OTP is valid - clean up and create/find user
await otpDoc.ref.delete();
await executeQuery('DELETE FROM otps WHERE email = $1', [email]);
// Generate a unique user ID for this email if it doesn't exist
let userId;
let userDoc;
let userExists = false;
// Check if user already exists by email
const existingUserQuery = await db.collection("users")
.where("email", "==", email)
.limit(1)
.get();
const existingUserRows = await executeQuery(
'SELECT id FROM users WHERE email = $1 LIMIT 1',
[email]
);
if (!existingUserQuery.empty) {
if (existingUserRows.length > 0) {
// User exists, get their ID
userDoc = existingUserQuery.docs[0];
userId = userDoc.id;
userId = existingUserRows[0].id;
userExists = true;
} else {
// Create new user
userId = `user_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
userDoc = null;
}
// Prepare user object for response
@@ -765,53 +790,52 @@ exports.verifyOTP = onRequest({cors: true}, async (req, res) => {
emailVerified: true,
};
// Create or update user document in Firestore
const userRef = db.collection("users").doc(userId);
const userData = {
id: userId,
email: email,
displayName: email.split("@")[0],
emailVerified: true,
lastLoginAt: Date.now(),
};
if (!userDoc) {
// Create or update user document in PostgreSQL
const currentTime = Date.now();
if (!userExists) {
// Create new user document
userData.createdAt = Date.now();
await userRef.set(userData);
await executeQuery(
`INSERT INTO users (id, email, display_name, email_verified, created_at, last_login_at)
VALUES ($1, $2, $3, $4, $5, $6)`,
[userId, email, email.split("@")[0], true, currentTime, currentTime]
);
} else {
// Update existing user with latest login info
await userRef.update({
lastLoginAt: Date.now(),
});
await executeQuery(
'UPDATE users SET last_login_at = $1 WHERE id = $2',
[currentTime, userId]
);
}
// Generate a simple session token (in production, use proper JWT)
const customToken = `session_${userId}_${Date.now()}`;
// Store auth token in Firestore for validation
await db.collection("authTokens").doc(customToken).set({
userId: userId,
createdAt: Date.now(),
expiresAt: Date.now() + (30 * 24 * 60 * 60 * 1000), // 30 days
lastUsedAt: Date.now(),
isActive: true
});
// Store auth token in PostgreSQL for validation
await executeQuery(
`INSERT INTO auth_tokens (token, user_id, created_at, expires_at, last_used_at, is_active)
VALUES ($1, $2, $3, $4, $5, $6)`,
[
customToken,
userId,
currentTime,
currentTime + (30 * 24 * 60 * 60 * 1000), // 30 days
currentTime,
true
]
);
// Handle invitation if present
let inviteData = null;
if (otpData.inviteCode) {
if (otpData.invite_code) {
try {
const inviteDoc = await db
.collectionGroup("invites")
.where("code", "==", otpData.inviteCode)
.where("status", "==", "pending")
.limit(1)
.get();
const inviteRows = await executeQuery(
'SELECT * FROM invites WHERE code = $1 AND status = $2 LIMIT 1',
[otpData.invite_code, 'pending']
);
if (!inviteDoc.empty) {
inviteData = inviteDoc.docs[0].data();
if (inviteRows.length > 0) {
inviteData = inviteRows[0];
}
} catch (error) {
console.error("Error fetching invite:", error);
@@ -833,9 +857,8 @@ exports.verifyOTP = onRequest({cors: true}, async (req, res) => {
//region Create Invitation
exports.createInvitation = onRequest({cors: true}, async (req, res) => {
if (req.method === 'OPTIONS') {
res.status(204).send('');
const authContext = await validateAuthAndGetContext(req, res);
if (!authContext) {
return;
}
@@ -844,8 +867,6 @@ exports.createInvitation = onRequest({cors: true}, async (req, res) => {
}
try {
// Validate auth token and get user context
const authContext = await validateAuthAndGetContext(req, res);
const { name, email, role = "employee", department } = req.body;
if (!email || !name) {
@@ -925,7 +946,7 @@ exports.createInvitation = onRequest({cors: true}, async (req, res) => {
},
},
],
from: { email: 'no-reply@auditly.com', name: 'Auditly' },
from: { email: 'no-reply@orbitly.com', name: 'Orbitly' },
template_id: process.env.SENDGRID_TEMPLATE_ID,
}),
});
@@ -1196,6 +1217,9 @@ exports.submitEmployeeAnswers = onRequest({cors: true}, async (req, res) => {
} else {
// Authenticated submission
const authContext = await validateAuthAndGetContext(req, res);
if (!authContext) {
return;
}
if (!employeeId || !answers) {
return res.status(400).json({ error: "Employee ID and answers are required for authenticated submissions" });
@@ -1391,9 +1415,8 @@ Be thorough, professional, and focus on actionable insights.
//region Generate Employee Report
exports.generateEmployeeReport = onRequest({cors: true}, async (req, res) => {
if (req.method === 'OPTIONS') {
res.status(204).send('');
const authContext = await validateAuthAndGetContext(req, res);
if (!authContext) {
return;
}
@@ -1511,8 +1534,8 @@ Be thorough, professional, and focus on actionable insights.
//region Generate Company Report
exports.generateCompanyReport = onRequest({cors: true}, async (req, res) => {
if (req.method === 'OPTIONS') {
res.status(204).send('');
const authContext = await validateAuthAndGetContext(req, res);
if (!authContext) {
return;
}
@@ -1520,8 +1543,6 @@ exports.generateCompanyReport = onRequest({cors: true}, async (req, res) => {
return res.status(405).json({ error: "Method not allowed" });
}
const authContext = await validateAuthAndGetContext(req, res);
const orgId = authContext.orgId;
if (!orgId) {
return res.status(400).json({ error: "User has no associated organizations" });
@@ -1646,9 +1667,8 @@ Be thorough, professional, and focus on actionable insights.`;
//region Chat
exports.chat = onRequest({cors: true}, async (req, res) => {
if (req.method === 'OPTIONS') {
res.status(204).send('');
const authContext = await validateAuthAndGetContext(req, res);
if (!authContext) {
return;
}
@@ -1739,8 +1759,8 @@ Instructions:
//region Create Organization
exports.createOrganization = onRequest({cors: true}, async (req, res) => {
if (req.method === 'OPTIONS') {
res.status(204).send('');
const authContext = await validateAuthAndGetContext(req, res);
if (!authContext) {
return;
}
@@ -1749,90 +1769,63 @@ exports.createOrganization = onRequest({cors: true}, async (req, res) => {
}
try {
// Validate auth token and get user context
const authContext = await validateAuthAndGetContext(req, res);
const { name } = req.body;
if (!name) {
return res.status(400).json({ error: "Organization name is required" });
}
// Generate unique organization ID
const orgId = `org_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
const currentTime = Date.now();
// Create comprehensive organization document
const orgData = {
name,
createdAt: Date.now(),
updatedAt: Date.now(),
onboardingCompleted: false,
ownerId: authContext.userId,
// Subscription fields (will be populated after Stripe setup)
subscription: {
status: 'trial', // trial, active, past_due, canceled
stripeCustomerId: null,
stripeSubscriptionId: null,
currentPeriodStart: null,
currentPeriodEnd: null,
trialEnd: Date.now() + (14 * 24 * 60 * 60 * 1000), // 14 day trial
},
// Usage tracking
usage: {
employeeCount: 0,
reportsGenerated: 0,
lastReportGeneration: null,
},
// Organization settings
settings: {
allowedEmployeeCount: 50, // Default limit
featuresEnabled: {
aiReports: true,
chat: true,
analytics: true,
}
await executeTransaction(async (client) => {
// Create comprehensive organization document
if (process.env.USE_NEON_SERVERLESS === 'true') {
await client(
`INSERT INTO organizations (
id, name, owner_id, onboarding_completed,
subscription_status, trial_end, employee_count, reports_generated,
allowed_employee_count, features_enabled, created_at, updated_at
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)`,
[
orgId, name, authContext.userId, false,
'trial', currentTime + (14 * 24 * 60 * 60 * 1000), // 14 day trial
0, 0, 50,
JSON.stringify({"aiReports": true, "chat": true, "analytics": true}),
currentTime, currentTime
]
);
// Add organization to user's organizations (for multi-org support)
await client(
`INSERT INTO user_organizations (user_id, organization_id, role, onboarding_completed, joined_at)
VALUES ($1, $2, $3, $4, $5)`,
[authContext.userId, orgId, 'owner', false, currentTime]
);
} else {
await client.query(
`INSERT INTO organizations (
id, name, owner_id, onboarding_completed,
subscription_status, trial_end, employee_count, reports_generated,
allowed_employee_count, features_enabled, created_at, updated_at
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)`,
[
orgId, name, authContext.userId, false,
'trial', currentTime + (14 * 24 * 60 * 60 * 1000), // 14 day trial
0, 0, 50,
JSON.stringify({"aiReports": true, "chat": true, "analytics": true}),
currentTime, currentTime
]
);
// Add organization to user's organizations (for multi-org support)
await client.query(
`INSERT INTO user_organizations (user_id, organization_id, role, onboarding_completed, joined_at)
VALUES ($1, $2, $3, $4, $5)`,
[authContext.userId, orgId, 'owner', false, currentTime]
);
}
};
const orgRef = db.collection("orgs").doc(orgId);
await orgRef.set(orgData);
// Get user information from Firestore (since we don't use Firebase Auth)
const userRef = db.collection("users").doc(authContext.userId);
const userDoc = await userRef.get();
if (!userDoc.exists) {
console.error("User document not found:", authContext.userId);
return res.status(400).json({ error: "User not found" });
}
const userData = userDoc.data();
// Add owner info to organization document (owners are NOT employees)
const ownerInfo = {
id: authContext.userId,
name: userData.displayName || userData.email.split("@")[0],
email: userData.email,
joinedAt: Date.now()
};
// Update org document with owner info
await orgRef.update({
ownerInfo: ownerInfo,
updatedAt: Date.now()
});
// Add organization to user's organizations (for multi-org support)
const userOrgRef = db.collection("users").doc(authContext.userId).collection("organizations").doc(orgId);
await userOrgRef.set({
orgId,
name,
role: "owner",
onboardingCompleted: false,
joinedAt: Date.now(),
});
// Update user document with latest activity
await userRef.update({
lastLoginAt: Date.now(),
});
res.json({
@@ -1841,9 +1834,12 @@ exports.createOrganization = onRequest({cors: true}, async (req, res) => {
name,
role: "owner",
onboardingCompleted: false,
joinedAt: Date.now(),
subscription: orgData.subscription,
requiresSubscription: true, // Signal frontend to show subscription flow
joinedAt: currentTime,
subscription: {
status: 'trial',
trialEnd: currentTime + (14 * 24 * 60 * 60 * 1000)
},
requiresSubscription: true,
});
} catch (error) {
console.error("Create organization error:", error);
@@ -1853,17 +1849,9 @@ exports.createOrganization = onRequest({cors: true}, async (req, res) => {
//endregion Create Organization
//region Get Organizations
exports.getUserOrganizations = onRequest(async (req, res) => {
let authContext;
try {
authContext = await validateAuthAndGetContext(req, res);
} catch (error) {
logger.debug("Auth validation failed:", error);
return;
}
if (req.method === 'OPTIONS') {
res.status(204).send('');
exports.getUserOrganizations = onRequest({cors: true}, async (req, res) => {
const authContext = await validateAuthAndGetContext(req, res);
if (!authContext) {
return;
}
@@ -1872,8 +1860,6 @@ exports.getUserOrganizations = onRequest(async (req, res) => {
}
try {
// Validate auth token and get user context
// Get user's organizations
const userOrgsSnapshot = await db
.collection("users")
@@ -1906,8 +1892,8 @@ exports.getUserOrganizations = onRequest(async (req, res) => {
//region Join Organization
exports.joinOrganization = onRequest({cors: true}, async (req, res) => {
if (req.method === 'OPTIONS') {
res.status(204).send('');
const authContext = await validateAuthAndGetContext(req, res);
if (!authContext) {
return;
}
@@ -1916,8 +1902,6 @@ exports.joinOrganization = onRequest({cors: true}, async (req, res) => {
}
try {
// Validate auth token and get user context
const authContext = await validateAuthAndGetContext(req, res);
const { inviteCode } = req.body;
if (!inviteCode) {
@@ -2167,7 +2151,7 @@ exports.joinOrganization = onRequest({cors: true}, async (req, res) => {
// res.status(500).json({ error: 'Webhook handler failed' });
// }
// });
//endregion Stripe Webhook
//#endregion Stripe Webhook
//region Get Sub Status
// exports.getSubscriptionStatus = onRequest(async (req, res) => {
@@ -2228,7 +2212,7 @@ exports.joinOrganization = onRequest({cors: true}, async (req, res) => {
// res.status(500).json({ error: "Failed to get subscription status" });
// }
// });
//endregion Get Sub Status
//#endregion Get Sub Status
//region Save Company Report
// exports.saveCompanyReport = onRequest(async (req, res) => {
@@ -2272,12 +2256,12 @@ exports.joinOrganization = onRequest({cors: true}, async (req, res) => {
// res.status(500).json({ error: "Failed to save company report" });
// }
// });
//endregion Save Company Report
//#endregion Save Company Report
//region Get Org Data
exports.getOrgData = onRequest({cors: true}, async (req, res) => {
if (req.method === 'OPTIONS') {
res.status(204).send('');
const authContext = await validateAuthAndGetContext(req, res);
if (!authContext) {
return;
}
@@ -2286,21 +2270,34 @@ exports.getOrgData = onRequest({cors: true}, async (req, res) => {
}
try {
// Validate auth token and get user context
const authContext = await validateAuthAndGetContext(req, res);
const orgId = authContext.orgId;
if (!orgId) {
return res.status(400).json({ error: "User has no associated organizations" });
}
// Get organization data
const orgDoc = await db.collection("orgs").doc(orgId).get();
if (!orgDoc.exists) {
const orgRows = await executeQuery(
'SELECT * FROM organizations WHERE id = $1',
[orgId]
);
if (orgRows.length === 0) {
return res.status(404).json({ error: "Organization not found" });
}
const orgData = { id: orgId, ...orgDoc.data() };
const orgData = {
id: orgRows[0].id,
name: orgRows[0].name,
industry: orgRows[0].industry,
description: orgRows[0].description,
mission: orgRows[0].mission,
vision: orgRows[0].vision,
values: orgRows[0].values,
onboardingCompleted: orgRows[0].onboarding_completed,
onboardingData: orgRows[0].onboarding_data,
createdAt: orgRows[0].created_at,
updatedAt: orgRows[0].updated_at
};
res.json({
success: true,
@@ -2308,10 +2305,6 @@ exports.getOrgData = onRequest({cors: true}, async (req, res) => {
});
} catch (error) {
console.error("Get org data error:", error);
if (error.message.includes('Missing or invalid authorization') ||
error.message.includes('Token')) {
return res.status(401).json({ error: error.message });
}
res.status(500).json({ error: "Failed to get organization data" });
}
});
@@ -2319,8 +2312,8 @@ exports.getOrgData = onRequest({cors: true}, async (req, res) => {
//region Update Organization Data
exports.updateOrgData = onRequest({cors: true}, async (req, res) => {
if (req.method === 'OPTIONS') {
res.status(204).send('');
const authContext = await validateAuthAndGetContext(req, res);
if (!authContext) {
return;
}
@@ -2329,8 +2322,6 @@ exports.updateOrgData = onRequest({cors: true}, async (req, res) => {
}
try {
// Validate auth token and get user context
const authContext = await validateAuthAndGetContext(req, res);
const { data } = req.body;
if (!data) {
@@ -2366,8 +2357,8 @@ exports.updateOrgData = onRequest({cors: true}, async (req, res) => {
//region Get Employees
exports.getEmployees = onRequest({cors: true}, async (req, res) => {
if (req.method === 'OPTIONS') {
res.status(204).send('');
const authContext = await validateAuthAndGetContext(req, res);
if (!authContext) {
return;
}
@@ -2376,9 +2367,6 @@ exports.getEmployees = onRequest({cors: true}, async (req, res) => {
}
try {
// Validate auth token and get user context
const authContext = await validateAuthAndGetContext(req, res);
const orgId = authContext.orgId;
if (!orgId) {
return res.status(400).json({ error: "User has no associated organizations" });
@@ -2413,8 +2401,8 @@ exports.getEmployees = onRequest({cors: true}, async (req, res) => {
//region Get Submissions
exports.getSubmissions = onRequest({cors: true}, async (req, res) => {
if (req.method === 'OPTIONS') {
res.status(204).send('');
const authContext = await validateAuthAndGetContext(req, res);
if (!authContext) {
return;
}
@@ -2423,9 +2411,6 @@ exports.getSubmissions = onRequest({cors: true}, async (req, res) => {
}
try {
// Validate auth token and get user context
const authContext = await validateAuthAndGetContext(req, res);
const orgId = authContext.orgId;
if (!orgId) {
return res.status(400).json({ error: "User has no associated organizations" });
@@ -2456,8 +2441,8 @@ exports.getSubmissions = onRequest({cors: true}, async (req, res) => {
//region Get Reports
exports.getReports = onRequest({cors: true}, async (req, res) => {
if (req.method === 'OPTIONS') {
res.status(204).send('');
const authContext = await validateAuthAndGetContext(req, res);
if (!authContext) {
return;
}
@@ -2466,9 +2451,6 @@ exports.getReports = onRequest({cors: true}, async (req, res) => {
}
try {
// Validate auth token and get user context
const authContext = await validateAuthAndGetContext(req, res);
const orgId = authContext.orgId;
if (!orgId) {
return res.status(400).json({ error: "User has no associated organizations" });
@@ -2552,8 +2534,8 @@ exports.getReports = onRequest({cors: true}, async (req, res) => {
//region Save Report
exports.saveReport = onRequest({cors: true}, async (req, res) => {
if (req.method === 'OPTIONS') {
res.status(204).send('');
const authContext = await validateAuthAndGetContext(req, res);
if (!authContext) {
return;
}
@@ -2568,12 +2550,6 @@ exports.saveReport = onRequest({cors: true}, async (req, res) => {
}
try {
// Verify user authorization
const isAuthorized = await verifyUserAuthorization(userId, orgId);
if (!isAuthorized) {
return res.status(403).json({ error: "Unauthorized access to organization" });
}
// Add metadata
const currentTime = Date.now();
if (!reportData.id) {
@@ -2603,8 +2579,8 @@ exports.saveReport = onRequest({cors: true}, async (req, res) => {
//region Get Company Reports
exports.getCompanyReports = onRequest({cors: true}, async (req, res) => {
if (req.method === 'OPTIONS') {
res.status(204).send('');
const authContext = await validateAuthAndGetContext(req, res);
if (!authContext) {
return;
}
@@ -2613,8 +2589,6 @@ exports.getCompanyReports = onRequest({cors: true}, async (req, res) => {
}
try {
const authContext = await validateAuthAndGetContext(req, res);
const orgId = authContext.orgId;
if (!orgId) {
return res.status(400).json({ error: "User has no associated organizations" });
@@ -2646,8 +2620,8 @@ exports.getCompanyReports = onRequest({cors: true}, async (req, res) => {
//region Upload Image
exports.uploadImage = onRequest({cors: true}, async (req, res) => {
if (req.method === 'OPTIONS') {
res.status(204).send('');
const authContext = await validateAuthAndGetContext(req, res);
if (!authContext) {
return;
}
@@ -2655,19 +2629,14 @@ exports.uploadImage = onRequest({cors: true}, async (req, res) => {
return res.status(405).json({ error: "Method not allowed" });
}
const { orgId, userId, imageData } = req.body;
const { orgId, userId } = authContext;
const { imageData } = req.body;
if (!orgId || !userId || !imageData) {
return res.status(400).json({ error: "Organization ID, user ID, and image data are required" });
}
try {
// Verify user authorization
const isAuthorized = await verifyUserAuthorization(userId, orgId);
if (!isAuthorized) {
return res.status(403).json({ error: "Unauthorized access to organization" });
}
// Validate image data
const { collectionName, documentId, dataUrl, filename, originalSize, compressedSize, width, height } = imageData;
@@ -2764,7 +2733,7 @@ exports.uploadImage = onRequest({cors: true}, async (req, res) => {
// res.status(500).json({ error: "Failed to get image" });
// }
// });
//endregion Get Image
//#endregion Get Image
//region Delete Image
// exports.deleteImage = onRequest(async (req, res) => {
@@ -2809,4 +2778,4 @@ exports.uploadImage = onRequest({cors: true}, async (req, res) => {
// res.status(500).json({ error: "Failed to delete image" });
// }
// });
//endregion Delete Image
//#endregion Delete Image