VIBN Frontend for Coolify deployment
This commit is contained in:
77
lib/firebase/admin.ts
Normal file
77
lib/firebase/admin.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import * as admin from 'firebase-admin';
|
||||
|
||||
// Initialize Firebase Admin SDK
|
||||
// During build time on Vercel, env vars might not be available, so we skip initialization
|
||||
const projectId = process.env.FIREBASE_PROJECT_ID;
|
||||
const clientEmail = process.env.FIREBASE_CLIENT_EMAIL;
|
||||
const privateKey = process.env.FIREBASE_PRIVATE_KEY?.replace(/\\n/g, '\n');
|
||||
|
||||
if (!admin.apps.length) {
|
||||
// Only initialize if we have credentials (skip during build)
|
||||
if (projectId && clientEmail && privateKey) {
|
||||
try {
|
||||
console.log('[Firebase Admin] Initializing...');
|
||||
console.log('[Firebase Admin] Project ID:', projectId);
|
||||
console.log('[Firebase Admin] Client Email:', clientEmail);
|
||||
console.log('[Firebase Admin] Private Key length:', privateKey?.length);
|
||||
|
||||
admin.initializeApp({
|
||||
credential: admin.credential.cert({
|
||||
projectId,
|
||||
clientEmail,
|
||||
privateKey,
|
||||
}),
|
||||
storageBucket: `${projectId}.firebasestorage.app`,
|
||||
});
|
||||
|
||||
console.log('[Firebase Admin] Initialized successfully!');
|
||||
} catch (error) {
|
||||
console.error('[Firebase Admin] Initialization failed:', error);
|
||||
}
|
||||
} else {
|
||||
console.log('[Firebase Admin] Skipping initialization - credentials not available (likely build time)');
|
||||
}
|
||||
}
|
||||
|
||||
// Helper to ensure admin is initialized
|
||||
function ensureInitialized() {
|
||||
if (!projectId || !clientEmail || !privateKey) {
|
||||
throw new Error('Firebase Admin credentials not configured');
|
||||
}
|
||||
|
||||
if (!admin.apps.length) {
|
||||
// Try to initialize if not done yet
|
||||
admin.initializeApp({
|
||||
credential: admin.credential.cert({
|
||||
projectId,
|
||||
clientEmail,
|
||||
privateKey,
|
||||
}),
|
||||
storageBucket: `${projectId}.firebasestorage.app`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Export admin services with lazy initialization
|
||||
export function getAdminAuth() {
|
||||
ensureInitialized();
|
||||
return admin.auth();
|
||||
}
|
||||
|
||||
export function getAdminDb() {
|
||||
ensureInitialized();
|
||||
return admin.firestore();
|
||||
}
|
||||
|
||||
export function getAdminStorage() {
|
||||
ensureInitialized();
|
||||
return admin.storage();
|
||||
}
|
||||
|
||||
// Legacy exports for backward compatibility (will work at runtime)
|
||||
export const adminAuth = admin.apps.length > 0 ? admin.auth() : ({} as any);
|
||||
export const adminDb = admin.apps.length > 0 ? admin.firestore() : ({} as any);
|
||||
export const adminStorage = admin.apps.length > 0 ? admin.storage() : ({} as any);
|
||||
|
||||
export default admin;
|
||||
|
||||
71
lib/firebase/api-keys.ts
Normal file
71
lib/firebase/api-keys.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import { db } from './config';
|
||||
import { doc, getDoc, setDoc, serverTimestamp } from 'firebase/firestore';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
interface ApiKey {
|
||||
key: string;
|
||||
userId: string;
|
||||
createdAt: any;
|
||||
lastUsed?: any;
|
||||
isActive: boolean;
|
||||
}
|
||||
|
||||
// Generate a new API key for a user
|
||||
export async function generateApiKey(userId: string): Promise<string> {
|
||||
const apiKey = `vibn_${uuidv4().replace(/-/g, '')}`;
|
||||
|
||||
const keyDoc = doc(db, 'apiKeys', apiKey);
|
||||
await setDoc(keyDoc, {
|
||||
key: apiKey,
|
||||
userId,
|
||||
createdAt: serverTimestamp(),
|
||||
isActive: true,
|
||||
});
|
||||
|
||||
return apiKey;
|
||||
}
|
||||
|
||||
// Get or create API key for a user
|
||||
export async function getOrCreateApiKey(userId: string): Promise<string> {
|
||||
// Check if user already has an API key
|
||||
const userDoc = doc(db, 'users', userId);
|
||||
const userSnap = await getDoc(userDoc);
|
||||
|
||||
if (userSnap.exists() && userSnap.data().apiKey) {
|
||||
return userSnap.data().apiKey;
|
||||
}
|
||||
|
||||
// Generate new key
|
||||
const apiKey = await generateApiKey(userId);
|
||||
|
||||
// Store reference in user document
|
||||
await setDoc(userDoc, {
|
||||
apiKey,
|
||||
updatedAt: serverTimestamp(),
|
||||
}, { merge: true });
|
||||
|
||||
return apiKey;
|
||||
}
|
||||
|
||||
// Verify an API key and return the userId
|
||||
export async function verifyApiKey(apiKey: string): Promise<string | null> {
|
||||
try {
|
||||
const keyDoc = doc(db, 'apiKeys', apiKey);
|
||||
const keySnap = await getDoc(keyDoc);
|
||||
|
||||
if (!keySnap.exists() || !keySnap.data().isActive) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Update last used timestamp
|
||||
await setDoc(keyDoc, {
|
||||
lastUsed: serverTimestamp(),
|
||||
}, { merge: true });
|
||||
|
||||
return keySnap.data().userId;
|
||||
} catch (error) {
|
||||
console.error('Error verifying API key:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
116
lib/firebase/auth.ts
Normal file
116
lib/firebase/auth.ts
Normal file
@@ -0,0 +1,116 @@
|
||||
import {
|
||||
signInWithEmailAndPassword,
|
||||
createUserWithEmailAndPassword,
|
||||
signInWithPopup,
|
||||
GoogleAuthProvider,
|
||||
GithubAuthProvider,
|
||||
signOut as firebaseSignOut,
|
||||
onAuthStateChanged,
|
||||
User
|
||||
} from 'firebase/auth';
|
||||
import { auth } from './config';
|
||||
import { createUser, getUser } from './collections';
|
||||
|
||||
// Providers
|
||||
const googleProvider = new GoogleAuthProvider();
|
||||
const githubProvider = new GithubAuthProvider();
|
||||
|
||||
// Sign up with email/password
|
||||
export async function signUpWithEmail(email: string, password: string, displayName: string) {
|
||||
try {
|
||||
const userCredential = await createUserWithEmailAndPassword(auth, email, password);
|
||||
const user = userCredential.user;
|
||||
|
||||
// Create user document in Firestore
|
||||
// Generate workspace from email or name
|
||||
const workspace = displayName.toLowerCase().replace(/\s+/g, '-') + '-account';
|
||||
|
||||
await createUser(user.uid, {
|
||||
email: user.email!,
|
||||
displayName: displayName,
|
||||
workspace: workspace,
|
||||
});
|
||||
|
||||
return { user, workspace };
|
||||
} catch (error: any) {
|
||||
throw new Error(error.message || 'Failed to create account');
|
||||
}
|
||||
}
|
||||
|
||||
// Sign in with email/password
|
||||
export async function signInWithEmail(email: string, password: string) {
|
||||
try {
|
||||
const userCredential = await signInWithEmailAndPassword(auth, email, password);
|
||||
return userCredential.user;
|
||||
} catch (error: any) {
|
||||
throw new Error(error.message || 'Failed to sign in');
|
||||
}
|
||||
}
|
||||
|
||||
// Sign in with Google
|
||||
export async function signInWithGoogle() {
|
||||
try {
|
||||
const result = await signInWithPopup(auth, googleProvider);
|
||||
const user = result.user;
|
||||
|
||||
// Check if user exists, if not create
|
||||
const existingUser = await getUser(user.uid);
|
||||
if (!existingUser) {
|
||||
const workspace = (user.displayName || user.email!.split('@')[0]).toLowerCase().replace(/\s+/g, '-') + '-account';
|
||||
await createUser(user.uid, {
|
||||
email: user.email!,
|
||||
displayName: user.displayName || undefined,
|
||||
photoURL: user.photoURL || undefined,
|
||||
workspace: workspace,
|
||||
});
|
||||
}
|
||||
|
||||
return user;
|
||||
} catch (error: any) {
|
||||
throw new Error(error.message || 'Failed to sign in with Google');
|
||||
}
|
||||
}
|
||||
|
||||
// Sign in with GitHub
|
||||
export async function signInWithGitHub() {
|
||||
try {
|
||||
const result = await signInWithPopup(auth, githubProvider);
|
||||
const user = result.user;
|
||||
|
||||
// Check if user exists, if not create
|
||||
const existingUser = await getUser(user.uid);
|
||||
if (!existingUser) {
|
||||
const workspace = (user.displayName || user.email!.split('@')[0]).toLowerCase().replace(/\s+/g, '-') + '-account';
|
||||
await createUser(user.uid, {
|
||||
email: user.email!,
|
||||
displayName: user.displayName || undefined,
|
||||
photoURL: user.photoURL || undefined,
|
||||
workspace: workspace,
|
||||
});
|
||||
}
|
||||
|
||||
return user;
|
||||
} catch (error: any) {
|
||||
throw new Error(error.message || 'Failed to sign in with GitHub');
|
||||
}
|
||||
}
|
||||
|
||||
// Sign out
|
||||
export async function signOut() {
|
||||
try {
|
||||
await firebaseSignOut(auth);
|
||||
} catch (error: any) {
|
||||
throw new Error(error.message || 'Failed to sign out');
|
||||
}
|
||||
}
|
||||
|
||||
// Listen to auth state changes
|
||||
export function onAuthChange(callback: (user: User | null) => void) {
|
||||
return onAuthStateChanged(auth, callback);
|
||||
}
|
||||
|
||||
// Get current user
|
||||
export function getCurrentUser(): User | null {
|
||||
return auth.currentUser;
|
||||
}
|
||||
|
||||
167
lib/firebase/collections.ts
Normal file
167
lib/firebase/collections.ts
Normal file
@@ -0,0 +1,167 @@
|
||||
import { db } from './config';
|
||||
import {
|
||||
collection,
|
||||
doc,
|
||||
getDoc,
|
||||
getDocs,
|
||||
setDoc,
|
||||
updateDoc,
|
||||
query,
|
||||
where,
|
||||
serverTimestamp,
|
||||
Timestamp
|
||||
} from 'firebase/firestore';
|
||||
import type { ProjectPhase, ProjectPhaseData, ProjectPhaseScores } from '@/lib/types/project-artifacts';
|
||||
|
||||
// Type definitions
|
||||
export interface User {
|
||||
uid: string;
|
||||
email: string;
|
||||
displayName?: string;
|
||||
photoURL?: string;
|
||||
workspace: string; // e.g., "marks-account"
|
||||
createdAt: Timestamp;
|
||||
updatedAt: Timestamp;
|
||||
}
|
||||
|
||||
export interface Project {
|
||||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
userId: string;
|
||||
workspace: string;
|
||||
productName: string;
|
||||
productVision?: string;
|
||||
isForClient: boolean;
|
||||
hasLogo: boolean;
|
||||
hasDomain: boolean;
|
||||
hasWebsite: boolean;
|
||||
hasGithub: boolean;
|
||||
hasChatGPT: boolean;
|
||||
githubRepo?: string;
|
||||
chatGPTProjectId?: string;
|
||||
currentPhase: ProjectPhase;
|
||||
phaseStatus: 'not_started' | 'in_progress' | 'completed';
|
||||
phaseData?: ProjectPhaseData;
|
||||
phaseHistory?: Array<Record<string, unknown>>;
|
||||
phaseScores?: ProjectPhaseScores;
|
||||
createdAt: Timestamp;
|
||||
updatedAt: Timestamp;
|
||||
}
|
||||
|
||||
export interface Session {
|
||||
id: string;
|
||||
projectId?: string | null;
|
||||
userId: string;
|
||||
startTime: Timestamp;
|
||||
endTime?: Timestamp | null;
|
||||
duration?: number | null;
|
||||
workspacePath?: string | null;
|
||||
workspaceName?: string | null;
|
||||
tokensUsed: number;
|
||||
cost: number;
|
||||
model: string;
|
||||
filesModified?: string[];
|
||||
conversationSummary?: string | null;
|
||||
conversation?: Array<{
|
||||
role: string;
|
||||
content: string;
|
||||
timestamp: string | Date;
|
||||
}>;
|
||||
createdAt: Timestamp;
|
||||
}
|
||||
|
||||
export interface Analysis {
|
||||
id: string;
|
||||
projectId: string;
|
||||
type: 'code' | 'chatgpt' | 'github' | 'combined';
|
||||
summary: string;
|
||||
techStack?: string[];
|
||||
features?: string[];
|
||||
rawData?: any;
|
||||
createdAt: Timestamp;
|
||||
}
|
||||
|
||||
// User operations
|
||||
export async function createUser(uid: string, data: Partial<User>) {
|
||||
const userRef = doc(db, 'users', uid);
|
||||
await setDoc(userRef, {
|
||||
uid,
|
||||
...data,
|
||||
createdAt: serverTimestamp(),
|
||||
updatedAt: serverTimestamp(),
|
||||
});
|
||||
}
|
||||
|
||||
export async function getUser(uid: string): Promise<User | null> {
|
||||
const userRef = doc(db, 'users', uid);
|
||||
const userSnap = await getDoc(userRef);
|
||||
return userSnap.exists() ? (userSnap.data() as User) : null;
|
||||
}
|
||||
|
||||
// Project operations
|
||||
export async function createProject(projectData: Omit<Project, 'id' | 'createdAt' | 'updatedAt'>) {
|
||||
const projectRef = doc(collection(db, 'projects'));
|
||||
await setDoc(projectRef, {
|
||||
...projectData,
|
||||
id: projectRef.id,
|
||||
createdAt: serverTimestamp(),
|
||||
updatedAt: serverTimestamp(),
|
||||
});
|
||||
return projectRef.id;
|
||||
}
|
||||
|
||||
export async function getProject(projectId: string): Promise<Project | null> {
|
||||
const projectRef = doc(db, 'projects', projectId);
|
||||
const projectSnap = await getDoc(projectRef);
|
||||
return projectSnap.exists() ? (projectSnap.data() as Project) : null;
|
||||
}
|
||||
|
||||
export async function getUserProjects(userId: string): Promise<Project[]> {
|
||||
const q = query(collection(db, 'projects'), where('userId', '==', userId));
|
||||
const querySnapshot = await getDocs(q);
|
||||
return querySnapshot.docs.map(doc => doc.data() as Project);
|
||||
}
|
||||
|
||||
export async function updateProject(projectId: string, data: Partial<Project>) {
|
||||
const projectRef = doc(db, 'projects', projectId);
|
||||
await updateDoc(projectRef, {
|
||||
...data,
|
||||
updatedAt: serverTimestamp(),
|
||||
});
|
||||
}
|
||||
|
||||
// Session operations
|
||||
export async function createSession(sessionData: Omit<Session, 'id' | 'createdAt'>) {
|
||||
const sessionRef = doc(collection(db, 'sessions'));
|
||||
await setDoc(sessionRef, {
|
||||
...sessionData,
|
||||
id: sessionRef.id,
|
||||
createdAt: serverTimestamp(),
|
||||
});
|
||||
return sessionRef.id;
|
||||
}
|
||||
|
||||
export async function getProjectSessions(projectId: string): Promise<Session[]> {
|
||||
const q = query(collection(db, 'sessions'), where('projectId', '==', projectId));
|
||||
const querySnapshot = await getDocs(q);
|
||||
return querySnapshot.docs.map(doc => doc.data() as Session);
|
||||
}
|
||||
|
||||
// Analysis operations
|
||||
export async function createAnalysis(analysisData: Omit<Analysis, 'id' | 'createdAt'>) {
|
||||
const analysisRef = doc(collection(db, 'analyses'));
|
||||
await setDoc(analysisRef, {
|
||||
...analysisData,
|
||||
id: analysisRef.id,
|
||||
createdAt: serverTimestamp(),
|
||||
});
|
||||
return analysisRef.id;
|
||||
}
|
||||
|
||||
export async function getProjectAnalyses(projectId: string): Promise<Analysis[]> {
|
||||
const q = query(collection(db, 'analyses'), where('projectId', '==', projectId));
|
||||
const querySnapshot = await getDocs(q);
|
||||
return querySnapshot.docs.map(doc => doc.data() as Analysis);
|
||||
}
|
||||
|
||||
36
lib/firebase/config.ts
Normal file
36
lib/firebase/config.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { initializeApp, getApps, FirebaseApp } from 'firebase/app';
|
||||
import { getAuth, Auth } from 'firebase/auth';
|
||||
import { getFirestore, Firestore } from 'firebase/firestore';
|
||||
import { getStorage, FirebaseStorage } from 'firebase/storage';
|
||||
|
||||
const firebaseConfig = {
|
||||
apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
|
||||
authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
|
||||
projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
|
||||
storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET,
|
||||
messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
|
||||
appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID,
|
||||
measurementId: process.env.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID,
|
||||
};
|
||||
|
||||
// Only initialize if we have the API key (skip during build)
|
||||
let _app: FirebaseApp | undefined;
|
||||
let _auth: Auth | undefined;
|
||||
let _db: Firestore | undefined;
|
||||
let _storage: FirebaseStorage | undefined;
|
||||
|
||||
if (typeof window !== 'undefined' || firebaseConfig.apiKey) {
|
||||
// Initialize Firebase (client-side only, safe for browser)
|
||||
_app = getApps().length === 0 ? initializeApp(firebaseConfig) : getApps()[0];
|
||||
_auth = getAuth(_app);
|
||||
_db = getFirestore(_app);
|
||||
_storage = getStorage(_app);
|
||||
}
|
||||
|
||||
// Export with type assertions - these will be defined at runtime in the browser
|
||||
// During build, they may be undefined, but won't be accessed
|
||||
export const auth = _auth as Auth;
|
||||
export const db = _db as Firestore;
|
||||
export const storage = _storage as FirebaseStorage;
|
||||
export default _app;
|
||||
|
||||
Reference in New Issue
Block a user