init
This commit is contained in:
89
src/api/lineage/database/creds/route.ts
Normal file
89
src/api/lineage/database/creds/route.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
import { env } from "@/env.mjs";
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import jwt from "jsonwebtoken";
|
||||
import { LineageConnectionFactory } from "@/app/utils";
|
||||
import { OAuth2Client } from "google-auth-library";
|
||||
const CLIENT_ID = env.NEXT_PUBLIC_GOOGLE_CLIENT_ID_MAGIC_DELVE;
|
||||
|
||||
const client = new OAuth2Client(CLIENT_ID);
|
||||
|
||||
export async function POST(req: NextRequest) {
|
||||
const authHeader = req.headers.get("authorization");
|
||||
if (!authHeader || !authHeader.startsWith("Bearer ")) {
|
||||
return new NextResponse(JSON.stringify({ valid: false }), { status: 401 });
|
||||
}
|
||||
const { email, provider } = await req.json();
|
||||
if (!email) {
|
||||
return new NextResponse(
|
||||
JSON.stringify({ success: false, message: "missing email in body" }),
|
||||
{
|
||||
status: 401,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
const token = authHeader.split(" ")[1];
|
||||
|
||||
try {
|
||||
let valid_request = false;
|
||||
if (provider == "email") {
|
||||
const decoded = jwt.verify(token, env.JWT_SECRET_KEY) as jwt.JwtPayload;
|
||||
if (decoded.email == email) {
|
||||
valid_request = true;
|
||||
}
|
||||
} else if (provider == "google") {
|
||||
const ticket = await client.verifyIdToken({
|
||||
idToken: token,
|
||||
audience: CLIENT_ID,
|
||||
});
|
||||
if (ticket.getPayload()?.email == email) {
|
||||
valid_request = true;
|
||||
}
|
||||
} else {
|
||||
const conn = LineageConnectionFactory();
|
||||
const query = "SELECT * FROM User WHERE apple_user_string = ?";
|
||||
const res = await conn.execute({ sql: query, args: [token] });
|
||||
if (res.rows.length > 0 && res.rows[0].email == email) {
|
||||
valid_request = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (valid_request) {
|
||||
const conn = LineageConnectionFactory();
|
||||
const query = "SELECT * FROM User WHERE email = ? LIMIT 1";
|
||||
const params = [email];
|
||||
const res = await conn.execute({ sql: query, args: params });
|
||||
if (res.rows.length === 1) {
|
||||
const user = res.rows[0];
|
||||
return new NextResponse(
|
||||
JSON.stringify({
|
||||
success: true,
|
||||
db_name: user.database_name,
|
||||
db_token: user.database_token,
|
||||
}),
|
||||
{ status: 200 },
|
||||
);
|
||||
}
|
||||
return new NextResponse(
|
||||
JSON.stringify({ success: false, message: "no user found" }),
|
||||
{
|
||||
status: 404,
|
||||
},
|
||||
);
|
||||
} else {
|
||||
return new NextResponse(
|
||||
JSON.stringify({ success: false, message: "destroy token" }),
|
||||
{
|
||||
status: 401,
|
||||
},
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
return new NextResponse(
|
||||
JSON.stringify({ success: false, message: error }),
|
||||
{
|
||||
status: 401,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
69
src/api/lineage/database/deletion/cancel/route.ts
Normal file
69
src/api/lineage/database/deletion/cancel/route.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import { LineageConnectionFactory, validateLineageRequest } from "@/app/utils";
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
|
||||
export async function POST(req: NextRequest) {
|
||||
const authHeader = req.headers.get("authorization");
|
||||
if (!authHeader || !authHeader.startsWith("Bearer ")) {
|
||||
return NextResponse.json({
|
||||
status: 401,
|
||||
ok: false,
|
||||
message: "Missing or invalid authorization header.",
|
||||
});
|
||||
}
|
||||
const auth_token = authHeader.split(" ")[1];
|
||||
|
||||
const { email } = await req.json();
|
||||
if (!email) {
|
||||
return NextResponse.json({
|
||||
status: 400,
|
||||
ok: false,
|
||||
message: "Email is required to cancel the cron job.",
|
||||
});
|
||||
}
|
||||
|
||||
const conn = LineageConnectionFactory();
|
||||
|
||||
const resUser = await conn.execute({
|
||||
sql: `SELECT * FROM User WHERE email = ?;`,
|
||||
args: [email],
|
||||
});
|
||||
if (resUser.rows.length === 0) {
|
||||
return NextResponse.json({
|
||||
status: 404,
|
||||
ok: false,
|
||||
message: "User not found.",
|
||||
});
|
||||
}
|
||||
const userRow = resUser.rows[0];
|
||||
if (!userRow) {
|
||||
return NextResponse.json({ status: 404, ok: false });
|
||||
}
|
||||
|
||||
const valid = await validateLineageRequest({ auth_token, userRow });
|
||||
if (!valid) {
|
||||
return NextResponse.json({
|
||||
status: 401,
|
||||
ok: false,
|
||||
message: "Invalid credentials for cancelation.",
|
||||
});
|
||||
}
|
||||
|
||||
const result = await conn.execute({
|
||||
sql: `DELETE FROM cron WHERE email = ?;`,
|
||||
args: [email],
|
||||
});
|
||||
|
||||
if (result.rowsAffected > 0) {
|
||||
return NextResponse.json({
|
||||
status: 200,
|
||||
ok: true,
|
||||
message: "Cron job(s) canceled successfully.",
|
||||
});
|
||||
} else {
|
||||
return NextResponse.json({
|
||||
status: 404,
|
||||
ok: false,
|
||||
message: "No cron job found for the given email.",
|
||||
});
|
||||
}
|
||||
}
|
||||
24
src/api/lineage/database/deletion/check/route.ts
Normal file
24
src/api/lineage/database/deletion/check/route.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { LineageConnectionFactory } from "@/app/utils";
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
|
||||
export async function POST(req: NextRequest) {
|
||||
const { email } = await req.json();
|
||||
const conn = LineageConnectionFactory();
|
||||
try {
|
||||
const res = await conn.execute({
|
||||
sql: `SELECT * FROM cron WHERE email = ?`,
|
||||
args: [email],
|
||||
});
|
||||
const cronRow = res.rows[0];
|
||||
if (!cronRow) {
|
||||
return NextResponse.json({ status: 204, ok: true });
|
||||
}
|
||||
return NextResponse.json({
|
||||
ok: true,
|
||||
status: 200,
|
||||
created_at: cronRow.created_at,
|
||||
});
|
||||
} catch (e) {
|
||||
return NextResponse.json({ status: 500, ok: false });
|
||||
}
|
||||
}
|
||||
73
src/api/lineage/database/deletion/cron/route.ts
Normal file
73
src/api/lineage/database/deletion/cron/route.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import { dumpAndSendDB, LineageConnectionFactory } from "@/app/utils";
|
||||
import { NextResponse } from "next/server";
|
||||
import { createClient as createAPIClient } from "@tursodatabase/api";
|
||||
import { env } from "@/env.mjs";
|
||||
|
||||
export async function GET() {
|
||||
const conn = LineageConnectionFactory();
|
||||
const res = await conn.execute(
|
||||
`SELECT * FROM cron WHERE created_at <= datetime('now', '-1 day');`,
|
||||
);
|
||||
|
||||
if (res.rows.length > 0) {
|
||||
const executed_ids = [];
|
||||
for (const row of res.rows) {
|
||||
const { id, db_name, db_token, send_dump_target, email } = row;
|
||||
|
||||
if (send_dump_target) {
|
||||
const res = await dumpAndSendDB({
|
||||
dbName: db_name as string,
|
||||
dbToken: db_token as string,
|
||||
sendTarget: send_dump_target as string,
|
||||
});
|
||||
if (res.success) {
|
||||
//const res = await turso.databases.delete(db_name as string);
|
||||
//
|
||||
const res = await fetch(
|
||||
`https://api.turso.tech/v1/organizations/mikefreno/databases/${db_name}`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
Authorization: `Bearer ${env.TURSO_DB_API_TOKEN}`,
|
||||
},
|
||||
},
|
||||
);
|
||||
if (res.ok) {
|
||||
executed_ids.push(id);
|
||||
// Shouldn't fail. No idea what the response from turso would be at this point - not documented
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const res = await fetch(
|
||||
`https://api.turso.tech/v1/organizations/mikefreno/databases/${db_name}`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
Authorization: `Bearer ${env.TURSO_DB_API_TOKEN}`,
|
||||
},
|
||||
},
|
||||
);
|
||||
if (res.ok) {
|
||||
conn.execute({
|
||||
sql: `DELETE FROM User WHERE email = ?`,
|
||||
args: [email],
|
||||
});
|
||||
executed_ids.push(id);
|
||||
// Shouldn't fail. No idea what the response from turso would be at this point - not documented
|
||||
}
|
||||
}
|
||||
}
|
||||
if (executed_ids.length > 0) {
|
||||
const placeholders = executed_ids.map(() => "?").join(", ");
|
||||
const deleteQuery = `DELETE FROM cron WHERE id IN (${placeholders});`;
|
||||
await conn.execute({ sql: deleteQuery, args: executed_ids });
|
||||
|
||||
return NextResponse.json({
|
||||
status: 200,
|
||||
message:
|
||||
"Processed databases deleted and corresponding cron rows removed.",
|
||||
});
|
||||
}
|
||||
}
|
||||
return NextResponse.json({ status: 200, ok: true });
|
||||
}
|
||||
154
src/api/lineage/database/deletion/init/route.ts
Normal file
154
src/api/lineage/database/deletion/init/route.ts
Normal file
@@ -0,0 +1,154 @@
|
||||
import {
|
||||
dumpAndSendDB,
|
||||
LineageConnectionFactory,
|
||||
validateLineageRequest,
|
||||
} from "@/app/utils";
|
||||
import { env } from "@/env.mjs";
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
|
||||
export async function POST(req: NextRequest) {
|
||||
const authHeader = req.headers.get("authorization");
|
||||
if (!authHeader || !authHeader.startsWith("Bearer ")) {
|
||||
return NextResponse.json({ status: 401, ok: false });
|
||||
}
|
||||
|
||||
const auth_token = authHeader.split(" ")[1];
|
||||
const { email, db_name, db_token, skip_cron, send_dump_target } =
|
||||
await req.json();
|
||||
if (!email || !db_name || !db_token || !auth_token) {
|
||||
return NextResponse.json({
|
||||
status: 401,
|
||||
message: "Missing required fields",
|
||||
});
|
||||
}
|
||||
|
||||
const conn = LineageConnectionFactory();
|
||||
const res = await conn.execute({
|
||||
sql: `SELECT * FROM User WHERE email = ?`,
|
||||
args: [email],
|
||||
});
|
||||
const userRow = res.rows[0];
|
||||
if (!userRow) {
|
||||
return NextResponse.json({ status: 404, ok: false });
|
||||
}
|
||||
|
||||
const valid = await validateLineageRequest({ auth_token, userRow });
|
||||
if (!valid) {
|
||||
return NextResponse.json({
|
||||
ok: false,
|
||||
status: 401,
|
||||
message: "Invalid Verification",
|
||||
});
|
||||
}
|
||||
|
||||
const { database_token, database_name } = userRow;
|
||||
|
||||
if (database_token !== db_token || database_name !== db_name) {
|
||||
return NextResponse.json({
|
||||
ok: false,
|
||||
status: 401,
|
||||
message: "Incorrect Verification",
|
||||
});
|
||||
}
|
||||
|
||||
if (skip_cron) {
|
||||
if (send_dump_target) {
|
||||
const res = await dumpAndSendDB({
|
||||
dbName: db_name,
|
||||
dbToken: db_token,
|
||||
sendTarget: send_dump_target,
|
||||
});
|
||||
if (res.success) {
|
||||
//const turso = createAPIClient({
|
||||
//org: "mikefreno",
|
||||
//token: env.TURSO_DB_API_TOKEN,
|
||||
//});
|
||||
//const res = await turso.databases.delete(db_name); // seems unreliable, using rest api instead
|
||||
const res = await fetch(
|
||||
`https://api.turso.tech/v1/organizations/mikefreno/databases/${db_name}`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
Authorization: `Bearer ${env.TURSO_DB_API_TOKEN}`,
|
||||
},
|
||||
},
|
||||
);
|
||||
if (res.ok) {
|
||||
conn.execute({
|
||||
sql: `DELETE FROM User WHERE email = ?`,
|
||||
args: [email],
|
||||
});
|
||||
return NextResponse.json({
|
||||
ok: true,
|
||||
status: 200,
|
||||
message: `Account and Database deleted, db dump sent to email: ${send_dump_target}`,
|
||||
});
|
||||
} else {
|
||||
// Shouldn't fail. No idea what the response from turso would be at this point - not documented
|
||||
return NextResponse.json({
|
||||
status: 500,
|
||||
message: "Unknown",
|
||||
ok: false,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
return NextResponse.json({
|
||||
ok: false,
|
||||
status: 500,
|
||||
message: res.reason,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
//const turso = createAPIClient({
|
||||
//org: "mikefreno",
|
||||
//token: env.TURSO_DB_API_TOKEN,
|
||||
//});
|
||||
//const res = await turso.databases.delete(db_name);
|
||||
const res = await fetch(
|
||||
`https://api.turso.tech/v1/organizations/mikefreno/databases/${db_name}`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
Authorization: `Bearer ${env.TURSO_DB_API_TOKEN}`,
|
||||
},
|
||||
},
|
||||
);
|
||||
if (res.ok) {
|
||||
conn.execute({
|
||||
sql: `DELETE FROM User WHERE email = ?`,
|
||||
args: [email],
|
||||
});
|
||||
return NextResponse.json({
|
||||
ok: true,
|
||||
status: 200,
|
||||
message: `Account and Database deleted`,
|
||||
});
|
||||
} else {
|
||||
// Shouldn't fail. No idea what the response from turso would be at this point - not documented
|
||||
return NextResponse.json({
|
||||
ok: false,
|
||||
status: 500,
|
||||
message: "Unknown",
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const insertRes = await conn.execute({
|
||||
sql: `INSERT INTO cron (email, db_name, db_token, send_dump_target) VALUES (?, ?, ?, ?)`,
|
||||
args: [email, db_name, db_token, send_dump_target],
|
||||
});
|
||||
if (insertRes.rowsAffected > 0) {
|
||||
return NextResponse.json({
|
||||
ok: true,
|
||||
status: 200,
|
||||
message: `Deletion scheduled.`,
|
||||
});
|
||||
} else {
|
||||
return NextResponse.json({
|
||||
ok: false,
|
||||
status: 500,
|
||||
message: `Deletion not scheduled, due to server failure`,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user