Compare commits

..

17 Commits

Author SHA1 Message Date
admtracksteel ae68e1160a verify: change title to v1.6 2026-03-14 22:45:50 -03:00
admtracksteel ef50711a35 fix: align tsconfig rootDir and Dockerfile CMD path 2026-03-14 22:03:55 -03:00
admtracksteel 31c57d741f chore: force rebuild to clear possible clerk cache 2026-03-14 22:02:15 -03:00
admtracksteel 91ac5e76d5 fix: correct output directory structure to match Dockerfile CMD 2026-03-14 21:56:57 -03:00
admtracksteel 25eaaa8fcc fix: disable auto migration to stabilize startup 2026-03-14 21:56:15 -03:00
admtracksteel 424b75389f fix: update Dockerfile for production build 2026-03-14 21:31:02 -03:00
admtracksteel 62211c88d9 final 0 , sem clerk 2026-03-14 21:27:32 -03:00
admtracksteel 13d18d3fc3 sem clerk 2026-03-14 21:24:23 -03:00
admtracksteel e126b0d3f9 sem clerk 2026-03-14 21:12:25 -03:00
admtracksteel 286867739d remocao do clerk 2026-03-14 21:11:43 -03:00
admtracksteel c6f69e1c1d fix: pdf cors and build with live clerk key 2026-03-14 18:29:26 +00:00
admtracksteel 74df03691d fix: express 5 wildcard removed 2026-03-14 16:12:01 +00:00
admtracksteel a40436bb88 fix: express 5 wildcard splat 2026-03-14 16:03:08 +00:00
admtracksteel e81a0653e3 fix: express 5 wildcard syntax again 2026-03-14 15:58:03 +00:00
admtracksteel 4be695886f fix: wildcard path for express 5 2026-03-14 15:53:50 +00:00
admtracksteel 1d744df55e feat: serve frontend from backend for production deploy 2026-03-14 01:31:03 +00:00
admtracksteel b4ffe72b3e chore: synchronize local fixes to gitea 2026-03-14 00:25:56 +00:00
35 changed files with 100 additions and 389 deletions
+1 -38
View File
@@ -35,44 +35,7 @@ app.use(extractUser);
// Static Uploads
app.use('/uploads', express.static(path.join(process.cwd(), 'uploads')));
import pool from '../src/server/config/postgres.js';
// ... (existing routes)
app.get('/api/admin/migrate-to-gpi', async (req, res) => {
const TABLES = [
"organizations",
"users",
"user_organizations",
"projects",
"parts",
"painting_schemes",
"inspections",
"instruments",
"stock_items",
"stock_movements"
];
try {
const client = await pool.connect();
await client.query("CREATE SCHEMA IF NOT EXISTS gpi;");
const results = [];
for (const table of TABLES) {
try {
await client.query(`ALTER TABLE public.${table} SET SCHEMA gpi;`);
results.push({ table, status: 'success' });
} catch (err: any) {
results.push({ table, status: 'failed', error: err.message });
}
}
client.release();
res.json({ message: "Migration completed", results });
} catch (error: any) {
res.status(500).json({ error: error.message });
}
});
// Routes
app.use('/api/auth', authRoutes);
app.use('/api/users', userRoutes);
app.use('/api/projects', projectRoutes);
-7
View File
@@ -1,7 +0,0 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACAp2sxQE/eO30etME9tLKJjguVPUg3W8k3j2H7F/kBVogAAAJAeNjMSHjYz
EgAAAAtzc2gtZWQyNTUxOQAAACAp2sxQE/eO30etME9tLKJjguVPUg3W8k3j2H7F/kBVog
AAAEDLm78AwM6lbNoz7iVUh1xvlphZzNhitquW4jHyR7lIhCnazFAT947fR60wT20somOC
5U9SDdbyTePYfsX+QFWiAAAAC2FudGlncmF2aXR5AQI=
-----END OPENSSH PRIVATE KEY-----
-1
View File
@@ -1 +0,0 @@
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICnazFAT947fR60wT20somOC5U9SDdbyTePYfsX+QFWi antigravity
-151
View File
@@ -27,7 +27,6 @@
"mongoose": "^9.1.5",
"multer": "^2.0.2",
"pdf-parse": "^1.1.1",
"pg": "^8.20.0",
"prop-types": "^15.8.1",
"react": "^19.2.0",
"react-dom": "^19.2.0",
@@ -48,7 +47,6 @@
"@types/jsonwebtoken": "^9.0.10",
"@types/multer": "^2.0.0",
"@types/node": "^24.10.1",
"@types/pg": "^8.18.0",
"@types/react": "^19.2.5",
"@types/react-dom": "^19.2.3",
"@vercel/node": "^5.5.28",
@@ -3494,18 +3492,6 @@
"undici-types": "~7.16.0"
}
},
"node_modules/@types/pg": {
"version": "8.18.0",
"resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.18.0.tgz",
"integrity": "sha512-gT+oueVQkqnj6ajGJXblFR4iavIXWsGAFCk3dP4Kki5+a9R4NMt0JARdk6s8cUKcfUoqP5dAtDSLU8xYUTFV+Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/node": "*",
"pg-protocol": "*",
"pg-types": "^2.2.0"
}
},
"node_modules/@types/qs": {
"version": "6.14.0",
"dev": true,
@@ -9073,95 +9059,6 @@
"ms": "^2.1.1"
}
},
"node_modules/pg": {
"version": "8.20.0",
"resolved": "https://registry.npmjs.org/pg/-/pg-8.20.0.tgz",
"integrity": "sha512-ldhMxz2r8fl/6QkXnBD3CR9/xg694oT6DZQ2s6c/RI28OjtSOpxnPrUCGOBJ46RCUxcWdx3p6kw/xnDHjKvaRA==",
"license": "MIT",
"dependencies": {
"pg-connection-string": "^2.12.0",
"pg-pool": "^3.13.0",
"pg-protocol": "^1.13.0",
"pg-types": "2.2.0",
"pgpass": "1.0.5"
},
"engines": {
"node": ">= 16.0.0"
},
"optionalDependencies": {
"pg-cloudflare": "^1.3.0"
},
"peerDependencies": {
"pg-native": ">=3.0.1"
},
"peerDependenciesMeta": {
"pg-native": {
"optional": true
}
}
},
"node_modules/pg-cloudflare": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.3.0.tgz",
"integrity": "sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ==",
"license": "MIT",
"optional": true
},
"node_modules/pg-connection-string": {
"version": "2.12.0",
"resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.12.0.tgz",
"integrity": "sha512-U7qg+bpswf3Cs5xLzRqbXbQl85ng0mfSV/J0nnA31MCLgvEaAo7CIhmeyrmJpOr7o+zm0rXK+hNnT5l9RHkCkQ==",
"license": "MIT"
},
"node_modules/pg-int8": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz",
"integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==",
"license": "ISC",
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/pg-pool": {
"version": "3.13.0",
"resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.13.0.tgz",
"integrity": "sha512-gB+R+Xud1gLFuRD/QgOIgGOBE2KCQPaPwkzBBGC9oG69pHTkhQeIuejVIk3/cnDyX39av2AxomQiyPT13WKHQA==",
"license": "MIT",
"peerDependencies": {
"pg": ">=8.0"
}
},
"node_modules/pg-protocol": {
"version": "1.13.0",
"resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.13.0.tgz",
"integrity": "sha512-zzdvXfS6v89r6v7OcFCHfHlyG/wvry1ALxZo4LqgUoy7W9xhBDMaqOuMiF3qEV45VqsN6rdlcehHrfDtlCPc8w==",
"license": "MIT"
},
"node_modules/pg-types": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz",
"integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
"license": "MIT",
"dependencies": {
"pg-int8": "1.0.1",
"postgres-array": "~2.0.0",
"postgres-bytea": "~1.0.0",
"postgres-date": "~1.0.4",
"postgres-interval": "^1.1.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/pgpass": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz",
"integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==",
"license": "MIT",
"dependencies": {
"split2": "^4.1.0"
}
},
"node_modules/picocolors": {
"version": "1.1.1",
"license": "ISC"
@@ -9220,45 +9117,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/postgres-array": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
"integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==",
"license": "MIT",
"engines": {
"node": ">=4"
}
},
"node_modules/postgres-bytea": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz",
"integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/postgres-date": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz",
"integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/postgres-interval": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz",
"integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
"license": "MIT",
"dependencies": {
"xtend": "^4.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/prelude-ls": {
"version": "1.2.1",
"dev": true,
@@ -10319,15 +10177,6 @@
"memory-pager": "^1.0.2"
}
},
"node_modules/split2": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
"integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
"license": "ISC",
"engines": {
"node": ">= 10.x"
}
},
"node_modules/statuses": {
"version": "2.0.2",
"license": "MIT",
-2
View File
@@ -32,7 +32,6 @@
"mongoose": "^9.1.5",
"multer": "^2.0.2",
"pdf-parse": "^1.1.1",
"pg": "^8.20.0",
"prop-types": "^15.8.1",
"react": "^19.2.0",
"react-dom": "^19.2.0",
@@ -53,7 +52,6 @@
"@types/jsonwebtoken": "^9.0.10",
"@types/multer": "^2.0.0",
"@types/node": "^24.10.1",
"@types/pg": "^8.18.0",
"@types/react": "^19.2.5",
"@types/react-dom": "^19.2.3",
"@vercel/node": "^5.5.28",
+1 -1
View File
@@ -8,7 +8,7 @@ interface OrganizationMember {
_id: string;
name: string;
email: string;
userId: string;
clerkUserId: string;
role: string;
}
-2
View File
@@ -7,7 +7,6 @@ const API_URL = getBaseUrl();
export interface AuthContextType {
appUser: AppUser | null;
isLoading: boolean;
isSignedIn: boolean;
error: string | null;
token: string | null;
login: (token: string, user: AppUser) => void;
@@ -100,7 +99,6 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
value={{
appUser,
isLoading,
isSignedIn: !!appUser,
error,
token,
login,
+3
View File
@@ -1,3 +1,4 @@
import { createContext } from 'react';
import type { AppUser } from '../types';
export interface AuthContextType {
@@ -12,3 +13,5 @@ export interface AuthContextType {
canEdit: () => boolean;
refetchUser: () => Promise<void>;
}
export const AuthContext = createContext<AuthContextType | undefined>(undefined);
+10 -1
View File
@@ -1 +1,10 @@
export { useAuth } from './AuthContext';
import { useContext } from 'react';
import { AuthContext } from './AuthContextType';
export const useAuth = () => {
const context = useContext(AuthContext);
if (!context) {
throw new Error('useAuth must be used within an AuthProvider');
}
return context;
};
+1 -1
View File
@@ -6,7 +6,7 @@ export interface ActiveUser {
_id: string;
name: string;
email: string;
externalId: string;
clerkId: string;
lastSeenAt: string;
}
+2 -2
View File
@@ -419,7 +419,7 @@ export const DeveloperDashboard: React.FC = () => {
</h3>
<div className="space-y-2">
{admins.map(admin => (
<div key={admin.userId} className="flex items-center gap-3 p-2 bg-surface rounded-lg border border-indigo-500/20">
<div key={admin.clerkUserId} className="flex items-center gap-3 p-2 bg-surface rounded-lg border border-indigo-500/20">
<div className="w-8 h-8 rounded-full bg-indigo-500/20 flex items-center justify-center text-indigo-500 font-bold text-xs">
{admin.name.charAt(0).toUpperCase()}
</div>
@@ -441,7 +441,7 @@ export const DeveloperDashboard: React.FC = () => {
</h3>
<div className="space-y-2">
{commonUsers.map(user => (
<div key={user.userId} className="flex items-center gap-3 p-2 bg-surface rounded-lg border border-border/40">
<div key={user.clerkUserId} className="flex items-center gap-3 p-2 bg-surface rounded-lg border border-border/40">
<div className={clsx(
"w-8 h-8 rounded-full flex items-center justify-center font-bold text-xs",
user.role === 'user' ? "bg-green-500/10 text-green-500" : "bg-gray-500/10 text-gray-500"
+3 -3
View File
@@ -15,7 +15,7 @@ export const systemSettingsService = {
},
updateSettings: async (settings: Partial<SystemSettings>): Promise<SystemSettings> => {
// Axios interceptors in api.ts automatically handle x-auth-user-id and x-organization-id headers
// Axios interceptors in api.ts automatically handle x-clerk-user-id and x-organization-id headers
const response = await api.put('/system-settings', settings);
return response.data;
},
@@ -51,7 +51,7 @@ export const systemSettingsService = {
export interface GlobalUser {
_id: string;
externalId: string;
clerkId: string;
name: string;
email: string;
role: string;
@@ -69,7 +69,7 @@ export interface GlobalOrganization {
name: string;
email: string;
role: 'admin' | 'user' | 'guest';
userId: string;
clerkUserId: string;
isBanned: boolean;
}[];
}
+1 -1
View File
@@ -190,7 +190,7 @@ export type UserRole = 'guest' | 'user' | 'admin';
export interface AppUser {
id: string;
_id?: string;
externalId: string;
clerkId: string;
email: string;
name: string;
role: UserRole;
-28
View File
@@ -51,34 +51,6 @@ if (!fs.existsSync(uploadsPath)) {
app.use('/uploads', express.static(uploadsPath));
// Migration Route (Temporary)
import pool from './config/postgres.js';
app.get('/api/admin/migrate-schema', async (req, res) => {
const TABLES = [
"organizations", "users", "user_organizations", "projects",
"parts", "painting_schemes", "inspections", "instruments",
"stock_items", "stock_movements"
];
try {
const client = await pool.connect();
await client.query("CREATE SCHEMA IF NOT EXISTS gpi;");
const results = [];
for (const table of TABLES) {
try {
// Try to move from public to gpi
await client.query(`ALTER TABLE public."${table}" SET SCHEMA gpi;`);
results.push({ table, status: 'moved to gpi' });
} catch (err: any) {
results.push({ table, status: 'error', error: err.message });
}
}
client.release();
res.json({ message: "Schema migration finished", results });
} catch (err: any) {
res.status(500).json({ error: err.message });
}
});
// Routes
app.use('/api/auth', authRoutes);
app.use('/api/users', userRoutes);
-18
View File
@@ -1,18 +0,0 @@
import pg from 'pg';
const { Pool } = pg;
import dotenv from 'dotenv';
dotenv.config();
const pool = new Pool({
host: process.env.DB_HOST || 'supabase-db',
port: parseInt(process.env.DB_PORT || '5432'),
user: process.env.DB_USER || 'postgres',
password: process.env.DB_PASSWORD || 'Xz0oyb6ArGYG5uAVTVwcvJxRrMuT7EIJ',
database: process.env.DB_NAME || 'postgres',
ssl: false // Internal network usually doesn't need SSL
});
export const query = (text: string, params?: any[]) => pool.query(text, params);
export default pool;
@@ -5,7 +5,7 @@ import '../middleware/roleMiddleware.js'; // Ensure type augmentation
export const createApplicationRecord = async (req: Request, res: Response) => {
try {
const organizationId = req.appUser?.organizationId;
const createdBy = req.appUser?.externalId;
const createdBy = req.appUser?.clerkId;
const record = await appRecordService.createApplicationRecord({ ...req.body, organizationId, createdBy });
res.status(201).json(record);
} catch (error: unknown) {
@@ -29,7 +29,7 @@ export const getApplicationRecordsByProject = async (req: Request, res: Response
export const updateApplicationRecord = async (req: Request, res: Response) => {
try {
const organizationId = req.appUser?.organizationId;
const userId = req.appUser?.externalId;
const userId = req.appUser?.clerkId;
const userRole = req.appUser?.organizationRole || req.appUser?.role;
const isDeveloper = req.appUser?.email === 'admtracksteel@gmail.com';
@@ -52,7 +52,7 @@ export const updateApplicationRecord = async (req: Request, res: Response) => {
export const deleteApplicationRecord = async (req: Request, res: Response) => {
try {
const organizationId = req.appUser?.organizationId;
const userId = req.appUser?.externalId;
const userId = req.appUser?.clerkId;
const userRole = req.appUser?.organizationRole || req.appUser?.role;
const isDeveloper = req.appUser?.email === 'admtracksteel@gmail.com';
+8 -38
View File
@@ -1,8 +1,7 @@
import { Request, Response } from 'express';
import bcrypt from 'bcryptjs';
import jwt from 'jsonwebtoken';
import User, { IUser } from '../models/User.js';
import { IAppUser } from '../middleware/roleMiddleware.js';
import User from '../models/User.js';
import { v4 as uuidv4 } from 'uuid';
const JWT_SECRET = process.env.JWT_SECRET || 'fallback_secret_key_change_in_prod';
@@ -25,14 +24,14 @@ export const register = async (req: Request, res: Response): Promise<void> => {
const salt = await bcrypt.genSalt(10);
const passwordHash = await bcrypt.hash(password, salt);
// Gere um externalId falso apenas para manter retrocompatibilidade no banco
const fakeAuthId = `user_${uuidv4().replace(/-/g, '')}`;
// Gere um clerkId falso apenas para manter retrocompatibilidade no banco
const fakeClerkId = `user_${uuidv4().replace(/-/g, '')}`;
const newUser = new User({
name,
email,
passwordHash,
externalId: fakeAuthId,
clerkId: fakeClerkId,
role: 'member',
isBanned: false
});
@@ -40,7 +39,7 @@ export const register = async (req: Request, res: Response): Promise<void> => {
await newUser.save();
const token = jwt.sign(
{ userId: newUser._id.toString(), externalId: newUser.externalId, role: newUser.role, organizationId: newUser.organizationId },
{ userId: newUser._id.toString(), clerkId: newUser.clerkId, role: newUser.role, organizationId: newUser.organizationId },
JWT_SECRET,
{ expiresIn: '7d' }
);
@@ -48,7 +47,7 @@ export const register = async (req: Request, res: Response): Promise<void> => {
res.status(201).json({
message: 'Usuário criado com sucesso',
token,
user: { id: newUser._id, name: newUser.name, email: newUser.email, role: newUser.role, externalId: newUser.externalId }
user: { id: newUser._id, name: newUser.name, email: newUser.email, role: newUser.role, clerkId: newUser.clerkId }
});
} catch (error) {
console.error('Register Error:', error);
@@ -83,7 +82,7 @@ export const login = async (req: Request, res: Response): Promise<void> => {
}
const token = jwt.sign(
{ userId: user._id.toString(), externalId: user.externalId, role: user.role, organizationId: user.organizationId },
{ userId: user._id.toString(), clerkId: user.clerkId, role: user.role, organizationId: user.organizationId },
JWT_SECRET,
{ expiresIn: '7d' }
);
@@ -91,39 +90,10 @@ export const login = async (req: Request, res: Response): Promise<void> => {
res.status(200).json({
message: 'Login realizado com sucesso',
token,
user: {
id: user._id,
name: user.name,
email: user.email,
role: user.role,
externalId: user.externalId,
organizationId: user.organizationId
}
user: { id: user._id, name: user.name, email: user.email, role: user.role, clerkId: user.clerkId }
});
} catch (error) {
console.error('Login Error:', error);
res.status(500).json({ error: 'Erro no servidor' });
}
};
export const getMe = async (req: Request, res: Response): Promise<void> => {
try {
// O usuário é extraído pelo middleware extractUser e colocado em req.appUser
if (!req.appUser) {
res.status(401).json({ error: 'Não autorizado' });
return;
}
res.status(200).json({
id: req.appUser._id,
name: req.appUser.name,
email: req.appUser.email,
role: req.appUser.role,
externalId: req.appUser.externalId,
organizationId: req.appUser.organizationId
});
} catch (error) {
console.error('GetMe Error:', error);
res.status(500).json({ error: 'Erro no servidor' });
}
};
@@ -6,7 +6,7 @@ import '../middleware/roleMiddleware.js'; // Ensure type augmentation
export const createInspection = async (req: Request, res: Response) => {
try {
const organizationId = req.appUser?.organizationId;
const createdBy = req.appUser?.externalId;
const createdBy = req.appUser?.clerkId;
const inspection = await inspectionService.createInspection({
...req.body,
organizationId,
@@ -46,7 +46,7 @@ export const getInspectionsByProject = async (req: Request, res: Response) => {
export const updateInspection = async (req: Request, res: Response) => {
try {
const organizationId = req.appUser?.organizationId;
const userId = req.appUser?.externalId;
const userId = req.appUser?.clerkId;
const userRole = req.appUser?.organizationRole || req.appUser?.role;
const isDeveloper = req.appUser?.email === 'admtracksteel@gmail.com';
@@ -69,7 +69,7 @@ export const updateInspection = async (req: Request, res: Response) => {
export const deleteInspection = async (req: Request, res: Response) => {
try {
const organizationId = req.appUser?.organizationId;
const userId = req.appUser?.externalId;
const userId = req.appUser?.clerkId;
const userRole = req.appUser?.organizationRole || req.appUser?.role;
const isDeveloper = req.appUser?.email === 'admtracksteel@gmail.com';
+9 -9
View File
@@ -6,7 +6,7 @@ import OrganizationMember from '../models/OrganizationMember.js';
export const sendMessage = async (req: Request, res: Response) => {
try {
const { toUserId, message } = req.body;
const fromUserId = req.appUser?.externalId;
const fromUserId = req.appUser?.clerkId;
const organizationId = req.headers['x-organization-id'] as string;
if (!organizationId) {
@@ -60,7 +60,7 @@ export const sendMessage = async (req: Request, res: Response) => {
// Get unread messages for current user
export const getUnreadMessages = async (req: Request, res: Response) => {
try {
const toUserId = req.appUser?.externalId;
const toUserId = req.appUser?.clerkId;
const organizationId = req.headers['x-organization-id'] as string;
if (!organizationId) {
@@ -82,7 +82,7 @@ export const getUnreadMessages = async (req: Request, res: Response) => {
// Populate sender info
const messagesWithSender = await Promise.all(
messages.map(async (msg) => {
const sender = await OrganizationMember.findOne({ userId: msg.fromUserId });
const sender = await OrganizationMember.findOne({ clerkUserId: msg.fromUserId });
return {
...msg.toObject(),
fromUser: sender ? { name: sender.name, email: sender.email } : null,
@@ -101,7 +101,7 @@ export const getUnreadMessages = async (req: Request, res: Response) => {
export const markMessageAsRead = async (req: Request, res: Response) => {
try {
const { id } = req.params;
const userId = req.appUser?.externalId;
const userId = req.appUser?.clerkId;
const organizationId = req.headers['x-organization-id'] as string;
if (!organizationId) {
@@ -136,7 +136,7 @@ export const markMessageAsRead = async (req: Request, res: Response) => {
// Get my pending (unread) sent messages
export const getMyPendingMessages = async (req: Request, res: Response) => {
try {
const fromUserId = req.appUser?.externalId;
const fromUserId = req.appUser?.clerkId;
const organizationId = req.headers['x-organization-id'] as string;
if (!organizationId) {
@@ -156,7 +156,7 @@ export const getMyPendingMessages = async (req: Request, res: Response) => {
// Populate recipient info
const messagesWithRecipient = await Promise.all(
messages.map(async (msg) => {
const recipient = await OrganizationMember.findOne({ userId: msg.toUserId });
const recipient = await OrganizationMember.findOne({ clerkUserId: msg.toUserId });
return {
...msg.toObject(),
toUser: recipient ? { name: recipient.name, email: recipient.email } : null,
@@ -175,7 +175,7 @@ export const getMyPendingMessages = async (req: Request, res: Response) => {
export const deleteMessage = async (req: Request, res: Response) => {
try {
const { id } = req.params;
const userId = req.appUser?.externalId;
const userId = req.appUser?.clerkId;
const organizationId = req.headers['x-organization-id'] as string;
if (!organizationId) {
@@ -209,7 +209,7 @@ export const deleteMessage = async (req: Request, res: Response) => {
export const archiveMessage = async (req: Request, res: Response) => {
try {
const { id } = req.params;
const userId = req.appUser?.externalId;
const userId = req.appUser?.clerkId;
const organizationId = req.headers['x-organization-id'] as string;
const message = await Message.findOne({ _id: id, toUserId: userId, organizationId });
@@ -228,7 +228,7 @@ export const archiveMessage = async (req: Request, res: Response) => {
export const recipientDeleteMessage = async (req: Request, res: Response) => {
try {
const { id } = req.params;
const userId = req.appUser?.externalId;
const userId = req.appUser?.clerkId;
const organizationId = req.headers['x-organization-id'] as string;
const message = await Message.findOne({ _id: id, toUserId: userId, organizationId });
@@ -5,7 +5,7 @@ export const notificationController = {
getUserNotifications: async (req: Request, res: Response) => {
try {
const organizationId = req.headers['x-organization-id'] as string;
const userId = req.headers['x-user-id'] as string; // Assumindo que temos o ID do usuário (externalId ou email)
const userId = req.headers['x-user-id'] as string; // Assumindo que temos o ID do usuário (clerkId ou email)
// Se não tiver userId no header (ainda não implementado auth full), tentar pegar do query ou usar um fallback
// Nota: Idealmente o middleware de auth popula req.user. Vamos assumir que passamos x-user-id no frontend por enquanto.
+6 -6
View File
@@ -67,7 +67,7 @@ export const createStockItem = async (req: AuthRequest, res: Response) => {
const newItem = new StockItem({
organizationId,
createdBy: req.appUser?.externalId,
createdBy: req.appUser?.clerkId,
dataSheetId,
rrNumber,
batchNumber,
@@ -86,7 +86,7 @@ export const createStockItem = async (req: AuthRequest, res: Response) => {
// Create Initial Movement (ENTRY)
await StockMovement.create({
organizationId,
createdBy: req.appUser?.externalId,
createdBy: req.appUser?.clerkId,
stockItemId: savedItem._id,
movementNumber: 1,
type: 'ENTRY',
@@ -195,7 +195,7 @@ export const adjustStock = async (req: AuthRequest, res: Response) => {
// Register Movement
await StockMovement.create({
organizationId,
createdBy: req.appUser?.externalId,
createdBy: req.appUser?.clerkId,
stockItemId: item._id,
movementNumber,
type: 'ADJUSTMENT',
@@ -241,7 +241,7 @@ export const consumeStock = async (req: AuthRequest, res: Response) => {
// Register Movement (Negative quantity for consumption)
await StockMovement.create({
organizationId,
createdBy: req.appUser?.externalId,
createdBy: req.appUser?.clerkId,
stockItemId: item._id,
movementNumber,
type: 'CONSUMPTION',
@@ -348,7 +348,7 @@ export const updateStockMovement = async (req: AuthRequest, res: Response) => {
const { id } = req.params; // Movement ID
const organizationId = req.appUser?.organizationId;
const userName = req.appUser?.name || req.appUser?.email || 'Unknown User';
const userId = req.appUser?.externalId || 'system';
const userId = req.appUser?.clerkId || 'system';
const isAdmin = req.appUser?.role === 'admin' || req.appUser?.organizationRole === 'admin';
if (!isAdmin) {
@@ -431,7 +431,7 @@ export const deleteStockMovement = async (req: AuthRequest, res: Response) => {
const { id } = req.params;
const organizationId = req.appUser?.organizationId;
const userName = req.appUser?.name || req.appUser?.email || 'Unknown User';
const userId = req.appUser?.externalId || 'system';
const userId = req.appUser?.clerkId || 'system';
const isAdmin = req.appUser?.role === 'admin' || req.appUser?.organizationRole === 'admin';
if (!isAdmin) {
@@ -114,7 +114,7 @@ export const getGlobalOrganizations = async (req: Request, res: Response) => {
name: '$name',
email: '$email',
role: '$role',
userId: '$userId',
clerkUserId: '$clerkUserId',
isBanned: '$isBanned'
}
},
@@ -125,7 +125,7 @@ export const getGlobalOrganizations = async (req: Request, res: Response) => {
$lookup: {
from: 'organizations', // Ensure this matches the collection name of Organization model
localField: '_id',
foreignField: 'externalId',
foreignField: 'clerkId',
as: 'orgDetails'
}
},
@@ -165,7 +165,7 @@ export const toggleOrganizationBan = async (req: Request, res: Response) => {
// Upsert the Organization record
const org = await Organization.findOneAndUpdate(
{ externalId: organizationId },
{ clerkId: organizationId },
{ isBanned: isBanned },
{ new: true, upsert: true }
);
+16 -16
View File
@@ -14,21 +14,21 @@ interface AuthRequest extends Request {
}
/**
* Sync user from Auth to MongoDB
* Sync user from Clerk to MongoDB
* Creates user if doesn't exist, updates if exists
* Also creates/updates OrganizationMember for the current organization
*/
export const syncUser = async (req: Request, res: Response) => {
console.log('--- syncUser called ---', req.body);
try {
const { externalId, email, name, organizationId, clerkRole } = req.body;
const { clerkId, email, name, organizationId, clerkRole } = req.body;
if (!externalId || !email || !name) {
return res.status(400).json({ error: 'externalId, email e name são obrigatórios.' });
if (!clerkId || !email || !name) {
return res.status(400).json({ error: 'clerkId, email e name são obrigatórios.' });
}
// 1. Upsert the global User record
let user = await User.findOne({ externalId });
let user = await User.findOne({ clerkId });
if (user) {
user.email = email;
@@ -36,7 +36,7 @@ export const syncUser = async (req: Request, res: Response) => {
await user.save();
} else {
user = await User.create({
externalId,
clerkId,
email,
name,
role: 'guest', // Default global role
@@ -46,7 +46,7 @@ export const syncUser = async (req: Request, res: Response) => {
if (organizationId) {
// Map Auth role to our app role
// Map Clerk role to our app role
let appRole: OrgRole = 'guest';
if (clerkRole === 'org:admin') {
appRole = 'admin';
@@ -57,7 +57,7 @@ export const syncUser = async (req: Request, res: Response) => {
// Use findOneAndUpdate with upsert to handle race conditions atomically
// This avoids the need for try/catch on create and handles existing members too
const member = await OrganizationMember.findOneAndUpdate(
{ userId: externalId, organizationId },
{ clerkUserId: clerkId, organizationId },
{
$set: {
name,
@@ -65,7 +65,7 @@ export const syncUser = async (req: Request, res: Response) => {
// Only update role if it's the first time (creation)
// Or we can optionally update it if needed.
// For now, let's NOT overwrite role on update to preserve local changes,
// UNLESS we want to force sync with Auth.
// UNLESS we want to force sync with Clerk.
// Let's use $setOnInsert for fields we only want to set on creation.
},
$setOnInsert: {
@@ -106,7 +106,7 @@ export const getCurrentUser = async (req: AuthRequest, res: Response) => {
if (organizationId) {
const member = await OrganizationMember.findOne({
userId: req.appUser.externalId,
clerkUserId: req.appUser.clerkId,
organizationId
});
@@ -141,7 +141,7 @@ export const getAllUsers = async (req: Request, res: Response) => {
}
const members = await OrganizationMember.find({ organizationId }).sort({ createdAt: -1 });
console.log(`Found ${members.length} members for org ${organizationId}:`, members.map(m => ({ name: m.name, email: m.email, externalId: m.userId })));
console.log(`Found ${members.length} members for org ${organizationId}:`, members.map(m => ({ name: m.name, email: m.email, clerkId: m.clerkUserId })));
res.json(members);
} catch (error) {
console.error('Error getting users:', error);
@@ -208,7 +208,7 @@ export const toggleBanUser = async (req: AuthRequest, res: Response) => {
}
// Prevent banning yourself
if (req.appUser && member.userId === req.appUser.externalId) {
if (req.appUser && member.clerkUserId === req.appUser.clerkId) {
return res.status(400).json({ error: 'Você não pode banir a si mesmo.' });
}
@@ -271,17 +271,17 @@ export const getActiveUsers = async (req: AuthRequest, res: Response) => {
// Find members of this org
const members = await OrganizationMember.find({ organizationId });
// Get their Auth IDs
const externalIds = members.map(m => m.userId);
// Get their Clerk IDs
const clerkIds = members.map(m => m.clerkUserId);
// Find Users who were seen recently (2 minutes)
const twoMinutesAgo = new Date(Date.now() - 2 * 60 * 1000);
const activeUsers = await User.find({
externalId: { $in: externalIds },
clerkId: { $in: clerkIds },
lastSeenAt: { $gte: twoMinutesAgo },
_id: { $ne: currentUserId } // Optional: exclude self
}).select('name email lastSeenAt externalId'); // Only needed fields
}).select('name email lastSeenAt clerkId'); // Only needed fields
res.json(activeUsers);
} catch (error) {
+2 -2
View File
@@ -14,9 +14,9 @@ export const authMiddleware = (req: Request, res: Response, next: NextFunction)
const token = authHeader.split(' ')[1];
const decoded = jwt.verify(token, JWT_SECRET) as any;
// Injeta o externalId no header para que o extractUser (roleMiddleware)
// Injeta o clerkId no header para que o extractUser (roleMiddleware)
// continue seu trabalho de carregar o usuário do banco instanciado e popular req.appUser
req.headers['x-auth-user-id'] = decoded.externalId;
req.headers['x-clerk-user-id'] = decoded.clerkId;
next();
} catch (error) {
+10 -10
View File
@@ -18,19 +18,19 @@ declare module 'express-serve-static-core' {
}
/**
* Middleware to extract and verify user from Auth ID header
* Middleware to extract and verify user from Clerk ID header
* Also loads organization-specific role if organization context is provided
*/
export const extractUser = async (req: Request, res: Response, next: NextFunction) => {
try {
const externalId = req.headers['x-auth-user-id'] as string;
const clerkId = req.headers['x-clerk-user-id'] as string;
const organizationId = req.headers['x-organization-id'] as string;
if (!externalId) {
if (!clerkId) {
return next(); // No user, continue without
}
const user = await User.findOne({ externalId });
const user = await User.findOne({ clerkId });
if (user) {
if (user.isBanned) {
@@ -39,12 +39,12 @@ export const extractUser = async (req: Request, res: Response, next: NextFunctio
// Create extended user object
const appUser: IAppUser = user.toObject() as IAppUser;
appUser.organizationId = organizationId || user.organizationId;
appUser.organizationId = organizationId;
// If organization context, get org-specific role
if (organizationId) {
// Check if Organization is globally banned (subscription specific, etc.)
const orgStatus = await Organization.findOne({ externalId: organizationId });
const orgStatus = await Organization.findOne({ clerkId: organizationId });
const orgName = req.headers['x-organization-name'] ? decodeURIComponent(req.headers['x-organization-name'] as string) : undefined;
if (orgStatus) {
@@ -52,7 +52,7 @@ export const extractUser = async (req: Request, res: Response, next: NextFunctio
if (orgName && orgStatus.name !== orgName) {
try {
await Organization.updateOne(
{ externalId: organizationId },
{ clerkId: organizationId },
{ name: orgName }
);
} catch (err) {
@@ -69,7 +69,7 @@ export const extractUser = async (req: Request, res: Response, next: NextFunctio
// Create new org with name if present
try {
await Organization.create({
externalId: organizationId,
clerkId: organizationId,
name: orgName
});
} catch (_e) {
@@ -77,7 +77,7 @@ export const extractUser = async (req: Request, res: Response, next: NextFunctio
}
}
const member = await OrganizationMember.findOne({ userId: externalId, organizationId });
const member = await OrganizationMember.findOne({ clerkUserId: clerkId, organizationId });
if (member) {
if (member.isBanned) {
return res.status(403).json({ error: 'Acesso bloqueado nesta organização.' });
@@ -94,7 +94,7 @@ export const extractUser = async (req: Request, res: Response, next: NextFunctio
req.appUser = appUser;
// console.log(`✅ Request authenticated as: ${appUser.name} (${appUser.role})`);
} else {
console.warn(`⚠️ User with Auth ID ${externalId} not found in MongoDB. Sync required.`);
console.warn(`⚠️ User with Clerk ID ${clerkId} not found in MongoDB. Sync required.`);
}
next();
+1 -1
View File
@@ -2,7 +2,7 @@ import mongoose, { Schema, Document } from 'mongoose';
export interface IInspection extends Document {
organizationId?: string;
createdBy?: string; // Auth User ID
createdBy?: string; // Clerk User ID
projectId: mongoose.Types.ObjectId;
type: 'painting' | 'surface_treatment';
+2 -2
View File
@@ -2,8 +2,8 @@ import mongoose, { Schema, Document } from 'mongoose';
export interface IMessage extends Document {
organizationId: string;
fromUserId: string; // externalId do remetente
toUserId: string; // externalId do destinatário
fromUserId: string; // clerkId do remetente
toUserId: string; // clerkId do destinatário
message: string;
isRead: boolean;
readAt?: Date;
+2 -2
View File
@@ -1,7 +1,7 @@
import mongoose, { Schema, Document } from 'mongoose';
export interface IOrganization extends Document {
externalId: string;
clerkId: string;
name?: string;
isBanned: boolean;
createdAt: Date;
@@ -9,7 +9,7 @@ export interface IOrganization extends Document {
}
const OrganizationSchema: Schema = new Schema({
externalId: { type: String, required: true, unique: true, index: true },
clerkId: { type: String, required: true, unique: true, index: true },
name: { type: String },
isBanned: { type: Boolean, default: false },
}, { timestamps: true });
+3 -3
View File
@@ -3,7 +3,7 @@ import mongoose, { Schema, Document } from 'mongoose';
export type OrgRole = 'guest' | 'user' | 'admin';
export interface IOrganizationMember extends Document {
userId: string;
clerkUserId: string;
organizationId: string;
role: OrgRole;
isBanned: boolean;
@@ -15,7 +15,7 @@ export interface IOrganizationMember extends Document {
}
const OrganizationMemberSchema: Schema = new Schema({
userId: {
clerkUserId: {
type: String,
required: true,
index: true
@@ -47,6 +47,6 @@ const OrganizationMemberSchema: Schema = new Schema({
});
// Compound index for unique user per organization
OrganizationMemberSchema.index({ userId: 1, organizationId: 1 }, { unique: true });
OrganizationMemberSchema.index({ clerkUserId: 1, organizationId: 1 }, { unique: true });
export default mongoose.models.OrganizationMember || mongoose.model<IOrganizationMember>('OrganizationMember', OrganizationMemberSchema);
+2 -2
View File
@@ -3,7 +3,7 @@ import mongoose, { Schema, Document } from 'mongoose';
export type UserRole = 'guest' | 'user' | 'admin';
export interface IUser extends Document {
externalId: string;
clerkId: string;
email: string;
name: string;
role: UserRole;
@@ -16,7 +16,7 @@ export interface IUser extends Document {
}
const UserSchema: Schema = new Schema({
externalId: {
clerkId: {
type: String,
required: true,
unique: true,
+1 -2
View File
@@ -1,10 +1,9 @@
import express from 'express';
import { login, register, getMe } from '../controllers/authController.js';
import { login, register } from '../controllers/authController.js';
const router = express.Router();
router.post('/login', login);
router.post('/register', register);
router.get('/me', getMe);
export default router;
+1 -1
View File
@@ -4,7 +4,7 @@ import { extractUser, requireAdmin } from '../middleware/roleMiddleware.js';
const router = express.Router();
// Sync user from Auth (public - called on login)
// Sync user from Clerk (public - called on login)
router.post('/sync', syncUser);
// Get current user (requires extractUser middleware)
-24
View File
@@ -1,24 +0,0 @@
import mongoose from 'mongoose';
const MONGODB_URI = 'mongodb+srv://admtracksteel:29OHAHpKTI8XcCNt@cluster0.a4xiilu.mongodb.net/ts_gpi?retryWrites=true&w=majority&appName=Cluster0';
const UserSchema = new mongoose.Schema({
email: String,
role: String,
passwordHash: String,
externalId: String
});
async function check() {
try {
await mongoose.connect(MONGODB_URI);
const User = mongoose.model('User', UserSchema);
const user = await User.findOne({ email: 'admtracksteel@gmail.com' });
console.log('User found:', JSON.stringify(user, null, 2));
} catch (err) {
console.error(err);
} finally {
await mongoose.disconnect();
}
}
check();
+4 -4
View File
@@ -5,7 +5,7 @@ import { v4 as uuidv4 } from 'uuid';
const MONGODB_URI = 'mongodb+srv://admtracksteel:29OHAHpKTI8XcCNt@cluster0.a4xiilu.mongodb.net/ts_gpi?retryWrites=true&w=majority&appName=Cluster0';
const UserSchema = new mongoose.Schema({
externalId: { type: String, required: true, unique: true },
clerkId: { type: String, required: true, unique: true },
email: { type: String, required: true, unique: true },
passwordHash: { type: String, required: true },
name: { type: String, required: true },
@@ -41,16 +41,16 @@ async function fixAdmin() {
);
console.log(`✅ Usuário ${email} atualizado para admin e senha definida.`);
} else {
const fakeAuthId = `user_${uuidv4().replace(/-/g, '')}`;
const fakeClerkId = `user_${uuidv4().replace(/-/g, '')}`;
await User.create({
externalId: fakeAuthId,
clerkId: fakeClerkId,
email,
passwordHash: hashedPassword,
name: 'Admin TrackSteel',
role: 'admin',
organizationId: 'default-org'
});
console.log(`✅ Usuário ${email} criado como admin (AuthId: ${fakeAuthId}).`);
console.log(`✅ Usuário ${email} criado como admin (ClerkId: ${fakeClerkId}).`);
}
// Listar todos os usuários
+1 -1
View File
@@ -4,7 +4,7 @@ declare global {
namespace Express {
interface Request {
appUser?: IUser;
userId?: string;
clerkUserId?: string;
}
}
}