add user info, add table drop for dev env, fix invite route
All checks were successful
Code quality checks / biome (push) Successful in 8s
All checks were successful
Code quality checks / biome (push) Successful in 8s
This commit is contained in:
parent
320e2cc121
commit
0cb7ebb245
11 changed files with 209 additions and 21 deletions
|
@ -64,14 +64,14 @@ async function setup(): Promise<void> {
|
||||||
}
|
}
|
||||||
|
|
||||||
setup()
|
setup()
|
||||||
.catch((error: Error) => {
|
.catch((error) => {
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(async () => {
|
||||||
cassandra.shutdown().catch((error: Error) => {
|
try {
|
||||||
logger.error(["Error shutting down Cassandra client:", error]);
|
await cassandra.shutdown();
|
||||||
});
|
} catch (error) {
|
||||||
|
logger.error(["Error shutting down Cassandra client:", error as Error]);
|
||||||
process.exit(0);
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -13,7 +13,7 @@ async function createTable() {
|
||||||
|
|
||||||
system_channel_id TEXT,
|
system_channel_id TEXT,
|
||||||
rules_channel_id TEXT,
|
rules_channel_id TEXT,
|
||||||
anouncements_channel_id TEXT,
|
announcements_channel_id TEXT,
|
||||||
|
|
||||||
created_at TIMESTAMP,
|
created_at TIMESTAMP,
|
||||||
updated_at TIMESTAMP
|
updated_at TIMESTAMP
|
||||||
|
@ -31,4 +31,16 @@ async function createTable() {
|
||||||
`);
|
`);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { createTable };
|
async function dropTable() {
|
||||||
|
const client = cassandra.getClient();
|
||||||
|
|
||||||
|
await client.execute(`
|
||||||
|
DROP TABLE IF EXISTS guilds;
|
||||||
|
`);
|
||||||
|
|
||||||
|
await client.execute(`
|
||||||
|
DROP TABLE IF EXISTS guilds_by_owner;
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { createTable, dropTable };
|
||||||
|
|
|
@ -17,4 +17,12 @@ async function createTable() {
|
||||||
`);
|
`);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { createTable };
|
async function dropTable() {
|
||||||
|
const client = cassandra.getClient();
|
||||||
|
|
||||||
|
await client.execute(`
|
||||||
|
DROP TABLE IF EXISTS guild_invites;
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { createTable, dropTable };
|
||||||
|
|
|
@ -29,4 +29,16 @@ async function createTable() {
|
||||||
`);
|
`);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { createTable };
|
async function dropTable() {
|
||||||
|
const client = cassandra.getClient();
|
||||||
|
|
||||||
|
await client.execute(`
|
||||||
|
DROP TABLE IF EXISTS guild_members;
|
||||||
|
`);
|
||||||
|
|
||||||
|
await client.execute(`
|
||||||
|
DROP TABLE IF EXISTS members_by_user;
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { createTable, dropTable };
|
||||||
|
|
|
@ -24,4 +24,18 @@ async function createTable() {
|
||||||
`);
|
`);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { createTable };
|
async function dropTable() {
|
||||||
|
await cassandra.getClient().execute(`
|
||||||
|
DROP TABLE IF EXISTS users;
|
||||||
|
`);
|
||||||
|
|
||||||
|
await cassandra.getClient().execute(`
|
||||||
|
DROP INDEX IF EXISTS users_username_idx;
|
||||||
|
`);
|
||||||
|
|
||||||
|
await cassandra.getClient().execute(`
|
||||||
|
DROP INDEX IF EXISTS users_email_idx;
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { createTable, dropTable };
|
||||||
|
|
64
config/setup/teardown.ts
Normal file
64
config/setup/teardown.ts
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
import { readdir, stat } from "node:fs/promises";
|
||||||
|
import { extname, join, resolve } from "node:path";
|
||||||
|
import { pathToFileURL } from "node:url";
|
||||||
|
import { cassandra as cassandraConfig, verifyRequiredVariables } from "@config";
|
||||||
|
import { logger } from "@creations.works/logger";
|
||||||
|
import { cassandra } from "@lib/cassandra";
|
||||||
|
|
||||||
|
async function dropTables(dir: string): Promise<void> {
|
||||||
|
const entries = await readdir(dir);
|
||||||
|
|
||||||
|
for (const entry of entries) {
|
||||||
|
const fullPath = join(dir, entry);
|
||||||
|
const stats = await stat(fullPath);
|
||||||
|
|
||||||
|
if (stats.isDirectory()) {
|
||||||
|
await dropTables(fullPath);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (extname(entry) !== ".ts") continue;
|
||||||
|
|
||||||
|
const modulePath = pathToFileURL(fullPath).href;
|
||||||
|
const mod = await import(modulePath);
|
||||||
|
|
||||||
|
if (typeof mod.dropTable === "function") {
|
||||||
|
await mod.dropTable();
|
||||||
|
logger.info(`Ran dropTable from ${fullPath}`);
|
||||||
|
} else {
|
||||||
|
logger.warn(`No dropTable export found in ${fullPath}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function teardown(): Promise<void> {
|
||||||
|
verifyRequiredVariables();
|
||||||
|
await cassandra.connect({ withKeyspace: true });
|
||||||
|
|
||||||
|
const keyspace = cassandraConfig.keyspace;
|
||||||
|
|
||||||
|
if (!keyspace) {
|
||||||
|
logger.error("No Cassandra keyspace configured in environment.");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info(`Dropping all tables in keyspace "${keyspace}"...`);
|
||||||
|
|
||||||
|
const tablesDir = resolve("config", "setup", "tables");
|
||||||
|
await dropTables(tablesDir);
|
||||||
|
|
||||||
|
logger.info("Teardown complete.");
|
||||||
|
}
|
||||||
|
|
||||||
|
teardown()
|
||||||
|
.catch((error) => {
|
||||||
|
logger.error(error);
|
||||||
|
process.exit(1);
|
||||||
|
})
|
||||||
|
.finally(async () => {
|
||||||
|
try {
|
||||||
|
await cassandra.shutdown();
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(["Error shutting down Cassandra client:", error as Error]);
|
||||||
|
}
|
||||||
|
});
|
|
@ -8,7 +8,8 @@
|
||||||
"lint": "bunx biome check",
|
"lint": "bunx biome check",
|
||||||
"lint:fix": "bunx biome check --fix",
|
"lint:fix": "bunx biome check --fix",
|
||||||
"cleanup": "rm -rf logs node_modules bun.lockdb",
|
"cleanup": "rm -rf logs node_modules bun.lockdb",
|
||||||
"setup": "bun run config/setup/index.ts"
|
"setup": "bun run config/setup/index.ts",
|
||||||
|
"teardown": "bun run config/setup/teardown.ts"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/bun": "^1.2.11",
|
"@types/bun": "^1.2.11",
|
||||||
|
|
|
@ -18,7 +18,7 @@ async function handler(request: ExtendedRequest): Promise<Response> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const { id: invite } = request.params;
|
const { invite } = request.params;
|
||||||
|
|
||||||
if (!invite) {
|
if (!invite) {
|
||||||
return jsonResponse(400, {
|
return jsonResponse(400, {
|
||||||
|
@ -88,7 +88,7 @@ async function handler(request: ExtendedRequest): Promise<Response> {
|
||||||
`INSERT INTO guild_members (
|
`INSERT INTO guild_members (
|
||||||
guild_id, user_id, roles, joined_at, is_banned, invite_id
|
guild_id, user_id, roles, joined_at, is_banned, invite_id
|
||||||
) VALUES (?, ?, ?, ?, ?, ?)`,
|
) VALUES (?, ?, ?, ?, ?, ?)`,
|
||||||
[guildId, user.id, ["member"], now, false, invite],
|
[guildId, user.id, [], now, false, invite],
|
||||||
{ prepare: true },
|
{ prepare: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -96,7 +96,7 @@ async function handler(request: ExtendedRequest): Promise<Response> {
|
||||||
`INSERT INTO members_by_user (
|
`INSERT INTO members_by_user (
|
||||||
user_id, guild_id, roles, joined_at, is_banned, invite_id
|
user_id, guild_id, roles, joined_at, is_banned, invite_id
|
||||||
) VALUES (?, ?, ?, ?, ?, ?)`,
|
) VALUES (?, ?, ?, ?, ?, ?)`,
|
||||||
[user.id, guildId, ["member"], now, false, invite],
|
[user.id, guildId, [], now, false, invite],
|
||||||
{ prepare: true },
|
{ prepare: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -117,7 +117,6 @@ async function handler(request: ExtendedRequest): Promise<Response> {
|
||||||
message: "Joined guild successfully",
|
message: "Joined guild successfully",
|
||||||
data: {
|
data: {
|
||||||
guild_id: guildId,
|
guild_id: guildId,
|
||||||
role: "member",
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
67
src/routes/user/[id].ts
Normal file
67
src/routes/user/[id].ts
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
import { cassandra } from "@lib/cassandra";
|
||||||
|
import { jsonResponse } from "@lib/http";
|
||||||
|
|
||||||
|
function toSafeUser(user: User, sessionId?: string): UserSafe | UserPrivate {
|
||||||
|
const base: UserSafe = {
|
||||||
|
id: user.id,
|
||||||
|
username: user.username,
|
||||||
|
display_name: user.display_name,
|
||||||
|
avatar_url: user.avatar_url,
|
||||||
|
is_verified: user.is_verified,
|
||||||
|
created_at: user.created_at,
|
||||||
|
updated_at: user.updated_at,
|
||||||
|
};
|
||||||
|
|
||||||
|
return sessionId === user.id ? { ...base, email: user.email } : base;
|
||||||
|
}
|
||||||
|
|
||||||
|
const routeDef: RouteDef = {
|
||||||
|
method: "GET",
|
||||||
|
accepts: "*/*",
|
||||||
|
returns: "application/json;charset=utf-8",
|
||||||
|
};
|
||||||
|
|
||||||
|
async function handler(request: ExtendedRequest): Promise<Response> {
|
||||||
|
const { id: userId } = request.params;
|
||||||
|
|
||||||
|
if (!userId) {
|
||||||
|
return jsonResponse(400, { error: "Missing user ID in request." });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const client = cassandra.getClient();
|
||||||
|
const query = `
|
||||||
|
SELECT id, username, display_name, email, avatar_url, is_verified, created_at, updated_at
|
||||||
|
FROM users
|
||||||
|
WHERE id = ?
|
||||||
|
`;
|
||||||
|
const result = await client.execute(query, [userId], { prepare: true });
|
||||||
|
|
||||||
|
if (result.rowLength === 0) {
|
||||||
|
return jsonResponse(404, {
|
||||||
|
error: "User not found.",
|
||||||
|
message: "No user exists with the given ID.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const row = result.first();
|
||||||
|
const user: User = {
|
||||||
|
id: row.id,
|
||||||
|
username: row.username,
|
||||||
|
display_name: row.display_name,
|
||||||
|
email: row.email,
|
||||||
|
password: "", // this is never actually used or returned
|
||||||
|
avatar_url: row.avatar_url,
|
||||||
|
is_verified: row.is_verified,
|
||||||
|
created_at: row.created_at,
|
||||||
|
updated_at: row.updated_at,
|
||||||
|
};
|
||||||
|
|
||||||
|
return jsonResponse(200, { user: toSafeUser(user, request.session?.id) });
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
return jsonResponse(500, { error: "Internal server error." });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { handler, routeDef };
|
|
@ -17,8 +17,7 @@ async function handler(request: ExtendedRequest): Promise<Response> {
|
||||||
await redis.del(key);
|
await redis.del(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Response(null, {
|
return jsonResponse(204, {
|
||||||
status: 204,
|
|
||||||
headers: {
|
headers: {
|
||||||
"Set-Cookie":
|
"Set-Cookie":
|
||||||
"session=; Max-Age=0; Path=/; HttpOnly; Secure; SameSite=Strict",
|
"session=; Max-Age=0; Path=/; HttpOnly; Secure; SameSite=Strict",
|
||||||
|
@ -43,8 +42,7 @@ async function handler(request: ExtendedRequest): Promise<Response> {
|
||||||
|
|
||||||
await sessionManager.invalidateSession(request);
|
await sessionManager.invalidateSession(request);
|
||||||
|
|
||||||
return new Response(null, {
|
return jsonResponse(204, {
|
||||||
status: 204,
|
|
||||||
headers: {
|
headers: {
|
||||||
"Set-Cookie":
|
"Set-Cookie":
|
||||||
"session=; Max-Age=0; Path=/; HttpOnly; Secure; SameSite=Strict",
|
"session=; Max-Age=0; Path=/; HttpOnly; Secure; SameSite=Strict",
|
||||||
|
|
13
types/tables/user.d.ts
vendored
13
types/tables/user.d.ts
vendored
|
@ -14,3 +14,16 @@ type UserInsert = Pick<
|
||||||
User,
|
User,
|
||||||
"id" | "username" | "email" | "password" | "created_at" | "updated_at"
|
"id" | "username" | "email" | "password" | "created_at" | "updated_at"
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
type UserSafe = Pick<
|
||||||
|
User,
|
||||||
|
| "id"
|
||||||
|
| "username"
|
||||||
|
| "display_name"
|
||||||
|
| "avatar_url"
|
||||||
|
| "is_verified"
|
||||||
|
| "created_at"
|
||||||
|
| "updated_at"
|
||||||
|
>;
|
||||||
|
|
||||||
|
type UserPrivate = Omit<User, "password">;
|
||||||
|
|
Loading…
Add table
Reference in a new issue