init. moved lil website for madeline into fuse project so I can write apis in here

This commit is contained in:
Gabriel Brown 2024-09-09 22:39:25 -05:00
commit 28f44c35c6
23 changed files with 4867 additions and 0 deletions

17
.env.example Executable file
View File

@ -0,0 +1,17 @@
# Since the ".env" file is gitignored, you can use the ".env.example" file to
# build a new ".env" file when you clone the repo. Keep this file up-to-date
# when you add new variables to `.env`.
# This file will be committed to version control, so make sure not to have any
# secrets in it. If you are cloning this repo, create a copy of this file named
# ".env" and populate it with your secrets.
# When adding additional environment variables, the schema in "/src/env.js"
# should be updated accordingly.
# Drizzle
DATABASE_URL="postgresql://postgres:password@localhost:5432/fuse_next"
# Example:
# SERVERVAR="foo"
# NEXT_PUBLIC_CLIENTVAR="bar"

61
.eslintrc.cjs Executable file
View File

@ -0,0 +1,61 @@
/** @type {import("eslint").Linter.Config} */
const config = {
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": true
},
"plugins": [
"@typescript-eslint",
"drizzle"
],
"extends": [
"next/core-web-vitals",
"plugin:@typescript-eslint/recommended-type-checked",
"plugin:@typescript-eslint/stylistic-type-checked"
],
"rules": {
"@typescript-eslint/array-type": "off",
"@typescript-eslint/consistent-type-definitions": "off",
"@typescript-eslint/consistent-type-imports": [
"warn",
{
"prefer": "type-imports",
"fixStyle": "inline-type-imports"
}
],
"@typescript-eslint/no-unused-vars": [
"warn",
{
"argsIgnorePattern": "^_"
}
],
"@typescript-eslint/require-await": "off",
"@typescript-eslint/no-misused-promises": [
"error",
{
"checksVoidReturn": {
"attributes": false
}
}
],
"drizzle/enforce-delete-with-where": [
"error",
{
"drizzleObjectName": [
"db",
"ctx.db"
]
}
],
"drizzle/enforce-update-with-where": [
"error",
{
"drizzleObjectName": [
"db",
"ctx.db"
]
}
]
}
}
module.exports = config;

46
.gitignore vendored Executable file
View File

@ -0,0 +1,46 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# database
/prisma/db.sqlite
/prisma/db.sqlite-journal
db.sqlite
# next.js
/.next/
/out/
next-env.d.ts
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# local env files
# do not commit any .env files to git, except for the .env.example file. https://create.t3.gg/en/usage/env-variables#using-environment-variables
.env
.env*.local
# vercel
.vercel
# typescript
*.tsbuildinfo
# idea files
.idea

12
drizzle.config.ts Executable file
View File

@ -0,0 +1,12 @@
import { type Config } from "drizzle-kit";
import { env } from "~/env";
export default {
schema: "./src/server/db/schema.ts",
dialect: "postgresql",
dbCredentials: {
url: env.DATABASE_URL,
},
tablesFilter: ["fuse_next_*"],
} satisfies Config;

10
next.config.js Executable file
View File

@ -0,0 +1,10 @@
/**
* Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially useful
* for Docker builds.
*/
await import("./src/env.js");
/** @type {import("next").NextConfig} */
const config = {};
export default config;

48
package.json Executable file
View File

@ -0,0 +1,48 @@
{
"name": "fuse_next",
"version": "0.1.0",
"private": true,
"type": "module",
"scripts": {
"build": "next build",
"db:generate": "drizzle-kit generate",
"db:migrate": "drizzle-kit migrate",
"db:push": "drizzle-kit push",
"db:studio": "drizzle-kit studio",
"dev": "next dev",
"lint": "next lint",
"start": "next start",
"go": "git pull && next build && next start"
},
"dependencies": {
"@t3-oss/env-nextjs": "^0.10.1",
"drizzle-orm": "^0.33.0",
"geist": "^1.3.0",
"next": "^14.2.4",
"postgres": "^3.4.4",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"zod": "^3.23.3"
},
"devDependencies": {
"@types/eslint": "^8.56.10",
"@types/node": "^20.14.10",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@typescript-eslint/eslint-plugin": "^8.1.0",
"@typescript-eslint/parser": "^8.1.0",
"drizzle-kit": "^0.24.0",
"eslint": "^8.57.0",
"eslint-config-next": "^14.2.4",
"eslint-plugin-drizzle": "^0.2.3",
"postcss": "^8.4.39",
"prettier": "^3.3.2",
"prettier-plugin-tailwindcss": "^0.6.5",
"tailwindcss": "^3.4.3",
"typescript": "^5.5.3"
},
"ct3aMetadata": {
"initVersion": "7.37.0"
},
"packageManager": "pnpm@9.10.0"
}

4308
pnpm-lock.yaml generated Executable file

File diff suppressed because it is too large Load Diff

7
postcss.config.cjs Executable file
View File

@ -0,0 +1,7 @@
const config = {
plugins: {
tailwindcss: {},
},
};
module.exports = config;

6
prettier.config.js Executable file
View File

@ -0,0 +1,6 @@
/** @type {import('prettier').Config & import('prettier-plugin-tailwindcss').PluginOptions} */
const config = {
plugins: ["prettier-plugin-tailwindcss"],
};
export default config;

BIN
public/favicon.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -0,0 +1,21 @@
'use server';
import { NextResponse } from 'next/server';
import { getCountdown } from '~/server/functions';
export const GET = async (request: Request) => {
try {
const url = new URL(request.url);
const apiKey = url.searchParams.get('apiKey');
if (apiKey !== process.env.API_KEY) {
console.log('Invalid API Key');
return NextResponse.json({ message: "Invalid API Key" }, { status: 401 });
} else {
const countdown = await getCountdown();
return NextResponse.json(countdown);
}
} catch (error) {
console.error(error);
return NextResponse.json({ message: "Error" }, { status: 500 });
}
};
// localhost:3000/api/getCountdown?apiKey=I_Love_Madeline

22
src/app/api/getMessage/route.ts Executable file
View File

@ -0,0 +1,22 @@
'use server';
import { NextResponse } from 'next/server';
import { getMessage } from '~/server/functions';
export const GET = async (request: Request) => {
try {
const url = new URL(request.url);
const apiKey = url.searchParams.get('apiKey');
if (apiKey !== process.env.API_KEY) {
console.log('Invalid API Key');
return NextResponse.json({ message: "Invalid API Key" }, { status: 401 });
} else {
const userId = url.searchParams.get('userId') ?? '2';
const message = await getMessage(parseInt(userId));
return NextResponse.json(message);
}
} catch (error) {
console.error(error);
return NextResponse.json({ message: "Error" }, { status: 500 });
}
};
// localhost:3000/api/getMessage?apiKey=I_Love_Madeline&userId=2

View File

@ -0,0 +1,23 @@
"use server";
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
import { setCountdown } from "~/server/functions";
export const POST = async (request: NextRequest) => {
try {
const url = new URL(request.url);
const apiKey = url.searchParams.get("apiKey");
if (apiKey !== process.env.API_KEY) {
console.log("Invalid API Key");
return NextResponse.json({ message: "Invalid API Key" }, { status: 401 });
} else {
const countdown = url.searchParams.get("countdown") ?? "2023-01-01T00:00:00.000Z";
await setCountdown(new Date(countdown));
return NextResponse.json({ message: "Countdown set successfully" });
}
} catch (error) {
console.error(error);
return NextResponse.json({ message: "Error" }, { status: 500 });
}
};
// localhost:3000/api/setCountdown?apiKey=I_Love_Madeline&countdown=2023-01-01T00:00:00.000Z

24
src/app/api/setMessage/route.ts Executable file
View File

@ -0,0 +1,24 @@
"use server";
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
import { setMessage } from "~/server/functions";
export const POST = async (request: NextRequest) => {
try {
const url = new URL(request.url);
const apiKey = url.searchParams.get("apiKey");
if (apiKey !== process.env.API_KEY) {
console.log("Invalid API Key");
return NextResponse.json({ message: "Invalid API Key" }, { status: 401 });
} else {
const userId = url.searchParams.get("userId") ?? "2";
const message = url.searchParams.get("message") ?? "Test";
await setMessage(parseInt(userId), message);
return NextResponse.json({ message: "Message set successfully" });
}
} catch (error) {
console.error(error);
return NextResponse.json({ message: "Error" }, { status: 500 });
}
};
// localhost:3000/api/setMessage?apiKey=I_Love_Madeline&userId=2&message=HelloWorld

20
src/app/layout.tsx Executable file
View File

@ -0,0 +1,20 @@
import "~/styles/globals.css";
import { GeistSans } from "geist/font/sans";
import { type Metadata } from "next";
export const metadata: Metadata = {
title: "Is Madeline the Cutest?",
description: "Answering the easiest question in the world!",
icons: [{ rel: "icon", url: "/favicon.png" }],
};
export default function RootLayout({
children,
}: Readonly<{ children: React.ReactNode }>) {
return (
<html lang="en" className={`${GeistSans.variable}`}>
<body>{children}</body>
</html>
);
}

35
src/app/page.tsx Executable file
View File

@ -0,0 +1,35 @@
"use client";
import { useState, useEffect } from "react";
const interestingYes = () => {
const yesArray = [
"Absolutely, yes.",
"Without a doubt.",
"Of course.",
"Definitely.",
"Obviously!",
"Certainly!",
"Positively.",
"100%",
];
return yesArray[Math.floor(Math.random() * yesArray.length)];
}
export default function HomePage() {
const [currentText, setCurrentText] = useState("");
useEffect(() => {
setCurrentText(interestingYes() ?? "Absolutely, yes.");
}, []);
const handleClick = () => {
setCurrentText(interestingYes() ?? "Absolutely, yes.");
};
return (
<main className="flex min-h-screen flex-col items-center justify-center
bg-gradient-to-b from-pink-500 to-orange-400 text-white cursor-pointer">
<h3 className="text-5xl font-extrabold tracking-tight
text-white sm:text-[5rem] text-center" onClick={handleClick}>
{currentText}
</h3>
</main>
);
}

46
src/env.js Executable file
View File

@ -0,0 +1,46 @@
import { createEnv } from "@t3-oss/env-nextjs";
import { z } from "zod";
export const env = createEnv({
/**
* Specify your server-side environment variables schema here. This way you can ensure the app
* isn't built with invalid env vars.
*/
server: {
DATABASE_URL: z.string().url(),
API_KEY: z.string(),
NODE_ENV: z
.enum(["development", "test", "production"])
.default("development"),
},
/**
* Specify your client-side environment variables schema here. This way you can ensure the app
* isn't built with invalid env vars. To expose them to the client, prefix them with
* `NEXT_PUBLIC_`.
*/
client: {
// NEXT_PUBLIC_CLIENTVAR: z.string(),
},
/**
* You can't destruct `process.env` as a regular object in the Next.js edge runtimes (e.g.
* middlewares) or client-side so we need to destruct manually.
*/
runtimeEnv: {
DATABASE_URL: process.env.DATABASE_URL,
API_KEY: process.env.API_KEY,
NODE_ENV: process.env.NODE_ENV,
// NEXT_PUBLIC_CLIENTVAR: process.env.NEXT_PUBLIC_CLIENTVAR,
},
/**
* Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially
* useful for Docker builds.
*/
skipValidation: !!process.env.SKIP_ENV_VALIDATION,
/**
* Makes it so that empty strings are treated as undefined. `SOME_VAR: z.string()` and
* `SOME_VAR=''` will throw an error.
*/
emptyStringAsUndefined: true,
});

18
src/server/db/index.ts Executable file
View File

@ -0,0 +1,18 @@
import { drizzle } from "drizzle-orm/postgres-js";
import postgres from "postgres";
import { env } from "~/env";
import * as schema from "./schema";
/**
* Cache the database connection in development. This avoids creating a new connection on every HMR
* update.
*/
const globalForDb = globalThis as unknown as {
conn: postgres.Sql | undefined;
};
const conn = globalForDb.conn ?? postgres(env.DATABASE_URL);
if (env.NODE_ENV !== "production") globalForDb.conn = conn;
export const db = drizzle(conn, { schema });

29
src/server/db/schema.ts Executable file
View File

@ -0,0 +1,29 @@
import { sql } from "drizzle-orm";
import {
pgTableCreator,
serial,
timestamp,
varchar,
} from "drizzle-orm/pg-core";
export const createTable = pgTableCreator((name) => `${name}`);
export const users = createTable(
"user",
{
id: serial("id").primaryKey(),
name: varchar("name", { length: 256 }),
message: varchar("message", { length: 256 }),
createdAt: timestamp("created_at", { withTimezone: true })
.default(sql`CURRENT_TIMESTAMP`)
.notNull(),
},
);
export const countdown = createTable(
"countdown",
{
id: serial("id").primaryKey(),
date: timestamp("date", { withTimezone: true }),
},
);

55
src/server/functions.ts Executable file
View File

@ -0,0 +1,55 @@
import 'server-only';
import { db } from '~/server/db';
import * as schema from '~/server/db/schema';
import { eq } from 'drizzle-orm';
export const getMessage = async (userId: number) => {
try {
let message = 1;
if (userId === 1)
message = 2;
const result = await db.select({
receivedMessage: schema.users.message,
}).from(schema.users)
.where(eq(schema.users.id, message))
return result;
} catch (error) {
console.error("Error fetching message", error);
throw new Error("Failed to fetch message");
}
};
export const setMessage = async (userId: number, message: string) => {
try {
await db.update(schema.users)
.set({ message: message })
.where(eq(schema.users.id, userId));
} catch (error) {
console.error("Error setting message", error);
throw new Error("Failed to set message");
}
};
export const getCountdown = async () => {
try {
const result = await db.select({
countdown: schema.countdown.date,
}).from(schema.countdown)
.where(eq(schema.countdown.id, 1))
return result;
} catch (error) {
console.error("Error fetching countdown", error);
throw new Error("Failed to fetch countdown");
}
};
export const setCountdown = async (date: Date) => {
try {
await db.update(schema.countdown)
.set({ date: date })
.where(eq(schema.countdown.id, 1));
} catch (error) {
console.error("Error setting countdown", error);
throw new Error("Failed to set countdown");
}
};

3
src/styles/globals.css Executable file
View File

@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

14
tailwind.config.ts Executable file
View File

@ -0,0 +1,14 @@
import { type Config } from "tailwindcss";
import { fontFamily } from "tailwindcss/defaultTheme";
export default {
content: ["./src/**/*.tsx"],
theme: {
extend: {
fontFamily: {
sans: ["var(--font-geist-sans)", ...fontFamily.sans],
},
},
},
plugins: [],
} satisfies Config;

42
tsconfig.json Executable file
View File

@ -0,0 +1,42 @@
{
"compilerOptions": {
/* Base Options: */
"esModuleInterop": true,
"skipLibCheck": true,
"target": "es2022",
"allowJs": true,
"resolveJsonModule": true,
"moduleDetection": "force",
"isolatedModules": true,
/* Strictness */
"strict": true,
"noUncheckedIndexedAccess": true,
"checkJs": true,
/* Bundled projects */
"lib": ["dom", "dom.iterable", "ES2022"],
"noEmit": true,
"module": "ESNext",
"moduleResolution": "Bundler",
"jsx": "preserve",
"plugins": [{ "name": "next" }],
"incremental": true,
/* Path Aliases */
"baseUrl": ".",
"paths": {
"~/*": ["./src/*"]
}
},
"include": [
".eslintrc.cjs",
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
"**/*.cjs",
"**/*.js",
".next/types/**/*.ts"
],
"exclude": ["node_modules"]
}