@@ -255,23 +255,31 @@ <h3>🗑️ Delete Job</h3>
255255< div class ="toast " id ="toast "> </ div >
256256
257257< div class ="footer ">
258- © 2025 < a href ="/ "> Simpatico HR Consultancy</ a > AI-Powered Recruitment
258+ © 2025 < a href ="/ "> Simpatico HR Consultancy</ a > • AI-Powered Recruitment
259259</ div >
260260
261261< script >
262262// ---------------------------------------------------------------
263- // CONFIG NO EXPOSED CREDENTIALS
263+ // CONFIG — MULTI-TENANT SAFE
264264// ---------------------------------------------------------------
265- const WORKER_URL = "https://evalis-ai.simpaticohrconsultancy.workers.dev" ;
265+ const WORKER_URL = window . SIMPATICO_CONFIG ?. workerUrl || "https://evalis-ai.simpaticohrconsultancy.workers.dev" ;
266266
267267let allJobs = [ ] ;
268268let currentFilter = 'all' ;
269269let isAdmin = false ;
270270let HR_TOKEN = null ;
271271let HR_USER = null ;
272272
273+ // Get the current tenant/company ID from the logged-in user context
274+ function getCurrentCompanyId ( ) {
275+ try {
276+ const user = HR_USER || JSON . parse ( localStorage . getItem ( 'simpatico_user' ) || '{}' ) ;
277+ return user ?. company_id || user ?. tenant_id || null ;
278+ } catch { return null ; }
279+ }
280+
273281// ---------------------------------------------------------------
274- // API HELPER All calls go through the worker
282+ // API HELPER — All calls go through the worker (tenant-isolated)
275283// ---------------------------------------------------------------
276284async function api ( method , path , body = null , auth = false ) {
277285 const opts = {
@@ -281,11 +289,14 @@ <h3>🗑️ Delete Job</h3>
281289 if ( auth && HR_TOKEN ) {
282290 opts . headers [ 'Authorization' ] = 'Bearer ' + HR_TOKEN ;
283291 }
292+ // Always send tenant context header for backend isolation
293+ const cid = getCurrentCompanyId ( ) ;
294+ if ( cid ) opts . headers [ 'X-Tenant-ID' ] = cid ;
284295 if ( body ) opts . body = JSON . stringify ( body ) ;
285296
286297 const res = await fetch ( WORKER_URL + path , opts ) ;
287298 const data = await res . json ( ) ;
288- if ( ! res . ok ) throw new Error ( data . error || 'HTTP ' + res . status ) ;
299+ if ( ! res . ok ) throw new Error ( data . error ?. message || data . error || 'HTTP ' + res . status ) ;
289300 return data ;
290301}
291302
@@ -297,32 +308,49 @@ <h3>🗑️ Delete Job</h3>
297308 t . textContent = msg ;
298309 t . className = `toast ${ type } show` ;
299310 setTimeout ( ( ) => t . classList . remove ( 'show' ) , 3000 ) ;
300- } // ---------------------------------------------------------------
301- // LOAD JOBS — Public endpoint GET /jobs
311+ }
312+ // ---------------------------------------------------------------
313+ // LOAD JOBS — Tenant-Isolated via Worker API
302314// ---------------------------------------------------------------
303315async function loadJobs ( ) {
304316 const el = document . getElementById ( "jobsList" ) ;
305317 try {
306- const params = new URLSearchParams ( ) ;
307318 const search = document . getElementById ( 'searchInput' ) ?. value ?. trim ( ) ;
308-
309- const SUPABASE_URL = window . SIMPATICO_CONFIG ?. supabaseUrl ;
310- const SUPABASE_ANON_KEY = window . SIMPATICO_CONFIG ?. supabaseAnonKey ;
311-
312- // Default query for open job postings
313- let sbUrl = `${ SUPABASE_URL } /rest/v1/jobs?status=eq.open&select=*,job_applications(count)&order=created_at.desc` ;
314-
315- if ( search ) sbUrl += '&title=ilike.*' + encodeURIComponent ( search ) + '*' ;
316- if ( currentFilter && currentFilter !== 'all' ) sbUrl += '&type=eq.' + currentFilter ;
317-
318- const res = await fetch ( sbUrl , {
319- headers : {
320- apikey : SUPABASE_ANON_KEY ,
321- Authorization : 'Bearer ' + SUPABASE_ANON_KEY
322- }
323- } ) ;
324-
325- const jobs = ( await res . json ( ) ) || [ ] ;
319+ const companyId = getCurrentCompanyId ( ) ;
320+ let jobs = [ ] ;
321+
322+ if ( isAdmin && HR_TOKEN && companyId ) {
323+ // ADMIN MODE: Fetch only this company's jobs via authenticated Worker endpoint
324+ const statusParam = ( currentFilter && currentFilter !== 'all' ) ? currentFilter : 'open' ;
325+ const data = await api ( 'GET' , `/recruitment/jobs?status=${ statusParam } ` , null , true ) ;
326+ jobs = data . jobs || data || [ ] ;
327+ } else if ( companyId ) {
328+ // LOGGED-IN USER: Fetch own company jobs via public endpoint
329+ const data = await api ( 'GET' , `/recruitment/public/jobs?company_id=${ companyId } ` ) ;
330+ jobs = data . jobs || data || [ ] ;
331+ } else {
332+ // PUBLIC VIEW (no company context): Show Simpatico HR's own jobs only
333+ // Use the configured default tenant ID as fallback
334+ const defaultTenant = window . SIMPATICO_CONFIG ?. tenantId || 'SIMP_PRO_MAIN' ;
335+ const data = await api ( 'GET' , `/recruitment/public/jobs?company_id=${ defaultTenant } ` ) ;
336+ jobs = data . jobs || data || [ ] ;
337+ }
338+
339+ // Client-side search filter
340+ if ( search ) {
341+ const q = search . toLowerCase ( ) ;
342+ jobs = jobs . filter ( j =>
343+ ( j . title || '' ) . toLowerCase ( ) . includes ( q ) ||
344+ ( j . department || '' ) . toLowerCase ( ) . includes ( q ) ||
345+ ( j . location || '' ) . toLowerCase ( ) . includes ( q ) ||
346+ ( j . description || '' ) . toLowerCase ( ) . includes ( q )
347+ ) ;
348+ }
349+
350+ // Client-side type filter (only for public/non-admin mode)
351+ if ( ! isAdmin && currentFilter && currentFilter !== 'all' ) {
352+ jobs = jobs . filter ( j => ( j . job_type || j . employment_type || '' ) === currentFilter ) ;
353+ }
326354
327355 if ( ! jobs . length ) {
328356 el . innerHTML = `<div class="empty">
@@ -573,12 +601,21 @@ <h2>${job.title || 'Untitled'}</h2>
573601 } ;
574602
575603 try {
576- await api ( 'POST' , '/db' , {
577- action : 'update' ,
578- table : 'jobs' ,
579- data : updateData ,
580- filters : { id : jobId }
581- } , true ) ;
604+ // ★ TENANT-ISOLATED: Use authenticated Worker endpoint with job ID
605+ // Backend enforces tenant_id check via sbFetch to prevent cross-tenant edits
606+ const res = await fetch ( WORKER_URL + `/recruitment/jobs/${ jobId } ` , {
607+ method : 'PATCH' ,
608+ headers : {
609+ 'Content-Type' : 'application/json' ,
610+ 'Authorization' : 'Bearer ' + HR_TOKEN ,
611+ 'X-Tenant-ID' : getCurrentCompanyId ( ) || ''
612+ } ,
613+ body : JSON . stringify ( updateData )
614+ } ) ;
615+ if ( ! res . ok ) {
616+ const err = await res . json ( ) . catch ( ( ) => ( { } ) ) ;
617+ throw new Error ( err . error ?. message || err . error || 'Update failed' ) ;
618+ }
582619
583620 showToast ( "✅ Job updated!" , "success" ) ;
584621 closeEditModal ( ) ;
@@ -605,9 +642,20 @@ <h2>${job.title || 'Untitled'}</h2>
605642 btn . disabled = true ; btn . textContent = '⏳...' ;
606643
607644 try {
608- await api ( 'POST' , '/db' , {
609- action : 'delete' , table : 'jobs' , filters : { id : jobId }
610- } , true ) ;
645+ // ★ TENANT-ISOLATED: Use authenticated Worker endpoint
646+ // Backend enforces tenant_id check via sbFetch to prevent cross-tenant deletes
647+ const res = await fetch ( WORKER_URL + `/recruitment/jobs/${ jobId } ` , {
648+ method : 'DELETE' ,
649+ headers : {
650+ 'Content-Type' : 'application/json' ,
651+ 'Authorization' : 'Bearer ' + HR_TOKEN ,
652+ 'X-Tenant-ID' : getCurrentCompanyId ( ) || ''
653+ }
654+ } ) ;
655+ if ( ! res . ok ) {
656+ const err = await res . json ( ) . catch ( ( ) => ( { } ) ) ;
657+ throw new Error ( err . error ?. message || err . error || 'Delete failed' ) ;
658+ }
611659
612660 showToast ( "🗑️ Job deleted" , "success" ) ;
613661 closeDeleteModal ( ) ;
0 commit comments