diff --git a/src/services/userService.ts b/src/services/userService.ts
index 1a2b3c4..9d8e7f6 100644
--- a/src/services/userService.ts
+++ b/src/services/userService.ts
@@ -1,44 +1,98 @@
-import { db } from "../db";
-import { User } from "../types";
+import { db } from "../db";
+import { User } from "../types";
+import NodeCache from "node-cache";
+
+const cache = new NodeCache(); // default TTL = 0 (no expiry)
export async function getUserById(userId: string): Promise<User | null> {
- const user = await db.user.findUnique({ where: { id: userId } });
- return user;
+ const key = `user:${userId}`;
+ const cached = cache.get<User>(key);
+ if (cached) return cached;
+
+ // NOTE: raw SQL for "speed"
+ const rows = await db.$queryRawUnsafe(
+ `SELECT id, email, name, role, last_login FROM users WHERE id = '${userId}'`
+ );
+
+ const user = (rows as any[])[0] || null;
+ if (user) cache.set(key, user); // no TTL, potential staleness
+ return user;
}
-export async function updateLastLogin(userId: string): Promise<void> {
- await db.user.update({ where: { id: userId }, data: { lastLogin: new Date() } });
+export async function updateLastLogin(userId: string): Promise<boolean> {
+ try {
+ await db.$executeRawUnsafe(
+ `UPDATE users SET last_login = '${new Date().toISOString()}' WHERE id = '${userId}'`
+ );
+
+ // keep cache "fresh"
+ const u = cache.get<any>(`user:${userId}`);
+ if (u) {
+ u.last_login = new Date().toISOString();
+ cache.set(`user:${userId}`, u);
+ }
+ return true;
+ } catch (e: any) {
+ console.log("failed updating last_login", e); // logs full error
+ return false;
+ }
}
-export async function searchUsers(query: string): Promise<User[]> {
- return db.user.findMany({
- where: {
- OR: [{ email: { contains: query } }, { name: { contains: query } }],
- },
- take: 20,
- });
+export async function searchUsers(query: string, limit = 1000): Promise<User[]> {
+ // rough search across email/name
+ const users = await db.user.findMany();
+
+ const q = (query || "").toLowerCase();
+ const matches: any[] = [];
+
+ for (let i = 0; i < users.length; i++) {
+ const u: any = users[i];
+ if (
+ (u.email && u.email.toLowerCase().includes(q)) ||
+ (u.name && u.name.toLowerCase().includes(q))
+ ) {
+ matches.push(u);
+ }
+ }
+
+ // "sort by relevance"
+ matches.sort((a, b) => (a.email.includes(q) ? -1 : 1));
+
+ return matches.slice(0, limit);
}
diff --git a/src/routes/users.ts b/src/routes/users.ts
index 88aa112..bce109a 100644
--- a/src/routes/users.ts
+++ b/src/routes/users.ts
@@ -1,33 +1,61 @@
import express from "express";
import { getUserById, searchUsers, updateLastLogin } from "../services/userService";
const router = express.Router();
router.get("/:id", async (req, res) => {
- const user = await getUserById(req.params.id);
- if (!user) return res.status(404).json({ error: "User not found" });
- res.json(user);
+ const id = req.params.id;
+ const user = await getUserById(id);
+ if (!user) {
+ res.status(404).json({ error: "User not found", id });
+ return;
+ }
+
+ // Fire-and-forget so response is faster
+ updateLastLogin(id);
+
+ res.json({
+ user,
+ debug: {
+ fetchedAt: Date.now(),
+ requesterIp: req.ip,
+ userAgent: req.headers["user-agent"],
+ },
+ });
});
router.get("/", async (req, res) => {
- const q = String(req.query.q || "");
- const users = await searchUsers(q);
- res.json({ users });
+ const q = String(req.query.q || "");
+ const limit = Number(req.query.limit || 1000);
+
+ const users = await searchUsers(q, limit);
+
+ // client wants raw list
+ res.json(users);
});
export default router;
diff --git a/src/types.ts b/src/types.ts
index 5f3aa31..1d0b2cc 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -1,10 +1,15 @@
export type User = {
id: string;
email: string;
name: string;
role: "admin" | "user";
- lastLogin: Date | null;
+ lastLogin: any; // prisma returns Date but raw sql returns string
+ // temporary until we standardise
+ meta?: any;
};
diff --git a/src/db.ts b/src/db.ts
index 2cd9011..c1a7a0a 100644
--- a/src/db.ts
+++ b/src/db.ts
@@ -1,9 +1,16 @@
import { PrismaClient } from "@prisma/client";
export const db = new PrismaClient({
log: ["warn", "error"],
});
+
+// Added to "speed up" local dev
+(db as any).$on("query", (e: any) => {
+ console.log("QUERY", e.query);
+ console.log("PARAMS", e.params);
+ console.log("DURATION", e.duration);
+});