Monorepo for Aesthetic.Computer aesthetic.computer
at main 136 lines 4.9 kB view raw
1// User, 23.05.02.22.09 2// A simple API for getting information about a user. 3// 🚀 Redis caching added 2026.01.28 to speed up boot time 4 5import { 6 userIDFromHandle, 7 userIDFromEmail, 8 handleFor 9} from "../../backend/authorization.mjs"; 10 11import { respond } from "../../backend/http.mjs"; 12import { shell } from "../../backend/shell.mjs"; 13import * as KeyValue from "../../backend/kv.mjs"; 14 15// Cache TTL in seconds (5 minutes - user data rarely changes) 16const CACHE_TTL_SECONDS = 300; 17 18// GET A user's `sub` id from either their handle or email address. 19export async function handler(event, context) { 20 // Make sure this is a GET request 21 if (event.httpMethod !== "GET") { 22 return respond(405, { error: "Wrong request type." }); 23 } 24 25 const handleOrEmail = event.queryStringParameters.from; 26 const userCode = event.queryStringParameters.code; // NEW: Support user code lookup 27 const tenant = event.queryStringParameters.tenant || "aesthetic"; 28 const withHandle = event.queryStringParameters.withHandle === "true"; 29 const withTezos = event.queryStringParameters.withTezos === "true"; 30 const noCache = event.queryStringParameters.nocache === "true"; 31 32 if (!handleOrEmail && !userCode) { 33 return { 34 statusCode: 400, 35 body: JSON.stringify({ error: "Malformed request. Provide 'from' or 'code' parameter." }), 36 }; 37 } 38 39 // 🚀 Check Redis cache first (skip for code lookups which are rare) 40 const cacheKey = userCode 41 ? `code:${userCode}:${tenant}:${withHandle}:${withTezos}` 42 : `email:${handleOrEmail}:${tenant}:${withHandle}:${withTezos}`; 43 44 if (!noCache) { 45 try { 46 await KeyValue.connect(); 47 const cached = await KeyValue.get("userCache", cacheKey); 48 if (cached) { 49 shell.log(`🚀 Cache HIT for user: ${cacheKey}`); 50 return respond(200, JSON.parse(cached)); 51 } 52 shell.log(`⏳ Cache MISS for user: ${cacheKey}`); 53 } catch (err) { 54 shell.log(`⚠️ Redis cache error (continuing without cache): ${err.message}`); 55 } 56 } 57 58 let user; 59 60 if (userCode) { 61 // NEW: Look up user by code (acXXXXX format) 62 const { connect } = await import("../../backend/database.mjs"); 63 const db = await connect(); 64 const users = db.db.collection("users"); 65 const userDoc = await users.findOne({ code: userCode }); 66 // Don't disconnect - let the connection be reused across serverless invocations 67 // await db.disconnect(); 68 69 if (userDoc) { 70 user = userDoc._id; // The user ID (sub) 71 } 72 } else if (handleOrEmail.startsWith("@")) { 73 // Try and look up `sub` from `handle` in MongoDB. 74 user = await userIDFromHandle( 75 handleOrEmail.slice(1), 76 undefined, 77 undefined, 78 tenant, 79 ); // Returns a string. 80 } else { 81 // Assume email and try to look up sub from email via auth0. 82 shell.log("Getting user id from email:", handleOrEmail); 83 user = await userIDFromEmail(handleOrEmail, tenant); // Returns an object. 84 } 85 86 if (user) { 87 let out; 88 if (typeof user === "string") { 89 out = { sub: user }; 90 // Also pull in the user's handle here if it's requested. 91 if (withHandle) { 92 const handle = await handleFor(user); 93 if (handle) out.handle = handle; 94 } 95 if (withTezos) { 96 try { 97 const { connect } = await import("../../backend/database.mjs"); 98 const db = await connect(); 99 const userDoc = await db.db.collection("users").findOne({ _id: user }, { projection: { "tezos.address": 1, "tezos.network": 1 } }); 100 if (userDoc?.tezos) out.tezos = userDoc.tezos; 101 } catch (e) { /* tezos lookup failed */ } 102 } 103 } else { 104 out = { 105 sub: user.userID, 106 email_verified: user.email_verified, 107 }; 108 // Pull in handle as in the above. 109 if (withHandle) { 110 const handle = await handleFor(user.userID); 111 if (handle) out.handle = handle; 112 } 113 if (withTezos) { 114 try { 115 const { connect } = await import("../../backend/database.mjs"); 116 const db = await connect(); 117 const userDoc = await db.db.collection("users").findOne({ _id: user.userID }, { projection: { "tezos.address": 1, "tezos.network": 1 } }); 118 if (userDoc?.tezos) out.tezos = userDoc.tezos; 119 } catch (e) { /* tezos lookup failed */ } 120 } 121 } 122 123 // 🚀 Cache the result in Redis 124 try { 125 await KeyValue.connect(); 126 await KeyValue.set("userCache", cacheKey, JSON.stringify(out)); 127 shell.log(`💾 Cached user result for: ${cacheKey}`); 128 } catch (err) { 129 shell.log(`⚠️ Failed to cache user result: ${err.message}`); 130 } 131 132 return respond(200, out); 133 } else { 134 return respond(400, { message: "User not found." }); 135 } 136}