This commit is contained in:
Ra
2025-08-24 00:48:41 -07:00
parent 9c20073755
commit f2145edf56
37 changed files with 2621 additions and 2692 deletions

View File

@@ -29,144 +29,114 @@ const RESPONSE_FORMAT = {
type: "object",
additionalProperties: false,
properties: {
report: {
companyPerformance: {
type: "object",
additionalProperties: false,
properties: {
companyPerformance: {
type: "object",
additionalProperties: false,
properties: {
summary: { type: "string" },
metrics: {
type: "array",
items: {
type: "object",
additionalProperties: false,
properties: {
name: { type: "string" },
value: { anyOf: [{ type: "string" }, { type: "number" }] },
trend: { enum: ["up", "down", "flat"] }
},
required: ["name", "value", "trend"]
}
}
},
required: ["summary", "metrics"]
},
keyPersonnelChanges: {
summary: { type: "string" },
metrics: {
type: "array",
items: {
type: "object",
additionalProperties: false,
properties: {
person: { type: "string" },
change: { type: "string" }, // e.g. "Promoted to VP Eng"
impact: { type: "string" },
effectiveDate: { type: "string" }
name: { type: "string" },
value: { anyOf: [{ type: "string" }, { type: "number" }] },
trend: { enum: ["up", "down", "flat"] }
},
required: ["person", "change", "impact", "effectiveDate"]
}
},
immediateHiringNeeds: {
type: "array",
items: {
type: "object",
additionalProperties: false,
properties: {
role: { type: "string" },
urgency: { enum: ["low", "medium", "high"] },
reason: { type: "string" }
},
required: ["role", "urgency", "reason"]
}
},
forwardOperatingPlan: {
type: "object",
additionalProperties: false,
properties: {
nextQuarterObjectives: { type: "array", items: { type: "string" } },
initiatives: {
type: "array",
items: {
type: "object",
additionalProperties: false,
properties: {
name: { type: "string" },
owner: { type: "string" },
kpis: { type: "array", items: { type: "string" } }
},
required: ["name", "owner", "kpis"]
}
},
risks: {
type: "array",
items: {
type: "object",
additionalProperties: false,
properties: {
risk: { type: "string" },
mitigation: { type: "string" }
},
required: ["risk", "mitigation"]
}
}
},
required: ["nextQuarterObjectives", "initiatives", "risks"]
},
organizationalInsights: {
type: "object",
additionalProperties: false,
properties: {
culture: { type: "string" },
teamDynamics: { type: "string" },
blockers: { type: "array", items: { type: "string" } }
},
required: ["culture", "teamDynamics", "blockers"]
},
strengths: { type: "array", items: { type: "string" } },
gradingOverview: {
type: "array",
items: {
type: "object",
additionalProperties: false,
properties: {
department: { type: "string" },
grade: { enum: ["A", "B", "C", "D", "F"] },
notes: { type: "string" }
},
required: ["department", "grade", "notes"]
required: ["name", "value", "trend"]
}
}
},
required: ["companyPerformance", "keyPersonnelChanges", "immediateHiringNeeds", "forwardOperatingPlan", "organizationalInsights", "strengths", "gradingOverview"]
required: ["summary", "metrics"]
},
wiki: {
keyPersonnelChanges: {
type: "array",
items: {
type: "object",
additionalProperties: false,
properties: {
person: { type: "string" },
change: { type: "string" }, // e.g. "Promoted to VP Eng"
impact: { type: "string" },
effectiveDate: { type: "string" }
},
required: ["person", "change", "impact", "effectiveDate"]
}
},
immediateHiringNeeds: {
type: "array",
items: {
type: "object",
additionalProperties: false,
properties: {
role: { type: "string" },
urgency: { enum: ["low", "medium", "high"] },
reason: { type: "string" }
},
required: ["role", "urgency", "reason"]
}
},
forwardOperatingPlan: {
type: "object",
additionalProperties: false,
properties: {
companyName: { type: "string" },
industry: { type: "string" },
description: { type: "string" },
mission: { type: "string" },
values: { type: "array", items: { type: "string" } },
culture: { type: "string" },
orgInfo: {
type: "object",
additionalProperties: false,
properties: {
hq: { type: "string" },
foundedYear: { type: "number" },
headcount: { type: "number" },
products: { type: "array", items: { type: "string" } }
},
required: ["hq", "foundedYear", "headcount", "products"]
nextQuarterObjectives: { type: "array", items: { type: "string" } },
initiatives: {
type: "array",
items: {
type: "object",
additionalProperties: false,
properties: {
name: { type: "string" },
owner: { type: "string" },
kpis: { type: "array", items: { type: "string" } }
},
required: ["name", "owner", "kpis"]
}
},
risks: {
type: "array",
items: {
type: "object",
additionalProperties: false,
properties: {
risk: { type: "string" },
mitigation: { type: "string" }
},
required: ["risk", "mitigation"]
}
}
},
required: ["companyName", "industry", "description", "mission", "values", "culture", "orgInfo"]
required: ["nextQuarterObjectives", "initiatives", "risks"]
},
organizationalInsights: {
type: "object",
additionalProperties: false,
properties: {
culture: { type: "string" },
teamDynamics: { type: "string" },
blockers: { type: "array", items: { type: "string" } }
},
required: ["culture", "teamDynamics", "blockers"]
},
strengths: { type: "array", items: { type: "string" } },
gradingOverview: {
type: "array",
items: {
type: "object",
additionalProperties: false,
properties: {
department: { type: "string" },
grade: { enum: ["A", "B", "C", "D", "F"] },
notes: { type: "string" }
},
required: ["department", "grade", "notes"]
}
}
},
required: ["report", "wiki"]
required: ["companyPerformance", "keyPersonnelChanges", "immediateHiringNeeds", "forwardOperatingPlan", "organizationalInsights", "strengths", "gradingOverview"]
}
}
};
@@ -782,6 +752,7 @@ exports.generateCompanyWiki = onRequest({ cors: true }, async (req, res) => {
if (openai) {
// Use OpenAI to generate the company report and wiki
db.collection("orgs").doc(orgId)
const system = "You are a cut-and-dry expert business analyst. Return ONLY JSON that conforms to the provided schema.";
const user = [
"Generate a COMPANY REPORT and COMPANY WIKI that fully leverage the input data.",
@@ -811,14 +782,20 @@ exports.generateCompanyWiki = onRequest({ cors: true }, async (req, res) => {
const report = {
generatedAt: Date.now(),
...parsed.report
...parsed
};
const wiki = {
companyName: org?.name ?? parsed.wiki.companyName,
generatedAt: Date.now(),
...parsed.wiki,
};
const companyReport = db.collection("orgs").doc(orgId).collection("companyReport");
await companyReport.set(report);
const companyWiki = db.collection("orgs").doc(orgId).collection("companyWiki");
await companyWiki.set(wiki);
console.log(report);
console.log(wiki);
@@ -1691,4 +1668,539 @@ exports.saveCompanyReport = onRequest({ cors: true }, async (req, res) => {
console.error("Save company report error:", error);
res.status(500).json({ error: "Failed to save company report" });
}
});
// Helper function to verify user authorization
const verifyUserAuthorization = async (userId, orgId) => {
if (!userId || !orgId) {
return false;
}
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;
} catch (error) {
console.error("Authorization check error:", error);
return false;
}
};
// Get Organization Data Function
exports.getOrgData = onRequest({ cors: true }, async (req, res) => {
if (req.method === 'OPTIONS') {
res.status(204).send('');
return;
}
if (req.method !== "GET") {
return res.status(405).json({ error: "Method not allowed" });
}
const { orgId, userId } = req.query;
if (!orgId || !userId) {
return res.status(400).json({ error: "Organization ID and user ID are required" });
}
try {
// Verify user authorization
const isAuthorized = await verifyUserAuthorization(userId, orgId);
if (!isAuthorized) {
return res.status(403).json({ error: "Unauthorized access to organization" });
}
// Get organization data
const orgDoc = await db.collection("orgs").doc(orgId).get();
if (!orgDoc.exists) {
return res.status(404).json({ error: "Organization not found" });
}
const orgData = { id: orgId, ...orgDoc.data() };
res.json({
success: true,
org: orgData
});
} catch (error) {
console.error("Get org data error:", error);
res.status(500).json({ error: "Failed to get organization data" });
}
});
// Update Organization Data Function
exports.updateOrgData = onRequest({ cors: true }, async (req, res) => {
if (req.method === 'OPTIONS') {
res.status(204).send('');
return;
}
if (req.method !== "PUT") {
return res.status(405).json({ error: "Method not allowed" });
}
const { orgId, userId, data } = req.body;
if (!orgId || !userId || !data) {
return res.status(400).json({ error: "Organization ID, user ID, and 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" });
}
// Update organization data
const orgRef = db.collection("orgs").doc(orgId);
await orgRef.update({
...data,
updatedAt: Date.now()
});
res.json({
success: true,
message: "Organization data updated successfully"
});
} catch (error) {
console.error("Update org data error:", error);
res.status(500).json({ error: "Failed to update organization data" });
}
});
// Get Employees Function
exports.getEmployees = onRequest({ cors: true }, async (req, res) => {
if (req.method === 'OPTIONS') {
res.status(204).send('');
return;
}
if (req.method !== "GET") {
return res.status(405).json({ error: "Method not allowed" });
}
const { orgId, userId } = req.query;
if (!orgId || !userId) {
return res.status(400).json({ error: "Organization ID and user ID are required" });
}
try {
// Verify user authorization
const isAuthorized = await verifyUserAuthorization(userId, orgId);
if (!isAuthorized) {
return res.status(403).json({ error: "Unauthorized access to organization" });
}
// Get all employees
const employeesSnapshot = await db.collection("orgs").doc(orgId).collection("employees").get();
const employees = [];
employeesSnapshot.forEach(doc => {
employees.push({ id: doc.id, ...doc.data() });
});
res.json({
success: true,
employees
});
} catch (error) {
console.error("Get employees error:", error);
res.status(500).json({ error: "Failed to get employees" });
}
});
// Get Submissions Function
exports.getSubmissions = onRequest({ cors: true }, async (req, res) => {
if (req.method === 'OPTIONS') {
res.status(204).send('');
return;
}
if (req.method !== "GET") {
return res.status(405).json({ error: "Method not allowed" });
}
const { orgId, userId } = req.query;
if (!orgId || !userId) {
return res.status(400).json({ error: "Organization ID and user ID are required" });
}
try {
// Verify user authorization
const isAuthorized = await verifyUserAuthorization(userId, orgId);
if (!isAuthorized) {
return res.status(403).json({ error: "Unauthorized access to organization" });
}
// Get all submissions
const submissionsSnapshot = await db.collection("orgs").doc(orgId).collection("submissions").get();
const submissions = {};
submissionsSnapshot.forEach(doc => {
submissions[doc.id] = { id: doc.id, ...doc.data() };
});
res.json({
success: true,
submissions
});
} catch (error) {
console.error("Get submissions error:", error);
res.status(500).json({ error: "Failed to get submissions" });
}
});
// Get Reports Function
exports.getReports = onRequest({ cors: true }, async (req, res) => {
if (req.method === 'OPTIONS') {
res.status(204).send('');
return;
}
if (req.method !== "GET") {
return res.status(405).json({ error: "Method not allowed" });
}
const { orgId, userId } = req.query;
if (!orgId || !userId) {
return res.status(400).json({ error: "Organization ID and user ID are required" });
}
try {
// Verify user authorization
const isAuthorized = await verifyUserAuthorization(userId, orgId);
if (!isAuthorized) {
return res.status(403).json({ error: "Unauthorized access to organization" });
}
// Get all reports
const reportsSnapshot = await db.collection("orgs").doc(orgId).collection("reports").get();
const reports = {};
reportsSnapshot.forEach(doc => {
reports[doc.id] = { id: doc.id, ...doc.data() };
});
res.json({
success: true,
reports
});
} catch (error) {
console.error("Get reports error:", error);
res.status(500).json({ error: "Failed to get reports" });
}
});
// Create/Update Employee Function
exports.upsertEmployee = onRequest({ cors: true }, async (req, res) => {
if (req.method === 'OPTIONS') {
res.status(204).send('');
return;
}
if (req.method !== "POST") {
return res.status(405).json({ error: "Method not allowed" });
}
const { orgId, userId, employeeData } = req.body;
if (!orgId || !userId || !employeeData) {
return res.status(400).json({ error: "Organization ID, user ID, and employee 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" });
}
// Generate employee ID if not provided
if (!employeeData.id) {
employeeData.id = `emp_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
// Add timestamps
const currentTime = Date.now();
if (!employeeData.createdAt) {
employeeData.createdAt = currentTime;
}
employeeData.updatedAt = currentTime;
// Save employee
const employeeRef = db.collection("orgs").doc(orgId).collection("employees").doc(employeeData.id);
await employeeRef.set(employeeData);
res.json({
success: true,
employee: employeeData,
message: "Employee saved successfully"
});
} catch (error) {
console.error("Upsert employee error:", error);
res.status(500).json({ error: "Failed to save employee" });
}
});
// Save Report Function
exports.saveReport = onRequest({ cors: true }, async (req, res) => {
if (req.method === 'OPTIONS') {
res.status(204).send('');
return;
}
if (req.method !== "POST") {
return res.status(405).json({ error: "Method not allowed" });
}
const { orgId, userId, employeeId, reportData } = req.body;
if (!orgId || !userId || !employeeId || !reportData) {
return res.status(400).json({ error: "Organization ID, user ID, employee ID, and report 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" });
}
// Add metadata
const currentTime = Date.now();
if (!reportData.id) {
reportData.id = `report_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
if (!reportData.createdAt) {
reportData.createdAt = currentTime;
}
reportData.updatedAt = currentTime;
reportData.employeeId = employeeId;
// Save report
const reportRef = db.collection("orgs").doc(orgId).collection("reports").doc(employeeId);
await reportRef.set(reportData);
res.json({
success: true,
report: reportData,
message: "Report saved successfully"
});
} catch (error) {
console.error("Save report error:", error);
res.status(500).json({ error: "Failed to save report" });
}
});
// Get Company Reports Function
exports.getCompanyReports = onRequest({ cors: true }, async (req, res) => {
if (req.method === 'OPTIONS') {
res.status(204).send('');
return;
}
if (req.method !== "GET") {
return res.status(405).json({ error: "Method not allowed" });
}
const { orgId, userId } = req.query;
if (!orgId || !userId) {
return res.status(400).json({ error: "Organization ID and user ID are required" });
}
try {
// Verify user authorization
const isAuthorized = await verifyUserAuthorization(userId, orgId);
if (!isAuthorized) {
return res.status(403).json({ error: "Unauthorized access to organization" });
}
// Get all company reports
const reportsSnapshot = await db.collection("orgs").doc(orgId).collection("fullCompanyReports").get();
const reports = [];
reportsSnapshot.forEach(doc => {
reports.push({ id: doc.id, ...doc.data() });
});
// Sort by creation date (newest first)
reports.sort((a, b) => (b.createdAt || 0) - (a.createdAt || 0));
res.json({
success: true,
reports
});
} catch (error) {
console.error("Get company reports error:", error);
res.status(500).json({ error: "Failed to get company reports" });
}
});
// Upload Image Function
exports.uploadImage = onRequest({ cors: true }, async (req, res) => {
if (req.method === 'OPTIONS') {
res.status(204).send('');
return;
}
if (req.method !== "POST") {
return res.status(405).json({ error: "Method not allowed" });
}
const { orgId, userId, 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;
if (!collectionName || !documentId || !dataUrl || !filename) {
return res.status(400).json({ error: "Missing required image data fields" });
}
// Create image document
const imageDoc = {
id: `${Date.now()}_${filename}`,
dataUrl,
filename,
originalSize: originalSize || 0,
compressedSize: compressedSize || 0,
uploadedAt: Date.now(),
width: width || 0,
height: height || 0,
orgId,
uploadedBy: userId
};
// Store image in organization's images collection
const imageRef = db.collection("orgs").doc(orgId).collection("images").doc(`${collectionName}_${documentId}`);
await imageRef.set({
...imageDoc,
collectionName,
documentId
});
res.json({
success: true,
imageId: imageDoc.id,
message: "Image uploaded successfully"
});
} catch (error) {
console.error("Upload image error:", error);
res.status(500).json({ error: "Failed to upload image" });
}
});
// Get Image Function
exports.getImage = onRequest({ cors: true }, async (req, res) => {
if (req.method === 'OPTIONS') {
res.status(204).send('');
return;
}
if (req.method !== "GET") {
return res.status(405).json({ error: "Method not allowed" });
}
const { orgId, userId, collectionName, documentId } = req.query;
if (!orgId || !userId || !collectionName || !documentId) {
return res.status(400).json({ error: "Organization ID, user ID, collection name, and document ID are required" });
}
try {
// Verify user authorization
const isAuthorized = await verifyUserAuthorization(userId, orgId);
if (!isAuthorized) {
return res.status(403).json({ error: "Unauthorized access to organization" });
}
// Get image document
const imageRef = db.collection("orgs").doc(orgId).collection("images").doc(`${collectionName}_${documentId}`);
const imageDoc = await imageRef.get();
if (!imageDoc.exists) {
return res.status(404).json({ error: "Image not found" });
}
const imageData = imageDoc.data();
// Return image data (excluding org-specific metadata)
const responseData = {
id: imageData.id,
dataUrl: imageData.dataUrl,
filename: imageData.filename,
originalSize: imageData.originalSize,
compressedSize: imageData.compressedSize,
uploadedAt: imageData.uploadedAt,
width: imageData.width,
height: imageData.height
};
res.json({
success: true,
image: responseData
});
} catch (error) {
console.error("Get image error:", error);
res.status(500).json({ error: "Failed to get image" });
}
});
// Delete Image Function
exports.deleteImage = onRequest({ cors: true }, async (req, res) => {
if (req.method === 'OPTIONS') {
res.status(204).send('');
return;
}
if (req.method !== "DELETE") {
return res.status(405).json({ error: "Method not allowed" });
}
const { orgId, userId, collectionName, documentId } = req.body;
if (!orgId || !userId || !collectionName || !documentId) {
return res.status(400).json({ error: "Organization ID, user ID, collection name, and document ID are required" });
}
try {
// Verify user authorization
const isAuthorized = await verifyUserAuthorization(userId, orgId);
if (!isAuthorized) {
return res.status(403).json({ error: "Unauthorized access to organization" });
}
// Delete image document
const imageRef = db.collection("orgs").doc(orgId).collection("images").doc(`${collectionName}_${documentId}`);
const imageDoc = await imageRef.get();
if (!imageDoc.exists) {
return res.status(404).json({ error: "Image not found" });
}
await imageRef.delete();
res.json({
success: true,
message: "Image deleted successfully"
});
} catch (error) {
console.error("Delete image error:", error);
res.status(500).json({ error: "Failed to delete image" });
}
});