o134's picture
Upload full TeamTasker system with all fixes
8314cf4 verified
Raw
History Blame Contribute Delete
8.04 kB
import { Router } from "express";
import { db } from "@workspace/db";
import { usersTable } from "@workspace/db";
import { eq } from "drizzle-orm";
import bcrypt from "bcryptjs";
import { logAudit } from "../lib/audit-logger.js";
const router = Router();
function serializeUser(u: typeof usersTable.$inferSelect) {
return {
id: u.id,
name: u.name,
email: u.email,
team: u.team,
role: u.role,
avatarColor: u.avatarColor,
createdAt: u.createdAt.toISOString(),
};
}
import { requireAuth, requireAdmin } from "../middleware/auth.js";
import { z } from "zod";
import { createClient } from "@supabase/supabase-js";
const supabaseUrl = process.env.SUPABASE_URL || "https://fgzfelifumkwjfdrswxc.supabase.co";
const supabaseKey = process.env.SUPABASE_SERVICE_ROLE_KEY || "your_supabase_service_role_key_here";
export const supabase = createClient(supabaseUrl, supabaseKey);
export const supabaseAdmin = createClient(supabaseUrl, supabaseKey, {
auth: { persistSession: false }
});
export async function uploadAttachment(
fileBuffer: Buffer,
fileName: string,
mimeType: string
): Promise<string> {
// Ensure the task-attachments bucket exists
try {
const { error: getError } = await supabaseAdmin.storage.getBucket("task-attachments");
if (getError) {
console.log("Bucket 'task-attachments' not found, attempting programmatic creation...");
const { error: createError } = await supabaseAdmin.storage.createBucket("task-attachments", {
public: true,
});
if (createError) {
console.warn("Bucket creation warning:", createError.message);
} else {
console.log("✅ Bucket 'task-attachments' created successfully!");
}
}
} catch (bucketErr: any) {
console.warn("Bucket pre-check error:", bucketErr.message);
}
const uniqueName = `${Date.now()}-${fileName}`;
const { data, error } = await supabaseAdmin.storage
.from("task-attachments")
.upload(uniqueName, fileBuffer, {
contentType: mimeType,
upsert: true,
});
if (error) {
throw new Error(`Failed to upload file to Supabase Storage: ${error.message}`);
}
const { data: publicUrlData } = supabaseAdmin.storage
.from("task-attachments")
.getPublicUrl(uniqueName);
if (!publicUrlData || !publicUrlData.publicUrl) {
throw new Error("Failed to retrieve public URL from Supabase Storage");
}
return publicUrlData.publicUrl;
}
export async function deleteAttachmentFromStorage(fileUrl: string): Promise<void> {
const urlParts = fileUrl.split("/task-attachments/");
if (urlParts.length < 2) {
throw new Error("Invalid attachment URL format");
}
const filePath = decodeURIComponent(urlParts[1]);
const { error } = await supabaseAdmin.storage
.from("task-attachments")
.remove([filePath]);
if (error) {
throw new Error(`Failed to delete file from Supabase Storage: ${error.message}`);
}
}
const loginSchema = z.object({
email: z.string().email("صيغة البريد الإلكتروني غير صحيحة").min(1, "البريد الإلكتروني مطلوب"),
password: z.string().min(1, "كلمة المرور مطلوبة")
}).strict();
router.post("/auth/login", async (req, res) => {
try {
const parseResult = loginSchema.safeParse(req.body);
if (!parseResult.success) {
res.status(400).json({ error: parseResult.error.errors[0].message });
return;
}
const { email, password } = parseResult.data;
const { data: authData, error: authErr } = await supabase.auth.signInWithPassword({
email: email.toLowerCase().trim(),
password,
});
if (authErr || !authData.user || !authData.session) {
logAudit({
userId: 1, // Fallback ID for unknown user
action: "login_failure",
entityType: "auth",
details: { attemptedEmail: email.toLowerCase().trim(), reason: authErr?.message ?? "Invalid credentials" },
req,
});
res.status(401).json({ error: "البريد الإلكتروني أو كلمة المرور غير صحيحة" });
return;
}
const supabaseUid = authData.user.id;
let [user] = await db.select().from(usersTable).where(eq(usersTable.userId, supabaseUid));
if (!user) {
// Fallback check by email to auto-link if needed
[user] = await db.select().from(usersTable).where(eq(usersTable.email, email.toLowerCase().trim()));
if (user && !user.userId) {
[user] = await db.update(usersTable).set({ userId: supabaseUid }).where(eq(usersTable.id, user.id)).returning();
}
}
if (!user) {
logAudit({
userId: 1, // Fallback ID for unknown user
action: "login_failure",
entityType: "auth",
details: { attemptedEmail: email, reason: "User exists in Auth but not in public.users", supabaseUid },
req,
});
res.status(401).json({ error: "حساب المستخدم غير موجود في النظام الأساسي" });
return;
}
logAudit({
userId: user.id, // Prefer internal user.id over supabaseUid
action: "login_success",
entityType: "auth",
details: { attemptedEmail: user.email, supabaseUid },
req,
});
// Return Supabase access token instead of local JWT
res.json({ token: authData.session.access_token, user: serializeUser(user) });
} catch (err) {
req.log.error({ err: err instanceof Error ? err.message : String(err) }, "Login failed");
res.status(500).json({ error: "Internal server error" });
}
});
// Use requireAuth for /auth/me
router.post("/auth/me", requireAuth, async (req: any, res: any) => {
try {
// req.user is already loaded by requireAuth
if (!req.user) {
res.status(404).json({ error: "User not found" });
return;
}
res.json({ user: serializeUser(req.user) });
} catch (err) {
req.log.error({ err }, "Auth me failed");
res.status(500).json({ error: "Internal server error" });
}
});
router.post("/auth/register", requireAuth, requireAdmin, async (req, res) => {
try {
const { name, email, password, team, role, avatarColor } = req.body as {
name?: string; email?: string; password?: string;
team?: string; role?: string; avatarColor?: string;
};
if (!name || !email || !password || !team) {
res.status(400).json({ error: "جميع الحقول مطلوبة" });
return;
}
const existing = await db.select().from(usersTable).where(eq(usersTable.email, email.toLowerCase().trim()));
if (existing.length > 0) {
res.status(409).json({ error: "البريد الإلكتروني مستخدم مسبقاً" });
return;
}
// Create in Supabase Auth first
const { data: authData, error: authErr } = await supabase.auth.admin.createUser({
email: email.toLowerCase().trim(),
password,
email_confirm: true,
user_metadata: { name },
});
if (authErr || !authData.user) {
res.status(400).json({ error: authErr?.message ?? "Failed to create user in Supabase Auth" });
return;
}
const supabaseUid = authData.user.id;
const passwordHash = await bcrypt.hash(password, 10);
const [user] = await db.insert(usersTable).values({
userId: supabaseUid,
name,
email: email.toLowerCase().trim(),
passwordHash,
team,
role: role ?? "member",
avatarColor: avatarColor ?? "#6366f1",
}).returning();
logAudit({
userId: (req as any).userId || user.id,
action: "role_change",
entityType: "user",
entityId: user.id,
details: { newUserId: user.id, email: user.email, role: user.role, supabaseUid, note: "User registered via admin auth endpoint" },
req,
});
res.status(201).json({ user: serializeUser(user) });
} catch (err) {
req.log.error({ err: err instanceof Error ? err.message : String(err) }, "Register failed");
res.status(500).json({ error: "Internal server error" });
}
});
export default router;