Switch to supabase (not fully complete)

This commit is contained in:
2025-10-12 12:57:04 +02:00
parent 2122d6d74f
commit 6bdf2233fe
11 changed files with 336 additions and 156 deletions
+133 -2
View File
@@ -21,6 +21,7 @@
"@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-slot": "^1.2.3",
"@radix-ui/react-tabs": "^1.1.13", "@radix-ui/react-tabs": "^1.1.13",
"@radix-ui/react-tooltip": "^1.2.8", "@radix-ui/react-tooltip": "^1.2.8",
"@supabase/supabase-js": "^2.75.0",
"@types/axios": "^0.9.36", "@types/axios": "^0.9.36",
"@types/date-fns": "^2.5.3", "@types/date-fns": "^2.5.3",
"@types/http-status-codes": "^1.2.0", "@types/http-status-codes": "^1.2.0",
@@ -1884,6 +1885,80 @@
"integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==", "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/@supabase/auth-js": {
"version": "2.75.0",
"resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.75.0.tgz",
"integrity": "sha512-J8TkeqCOMCV4KwGKVoxmEBuDdHRwoInML2vJilthOo7awVCro2SM+tOcpljORwuBQ1vHUtV62Leit+5wlxrNtw==",
"license": "MIT",
"dependencies": {
"@supabase/node-fetch": "2.6.15"
}
},
"node_modules/@supabase/functions-js": {
"version": "2.75.0",
"resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.75.0.tgz",
"integrity": "sha512-18yk07Moj/xtQ28zkqswxDavXC3vbOwt1hDuYM3/7xPnwwpKnsmPyZ7bQ5th4uqiJzQ135t74La9tuaxBR6e7w==",
"license": "MIT",
"dependencies": {
"@supabase/node-fetch": "2.6.15"
}
},
"node_modules/@supabase/node-fetch": {
"version": "2.6.15",
"resolved": "https://registry.npmjs.org/@supabase/node-fetch/-/node-fetch-2.6.15.tgz",
"integrity": "sha512-1ibVeYUacxWYi9i0cf5efil6adJ9WRyZBLivgjs+AUpewx1F3xPi7gLgaASI2SmIQxPoCEjAsLAzKPgMJVgOUQ==",
"license": "MIT",
"dependencies": {
"whatwg-url": "^5.0.0"
},
"engines": {
"node": "4.x || >=6.0.0"
}
},
"node_modules/@supabase/postgrest-js": {
"version": "2.75.0",
"resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-2.75.0.tgz",
"integrity": "sha512-YfBz4W/z7eYCFyuvHhfjOTTzRrQIvsMG2bVwJAKEVVUqGdzqfvyidXssLBG0Fqlql1zJFgtsPpK1n4meHrI7tg==",
"license": "MIT",
"dependencies": {
"@supabase/node-fetch": "2.6.15"
}
},
"node_modules/@supabase/realtime-js": {
"version": "2.75.0",
"resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.75.0.tgz",
"integrity": "sha512-B4Xxsf2NHd5cEnM6MGswOSPSsZKljkYXpvzKKmNxoUmNQOfB7D8HOa6NwHcUBSlxcjV+vIrYKcYXtavGJqeGrw==",
"license": "MIT",
"dependencies": {
"@supabase/node-fetch": "2.6.15",
"@types/phoenix": "^1.6.6",
"@types/ws": "^8.18.1",
"ws": "^8.18.2"
}
},
"node_modules/@supabase/storage-js": {
"version": "2.75.0",
"resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.75.0.tgz",
"integrity": "sha512-wpJMYdfFDckDiHQaTpK+Ib14N/O2o0AAWWhguKvmmMurB6Unx17GGmYp5rrrqCTf8S1qq4IfIxTXxS4hzrUySg==",
"license": "MIT",
"dependencies": {
"@supabase/node-fetch": "2.6.15"
}
},
"node_modules/@supabase/supabase-js": {
"version": "2.75.0",
"resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.75.0.tgz",
"integrity": "sha512-8UN/vATSgS2JFuJlMVr51L3eUDz+j1m7Ww63wlvHLKULzCDaVWYzvacCjBTLW/lX/vedI2LBI4Vg+01G9ufsJQ==",
"license": "MIT",
"dependencies": {
"@supabase/auth-js": "2.75.0",
"@supabase/functions-js": "2.75.0",
"@supabase/node-fetch": "2.6.15",
"@supabase/postgrest-js": "2.75.0",
"@supabase/realtime-js": "2.75.0",
"@supabase/storage-js": "2.75.0"
}
},
"node_modules/@swc/helpers": { "node_modules/@swc/helpers": {
"version": "0.5.15", "version": "0.5.15",
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
@@ -2249,7 +2324,6 @@
"version": "20.19.11", "version": "20.19.11",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.11.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.11.tgz",
"integrity": "sha512-uug3FEEGv0r+jrecvUUpbY8lLisvIjg6AAic6a2bSP5OEOLeJsDSnvhCDov7ipFFMXS3orMpzlmi0ZcuGkBbow==", "integrity": "sha512-uug3FEEGv0r+jrecvUUpbY8lLisvIjg6AAic6a2bSP5OEOLeJsDSnvhCDov7ipFFMXS3orMpzlmi0ZcuGkBbow==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"undici-types": "~6.21.0" "undici-types": "~6.21.0"
@@ -2267,6 +2341,12 @@
"pg-types": "^2.2.0" "pg-types": "^2.2.0"
} }
}, },
"node_modules/@types/phoenix": {
"version": "1.6.6",
"resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.6.tgz",
"integrity": "sha512-PIzZZlEppgrpoT2QgbnDU+MMzuR6BbCjllj0bM70lWoejMeNJAxCchxnv7J3XFkI8MpygtRpzXrIlmWUBclP5A==",
"license": "MIT"
},
"node_modules/@types/react": { "node_modules/@types/react": {
"version": "19.1.10", "version": "19.1.10",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.10.tgz", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.10.tgz",
@@ -2292,6 +2372,15 @@
"integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/ws": {
"version": "8.18.1",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
"integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@ungap/structured-clone": { "node_modules/@ungap/structured-clone": {
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz",
@@ -5051,6 +5140,12 @@
"node": ">=18" "node": ">=18"
} }
}, },
"node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
"license": "MIT"
},
"node_modules/trim-lines": { "node_modules/trim-lines": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz",
@@ -5105,7 +5200,6 @@
"version": "6.21.0", "version": "6.21.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/unified": { "node_modules/unified": {
@@ -5275,6 +5369,43 @@
"url": "https://opencollective.com/unified" "url": "https://opencollective.com/unified"
} }
}, },
"node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
"license": "BSD-2-Clause"
},
"node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
"license": "MIT",
"dependencies": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
}
},
"node_modules/ws": {
"version": "8.18.3",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
"integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/xtend": { "node_modules/xtend": {
"version": "4.0.2", "version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
+1
View File
@@ -24,6 +24,7 @@
"@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-slot": "^1.2.3",
"@radix-ui/react-tabs": "^1.1.13", "@radix-ui/react-tabs": "^1.1.13",
"@radix-ui/react-tooltip": "^1.2.8", "@radix-ui/react-tooltip": "^1.2.8",
"@supabase/supabase-js": "^2.75.0",
"@types/axios": "^0.9.36", "@types/axios": "^0.9.36",
"@types/date-fns": "^2.5.3", "@types/date-fns": "^2.5.3",
"@types/http-status-codes": "^1.2.0", "@types/http-status-codes": "^1.2.0",
-11
View File
@@ -1,11 +0,0 @@
import { StringMap } from "@/types/typeUtils";
type ServerAuth = {
server_ip: string;
user_id: number;
};
// { token: userid }
export const CLIENT_AUTH_TOKENS: StringMap<number> = {};
export const SERVER_AUTH_TOKENS: StringMap<ServerAuth> = {};
+48 -26
View File
@@ -1,32 +1,40 @@
import { NextRequest, NextResponse } from "next/server"; import { NextRequest, NextResponse } from "next/server";
import { CLIENT_AUTH_TOKENS, SERVER_AUTH_TOKENS } from "./auth";
import { StatusCodes } from "http-status-codes"; import { StatusCodes } from "http-status-codes";
import axios from "axios"; import { supabase } from "../supa";
const SERVER_AUTH_TOKENS = new Map<
string,
{ user_id: string; server_ip: string }
>();
/**
* GET /api/auth?token=<relayToken>
* Called by a (DM node / Server) to verify a temporary relay token.
*/
export async function GET(req: NextRequest) { export async function GET(req: NextRequest) {
const url = new URL(req.url); const url = new URL(req.url);
const token = url.searchParams.get("token"); const token = url.searchParams.get("token");
if (!token) if (!token) {
return NextResponse.json( return NextResponse.json(
{ message: "There should be a token parameter" }, { message: "There should be a token parameter" },
{ status: StatusCodes.BAD_REQUEST } { status: StatusCodes.BAD_REQUEST }
); );
}
const auth = SERVER_AUTH_TOKENS[token]; const auth = SERVER_AUTH_TOKENS.get(token);
if (!auth) {
if (!auth)
return NextResponse.json( return NextResponse.json(
{ message: "Invalid token" }, { message: "Invalid or expired token" },
{ status: StatusCodes.NOT_FOUND } { status: StatusCodes.NOT_FOUND }
); );
}
const ip = const ip =
req.headers.get("x-real-ip") || req.headers.get("x-real-ip") ||
req.headers.get("x-forwarded-for")?.split(",")[0] || req.headers.get("x-forwarded-for")?.split(",")[0] ||
"127.0.0.1"; "127.0.0.1";
// ::1 for testing
if (auth.server_ip !== ip && ip !== "::1") { if (auth.server_ip !== ip && ip !== "::1") {
return NextResponse.json( return NextResponse.json(
{ message: "Invalid address" }, { message: "Invalid address" },
@@ -34,35 +42,49 @@ export async function GET(req: NextRequest) {
); );
} }
delete SERVER_AUTH_TOKENS[token]; SERVER_AUTH_TOKENS.delete(token);
return NextResponse.json({ message: "ok", ...auth }); return NextResponse.json({ message: "ok", ...auth });
} }
/**
* POST /api/auth
* Called by the client to request a temporary relay token for a DM server.
*
* Body: { server_ip: string }
* Header: Authorization: Bearer <supabase_jwt>
*/
export async function POST(req: NextRequest) { export async function POST(req: NextRequest) {
let { server_ip } = await req.json(); const { server_ip } = await req.json();
const session_token = req.cookies.get("token")?.value; const authHeader = req.cookies.get("token");
if (!session_token) if (!authHeader) {
return NextResponse.json( return NextResponse.json(
{ message: "Token cookie not found" }, { message: "Missing Supabase Authorization header" },
{ status: StatusCodes.BAD_REQUEST }
);
}
const supabaseToken = authHeader.value;
const {
data: { user },
error,
} = await supabase.auth.getUser(supabaseToken);
if (error || !user) {
return NextResponse.json(
{ message: "Invalid Supabase token" },
{ status: StatusCodes.UNAUTHORIZED } { status: StatusCodes.UNAUTHORIZED }
); );
}
const token = crypto.randomUUID(); const relayToken = crypto.randomUUID();
const user_id = CLIENT_AUTH_TOKENS[session_token]; SERVER_AUTH_TOKENS.set(relayToken, {
user_id: user.id,
if (!user_id)
return NextResponse.json(
{ message: "Invalid token" },
{ status: StatusCodes.NOT_FOUND }
);
SERVER_AUTH_TOKENS[token] = {
user_id,
server_ip: String(server_ip), server_ip: String(server_ip),
}; });
return NextResponse.json({ message: "ok", token }); return NextResponse.json({ message: "ok", token: relayToken });
} }
-33
View File
@@ -1,33 +0,0 @@
import { envEnsure, envNumber } from "@/lib/env";
import { Pool } from "pg";
const pool = new Pool({
user: envEnsure("DB_USER"),
host: envEnsure("DB_HOST"),
database: "postgres",
password: envEnsure("DB_PASSWORD"),
port: envNumber("DB_PORT") || 5432,
});
const createUsersTableQuery = `
CREATE TABLE IF NOT EXISTS users (
id BIGSERIAL PRIMARY KEY,
username VARCHAR(32) UNIQUE NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
password_hash TEXT NOT NULL,
created_at TIMESTAMP DEFAULT NOW() NOT NULL,
updated_at TIMESTAMP DEFAULT NOW() NOT NULL
);
`;
export async function initDb() {
try {
await pool.query(createUsersTableQuery);
console.log("Users table is ready");
} catch (err) {
console.error("Error creating users table:", err);
throw err;
}
}
export default pool;
+41
View File
@@ -0,0 +1,41 @@
import { StatusCodes } from "http-status-codes";
import { NextRequest, NextResponse } from "next/server";
import { supabase } from "../supa";
/**
* GET /api/profile?id=<id>
* or cookie-based lookup
*/
export async function GET(req: NextRequest) {
const url = new URL(req.url);
const query_id = url.searchParams.get("id");
const token = req.cookies.get("token")?.value;
if (!query_id && !token)
return NextResponse.json(
{ message: "Query arg should be token or id" },
{ status: StatusCodes.BAD_REQUEST }
);
let user_id = query_id;
if (!user_id && token) {
const {
data: { user },
} = await supabase.auth.getUser(token);
user_id = user?.id ?? null;
}
if (!user_id)
return NextResponse.json(
{ message: "Invalid token" },
{ status: StatusCodes.NOT_FOUND }
);
const { data: profile } = await supabase
.from("profiles")
.select("id, username, name, email")
.eq("id", user_id)
.maybeSingle();
return NextResponse.json({ message: "ok", ...profile });
}
+6
View File
@@ -0,0 +1,6 @@
import { createClient } from "@supabase/supabase-js";
export const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!
);
+102 -81
View File
@@ -1,112 +1,133 @@
import { NextRequest, NextResponse } from "next/server"; import { NextRequest, NextResponse } from "next/server";
import pool, { initDb } from "../db";
import { StatusCodes } from "http-status-codes"; import { StatusCodes } from "http-status-codes";
import { CLIENT_AUTH_TOKENS } from "../auth/auth"; import { supabase } from "../supa";
/**
* POST /api/user
* Register new user
* Body: { name, username, email, password }
*/
export async function POST(req: NextRequest) { export async function POST(req: NextRequest) {
let { name, username, email, password } = await req.json(); const { name, username, email, password } = await req.json();
const user_ = await pool.query( if (!email || !password || !username)
"SELECT id, username, email FROM users WHERE username = $1;", return NextResponse.json(
[username] { message: "Missing required fields" },
); { status: StatusCodes.BAD_REQUEST }
);
if (user_.rows.length > 0) { const { data: existing } = await supabase
.from("profiles")
.select("username")
.eq("username", username)
.maybeSingle();
if (existing)
return NextResponse.json( return NextResponse.json(
{ message: "Username already used" }, { message: "Username already used" },
{ status: StatusCodes.CONFLICT } { status: StatusCodes.CONFLICT }
); );
}
const user_email = await pool.query( const { data: authData, error: authError } =
"SELECT id, username, email FROM users WHERE email = $1;", await supabase.auth.admin.createUser({
[email] email,
); password,
email_confirm: true,
user_metadata: { name, username },
});
if (user_email.rows.length > 0) { if (authError)
return NextResponse.json( return NextResponse.json(
{ message: "Email already used" }, { message: authError.message },
{ status: StatusCodes.CONFLICT }
);
}
// TODO: Hash the password
const result = await pool.query(
"INSERT INTO users (username, email, password_hash) VALUES ($1, $2, $3) RETURNING id;",
[username, email, password]
);
const token = crypto.randomUUID();
CLIENT_AUTH_TOKENS[token] = parseInt(result.rows[0].id);
return NextResponse.json({
message: "ok",
token,
user_id: result.rows[0].id,
});
}
export async function PUT(req: NextRequest) {
const { username, password } = await req.json();
if (!username || !password)
return NextResponse.json(
{ message: "Body must have username and password" },
{ status: StatusCodes.BAD_REQUEST } { status: StatusCodes.BAD_REQUEST }
); );
const user = await pool.query( await supabase.from("profiles").insert({
"SELECT id, username, email FROM users WHERE password_hash = $2 AND username = $1 OR email = $1;", id: authData.user.id,
[username, password] name,
); username,
});
if (user.rowCount === 0) return NextResponse.json({
message: "ok",
user_id: authData.user.id,
});
}
/**
* GET /api/user
* Login user
* Params: { username, password }
*/
export async function GET(req: NextRequest) {
const params = new URL(req.url).searchParams;
const [username, password] = [params.get("username"), params.get("password")];
if (!username || !password)
return NextResponse.json(
{ message: "Params username and password are required" },
{ status: StatusCodes.BAD_REQUEST }
);
let email = username;
if (!username.includes("@")) {
const { data } = await supabase
.from("profiles")
.select("email")
.eq("email", username)
.maybeSingle();
email = data?.email;
}
if (!email)
return NextResponse.json(
{ message: "Invalid username" },
{ status: StatusCodes.UNAUTHORIZED }
);
const { data, error } = await supabase.auth.signInWithPassword({
email,
password,
});
if (error)
return NextResponse.json( return NextResponse.json(
{ message: "Invalid credentials" }, { message: "Invalid credentials" },
{ status: StatusCodes.UNAUTHORIZED } { status: StatusCodes.UNAUTHORIZED }
); );
const token = crypto.randomUUID(); const { session } = data;
const res = NextResponse.json({
message: "ok",
access_token: session?.access_token,
refresh_token: session?.refresh_token,
user_id: session?.user?.id,
});
CLIENT_AUTH_TOKENS[token] = parseInt(user.rows[0].id); // Optionally set cookie
res.cookies.set("token", session?.access_token ?? "", { httpOnly: true });
return NextResponse.json({ message: "ok", token }); return res;
}
export async function GET(req: NextRequest) {
const url = new URL(req.url);
let query_id = url.searchParams.get("id");
const token = req.cookies.get("token")?.value;
if (!query_id && !token)
return NextResponse.json(
{ message: "Query arg should be token or id" },
{ status: StatusCodes.BAD_REQUEST }
);
else {
const id = query_id ? parseInt(query_id) : token ? CLIENT_AUTH_TOKENS[token] : null;
if (!id)
return NextResponse.json(
{ message: "Invalid token" },
{ status: StatusCodes.NOT_FOUND }
);
const user = await pool.query(
"SELECT id, username, email FROM users WHERE id = $1;",
[id]
);
return NextResponse.json({ message: "ok", ...user.rows[0] });
}
} }
/**
* DELETE /api/auth
* Body: { username }
*/
export async function DELETE(req: NextRequest) { export async function DELETE(req: NextRequest) {
let { username } = await req.json(); const { username } = await req.json();
await pool.query("DELETE FROM users WHERE username = $1;", [username]); // Delete both profile + auth user
const { data: profile } = await supabase
.from("profiles")
.select("id")
.eq("username", username)
.maybeSingle();
if (profile) {
await supabase.auth.admin.deleteUser(profile.id);
await supabase.from("profiles").delete().eq("id", profile.id);
}
return NextResponse.json({ message: "ok" }); return NextResponse.json({ message: "ok" });
} }
initDb();
+1 -1
View File
@@ -32,7 +32,7 @@ function MessageContainer({
}) { }) {
useEffect(() => { useEffect(() => {
axios axios
.get("/api/user/", { params: { id: message.from } }) .get("/api/profile/", { params: { id: message.from } })
.then((res) => .then((res) =>
setUserList((prev) => ({ ...prev, [message.from]: res.data as User })) setUserList((prev) => ({ ...prev, [message.from]: res.data as User }))
) )
+3 -1
View File
@@ -28,7 +28,9 @@ export default function Login() {
const login = async () => { const login = async () => {
setFeedback(undefined); setFeedback(undefined);
try { try {
const res = await axios.put("/api/user", { username, password }); const res = await axios.get("/api/user", {
params: { username, password },
});
Cookies.set("token", (res.data as any).token); Cookies.set("token", (res.data as any).token);
setFeedback({ setFeedback({
+1 -1
View File
@@ -25,7 +25,7 @@ export default function useUser() {
} }
try { try {
const res = (await axios.get("/api/user")).data as User; const res = (await axios.get("/api/profile")).data as User;
setUser(res); setUser(res);
} catch { } catch {