Brak opisu
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. // Data-access layer (Phase 1).
  2. // All SQL lives here, never in route/signaling handlers. This decouples the rest of
  3. // the app from SQLite so the store can later move to Postgres without touching callers.
  4. //
  5. // TENANT ABSTRACTION: a "tenant" currently maps 1:1 to a team (column `team_id`).
  6. // Repo signatures take `tenantId` so that when the tenant is later elevated to a
  7. // first-class Organization (Phase 3), callers and the API/auth built on top stay unchanged.
  8. const db = require('./db');
  9. const A = require('./auth');
  10. const now = () => Date.now();
  11. const teams = {
  12. first: () => db.prepare('SELECT * FROM teams LIMIT 1').get(),
  13. byId: (id) => db.prepare('SELECT * FROM teams WHERE id=?').get(id),
  14. create: (name) => {
  15. const id = A.id();
  16. db.prepare('INSERT INTO teams (id,name,created_at) VALUES (?,?,?)').run(id, name, now());
  17. return db.prepare('SELECT * FROM teams WHERE id=?').get(id);
  18. },
  19. };
  20. const users = {
  21. anyExists: () => !!db.prepare('SELECT 1 FROM users LIMIT 1').get(),
  22. byId: (id) => db.prepare('SELECT * FROM users WHERE id=?').get(id),
  23. byEmail: (email) => db.prepare('SELECT * FROM users WHERE email=? COLLATE NOCASE').get(email),
  24. emailExists: (email) => !!db.prepare('SELECT 1 FROM users WHERE email=? COLLATE NOCASE').get(email),
  25. listByTenant: (tenantId) =>
  26. db.prepare('SELECT id,email,name,role,active,created_at FROM users WHERE team_id=?').all(tenantId),
  27. inTenant: (id, tenantId) =>
  28. db.prepare('SELECT * FROM users WHERE id=? AND team_id=?').get(id, tenantId),
  29. create: ({ tenantId, email, hash, salt, role, name, mfaSecret }) => {
  30. const id = A.id();
  31. db.prepare(`INSERT INTO users (id,team_id,email,pw_hash,pw_salt,role,name,mfa_secret,mfa_enabled,created_at)
  32. VALUES (?,?,?,?,?,?,?,?,0,?)`)
  33. .run(id, tenantId, email, hash, salt, role, name || null, mfaSecret, now());
  34. return id;
  35. },
  36. enableMfa: (id) => db.prepare('UPDATE users SET mfa_enabled=1 WHERE id=?').run(id),
  37. setName: (id, name) => db.prepare('UPDATE users SET name=? WHERE id=?').run(name, id),
  38. setRole: (id, role) => db.prepare('UPDATE users SET role=? WHERE id=?').run(role, id),
  39. setPassword: (id, hash, salt) => db.prepare('UPDATE users SET pw_hash=?, pw_salt=? WHERE id=?').run(hash, salt, id),
  40. setActive: (id, active) => db.prepare('UPDATE users SET active=? WHERE id=?').run(active ? 1 : 0, id),
  41. remove: (id) => db.prepare('DELETE FROM users WHERE id=?').run(id),
  42. };
  43. const authSessions = {
  44. byToken: (token) => db.prepare('SELECT * FROM sessions_auth WHERE token=?').get(token),
  45. create: ({ token, userId, mfaPassed, ttl }) =>
  46. db.prepare('INSERT INTO sessions_auth (token,user_id,mfa_passed,created_at,expires_at) VALUES (?,?,?,?,?)')
  47. .run(token, userId, mfaPassed ? 1 : 0, now(), now() + ttl),
  48. markMfaPassed: (token) => db.prepare('UPDATE sessions_auth SET mfa_passed=1 WHERE token=?').run(token),
  49. deleteByToken: (token) => db.prepare('DELETE FROM sessions_auth WHERE token=?').run(token),
  50. deleteByUser: (userId) => db.prepare('DELETE FROM sessions_auth WHERE user_id=?').run(userId),
  51. };
  52. const machines = {
  53. byEnrollToken: (t) => db.prepare('SELECT * FROM machines WHERE enroll_token=?').get(t),
  54. inTenant: (id, tenantId) => db.prepare('SELECT * FROM machines WHERE id=? AND team_id=?').get(id, tenantId),
  55. listByTenant: (tenantId) =>
  56. db.prepare('SELECT id,name,unattended,last_seen FROM machines WHERE team_id=?').all(tenantId),
  57. create: ({ tenantId, name, enrollToken, unattended }) => {
  58. const id = A.id();
  59. db.prepare('INSERT INTO machines (id,team_id,name,enroll_token,unattended,created_at) VALUES (?,?,?,?,?,?)')
  60. .run(id, tenantId, name, enrollToken, unattended ? 1 : 0, now());
  61. return id;
  62. },
  63. touch: (id) => db.prepare('UPDATE machines SET last_seen=? WHERE id=?').run(now(), id),
  64. };
  65. const audit = {
  66. add: (e) =>
  67. db.prepare(`INSERT INTO audit_log (team_id,user_id,user_email,machine_id,machine_name,action,detail,at)
  68. VALUES (@team_id,@user_id,@user_email,@machine_id,@machine_name,@action,@detail,@at)`)
  69. .run({
  70. team_id: e.team_id, user_id: e.user_id || null, user_email: e.user_email || null,
  71. machine_id: e.machine_id || null, machine_name: e.machine_name || null,
  72. action: e.action, detail: e.detail || null, at: now(),
  73. }),
  74. listByTenant: (tenantId) =>
  75. db.prepare("SELECT * FROM audit_log WHERE team_id=? OR team_id='adhoc' ORDER BY at DESC LIMIT 200").all(tenantId),
  76. };
  77. const sessionsLog = {
  78. byId: (id) => db.prepare('SELECT * FROM sessions_log WHERE id=?').get(id),
  79. byIdInTenant: (id, tenantId) => db.prepare('SELECT * FROM sessions_log WHERE id=? AND team_id=?').get(id, tenantId),
  80. create: ({ id, tenantId, agentEmail, agentName, ticket }) =>
  81. db.prepare('INSERT INTO sessions_log (id,team_id,agent_email,agent_name,ticket,started_at) VALUES (?,?,?,?,?,?)')
  82. .run(id, tenantId, agentEmail, agentName, ticket || null, now()),
  83. end: (id) => db.prepare('UPDATE sessions_log SET ended_at=? WHERE id=? AND ended_at IS NULL').run(now(), id),
  84. setRecording: (id, fname) => db.prepare('UPDATE sessions_log SET recording=? WHERE id=?').run(fname, id),
  85. setTranscript: (id, fname) => db.prepare('UPDATE sessions_log SET transcript=? WHERE id=?').run(fname, id),
  86. // Role-scoping is the caller's job: pass agentEmail to restrict to one agent (non-admins).
  87. report: ({ tenantId, agentEmail, from, to }) => {
  88. let sql = 'SELECT * FROM sessions_log WHERE team_id=?';
  89. const args = [tenantId];
  90. if (agentEmail) { sql += ' AND agent_email=?'; args.push(agentEmail); }
  91. if (from) { sql += ' AND started_at>=?'; args.push(from); }
  92. if (to) { sql += ' AND started_at<=?'; args.push(to); }
  93. sql += ' ORDER BY started_at DESC LIMIT 500';
  94. return db.prepare(sql).all(...args);
  95. },
  96. };
  97. module.exports = { teams, users, authSessions, machines, audit, sessionsLog };