diff --git a/components/Projects/ProjectEntry.tsx b/components/Projects/ProjectEntry.tsx
deleted file mode 100644
index d6be635..0000000
--- a/components/Projects/ProjectEntry.tsx
+++ /dev/null
@@ -1,29 +0,0 @@
-import ProjectRepository from "@/server/actions/project.actions"
-import Image from "next/image"
-
-export default async function ProjectEntry({ id }: { id: string }) {
- const projects = new ProjectRepository();
- const project = await projects.getProjectById(id);
-
- if (!project) {
- return
Project not found!
- }
-
- return (
-
-
- {project.name}
- Started: {project.startDate.toLocaleString()}
- {project.endDate ? `Finished: ${project.endDate.toLocaleDateString()}` : "(In progress)"}
-
-
-
-
{project.description}
-
-
- { project.media && project.media.map((link, idx) => {
- return
- })}
-
- )
-}
diff --git a/env.mjs b/env.mjs
new file mode 100644
index 0000000..e2ab82f
--- /dev/null
+++ b/env.mjs
@@ -0,0 +1,17 @@
+import { createEnv } from "@t3-oss/env-nextjs";
+import { z } from 'zod';
+
+const env = createEnv({
+ server: {
+ POSTGRES_URL: z.string().url(),
+ POSTGRES_USER: z.string(),
+ POSTGRES_PASSWORD: z.string(),
+ },
+ runtimeEnv: {
+ POSTGRES_URL: process.env.POSTGRES_URL,
+ POSTGRES_USER: process.env.POSTGRES_USER,
+ POSTGRES_PASSWORD: process.env.POSTGRES_PASSWORD,
+ }
+})
+
+export { env }
diff --git a/next.config.js b/next.config.js
index 5ccd269..fae793b 100644
--- a/next.config.js
+++ b/next.config.js
@@ -2,10 +2,6 @@
const nextConfig = {
pageExtensions: ['js', 'jsx', 'ts', 'tsx'],
reactStrictMode: true,
- experimental: {
- // mdxRs: true,
- serverActions: true,
- }
}
module.exports = nextConfig;
diff --git a/package.json b/package.json
index 8a04b11..08dc84e 100644
--- a/package.json
+++ b/package.json
@@ -10,13 +10,13 @@
},
"dependencies": {
"@aws-sdk/client-s3": "^3.367.0",
- "@sendgrid/mail": "^7.7.0",
+ "@t3-oss/env-nextjs": "^0.7.0",
"autoprefixer": "10.4.14",
"eslint": "^8.46.0",
"eslint-config-next": "^13.4.12",
"next": "^13.4.12",
"pg": "^8.11.3",
- "postcss": "8.4.24",
+ "postcss": "^8.4.31",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-icons": "^4.9.0",
@@ -31,6 +31,7 @@
"@types/react-dom": "18.2.4",
"@types/uuid": "^9.0.1",
"@vercel/style-guide": "^5.0.1",
+ "ts-node": "^10.9.1",
"zod": "^3.22.4"
}
}
diff --git a/server/actions/mailer.actions.ts b/server/actions/mailer.actions.ts
deleted file mode 100644
index e728c47..0000000
--- a/server/actions/mailer.actions.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-'use server';
-import { Mailer } from "../services/sendgrid";
-
-export async function contactFormSubmit(e: FormData) {
- const data = [e.get('email'), e.get('message'), e.get('name')].map(each => each?.valueOf());
- const mailer = new Mailer();
-
- data.forEach(item => {
- if (typeof item !== 'string') throw new Error('Invalid form data')
- })
-
- const result = await mailer.send(...data as [string, string, string]);
- return result;
-}
-
-export async function testMailerSDK(e: FormData) {
- const sgMail = require('@sendgrid/mail')
- sgMail.setApiKey(process.env.SENDGRID_API_KEY)
- const msg = {
- to: 'mikaylaherself@gmail.com', // Change to your recipient
- from: 'me@mikayla.dev', // Change to your verified sender
- subject: 'Sending with SendGrid is Fun',
- text: 'and easy to do anywhere, even with Node.js',
- html: '
and easy to do anywhere, even with Node.js',
- }
- sgMail
- .send(msg)
- .then(() => {
- console.log('Email sent')
- })
- .catch((error: unknown) => {
- console.error(error)
- })
-}
diff --git a/server/actions/project.actions.ts b/server/actions/project.actions.ts
index 37273e2..8587e7c 100644
--- a/server/actions/project.actions.ts
+++ b/server/actions/project.actions.ts
@@ -1,14 +1,35 @@
import { Project, isProject } from "../entities/project";
-import { Client } from "pg";
import createClient from "../services/pg";
export default class ProjectRepository {
- async getProjects() {
- 'use server';
-
- const client = createClient();
+ async createProject(data: Project) {
+ const client = await createClient();
+ if (!client) return null;
+ await client.connect();
- const { rows } = await client.query("SELECT * FROM projects");
+ const { rows } = await client.query(
+ "INSERT INTO project (name, description, created, updated) VALUES ($1, $2, $3, $4) RETURNING *"
+ , [
+ data.name,
+ data.description,
+ data.created,
+ data.updated,
+ ]);
+
+ await client.end();
+
+ if (rows.every(row => isProject(row))) {
+ return rows[0] as Project;
+ }
+
+ return null;
+ }
+
+ async getProjects() {
+ const client = await createClient();
+ if (!client) return null;
+
+ const { rows } = await client.query("SELECT * FROM project");
await client.end();
if (rows.every(row => isProject(row))) {
@@ -19,10 +40,11 @@ export default class ProjectRepository {
}
async getProjectById(id: string) {
- 'use server';
-
- const client = createClient();
- const { rows } = await client.query("SELECT * FROM projects WHERE id = $1", [id]);
+ const client = await createClient();
+ if (!client) return null;
+ await client.connect();
+
+ const { rows } = await client.query("SELECT * FROM project WHERE id = $1", [id]);
await client.end();
if (rows.every(row => isProject(row))) {
diff --git a/server/actions/work.actions.ts b/server/actions/work.actions.ts
deleted file mode 100644
index bbbf251..0000000
--- a/server/actions/work.actions.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-export default class WorkActions {
- // static async getWork() {
- // const { data, error } = await this.api.from("work").select("*");
-
- // if (error) throw error;
- // return data;
- // }
-
- // static async getWorkById(id: string) {
- // const { data, error } = await this.api.from("work").select("*").eq("id", id);
-
- // if (error) throw error;
- // return data;
- // }
-}
diff --git a/server/entities/project.ts b/server/entities/project.ts
index 3f91f37..a414e63 100644
--- a/server/entities/project.ts
+++ b/server/entities/project.ts
@@ -3,8 +3,8 @@ import { z } from "zod";
export type Project = {
name: string;
description: string;
- startDate: Date;
- endDate?: Date;
+ created: Date;
+ updated?: Date;
tagIDs?: string[];
media?: string[]; // array of URLs
}
@@ -19,8 +19,8 @@ export class ProjectCreationError extends Error {
export const ZProject = z.object({
name: z.string(),
description: z.string(),
- startDate: z.date(),
- endDate: z.date().optional(),
+ created: z.date(),
+ updated: z.date().optional(),
tagIDs: z.array(z.string()).optional(),
media: z.array(z.string()).optional(),
})
@@ -34,8 +34,9 @@ export function createProject(data: Partial
) {
const completeInput = {
name: data.name,
description: data.description,
- startDate: data.startDate || today,
- }
+ created: data.created || today,
+ updated: today,
+ } satisfies Partial;
const parsedProject = ZProject.safeParse(completeInput);
diff --git a/server/services/pg.ts b/server/services/pg.ts
index cf2d653..dd2d9fb 100644
--- a/server/services/pg.ts
+++ b/server/services/pg.ts
@@ -1,4 +1,6 @@
-import { Client } from "pg";
+import { env } from "@/env.mjs";
+import pg from 'pg';
+const { Client } = pg;
export class PostgresError extends Error {
constructor(message: string) {
@@ -7,13 +9,16 @@ export class PostgresError extends Error {
}
}
-export default function createClient() {
- if (!process.env.POSTGRES_URL) {
- throw new PostgresError("Database connection configured incorrectly")
+export default async function createClient() {
+ try {
+ return new Client({
+ connectionString: env.POSTGRES_URL,
+ user: env.POSTGRES_USER,
+ password: env.POSTGRES_PASSWORD,
+ ssl: { rejectUnauthorized: false }
+ });
+ } catch(e) {
+ console.log('error creating client', e);
+ return null;
}
-
- return new Client({
- connectionString: process.env.POSTGRES_URL
- });
-
}
diff --git a/server/services/sendgrid.ts b/server/services/sendgrid.ts
deleted file mode 100644
index 5df1818..0000000
--- a/server/services/sendgrid.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-import mailer, { ClientResponse, MailDataRequired, MailService } from "@sendgrid/mail";
-
-export class Mailer {
- private mailer: MailService;
-
- constructor() {
- const service = mailer;
- const key = process.env.SENDGRID_API_KEY;
-
- if (!key) throw new Error("No SendGrid API key provided");
-
- service.setApiKey(key);
-
- this.mailer = service;
- }
-
- public async send(from: string, text: string, name: string) {
- const data: MailDataRequired = {
- text, from,
- cc: from,
- to: 'hello@mikayla.dev',
- subject: `Contact form submission from ${name}`
- }
- const result = await this.mailer.send(data);
- return result[0] as ClientResponse;
- }
-}
-