layouting fixes

This commit is contained in:
Michael Freno
2025-12-20 23:58:48 -05:00
parent 89e9a2ee45
commit 0e1b51af11
4 changed files with 399 additions and 191 deletions

View File

@@ -255,12 +255,13 @@ export default function PostForm(props: PostFormProps) {
}; };
return ( return (
<div class="bg-base text-text min-h-screen px-8 py-32"> <div class="bg-base text-text min-h-screen py-32">
<div class="text-center text-2xl tracking-wide"> <div class="text-center text-2xl tracking-wide">
{props.mode === "edit" ? "Edit a Blog" : "Create a Blog"} {props.mode === "edit" ? "Edit a Blog" : "Create a Blog"}
</div> </div>
<div class="flex h-full w-full justify-center"> <div class="flex h-full w-full justify-center">
<form onSubmit={handleSubmit} class="w-full md:w-3/4 lg:w-1/3 xl:w-1/2"> <form onSubmit={handleSubmit} class="px-4">
<div class="mx-auto w-full md:w-3/4 xl:w-1/2">
{/* Title */} {/* Title */}
<div class="input-group mx-4"> <div class="input-group mx-4">
<input <input
@@ -327,9 +328,10 @@ export default function PostForm(props: PostFormProps) {
: undefined : undefined
} }
/> />
</div>
{/* Text Editor */} {/* Text Editor */}
<div class="-mx-6 md:-mx-36"> <div class="">
<TextEditor updateContent={setBody} preSet={initialBody()} /> <TextEditor updateContent={setBody} preSet={initialBody()} />
</div> </div>

View File

@@ -9,6 +9,13 @@ import { TRPCError } from "@trpc/server";
import { OAuth2Client } from "google-auth-library"; import { OAuth2Client } from "google-auth-library";
import { jwtVerify } from "jose"; import { jwtVerify } from "jose";
import { createTRPCRouter, publicProcedure } from "~/server/api/utils"; import { createTRPCRouter, publicProcedure } from "~/server/api/utils";
import {
fetchWithTimeout,
checkResponse,
NetworkError,
TimeoutError,
APIError
} from "~/server/fetch-utils";
export const lineageDatabaseRouter = createTRPCRouter({ export const lineageDatabaseRouter = createTRPCRouter({
credentials: publicProcedure credentials: publicProcedure
@@ -155,17 +162,20 @@ export const lineageDatabaseRouter = createTRPCRouter({
}); });
if (dumpRes.success) { if (dumpRes.success) {
const deleteRes = await fetch( try {
const deleteRes = await fetchWithTimeout(
`https://api.turso.tech/v1/organizations/mikefreno/databases/${db_name}`, `https://api.turso.tech/v1/organizations/mikefreno/databases/${db_name}`,
{ {
method: "DELETE", method: "DELETE",
headers: { headers: {
Authorization: `Bearer ${env.TURSO_DB_API_TOKEN}` Authorization: `Bearer ${env.TURSO_DB_API_TOKEN}`
} },
timeout: 20000 // 20s for database deletion
} }
); );
if (deleteRes.ok) { await checkResponse(deleteRes);
await conn.execute({ await conn.execute({
sql: `DELETE FROM User WHERE email = ?`, sql: `DELETE FROM User WHERE email = ?`,
args: [email] args: [email]
@@ -175,12 +185,36 @@ export const lineageDatabaseRouter = createTRPCRouter({
status: 200, status: 200,
message: `Account and Database deleted, db dump sent to email: ${send_dump_target}` message: `Account and Database deleted, db dump sent to email: ${send_dump_target}`
}; };
} else { } catch (error) {
if (error instanceof TimeoutError) {
console.error("Database deletion timeout:", error.message);
throw new TRPCError({
code: "TIMEOUT",
message:
"Database deletion timed out. Please contact support."
});
} else if (error instanceof NetworkError) {
console.error(
"Network error during database deletion:",
error.message
);
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: "Network error deleting database. Please try again."
});
} else if (error instanceof APIError) {
console.error(
"API error deleting database:",
error.status,
error.statusText
);
throw new TRPCError({ throw new TRPCError({
code: "INTERNAL_SERVER_ERROR", code: "INTERNAL_SERVER_ERROR",
message: "Failed to delete database" message: "Failed to delete database"
}); });
} }
throw error;
}
} else { } else {
throw new TRPCError({ throw new TRPCError({
code: "INTERNAL_SERVER_ERROR", code: "INTERNAL_SERVER_ERROR",
@@ -188,17 +222,20 @@ export const lineageDatabaseRouter = createTRPCRouter({
}); });
} }
} else { } else {
const deleteRes = await fetch( try {
const deleteRes = await fetchWithTimeout(
`https://api.turso.tech/v1/organizations/mikefreno/databases/${db_name}`, `https://api.turso.tech/v1/organizations/mikefreno/databases/${db_name}`,
{ {
method: "DELETE", method: "DELETE",
headers: { headers: {
Authorization: `Bearer ${env.TURSO_DB_API_TOKEN}` Authorization: `Bearer ${env.TURSO_DB_API_TOKEN}`
} },
timeout: 20000
} }
); );
if (deleteRes.ok) { await checkResponse(deleteRes);
await conn.execute({ await conn.execute({
sql: `DELETE FROM User WHERE email = ?`, sql: `DELETE FROM User WHERE email = ?`,
args: [email] args: [email]
@@ -208,12 +245,35 @@ export const lineageDatabaseRouter = createTRPCRouter({
status: 200, status: 200,
message: `Account and Database deleted` message: `Account and Database deleted`
}; };
} else { } catch (error) {
if (error instanceof TimeoutError) {
console.error("Database deletion timeout:", error.message);
throw new TRPCError({
code: "TIMEOUT",
message: "Database deletion timed out. Please contact support."
});
} else if (error instanceof NetworkError) {
console.error(
"Network error during database deletion:",
error.message
);
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: "Network error deleting database. Please try again."
});
} else if (error instanceof APIError) {
console.error(
"API error deleting database:",
error.status,
error.statusText
);
throw new TRPCError({ throw new TRPCError({
code: "INTERNAL_SERVER_ERROR", code: "INTERNAL_SERVER_ERROR",
message: "Failed to delete database" message: "Failed to delete database"
}); });
} }
throw error;
}
} }
} else { } else {
const insertRes = await conn.execute({ const insertRes = await conn.execute({
@@ -343,41 +403,59 @@ export const lineageDatabaseRouter = createTRPCRouter({
}); });
if (dumpRes.success) { if (dumpRes.success) {
const deleteRes = await fetch( try {
const deleteRes = await fetchWithTimeout(
`https://api.turso.tech/v1/organizations/mikefreno/databases/${db_name}`, `https://api.turso.tech/v1/organizations/mikefreno/databases/${db_name}`,
{ {
method: "DELETE", method: "DELETE",
headers: { headers: {
Authorization: `Bearer ${env.TURSO_DB_API_TOKEN}` Authorization: `Bearer ${env.TURSO_DB_API_TOKEN}`
} },
timeout: 20000
} }
); );
if (deleteRes.ok) { await checkResponse(deleteRes);
await conn.execute({ await conn.execute({
sql: `DELETE FROM User WHERE email = ?`, sql: `DELETE FROM User WHERE email = ?`,
args: [email] args: [email]
}); });
executed_ids.push(id as number); executed_ids.push(id as number);
} catch (error) {
console.error(
`Failed to delete database ${db_name} in cron job:`,
error
);
// Continue with other deletions even if one fails
} }
} }
} else { } else {
const deleteRes = await fetch( try {
const deleteRes = await fetchWithTimeout(
`https://api.turso.tech/v1/organizations/mikefreno/databases/${db_name}`, `https://api.turso.tech/v1/organizations/mikefreno/databases/${db_name}`,
{ {
method: "DELETE", method: "DELETE",
headers: { headers: {
Authorization: `Bearer ${env.TURSO_DB_API_TOKEN}` Authorization: `Bearer ${env.TURSO_DB_API_TOKEN}`
} },
timeout: 20000
} }
); );
if (deleteRes.ok) { await checkResponse(deleteRes);
await conn.execute({ await conn.execute({
sql: `DELETE FROM User WHERE email = ?`, sql: `DELETE FROM User WHERE email = ?`,
args: [email] args: [email]
}); });
executed_ids.push(id as number); executed_ids.push(id as number);
} catch (error) {
console.error(
`Failed to delete database ${db_name} in cron job:`,
error
);
// Continue with other deletions even if one fails
} }
} }
} }

View File

@@ -12,7 +12,14 @@ import { TRPCError } from "@trpc/server";
import { ConnectionFactory } from "~/server/utils"; import { ConnectionFactory } from "~/server/utils";
import * as bcrypt from "bcrypt"; import * as bcrypt from "bcrypt";
import { getCookie, setCookie } from "vinxi/http"; import { getCookie, setCookie } from "vinxi/http";
import {
fetchWithTimeout,
checkResponse,
fetchWithRetry,
NetworkError,
TimeoutError,
APIError
} from "~/server/fetch-utils";
const assets: Record<string, string> = { const assets: Record<string, string> = {
"shapes-with-abigail": "shapes-with-abigail.apk", "shapes-with-abigail": "shapes-with-abigail.apk",
"magic-delve": "magic-delve.apk", "magic-delve": "magic-delve.apk",
@@ -285,16 +292,28 @@ export const miscRouter = createTRPCRouter({
}; };
try { try {
await fetch(apiUrl, { await fetchWithRetry(
async () => {
const response = await fetchWithTimeout(apiUrl, {
method: "POST", method: "POST",
headers: { headers: {
accept: "application/json", accept: "application/json",
"api-key": apiKey, "api-key": apiKey,
"content-type": "application/json" "content-type": "application/json"
}, },
body: JSON.stringify(sendinblueData) body: JSON.stringify(sendinblueData),
timeout: 15000
}); });
await checkResponse(response);
return response;
},
{
maxRetries: 2,
retryDelay: 1000
}
);
// Set cookie to prevent spam (60 second cooldown) // Set cookie to prevent spam (60 second cooldown)
const exp = new Date(Date.now() + 1 * 60 * 1000); const exp = new Date(Date.now() + 1 * 60 * 1000);
setCookie("contactRequestSent", exp.toUTCString(), { setCookie("contactRequestSent", exp.toUTCString(), {
@@ -304,11 +323,37 @@ export const miscRouter = createTRPCRouter({
return { message: "email sent" }; return { message: "email sent" };
} catch (error) { } catch (error) {
console.error(error); // Provide specific error messages for different failure types
if (error instanceof TimeoutError) {
console.error("Contact form email timeout:", error.message);
throw new TRPCError({
code: "TIMEOUT",
message:
"Email service timed out. Please try again or contact michael@freno.me"
});
} else if (error instanceof NetworkError) {
console.error("Contact form network error:", error.message);
throw new TRPCError({ throw new TRPCError({
code: "INTERNAL_SERVER_ERROR", code: "INTERNAL_SERVER_ERROR",
message: message:
"SMTP server error: Sorry! You can reach me at michael@freno.me" "Network error. Please try again or contact michael@freno.me"
});
} else if (error instanceof APIError) {
console.error(
"Contact form API error:",
error.status,
error.statusText
);
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: "Email service error. You can reach me at michael@freno.me"
});
}
console.error("Contact form error:", error);
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: "Sorry! You can reach me at michael@freno.me"
}); });
} }
}), }),
@@ -362,26 +407,43 @@ export const miscRouter = createTRPCRouter({
}; };
try { try {
// Send both emails // Send both emails with retry logic
await fetch(apiUrl, { await Promise.all([
fetchWithRetry(
async () => {
const response = await fetchWithTimeout(apiUrl, {
method: "POST", method: "POST",
headers: { headers: {
accept: "application/json", accept: "application/json",
"api-key": apiKey, "api-key": apiKey,
"content-type": "application/json" "content-type": "application/json"
}, },
body: JSON.stringify(sendinblueMyData) body: JSON.stringify(sendinblueMyData),
timeout: 15000
}); });
await checkResponse(response);
await fetch(apiUrl, { return response;
},
{ maxRetries: 2, retryDelay: 1000 }
),
fetchWithRetry(
async () => {
const response = await fetchWithTimeout(apiUrl, {
method: "POST", method: "POST",
headers: { headers: {
accept: "application/json", accept: "application/json",
"api-key": apiKey, "api-key": apiKey,
"content-type": "application/json" "content-type": "application/json"
}, },
body: JSON.stringify(sendinblueUserData) body: JSON.stringify(sendinblueUserData),
timeout: 15000
}); });
await checkResponse(response);
return response;
},
{ maxRetries: 2, retryDelay: 1000 }
)
]);
// Set cookie to prevent spam (60 second cooldown) // Set cookie to prevent spam (60 second cooldown)
const exp = new Date(Date.now() + 1 * 60 * 1000); const exp = new Date(Date.now() + 1 * 60 * 1000);
@@ -392,11 +454,35 @@ export const miscRouter = createTRPCRouter({
return { message: "request sent" }; return { message: "request sent" };
} catch (error) { } catch (error) {
console.error(error); // Provide specific error messages
if (error instanceof TimeoutError) {
console.error("Deletion request email timeout:", error.message);
throw new TRPCError({
code: "TIMEOUT",
message: "Email service timed out. Please try again."
});
} else if (error instanceof NetworkError) {
console.error("Deletion request network error:", error.message);
throw new TRPCError({ throw new TRPCError({
code: "INTERNAL_SERVER_ERROR", code: "INTERNAL_SERVER_ERROR",
message: message: "Network error. Please try again later."
"SMTP server error: Sorry! You can reach me at michael@freno.me" });
} else if (error instanceof APIError) {
console.error(
"Deletion request API error:",
error.status,
error.statusText
);
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: "Email service error. Please try again later."
});
}
console.error("Deletion request error:", error);
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: "Failed to send deletion request. Please try again."
}); });
} }
}) })

View File

@@ -4,6 +4,14 @@ import { v4 as uuid } from "uuid";
import { env } from "~/env/server"; import { env } from "~/env/server";
import type { H3Event } from "vinxi/http"; import type { H3Event } from "vinxi/http";
import { getUserID } from "./auth"; import { getUserID } from "./auth";
import {
fetchWithTimeout,
checkResponse,
fetchWithRetry,
NetworkError,
TimeoutError,
APIError
} from "~/server/fetch-utils";
let mainDBConnection: ReturnType<typeof createClient> | null = null; let mainDBConnection: ReturnType<typeof createClient> | null = null;
let lineageDBConnection: ReturnType<typeof createClient> | null = null; let lineageDBConnection: ReturnType<typeof createClient> | null = null;
@@ -83,16 +91,20 @@ export async function dumpAndSendDB({
success: boolean; success: boolean;
reason?: string; reason?: string;
}> { }> {
const res = await fetch(`https://${dbName}-mikefreno.turso.io/dump`, { try {
// Fetch database dump with timeout
const res = await fetchWithTimeout(
`https://${dbName}-mikefreno.turso.io/dump`,
{
method: "GET", method: "GET",
headers: { headers: {
Authorization: `Bearer ${dbToken}` Authorization: `Bearer ${dbToken}`
},
timeout: 30000 // 30s for database dump
} }
}); );
if (!res.ok) {
console.error(res); await checkResponse(res);
return { success: false, reason: "bad dump request response" };
}
const text = await res.text(); const text = await res.text();
const base64Content = Buffer.from(text, "utf-8").toString("base64"); const base64Content = Buffer.from(text, "utf-8").toString("base64");
@@ -119,20 +131,50 @@ export async function dumpAndSendDB({
} }
] ]
}; };
const sendRes = await fetch(apiUrl, {
// Send email with retry logic
await fetchWithRetry(
async () => {
const sendRes = await fetchWithTimeout(apiUrl, {
method: "POST", method: "POST",
headers: { headers: {
accept: "application/json", accept: "application/json",
"api-key": apiKey, "api-key": apiKey,
"content-type": "application/json" "content-type": "application/json"
}, },
body: JSON.stringify(emailPayload) body: JSON.stringify(emailPayload),
timeout: 20000 // 20s for email with attachment
}); });
if (!sendRes.ok) { await checkResponse(sendRes);
return { success: false, reason: "email send failure" }; return sendRes;
} else { },
{
maxRetries: 2,
retryDelay: 2000
}
);
return { success: true }; return { success: true };
} catch (error) {
// Log specific error types for debugging
if (error instanceof TimeoutError) {
console.error("Database dump timeout:", error.message);
return { success: false, reason: "Database dump timed out" };
} else if (error instanceof NetworkError) {
console.error("Network error during database dump:", error.message);
return { success: false, reason: "Network error" };
} else if (error instanceof APIError) {
console.error(
"API error during database dump:",
error.status,
error.statusText
);
return { success: false, reason: `API error: ${error.statusText}` };
}
console.error("Unexpected error during database dump:", error);
return { success: false, reason: "Unknown error occurred" };
} }
} }