Monorepo for Aesthetic.Computer
aesthetic.computer
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}