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()
|
||||
.catch((error: Error) => {
|
||||
.catch((error) => {
|
||||
logger.error(error);
|
||||
process.exit(1);
|
||||
})
|
||||
.finally(() => {
|
||||
cassandra.shutdown().catch((error: Error) => {
|
||||
logger.error(["Error shutting down Cassandra client:", error]);
|
||||
});
|
||||
|
||||
process.exit(0);
|
||||
.finally(async () => {
|
||||
try {
|
||||
await cassandra.shutdown();
|
||||
} catch (error) {
|
||||
logger.error(["Error shutting down Cassandra client:", error as Error]);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -13,7 +13,7 @@ async function createTable() {
|
|||
|
||||
system_channel_id TEXT,
|
||||
rules_channel_id TEXT,
|
||||
anouncements_channel_id TEXT,
|
||||
announcements_channel_id TEXT,
|
||||
|
||||
created_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:fix": "bunx biome check --fix",
|
||||
"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": {
|
||||
"@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) {
|
||||
return jsonResponse(400, {
|
||||
|
@ -88,7 +88,7 @@ async function handler(request: ExtendedRequest): Promise<Response> {
|
|||
`INSERT INTO guild_members (
|
||||
guild_id, user_id, roles, joined_at, is_banned, invite_id
|
||||
) VALUES (?, ?, ?, ?, ?, ?)`,
|
||||
[guildId, user.id, ["member"], now, false, invite],
|
||||
[guildId, user.id, [], now, false, invite],
|
||||
{ prepare: true },
|
||||
);
|
||||
|
||||
|
@ -96,7 +96,7 @@ async function handler(request: ExtendedRequest): Promise<Response> {
|
|||
`INSERT INTO members_by_user (
|
||||
user_id, guild_id, roles, joined_at, is_banned, invite_id
|
||||
) VALUES (?, ?, ?, ?, ?, ?)`,
|
||||
[user.id, guildId, ["member"], now, false, invite],
|
||||
[user.id, guildId, [], now, false, invite],
|
||||
{ prepare: true },
|
||||
);
|
||||
|
||||
|
@ -117,7 +117,6 @@ async function handler(request: ExtendedRequest): Promise<Response> {
|
|||
message: "Joined guild successfully",
|
||||
data: {
|
||||
guild_id: guildId,
|
||||
role: "member",
|
||||
},
|
||||
});
|
||||
} 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);
|
||||
}
|
||||
|
||||
return new Response(null, {
|
||||
status: 204,
|
||||
return jsonResponse(204, {
|
||||
headers: {
|
||||
"Set-Cookie":
|
||||
"session=; Max-Age=0; Path=/; HttpOnly; Secure; SameSite=Strict",
|
||||
|
@ -43,8 +42,7 @@ async function handler(request: ExtendedRequest): Promise<Response> {
|
|||
|
||||
await sessionManager.invalidateSession(request);
|
||||
|
||||
return new Response(null, {
|
||||
status: 204,
|
||||
return jsonResponse(204, {
|
||||
headers: {
|
||||
"Set-Cookie":
|
||||
"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,
|
||||
"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