mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-24 07:53:09 +00:00
chore: move trip-invite backend to proper DI
Fold the legacy tripInviteService SQL into the Nest TripInviteService over the injected DatabaseService (byte-identical statements; the JS-side expiry check and non-positive-means-no-expiry coercion preserved) and delete the legacy module. No bridge, no MCP port and no plugin-host change needed — nothing outside the container consumed this domain. Also wraps the rotation's probe + write + re-select in db.transaction() per the repo rule that multi-statement writes are never bare. Unit suite moved to tests/unit/nest preserving TRIP-INVITE-001..006 and adding 007 (expiry pinning); the e2e converted to the DI-native pattern (real SQL against temp trips/trip_invite_tokens tables).
This commit is contained in:
@@ -1,83 +0,0 @@
|
||||
import { db } from '../db/database';
|
||||
import crypto from 'crypto';
|
||||
|
||||
/**
|
||||
* Per-trip invite links (#1143).
|
||||
*
|
||||
* A trip has at most ONE invite token (mirrors the public share link: one
|
||||
* rotating token per trip). Unlike the share link, this one is NOT public —
|
||||
* opening it only does something for a logged-in user with an existing account,
|
||||
* who is then added to the trip as a member. Rotating or disabling the link
|
||||
* immediately invalidates the old URL.
|
||||
*/
|
||||
|
||||
export interface TripInviteInfo {
|
||||
token: string;
|
||||
expires_at: string | null;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
/** The current invite link for a trip, or null if none exists. */
|
||||
export function getTripInviteLink(tripId: string | number): TripInviteInfo | null {
|
||||
const row = db
|
||||
.prepare('SELECT token, expires_at, created_at FROM trip_invite_tokens WHERE trip_id = ?')
|
||||
.get(tripId) as { token: string; expires_at: string | null; created_at: string } | undefined;
|
||||
return row ? { token: row.token, expires_at: row.expires_at, created_at: row.created_at } : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the trip's invite link, or rotate it to a fresh token (there is only
|
||||
* ever one row per trip). An optional expiry (days) can bound the link's life.
|
||||
*/
|
||||
export function createOrRotateTripInviteLink(
|
||||
tripId: string | number,
|
||||
createdBy: number,
|
||||
expiresInDays?: number | null,
|
||||
): TripInviteInfo {
|
||||
const token = crypto.randomBytes(24).toString('base64url');
|
||||
const expiresAt =
|
||||
expiresInDays && expiresInDays > 0
|
||||
? new Date(Date.now() + expiresInDays * 86400000).toISOString()
|
||||
: null;
|
||||
|
||||
const existing = db.prepare('SELECT id FROM trip_invite_tokens WHERE trip_id = ?').get(tripId);
|
||||
if (existing) {
|
||||
db.prepare(
|
||||
'UPDATE trip_invite_tokens SET token = ?, expires_at = ?, created_by = ?, created_at = CURRENT_TIMESTAMP WHERE trip_id = ?',
|
||||
).run(token, expiresAt, createdBy, tripId);
|
||||
} else {
|
||||
db.prepare(
|
||||
'INSERT INTO trip_invite_tokens (trip_id, token, created_by, expires_at) VALUES (?, ?, ?, ?)',
|
||||
).run(tripId, token, createdBy, expiresAt);
|
||||
}
|
||||
return getTripInviteLink(tripId)!;
|
||||
}
|
||||
|
||||
/** Remove the trip's invite link entirely (disable). */
|
||||
export function deleteTripInviteLink(tripId: string | number): void {
|
||||
db.prepare('DELETE FROM trip_invite_tokens WHERE trip_id = ?').run(tripId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve an invite token to its (still-existing, unexpired) trip. Returns null
|
||||
* for unknown/expired tokens. Only ever called for an authenticated user, so the
|
||||
* trip title is safe to return for the join confirmation screen; an anonymous
|
||||
* caller never reaches this (the endpoint is JWT-guarded).
|
||||
*/
|
||||
export function resolveTripInvite(token: string): { trip_id: number; title: string } | null {
|
||||
const row = db
|
||||
.prepare(
|
||||
`SELECT t.id AS trip_id, t.title AS title, ti.expires_at AS expires_at
|
||||
FROM trip_invite_tokens ti
|
||||
JOIN trips t ON ti.trip_id = t.id
|
||||
WHERE ti.token = ?`,
|
||||
)
|
||||
.get(token) as { trip_id: number; title: string; expires_at: string | null } | undefined;
|
||||
if (!row) return null;
|
||||
// Check expiry in JS, not SQL: expires_at is stored as an ISO-8601 string
|
||||
// (…T…Z) which does not compare lexicographically against SQLite's
|
||||
// space-separated datetime('now'), so a SQL `>` would keep an expired link
|
||||
// alive until the end of the day. Mirrors the admin-invite validation.
|
||||
if (row.expires_at && new Date(row.expires_at) < new Date()) return null;
|
||||
return { trip_id: row.trip_id, title: row.title };
|
||||
}
|
||||
Reference in New Issue
Block a user