Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wdn/join explorer #98

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 81 additions & 14 deletions app/data.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
"use server";
import { access, constants, appendFileSync, readFile } from "node:fs";
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
import { JoinFormValues } from "@/components/JoinForm/JoinForm";
import { S3Client, PutObjectCommand, ListObjectsV2Command, GetObjectCommand } from "@aws-sdk/client-s3";
import { Readable } from "node:stream";
import { JoinFormValues } from "./types";

const JOIN_FORM_LOG = process.env.JOIN_FORM_LOG as string;

const S3_REGION = process.env.S3_REGION as string;
Expand All @@ -11,6 +13,24 @@ const S3_BASE_NAME = process.env.S3_BASE_NAME as string;
const S3_ACCESS_KEY = process.env.S3_ACCESS_KEY as string;
const S3_SECRET_KEY = process.env.S3_SECRET_KEY as string;

if (S3_ACCESS_KEY === undefined || S3_SECRET_KEY === undefined) {
console.error(
"S3 credentials not configured. I WILL NOT SAVE THIS SUBMISSION.",
);
}

const s3Client = new S3Client({
region: S3_REGION != undefined ? S3_REGION : "us-east-1",
endpoint:
S3_ENDPOINT != undefined
? S3_ENDPOINT
: "https://s3.us-east-1.amazonaws.com",
credentials: {
accessKeyId: S3_ACCESS_KEY,
secretAccessKey: S3_SECRET_KEY,
},
});

export async function recordJoinFormSubmissionToCSV(
submission: JoinFormValues,
) {
Expand Down Expand Up @@ -41,18 +61,6 @@ export async function recordJoinFormSubmissionToS3(submission: JoinFormValues) {
return;
}

const s3Client = new S3Client({
region: S3_REGION != undefined ? S3_REGION : "us-east-1",
endpoint:
S3_ENDPOINT != undefined
? S3_ENDPOINT
: "https://s3.us-east-1.amazonaws.com",
credentials: {
accessKeyId: S3_ACCESS_KEY,
secretAccessKey: S3_SECRET_KEY,
},
});

const submissionKey = new Date()
.toISOString()
.replace(/[-:T]/g, "/")
Expand All @@ -74,3 +82,62 @@ export async function recordJoinFormSubmissionToS3(submission: JoinFormValues) {
throw err;
}
}


async function streamToString(stream: Readable): Promise<string> {
return await new Promise((resolve, reject) => {
const chunks: Buffer[] = [];
stream.on("data", (chunk) => chunks.push(chunk));
stream.on("end", () => resolve(Buffer.concat(chunks).toString("utf-8")));
stream.on("error", reject);
});
}


async function listAllObjects(bucketName: string) {
let continuationToken: string | undefined = undefined;
const allObjects: Array<JoinLogLine> = [];

try {
do {
const listCommand = new ListObjectsV2Command({
Bucket: bucketName,
ContinuationToken: continuationToken,
});

const listResponse = await s3Client.send(listCommand);

for (const obj of listResponse.Contents || []) {
const getObjectCommand = new GetObjectCommand({
Bucket: bucketName,
Key: obj.Key,
});
const getObjectResponse = await s3Client.send(getObjectCommand);

if (getObjectResponse.Body) {
const content = await streamToString(getObjectResponse.Body as Readable);
allObjects.push({
key: obj.Key,
submission: JSON.parse(content)
});
}
}

// Update the continuation token to get the next page of results
continuationToken = listResponse.NextContinuationToken;

} while (continuationToken);

return allObjects; // Contains all objects in the bucket
} catch (error) {
console.error("Error listing S3 objects:", error);
throw error;
}
}


export type { JoinLogLine };

export async function fetchSubmissions(): Array<JoinLogLine> {
return listAllObjects(S3_BUCKET_NAME);
}
15 changes: 15 additions & 0 deletions app/join-explorer/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export default function JoinExplorerLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<>
<section className="flex flex-col items-center justify-center gap-4 py-8 md:py-10">
<div className="inline-block max-w-lg text-center justify-center">
{children}
</div>
</section>
</>
);
}
11 changes: 11 additions & 0 deletions app/join-explorer/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { JoinExplorer } from "@/components/JoinExplorer/JoinExplorer";

export default async function Query() {
return (
<>
<main>
<JoinExplorer />
</main>
</>
);
}
6 changes: 1 addition & 5 deletions app/query/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,7 @@ import { Footer } from "@/components/Footer/Footer";
import { Header } from "@/components/Header/Header";
import { QueryForm } from "@/components/QueryForm/QueryForm";

// TODO:
// https://www.npmjs.com/package/react-phone-number-input
// https://www.npmjs.com/package/react-error-boundary

export default async function Join() {
export default async function Query() {
return (
<>
<main>
Expand Down
47 changes: 47 additions & 0 deletions app/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
type JoinFormValues = {
first_name: string;
last_name: string;
email_address: string;
phone_number: string;
street_address: string;
apartment: string;
city: string;
state: string;
zip_code: string;
roof_access: boolean;
referral: string;
ncl: boolean;
trust_me_bro: boolean;
};

// Coding like it's 1997
export function NewJoinFormValues() {
return {
first_name: "",
last_name: "",
email_address: "",
phone_number: "",
street_address: "",
apartment: "",
city: "",
state: "",
zip_code: "",
roof_access: false,
referral: "",
ncl: false,
trust_me_bro: false,
};
}

export type { JoinFormValues };

type JoinLogLine = {
key: string;
submission: JoinFormValues;
};

export async function NewJoinLogLine() {
return {key: "", submission: NewJoinFormValues()}
}

export type { JoinLogLine };
Empty file.
26 changes: 26 additions & 0 deletions components/JoinExplorer/JoinExplorer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"use client"
import React, { useState } from "react";
import { fetchSubmissions } from "@/app/data";
import { Button } from "@mui/material";
import { JoinLogLine, NewJoinLogLine } from "@/app/types";

export function JoinExplorer() {
const [joinList, setJoinList]: Array<JoinLogLine> = useState([NewJoinLogLine()]);

async function loadSubmissions() {
console.log("Chom");
setJoinList(await fetchSubmissions());

}

return (<>
<Button onClick={loadSubmissions}>Refresh</Button>
<ul>
{joinList.map((s) => (
<li>
{s}
</li>
))}
</ul>
</>);
}
37 changes: 0 additions & 37 deletions components/JoinForm/JoinForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,43 +17,6 @@ import { recordJoinFormSubmissionToS3 } from "@/app/data";
import { getMeshDBAPIEndpoint } from "@/app/endpoint";
import InfoConfirmationDialog from "../InfoConfirmation/InfoConfirmation";

type JoinFormValues = {
first_name: string;
last_name: string;
email_address: string;
phone_number: string;
street_address: string;
apartment: string;
city: string;
state: string;
zip_code: string;
roof_access: boolean;
referral: string;
ncl: boolean;
trust_me_bro: boolean;
};

// Coding like it's 1997
export function NewJoinFormValues() {
return {
first_name: "",
last_name: "",
email_address: "",
phone_number: "",
street_address: "",
apartment: "",
city: "",
state: "",
zip_code: "",
roof_access: false,
referral: "",
ncl: false,
trust_me_bro: false,
};
}

export type { JoinFormValues };

type ConfirmationField = {
key: keyof JoinFormValues;
original: string;
Expand Down
1 change: 1 addition & 0 deletions components/Landing/Landing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const Landing = () => {
text: "MeshDB Admin",
link: process.env.NEXT_PUBLIC_MESHDB_URL + "/admin/",
},
{ text: "View Submissions", link: "/join-explorer" },
];

return (
Expand Down
3 changes: 1 addition & 2 deletions tests/join_form.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { JoinFormInput, JoinFormResponse } from "@/app/io";
import { JoinFormValues } from "@/components/JoinForm/JoinForm";
import { JoinFormValues } from "@/app/types";
import { test, expect } from "@/tests/mock/test";

import {
Expand Down
Loading