feat: build TeaBoy OS — bilingual Arabic/English internal OS platform
Completed full-stack TeaBoy OS build: Backend (artifacts/api-server): - Session-based auth with express-session + connect-pg-simple (PostgreSQL sessions) - RBAC middleware (requireAuth, requireAdmin) with role-based guards - REST routes for: auth (login/logout/register/me/language), apps CRUD, services CRUD, conversations + messages, notifications, users (admin), home stats - Socket.IO mounted on same HTTP server at /api/socket.io path - bcryptjs for password hashing - Manually created user_sessions table (connect-pg-simple requires it) Frontend (artifacts/teaboy-os): - React + Vite with i18next/react-i18next (Arabic default, full RTL) - i18n locale files: ar.json + en.json with all UI strings - AuthContext with auto-redirect to /login when unauthenticated - Pages: login, register, home (OS screen), services, chat, notifications, admin - OS home screen: animated gradient bg, status bar (clock+user+bell+language+logout), 4-column app icon grid with Lucide icons, bottom dock - خدماتي services page: service cards with availability badges - Chat: Socket.IO real-time messages, conversation list, send messages - Notifications: list with mark-as-read per item + mark all - Admin panel: full CRUD for apps/services/users with toggle switches - Vite proxy /api → localhost:8080 for same-origin cookie auth - credentials: "include" added to customFetch for session cookies Database: - Seed script with demo users (admin/admin123, ahmed/user123) - 8 demo apps, 6 services, 4 categories seeded All e2e tests pass: login, home screen, services, language toggle, admin, logout
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
import { type Request, type Response, type NextFunction } from "express";
|
||||
import { db } from "@workspace/db";
|
||||
import {
|
||||
usersTable,
|
||||
userRolesTable,
|
||||
rolesTable,
|
||||
} from "@workspace/db";
|
||||
import { eq } from "drizzle-orm";
|
||||
|
||||
declare module "express-session" {
|
||||
interface SessionData {
|
||||
userId: number;
|
||||
}
|
||||
}
|
||||
|
||||
export async function requireAuth(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction,
|
||||
): Promise<void> {
|
||||
if (!req.session.userId) {
|
||||
res.status(401).json({ error: "Unauthorized" });
|
||||
return;
|
||||
}
|
||||
|
||||
const [user] = await db
|
||||
.select()
|
||||
.from(usersTable)
|
||||
.where(eq(usersTable.id, req.session.userId));
|
||||
|
||||
if (!user || !user.isActive) {
|
||||
res.status(401).json({ error: "Unauthorized" });
|
||||
return;
|
||||
}
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
export async function requireAdmin(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction,
|
||||
): Promise<void> {
|
||||
if (!req.session.userId) {
|
||||
res.status(401).json({ error: "Unauthorized" });
|
||||
return;
|
||||
}
|
||||
|
||||
const userRoles = await db
|
||||
.select({ roleName: rolesTable.name })
|
||||
.from(userRolesTable)
|
||||
.innerJoin(rolesTable, eq(userRolesTable.roleId, rolesTable.id))
|
||||
.where(eq(userRolesTable.userId, req.session.userId));
|
||||
|
||||
const isAdmin = userRoles.some((r) => r.roleName === "admin");
|
||||
|
||||
if (!isAdmin) {
|
||||
res.status(403).json({ error: "Forbidden" });
|
||||
return;
|
||||
}
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
export async function getUserRoles(userId: number): Promise<string[]> {
|
||||
const roles = await db
|
||||
.select({ roleName: rolesTable.name })
|
||||
.from(userRolesTable)
|
||||
.innerJoin(rolesTable, eq(userRolesTable.roleId, rolesTable.id))
|
||||
.where(eq(userRolesTable.userId, userId));
|
||||
return roles.map((r) => r.roleName);
|
||||
}
|
||||
Reference in New Issue
Block a user