Skip to content

Commit

Permalink
add database row for new query (#33)
Browse files Browse the repository at this point in the history
* add database row for new query

* add timestamp to query displayid, remove token

* rename query.form_data to query.params

* wrap the NewQueryID output with encodeURIComponent
  • Loading branch information
eriktaubeneck authored Jun 8, 2024
1 parent 3051e42 commit 9ca2f1f
Show file tree
Hide file tree
Showing 9 changed files with 725 additions and 31 deletions.
7 changes: 7 additions & 0 deletions server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,10 @@ You can check out [the Next.js GitHub repository](https://github.com/vercel/next
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.

Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.


## Deploy on Supabase

There is also a database / auth component, which uses Supabase. It runs locally, but can also run within the free tier on [supabase.com](https://supabase.com).

If you add a migration locally, you can deploy that migration with `supabase db push`.
26 changes: 16 additions & 10 deletions server/app/query/create/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,35 +15,41 @@ import {
IPARemoteServers,
RemoteServersType,
} from "@/app/query/servers";
import NewQueryId from "@/app/query/haikunator";
import { Branch, Branches, Commits } from "@/app/query/github";
import { createNewQuery, Query } from "@/data/query";
import { Database } from "@/data/supabaseTypes";

type QueryType = Database["public"]["Enums"]["query_type"];

export default function Page() {
const [queryId, setQueryId] = useState<string | null>(null);
const router = useRouter();

const handleFormSubmit = async (
event: FormEvent<HTMLFormElement>,
queryType: QueryType,
remoteServers: RemoteServersType,
) => {
event.preventDefault();
try {
const newQueryId = NewQueryId();
setQueryId(newQueryId);
const params = new FormData(event.currentTarget);
const query: Query = await createNewQuery(params, queryType);

setQueryId(query.displayId);

// Send a POST request to start the process
const formData = new FormData(event.currentTarget);
for (const remoteServer of Object.values(remoteServers)) {
const response = await fetch(remoteServer.startURL(newQueryId), {
const response = await fetch(remoteServer.startURL(query.uuid), {
method: "POST",
body: formData,
body: params,
});
const data = await response.json();
const _data = await response.json();
}

await new Promise((f) => setTimeout(f, 1000));

// Redirect to /query/view/<newQueryId>
router.push(`/query/view/${newQueryId}`);
router.push(`/query/view/${query.displayId}`);
} catch (error) {
console.error("Error starting process:", error);
}
Expand All @@ -52,11 +58,11 @@ export default function Page() {
const handleDemoLogsFormSubmit = async (
event: FormEvent<HTMLFormElement>,
) => {
await handleFormSubmit(event, DemoLoggerRemoteServers);
await handleFormSubmit(event, "DEMO_LOGGER", DemoLoggerRemoteServers);
};

const handleIPAFormSubmit = async (event: FormEvent<HTMLFormElement>) => {
await handleFormSubmit(event, IPARemoteServers);
await handleFormSubmit(event, "IPA", IPARemoteServers);
};

return (
Expand Down
12 changes: 11 additions & 1 deletion server/app/query/haikunator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,16 @@ const haikunator = new Haikunator({
nouns: nouns,
});

function getCurrentTimestamp() {
const now = new Date();
const year = now.getFullYear();
const month = (now.getMonth() + 1).toString().padStart(2, '0');
const day = now.getDate().toString().padStart(2, '0');
const hours = now.getHours().toString().padStart(2, '0');
const minutes = now.getMinutes().toString().padStart(2, '0');
return `${year}-${month}-${day}T${hours}${minutes}`;
}

export default function NewQueryId(): string {
return haikunator.haikunate();
return encodeURIComponent(haikunator.haikunate({tokenLength: 0}) + getCurrentTimestamp());
}
1 change: 1 addition & 0 deletions server/app/query/servers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export interface ServerLog {
}

export enum Status {
QUEUED = "QUEUED",
STARTING = "STARTING",
COMPILING = "COMPILING",
WAITING_TO_START = "WAITING_TO_START",
Expand Down
1 change: 1 addition & 0 deletions server/app/query/view/[id]/components.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ type StatusClassNameMixinsType = {
};

const StatusClassNameMixins: StatusClassNameMixinsType = {
QUEUED: "bg-slate-300 dark:bg-slate-700",
STARTING: "bg-emerald-300 dark:bg-emerald-700 animate-pulse",
COMPILING: "bg-emerald-300 dark:bg-emerald-700 animate-pulse",
WAITING_TO_START: "bg-emerald-300 dark:bg-emerald-700 animate-pulse",
Expand Down
48 changes: 28 additions & 20 deletions server/app/query/view/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@ import {
initialRunTimeByRemoteServer,
} from "@/app/query/servers";
import { StatsComponent } from "@/app/query/view/[id]/charts";
import { getQuery, Query } from "@/data/query";

export default function Query({ params }: { params: { id: string } }) {
const [query, setQuery] = useState<Query | null>(null);
// display controls
const [logsHidden, setLogsHidden] = useState<boolean>(true);
const [statsHidden, setStatsHidden] = useState<boolean>(true);
Expand All @@ -44,9 +46,11 @@ export default function Query({ params }: { params: { id: string } }) {
}

const kill = async (remoteServers: RemoteServersType) => {
const query: Query = await getQuery(params.id);

const fetchPromises = Object.values(remoteServers).map(
async (remoteServer) => {
await fetch(remoteServer.killURL(params.id), {
await fetch(remoteServer.killURL(query.uuid), {
method: "POST",
});
},
Expand All @@ -56,26 +60,30 @@ export default function Query({ params }: { params: { id: string } }) {
};

useEffect(() => {
let webSockets: WebSocket[] = [];
for (const remoteServer of Object.values(IPARemoteServers)) {
const loggingWs = remoteServer.openLogSocket(params.id, setLogs);
const statusWs = remoteServer.openStatusSocket(
params.id,
setStatusByRemoteServer,
);
const statsWs = remoteServer.openStatsSocket(
params.id,
setStatsByRemoteServer,
setRunTimeByRemoteServer,
);
webSockets = [...webSockets, loggingWs, statusWs, statsWs];
}

return () => {
for (const ws of webSockets) {
ws.close();
(async () => {
const query: Query = await getQuery(params.id);
setQuery(query);
let webSockets: WebSocket[] = [];
for (const remoteServer of Object.values(IPARemoteServers)) {
const loggingWs = remoteServer.openLogSocket(query.uuid, setLogs);
const statusWs = remoteServer.openStatusSocket(
query.uuid,
setStatusByRemoteServer,
);
const statsWs = remoteServer.openStatsSocket(
query.uuid,
setStatsByRemoteServer,
setRunTimeByRemoteServer,
);
webSockets = [...webSockets, loggingWs, statusWs, statsWs];
}
};

return () => {
for (const ws of webSockets) {
ws.close();
}
};
})();
}, [params]);

return (
Expand Down
119 changes: 119 additions & 0 deletions server/data/query.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
"use server";

import { notFound } from "next/navigation";
import { cookies } from "next/headers";
import { createServerClient } from "@supabase/ssr";
import { Database, Json } from "@/data/supabaseTypes";
import { Status } from "@/app/query/servers";
import NewQueryId from "@/app/query/haikunator";

type QueryRow = Database["public"]["Tables"]["queries"]["Row"];
type QueryType = Database["public"]["Enums"]["query_type"];

export interface Query {
uuid: string;
displayId: string;
type: QueryType;
status: Status;
params: Json;
createdAt: string;
startedAt: string | null;
endedAt: string | null;
}

function processQueryData(data: QueryRow): Query {
return {
uuid: data.uuid,
displayId: data.display_id,
type: data.type as QueryType,
status: data.status as Status,
params: data.params,
createdAt: data.created_at,
startedAt: data.started_at,
endedAt: data.ended_at,
};
}

export async function getQuery(displayId: string): Promise<Query> {
const cookieStore = cookies();

const supabase = createServerClient<Database>(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
get(name: string) {
return cookieStore.get(name)?.value;
},
},
},
);

const { status, data, error } = await supabase
.from("queries")
.select("*")
.eq("display_id", displayId)
.limit(1)
.maybeSingle();

if (error) {
console.error(error);
} else if (status === 200) {
if (data) {
return processQueryData(data);
} else {
notFound();
}
}
throw new Error(`${displayId} not found.`);
}

export async function createNewQuery(
params: FormData,
queryType: QueryType,
): Promise<Query> {
const json = JSON.stringify(Object.fromEntries(params));
const cookieStore = cookies();

const supabase = createServerClient<Database>(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
get(name: string) {
return cookieStore.get(name)?.value;
},
},
},
);

const newQueryId = NewQueryId();

const { data: uniqueDisplayId, error: rpcError } = await supabase.rpc(
"generate_unique_display_id",
{ p_display_id: newQueryId },
);

if (rpcError) {
throw new Error(`${rpcError}`);
}

const { data: queryRow, error: insertError } = await supabase
.from("queries")
.insert({
display_id: uniqueDisplayId,
status: "QUEUED",
type: queryType,
params: json,
})
.select()
.returns<QueryRow>()
.single();

if (insertError) {
throw new Error(`${insertError}`);
}

const query: Query = processQueryData(queryRow);
return query;
}
Loading

0 comments on commit 9ca2f1f

Please sign in to comment.