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 // Static Uploads
app.use('/uploads', express.static(path.join(process.cwd(), 'uploads'))); app.use('/uploads', express.static(path.join(process.cwd(), 'uploads')));
import pool from '../src/server/config/postgres.js'; // Routes
// ... (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 });
}
});
app.use('/api/auth', authRoutes); app.use('/api/auth', authRoutes);
app.use('/api/users', userRoutes); app.use('/api/users', userRoutes);
app.use('/api/projects', projectRoutes); 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", "mongoose": "^9.1.5",
"multer": "^2.0.2", "multer": "^2.0.2",
"pdf-parse": "^1.1.1", "pdf-parse": "^1.1.1",
"pg": "^8.20.0",
"prop-types": "^15.8.1", "prop-types": "^15.8.1",
"react": "^19.2.0", "react": "^19.2.0",
"react-dom": "^19.2.0", "react-dom": "^19.2.0",
@@ -48,7 +47,6 @@
"@types/jsonwebtoken": "^9.0.10", "@types/jsonwebtoken": "^9.0.10",
"@types/multer": "^2.0.0", "@types/multer": "^2.0.0",
"@types/node": "^24.10.1", "@types/node": "^24.10.1",
"@types/pg": "^8.18.0",
"@types/react": "^19.2.5", "@types/react": "^19.2.5",
"@types/react-dom": "^19.2.3", "@types/react-dom": "^19.2.3",
"@vercel/node": "^5.5.28", "@vercel/node": "^5.5.28",
@@ -3494,18 +3492,6 @@
"undici-types": "~7.16.0" "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": { "node_modules/@types/qs": {
"version": "6.14.0", "version": "6.14.0",
"dev": true, "dev": true,
@@ -9073,95 +9059,6 @@
"ms": "^2.1.1" "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": { "node_modules/picocolors": {
"version": "1.1.1", "version": "1.1.1",
"license": "ISC" "license": "ISC"
@@ -9220,45 +9117,6 @@
"dev": true, "dev": true,
"license": "MIT" "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": { "node_modules/prelude-ls": {
"version": "1.2.1", "version": "1.2.1",
"dev": true, "dev": true,
@@ -10319,15 +10177,6 @@
"memory-pager": "^1.0.2" "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": { "node_modules/statuses": {
"version": "2.0.2", "version": "2.0.2",
"license": "MIT", "license": "MIT",
-2
View File
@@ -32,7 +32,6 @@
"mongoose": "^9.1.5", "mongoose": "^9.1.5",
"multer": "^2.0.2", "multer": "^2.0.2",
"pdf-parse": "^1.1.1", "pdf-parse": "^1.1.1",
"pg": "^8.20.0",
"prop-types": "^15.8.1", "prop-types": "^15.8.1",
"react": "^19.2.0", "react": "^19.2.0",
"react-dom": "^19.2.0", "react-dom": "^19.2.0",
@@ -53,7 +52,6 @@
"@types/jsonwebtoken": "^9.0.10", "@types/jsonwebtoken": "^9.0.10",
"@types/multer": "^2.0.0", "@types/multer": "^2.0.0",
"@types/node": "^24.10.1", "@types/node": "^24.10.1",
"@types/pg": "^8.18.0",
"@types/react": "^19.2.5", "@types/react": "^19.2.5",
"@types/react-dom": "^19.2.3", "@types/react-dom": "^19.2.3",
"@vercel/node": "^5.5.28", "@vercel/node": "^5.5.28",
+1 -1
View File
@@ -8,7 +8,7 @@ interface OrganizationMember {
_id: string; _id: string;
name: string; name: string;
email: string; email: string;
userId: string; clerkUserId: string;
role: string; role: string;
} }
-2
View File
@@ -7,7 +7,6 @@ const API_URL = getBaseUrl();
export interface AuthContextType { export interface AuthContextType {
appUser: AppUser | null; appUser: AppUser | null;
isLoading: boolean; isLoading: boolean;
isSignedIn: boolean;
error: string | null; error: string | null;
token: string | null; token: string | null;
login: (token: string, user: AppUser) => void; login: (token: string, user: AppUser) => void;
@@ -100,7 +99,6 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
value={{ value={{
appUser, appUser,
isLoading, isLoading,
isSignedIn: !!appUser,
error, error,
token, token,
login, login,
+3
View File
@@ -1,3 +1,4 @@
import { createContext } from 'react';
import type { AppUser } from '../types'; import type { AppUser } from '../types';
export interface AuthContextType { export interface AuthContextType {
@@ -12,3 +13,5 @@ export interface AuthContextType {
canEdit: () => boolean; canEdit: () => boolean;
refetchUser: () => Promise<void>; 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; _id: string;
name: string; name: string;
email: string; email: string;
externalId: string; clerkId: string;
lastSeenAt: string; lastSeenAt: string;
} }
+2 -2
View File
@@ -419,7 +419,7 @@ export const DeveloperDashboard: React.FC = () => {
</h3> </h3>
<div className="space-y-2"> <div className="space-y-2">
{admins.map(admin => ( {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"> <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()} {admin.name.charAt(0).toUpperCase()}
</div> </div>
@@ -441,7 +441,7 @@ export const DeveloperDashboard: React.FC = () => {
</h3> </h3>
<div className="space-y-2"> <div className="space-y-2">
{commonUsers.map(user => ( {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( <div className={clsx(
"w-8 h-8 rounded-full flex items-center justify-center font-bold text-xs", "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" 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> => { 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); const response = await api.put('/system-settings', settings);
return response.data; return response.data;
}, },
@@ -51,7 +51,7 @@ export const systemSettingsService = {
export interface GlobalUser { export interface GlobalUser {
_id: string; _id: string;
externalId: string; clerkId: string;
name: string; name: string;
email: string; email: string;
role: string; role: string;
@@ -69,7 +69,7 @@ export interface GlobalOrganization {
name: string; name: string;
email: string; email: string;
role: 'admin' | 'user' | 'guest'; role: 'admin' | 'user' | 'guest';
userId: string; clerkUserId: string;
isBanned: boolean; isBanned: boolean;
}[]; }[];
} }
+1 -1
View File
@@ -190,7 +190,7 @@ export type UserRole = 'guest' | 'user' | 'admin';
export interface AppUser { export interface AppUser {
id: string; id: string;
_id?: string; _id?: string;
externalId: string; clerkId: string;
email: string; email: string;
name: string; name: string;
role: UserRole; role: UserRole;
-28
View File
@@ -51,34 +51,6 @@ if (!fs.existsSync(uploadsPath)) {
app.use('/uploads', express.static(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 // Routes
app.use('/api/auth', authRoutes); app.use('/api/auth', authRoutes);
app.use('/api/users', userRoutes); 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) => { export const createApplicationRecord = async (req: Request, res: Response) => {
try { try {
const organizationId = req.appUser?.organizationId; const organizationId = req.appUser?.organizationId;
const createdBy = req.appUser?.externalId; const createdBy = req.appUser?.clerkId;
const record = await appRecordService.createApplicationRecord({ ...req.body, organizationId, createdBy }); const record = await appRecordService.createApplicationRecord({ ...req.body, organizationId, createdBy });
res.status(201).json(record); res.status(201).json(record);
} catch (error: unknown) { } catch (error: unknown) {
@@ -29,7 +29,7 @@ export const getApplicationRecordsByProject = async (req: Request, res: Response
export const updateApplicationRecord = async (req: Request, res: Response) => { export const updateApplicationRecord = async (req: Request, res: Response) => {
try { try {
const organizationId = req.appUser?.organizationId; const organizationId = req.appUser?.organizationId;
const userId = req.appUser?.externalId; const userId = req.appUser?.clerkId;
const userRole = req.appUser?.organizationRole || req.appUser?.role; const userRole = req.appUser?.organizationRole || req.appUser?.role;
const isDeveloper = req.appUser?.email === 'admtracksteel@gmail.com'; 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) => { export const deleteApplicationRecord = async (req: Request, res: Response) => {
try { try {
const organizationId = req.appUser?.organizationId; const organizationId = req.appUser?.organizationId;
const userId = req.appUser?.externalId; const userId = req.appUser?.clerkId;
const userRole = req.appUser?.organizationRole || req.appUser?.role; const userRole = req.appUser?.organizationRole || req.appUser?.role;
const isDeveloper = req.appUser?.email === 'admtracksteel@gmail.com'; const isDeveloper = req.appUser?.email === 'admtracksteel@gmail.com';
+8 -38
View File
@@ -1,8 +1,7 @@
import { Request, Response } from 'express'; import { Request, Response } from 'express';
import bcrypt from 'bcryptjs'; import bcrypt from 'bcryptjs';
import jwt from 'jsonwebtoken'; import jwt from 'jsonwebtoken';
import User, { IUser } from '../models/User.js'; import User from '../models/User.js';
import { IAppUser } from '../middleware/roleMiddleware.js';
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
const JWT_SECRET = process.env.JWT_SECRET || 'fallback_secret_key_change_in_prod'; 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 salt = await bcrypt.genSalt(10);
const passwordHash = await bcrypt.hash(password, salt); const passwordHash = await bcrypt.hash(password, salt);
// Gere um externalId falso apenas para manter retrocompatibilidade no banco // Gere um clerkId falso apenas para manter retrocompatibilidade no banco
const fakeAuthId = `user_${uuidv4().replace(/-/g, '')}`; const fakeClerkId = `user_${uuidv4().replace(/-/g, '')}`;
const newUser = new User({ const newUser = new User({
name, name,
email, email,
passwordHash, passwordHash,
externalId: fakeAuthId, clerkId: fakeClerkId,
role: 'member', role: 'member',
isBanned: false isBanned: false
}); });
@@ -40,7 +39,7 @@ export const register = async (req: Request, res: Response): Promise<void> => {
await newUser.save(); await newUser.save();
const token = jwt.sign( 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, JWT_SECRET,
{ expiresIn: '7d' } { expiresIn: '7d' }
); );
@@ -48,7 +47,7 @@ export const register = async (req: Request, res: Response): Promise<void> => {
res.status(201).json({ res.status(201).json({
message: 'Usuário criado com sucesso', message: 'Usuário criado com sucesso',
token, 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) { } catch (error) {
console.error('Register Error:', error); console.error('Register Error:', error);
@@ -83,7 +82,7 @@ export const login = async (req: Request, res: Response): Promise<void> => {
} }
const token = jwt.sign( 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, JWT_SECRET,
{ expiresIn: '7d' } { expiresIn: '7d' }
); );
@@ -91,39 +90,10 @@ export const login = async (req: Request, res: Response): Promise<void> => {
res.status(200).json({ res.status(200).json({
message: 'Login realizado com sucesso', message: 'Login realizado com sucesso',
token, token,
user: { user: { id: user._id, name: user.name, email: user.email, role: user.role, clerkId: user.clerkId }
id: user._id,
name: user.name,
email: user.email,
role: user.role,
externalId: user.externalId,
organizationId: user.organizationId
}
}); });
} catch (error) { } catch (error) {
console.error('Login Error:', error); console.error('Login Error:', error);
res.status(500).json({ error: 'Erro no servidor' }); 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) => { export const createInspection = async (req: Request, res: Response) => {
try { try {
const organizationId = req.appUser?.organizationId; const organizationId = req.appUser?.organizationId;
const createdBy = req.appUser?.externalId; const createdBy = req.appUser?.clerkId;
const inspection = await inspectionService.createInspection({ const inspection = await inspectionService.createInspection({
...req.body, ...req.body,
organizationId, organizationId,
@@ -46,7 +46,7 @@ export const getInspectionsByProject = async (req: Request, res: Response) => {
export const updateInspection = async (req: Request, res: Response) => { export const updateInspection = async (req: Request, res: Response) => {
try { try {
const organizationId = req.appUser?.organizationId; const organizationId = req.appUser?.organizationId;
const userId = req.appUser?.externalId; const userId = req.appUser?.clerkId;
const userRole = req.appUser?.organizationRole || req.appUser?.role; const userRole = req.appUser?.organizationRole || req.appUser?.role;
const isDeveloper = req.appUser?.email === 'admtracksteel@gmail.com'; 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) => { export const deleteInspection = async (req: Request, res: Response) => {
try { try {
const organizationId = req.appUser?.organizationId; const organizationId = req.appUser?.organizationId;
const userId = req.appUser?.externalId; const userId = req.appUser?.clerkId;
const userRole = req.appUser?.organizationRole || req.appUser?.role; const userRole = req.appUser?.organizationRole || req.appUser?.role;
const isDeveloper = req.appUser?.email === 'admtracksteel@gmail.com'; 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) => { export const sendMessage = async (req: Request, res: Response) => {
try { try {
const { toUserId, message } = req.body; const { toUserId, message } = req.body;
const fromUserId = req.appUser?.externalId; const fromUserId = req.appUser?.clerkId;
const organizationId = req.headers['x-organization-id'] as string; const organizationId = req.headers['x-organization-id'] as string;
if (!organizationId) { if (!organizationId) {
@@ -60,7 +60,7 @@ export const sendMessage = async (req: Request, res: Response) => {
// Get unread messages for current user // Get unread messages for current user
export const getUnreadMessages = async (req: Request, res: Response) => { export const getUnreadMessages = async (req: Request, res: Response) => {
try { try {
const toUserId = req.appUser?.externalId; const toUserId = req.appUser?.clerkId;
const organizationId = req.headers['x-organization-id'] as string; const organizationId = req.headers['x-organization-id'] as string;
if (!organizationId) { if (!organizationId) {
@@ -82,7 +82,7 @@ export const getUnreadMessages = async (req: Request, res: Response) => {
// Populate sender info // Populate sender info
const messagesWithSender = await Promise.all( const messagesWithSender = await Promise.all(
messages.map(async (msg) => { messages.map(async (msg) => {
const sender = await OrganizationMember.findOne({ userId: msg.fromUserId }); const sender = await OrganizationMember.findOne({ clerkUserId: msg.fromUserId });
return { return {
...msg.toObject(), ...msg.toObject(),
fromUser: sender ? { name: sender.name, email: sender.email } : null, 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) => { export const markMessageAsRead = async (req: Request, res: Response) => {
try { try {
const { id } = req.params; const { id } = req.params;
const userId = req.appUser?.externalId; const userId = req.appUser?.clerkId;
const organizationId = req.headers['x-organization-id'] as string; const organizationId = req.headers['x-organization-id'] as string;
if (!organizationId) { if (!organizationId) {
@@ -136,7 +136,7 @@ export const markMessageAsRead = async (req: Request, res: Response) => {
// Get my pending (unread) sent messages // Get my pending (unread) sent messages
export const getMyPendingMessages = async (req: Request, res: Response) => { export const getMyPendingMessages = async (req: Request, res: Response) => {
try { try {
const fromUserId = req.appUser?.externalId; const fromUserId = req.appUser?.clerkId;
const organizationId = req.headers['x-organization-id'] as string; const organizationId = req.headers['x-organization-id'] as string;
if (!organizationId) { if (!organizationId) {
@@ -156,7 +156,7 @@ export const getMyPendingMessages = async (req: Request, res: Response) => {
// Populate recipient info // Populate recipient info
const messagesWithRecipient = await Promise.all( const messagesWithRecipient = await Promise.all(
messages.map(async (msg) => { messages.map(async (msg) => {
const recipient = await OrganizationMember.findOne({ userId: msg.toUserId }); const recipient = await OrganizationMember.findOne({ clerkUserId: msg.toUserId });
return { return {
...msg.toObject(), ...msg.toObject(),
toUser: recipient ? { name: recipient.name, email: recipient.email } : null, 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) => { export const deleteMessage = async (req: Request, res: Response) => {
try { try {
const { id } = req.params; const { id } = req.params;
const userId = req.appUser?.externalId; const userId = req.appUser?.clerkId;
const organizationId = req.headers['x-organization-id'] as string; const organizationId = req.headers['x-organization-id'] as string;
if (!organizationId) { if (!organizationId) {
@@ -209,7 +209,7 @@ export const deleteMessage = async (req: Request, res: Response) => {
export const archiveMessage = async (req: Request, res: Response) => { export const archiveMessage = async (req: Request, res: Response) => {
try { try {
const { id } = req.params; const { id } = req.params;
const userId = req.appUser?.externalId; const userId = req.appUser?.clerkId;
const organizationId = req.headers['x-organization-id'] as string; const organizationId = req.headers['x-organization-id'] as string;
const message = await Message.findOne({ _id: id, toUserId: userId, organizationId }); 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) => { export const recipientDeleteMessage = async (req: Request, res: Response) => {
try { try {
const { id } = req.params; const { id } = req.params;
const userId = req.appUser?.externalId; const userId = req.appUser?.clerkId;
const organizationId = req.headers['x-organization-id'] as string; const organizationId = req.headers['x-organization-id'] as string;
const message = await Message.findOne({ _id: id, toUserId: userId, organizationId }); const message = await Message.findOne({ _id: id, toUserId: userId, organizationId });
@@ -5,7 +5,7 @@ export const notificationController = {
getUserNotifications: async (req: Request, res: Response) => { getUserNotifications: async (req: Request, res: Response) => {
try { try {
const organizationId = req.headers['x-organization-id'] as string; 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 // 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. // 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({ const newItem = new StockItem({
organizationId, organizationId,
createdBy: req.appUser?.externalId, createdBy: req.appUser?.clerkId,
dataSheetId, dataSheetId,
rrNumber, rrNumber,
batchNumber, batchNumber,
@@ -86,7 +86,7 @@ export const createStockItem = async (req: AuthRequest, res: Response) => {
// Create Initial Movement (ENTRY) // Create Initial Movement (ENTRY)
await StockMovement.create({ await StockMovement.create({
organizationId, organizationId,
createdBy: req.appUser?.externalId, createdBy: req.appUser?.clerkId,
stockItemId: savedItem._id, stockItemId: savedItem._id,
movementNumber: 1, movementNumber: 1,
type: 'ENTRY', type: 'ENTRY',
@@ -195,7 +195,7 @@ export const adjustStock = async (req: AuthRequest, res: Response) => {
// Register Movement // Register Movement
await StockMovement.create({ await StockMovement.create({
organizationId, organizationId,
createdBy: req.appUser?.externalId, createdBy: req.appUser?.clerkId,
stockItemId: item._id, stockItemId: item._id,
movementNumber, movementNumber,
type: 'ADJUSTMENT', type: 'ADJUSTMENT',
@@ -241,7 +241,7 @@ export const consumeStock = async (req: AuthRequest, res: Response) => {
// Register Movement (Negative quantity for consumption) // Register Movement (Negative quantity for consumption)
await StockMovement.create({ await StockMovement.create({
organizationId, organizationId,
createdBy: req.appUser?.externalId, createdBy: req.appUser?.clerkId,
stockItemId: item._id, stockItemId: item._id,
movementNumber, movementNumber,
type: 'CONSUMPTION', type: 'CONSUMPTION',
@@ -348,7 +348,7 @@ export const updateStockMovement = async (req: AuthRequest, res: Response) => {
const { id } = req.params; // Movement ID const { id } = req.params; // Movement ID
const organizationId = req.appUser?.organizationId; const organizationId = req.appUser?.organizationId;
const userName = req.appUser?.name || req.appUser?.email || 'Unknown User'; 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'; const isAdmin = req.appUser?.role === 'admin' || req.appUser?.organizationRole === 'admin';
if (!isAdmin) { if (!isAdmin) {
@@ -431,7 +431,7 @@ export const deleteStockMovement = async (req: AuthRequest, res: Response) => {
const { id } = req.params; const { id } = req.params;
const organizationId = req.appUser?.organizationId; const organizationId = req.appUser?.organizationId;
const userName = req.appUser?.name || req.appUser?.email || 'Unknown User'; 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'; const isAdmin = req.appUser?.role === 'admin' || req.appUser?.organizationRole === 'admin';
if (!isAdmin) { if (!isAdmin) {
@@ -114,7 +114,7 @@ export const getGlobalOrganizations = async (req: Request, res: Response) => {
name: '$name', name: '$name',
email: '$email', email: '$email',
role: '$role', role: '$role',
userId: '$userId', clerkUserId: '$clerkUserId',
isBanned: '$isBanned' isBanned: '$isBanned'
} }
}, },
@@ -125,7 +125,7 @@ export const getGlobalOrganizations = async (req: Request, res: Response) => {
$lookup: { $lookup: {
from: 'organizations', // Ensure this matches the collection name of Organization model from: 'organizations', // Ensure this matches the collection name of Organization model
localField: '_id', localField: '_id',
foreignField: 'externalId', foreignField: 'clerkId',
as: 'orgDetails' as: 'orgDetails'
} }
}, },
@@ -165,7 +165,7 @@ export const toggleOrganizationBan = async (req: Request, res: Response) => {
// Upsert the Organization record // Upsert the Organization record
const org = await Organization.findOneAndUpdate( const org = await Organization.findOneAndUpdate(
{ externalId: organizationId }, { clerkId: organizationId },
{ isBanned: isBanned }, { isBanned: isBanned },
{ new: true, upsert: true } { 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 * Creates user if doesn't exist, updates if exists
* Also creates/updates OrganizationMember for the current organization * Also creates/updates OrganizationMember for the current organization
*/ */
export const syncUser = async (req: Request, res: Response) => { export const syncUser = async (req: Request, res: Response) => {
console.log('--- syncUser called ---', req.body); console.log('--- syncUser called ---', req.body);
try { try {
const { externalId, email, name, organizationId, clerkRole } = req.body; const { clerkId, email, name, organizationId, clerkRole } = req.body;
if (!externalId || !email || !name) { if (!clerkId || !email || !name) {
return res.status(400).json({ error: 'externalId, email e name são obrigatórios.' }); return res.status(400).json({ error: 'clerkId, email e name são obrigatórios.' });
} }
// 1. Upsert the global User record // 1. Upsert the global User record
let user = await User.findOne({ externalId }); let user = await User.findOne({ clerkId });
if (user) { if (user) {
user.email = email; user.email = email;
@@ -36,7 +36,7 @@ export const syncUser = async (req: Request, res: Response) => {
await user.save(); await user.save();
} else { } else {
user = await User.create({ user = await User.create({
externalId, clerkId,
email, email,
name, name,
role: 'guest', // Default global role role: 'guest', // Default global role
@@ -46,7 +46,7 @@ export const syncUser = async (req: Request, res: Response) => {
if (organizationId) { if (organizationId) {
// Map Auth role to our app role // Map Clerk role to our app role
let appRole: OrgRole = 'guest'; let appRole: OrgRole = 'guest';
if (clerkRole === 'org:admin') { if (clerkRole === 'org:admin') {
appRole = 'admin'; appRole = 'admin';
@@ -57,7 +57,7 @@ export const syncUser = async (req: Request, res: Response) => {
// Use findOneAndUpdate with upsert to handle race conditions atomically // Use findOneAndUpdate with upsert to handle race conditions atomically
// This avoids the need for try/catch on create and handles existing members too // This avoids the need for try/catch on create and handles existing members too
const member = await OrganizationMember.findOneAndUpdate( const member = await OrganizationMember.findOneAndUpdate(
{ userId: externalId, organizationId }, { clerkUserId: clerkId, organizationId },
{ {
$set: { $set: {
name, name,
@@ -65,7 +65,7 @@ export const syncUser = async (req: Request, res: Response) => {
// Only update role if it's the first time (creation) // Only update role if it's the first time (creation)
// Or we can optionally update it if needed. // Or we can optionally update it if needed.
// For now, let's NOT overwrite role on update to preserve local changes, // 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. // Let's use $setOnInsert for fields we only want to set on creation.
}, },
$setOnInsert: { $setOnInsert: {
@@ -106,7 +106,7 @@ export const getCurrentUser = async (req: AuthRequest, res: Response) => {
if (organizationId) { if (organizationId) {
const member = await OrganizationMember.findOne({ const member = await OrganizationMember.findOne({
userId: req.appUser.externalId, clerkUserId: req.appUser.clerkId,
organizationId organizationId
}); });
@@ -141,7 +141,7 @@ export const getAllUsers = async (req: Request, res: Response) => {
} }
const members = await OrganizationMember.find({ organizationId }).sort({ createdAt: -1 }); 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); res.json(members);
} catch (error) { } catch (error) {
console.error('Error getting users:', error); console.error('Error getting users:', error);
@@ -208,7 +208,7 @@ export const toggleBanUser = async (req: AuthRequest, res: Response) => {
} }
// Prevent banning yourself // 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.' }); 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 // Find members of this org
const members = await OrganizationMember.find({ organizationId }); const members = await OrganizationMember.find({ organizationId });
// Get their Auth IDs // Get their Clerk IDs
const externalIds = members.map(m => m.userId); const clerkIds = members.map(m => m.clerkUserId);
// Find Users who were seen recently (2 minutes) // Find Users who were seen recently (2 minutes)
const twoMinutesAgo = new Date(Date.now() - 2 * 60 * 1000); const twoMinutesAgo = new Date(Date.now() - 2 * 60 * 1000);
const activeUsers = await User.find({ const activeUsers = await User.find({
externalId: { $in: externalIds }, clerkId: { $in: clerkIds },
lastSeenAt: { $gte: twoMinutesAgo }, lastSeenAt: { $gte: twoMinutesAgo },
_id: { $ne: currentUserId } // Optional: exclude self _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); res.json(activeUsers);
} catch (error) { } 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 token = authHeader.split(' ')[1];
const decoded = jwt.verify(token, JWT_SECRET) as any; 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 // 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(); next();
} catch (error) { } 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 * Also loads organization-specific role if organization context is provided
*/ */
export const extractUser = async (req: Request, res: Response, next: NextFunction) => { export const extractUser = async (req: Request, res: Response, next: NextFunction) => {
try { 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; const organizationId = req.headers['x-organization-id'] as string;
if (!externalId) { if (!clerkId) {
return next(); // No user, continue without return next(); // No user, continue without
} }
const user = await User.findOne({ externalId }); const user = await User.findOne({ clerkId });
if (user) { if (user) {
if (user.isBanned) { if (user.isBanned) {
@@ -39,12 +39,12 @@ export const extractUser = async (req: Request, res: Response, next: NextFunctio
// Create extended user object // Create extended user object
const appUser: IAppUser = user.toObject() as IAppUser; const appUser: IAppUser = user.toObject() as IAppUser;
appUser.organizationId = organizationId || user.organizationId; appUser.organizationId = organizationId;
// If organization context, get org-specific role // If organization context, get org-specific role
if (organizationId) { if (organizationId) {
// Check if Organization is globally banned (subscription specific, etc.) // 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; const orgName = req.headers['x-organization-name'] ? decodeURIComponent(req.headers['x-organization-name'] as string) : undefined;
if (orgStatus) { if (orgStatus) {
@@ -52,7 +52,7 @@ export const extractUser = async (req: Request, res: Response, next: NextFunctio
if (orgName && orgStatus.name !== orgName) { if (orgName && orgStatus.name !== orgName) {
try { try {
await Organization.updateOne( await Organization.updateOne(
{ externalId: organizationId }, { clerkId: organizationId },
{ name: orgName } { name: orgName }
); );
} catch (err) { } catch (err) {
@@ -69,7 +69,7 @@ export const extractUser = async (req: Request, res: Response, next: NextFunctio
// Create new org with name if present // Create new org with name if present
try { try {
await Organization.create({ await Organization.create({
externalId: organizationId, clerkId: organizationId,
name: orgName name: orgName
}); });
} catch (_e) { } 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) {
if (member.isBanned) { if (member.isBanned) {
return res.status(403).json({ error: 'Acesso bloqueado nesta organização.' }); 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; req.appUser = appUser;
// console.log(`✅ Request authenticated as: ${appUser.name} (${appUser.role})`); // console.log(`✅ Request authenticated as: ${appUser.name} (${appUser.role})`);
} else { } 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(); next();
+1 -1
View File
@@ -2,7 +2,7 @@ import mongoose, { Schema, Document } from 'mongoose';
export interface IInspection extends Document { export interface IInspection extends Document {
organizationId?: string; organizationId?: string;
createdBy?: string; // Auth User ID createdBy?: string; // Clerk User ID
projectId: mongoose.Types.ObjectId; projectId: mongoose.Types.ObjectId;
type: 'painting' | 'surface_treatment'; type: 'painting' | 'surface_treatment';
+2 -2
View File
@@ -2,8 +2,8 @@ import mongoose, { Schema, Document } from 'mongoose';
export interface IMessage extends Document { export interface IMessage extends Document {
organizationId: string; organizationId: string;
fromUserId: string; // externalId do remetente fromUserId: string; // clerkId do remetente
toUserId: string; // externalId do destinatário toUserId: string; // clerkId do destinatário
message: string; message: string;
isRead: boolean; isRead: boolean;
readAt?: Date; readAt?: Date;
+2 -2
View File
@@ -1,7 +1,7 @@
import mongoose, { Schema, Document } from 'mongoose'; import mongoose, { Schema, Document } from 'mongoose';
export interface IOrganization extends Document { export interface IOrganization extends Document {
externalId: string; clerkId: string;
name?: string; name?: string;
isBanned: boolean; isBanned: boolean;
createdAt: Date; createdAt: Date;
@@ -9,7 +9,7 @@ export interface IOrganization extends Document {
} }
const OrganizationSchema: Schema = new Schema({ 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 }, name: { type: String },
isBanned: { type: Boolean, default: false }, isBanned: { type: Boolean, default: false },
}, { timestamps: true }); }, { timestamps: true });
+3 -3
View File
@@ -3,7 +3,7 @@ import mongoose, { Schema, Document } from 'mongoose';
export type OrgRole = 'guest' | 'user' | 'admin'; export type OrgRole = 'guest' | 'user' | 'admin';
export interface IOrganizationMember extends Document { export interface IOrganizationMember extends Document {
userId: string; clerkUserId: string;
organizationId: string; organizationId: string;
role: OrgRole; role: OrgRole;
isBanned: boolean; isBanned: boolean;
@@ -15,7 +15,7 @@ export interface IOrganizationMember extends Document {
} }
const OrganizationMemberSchema: Schema = new Schema({ const OrganizationMemberSchema: Schema = new Schema({
userId: { clerkUserId: {
type: String, type: String,
required: true, required: true,
index: true index: true
@@ -47,6 +47,6 @@ const OrganizationMemberSchema: Schema = new Schema({
}); });
// Compound index for unique user per organization // 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); 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 type UserRole = 'guest' | 'user' | 'admin';
export interface IUser extends Document { export interface IUser extends Document {
externalId: string; clerkId: string;
email: string; email: string;
name: string; name: string;
role: UserRole; role: UserRole;
@@ -16,7 +16,7 @@ export interface IUser extends Document {
} }
const UserSchema: Schema = new Schema({ const UserSchema: Schema = new Schema({
externalId: { clerkId: {
type: String, type: String,
required: true, required: true,
unique: true, unique: true,
+1 -2
View File
@@ -1,10 +1,9 @@
import express from 'express'; import express from 'express';
import { login, register, getMe } from '../controllers/authController.js'; import { login, register } from '../controllers/authController.js';
const router = express.Router(); const router = express.Router();
router.post('/login', login); router.post('/login', login);
router.post('/register', register); router.post('/register', register);
router.get('/me', getMe);
export default router; export default router;
+1 -1
View File
@@ -4,7 +4,7 @@ import { extractUser, requireAdmin } from '../middleware/roleMiddleware.js';
const router = express.Router(); const router = express.Router();
// Sync user from Auth (public - called on login) // Sync user from Clerk (public - called on login)
router.post('/sync', syncUser); router.post('/sync', syncUser);
// Get current user (requires extractUser middleware) // 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 MONGODB_URI = 'mongodb+srv://admtracksteel:29OHAHpKTI8XcCNt@cluster0.a4xiilu.mongodb.net/ts_gpi?retryWrites=true&w=majority&appName=Cluster0';
const UserSchema = new mongoose.Schema({ 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 }, email: { type: String, required: true, unique: true },
passwordHash: { type: String, required: true }, passwordHash: { type: String, required: true },
name: { 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.`); console.log(`✅ Usuário ${email} atualizado para admin e senha definida.`);
} else { } else {
const fakeAuthId = `user_${uuidv4().replace(/-/g, '')}`; const fakeClerkId = `user_${uuidv4().replace(/-/g, '')}`;
await User.create({ await User.create({
externalId: fakeAuthId, clerkId: fakeClerkId,
email, email,
passwordHash: hashedPassword, passwordHash: hashedPassword,
name: 'Admin TrackSteel', name: 'Admin TrackSteel',
role: 'admin', role: 'admin',
organizationId: 'default-org' 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 // Listar todos os usuários
+1 -1
View File
@@ -4,7 +4,7 @@ declare global {
namespace Express { namespace Express {
interface Request { interface Request {
appUser?: IUser; appUser?: IUser;
userId?: string; clerkUserId?: string;
} }
} }
} }