add initial user and post APIs for login/posting/adding users

This commit is contained in:
Aidan 2025-01-15 09:31:23 -05:00
parent 5f4f8ed144
commit cb02089224
9 changed files with 273 additions and 12 deletions

1
.env.example Normal file
View File

@ -0,0 +1 @@
BLOG_NAME=Blog

10
.gitignore vendored
View File

@ -31,7 +31,7 @@ yarn-error.log*
.pnpm-debug.log*
# env files (can opt-in for committing if needed)
.env*
.env
# vercel
.vercel
@ -39,3 +39,11 @@ yarn-error.log*
# typescript
*.tsbuildinfo
next-env.d.ts
# bun
bun.lockb
# server
server/db.sqlite
server/package-lock.json
server/node_modules

11
app/admin/login/page.tsx Normal file
View File

@ -0,0 +1,11 @@
import { LoginForm } from "@/components/login-form"
export default function Page() {
return (
<div className="flex min-h-svh w-full items-center justify-center p-6 md:p-10">
<div className="w-full max-w-sm">
<LoginForm />
</div>
</div>
)
}

View File

@ -30,10 +30,28 @@ export default function CreatePost() {
setContent(value || "")
}
const handleSubmit = (e: React.FormEvent) => {
const handleSubmit = async (e: React.FormEvent) => {
const date = Math.floor(Date.now() / 1000);
e.preventDefault()
// TODO: handle form submission here!
console.log({ title, description, category, slug, content })
try {
const response = await fetch('http://localhost:3001/api/posts/new', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ title, description, category, slug, content, date }),
});
if (!response.ok) {
throw new Error('Failed to create post');
}
const data = await response.json();
console.log('Success:', data);
} catch (error) {
console.error('Error:', error);
}
console.log({ title, description, category, slug, content, date })
}
return (

View File

@ -8,14 +8,31 @@ import { Button } from "@/components/ui/button"
import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
export default function CreateUser() {
const [name, setName] = useState("")
const [username, setUsername] = useState("")
const [email, setEmail] = useState("")
const [password, setPassword] = useState("")
const handleSubmit = (e: React.FormEvent) => {
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
// TODO: handle form submission here!
console.log({ name, email, password })
try {
const response = await fetch('http://localhost:3001/api/users/add', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ username, email, password }),
});
if (!response.ok) {
throw new Error('Failed to create user');
}
const data = await response.json();
console.log('Success:', data);
} catch (error) {
console.error('Error:', error);
}
console.log({ username, email, password })
}
return (
@ -28,11 +45,11 @@ export default function CreateUser() {
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-2">
<Label htmlFor="name">{strings.newUserNameFieldLabel}</Label>
<Label htmlFor="username">{strings.newUserNameFieldLabel}</Label>
<Input
id="name"
value={name}
onChange={(e) => setName(e.target.value)}
id="username"
value={username}
onChange={(e) => setUsername(e.target.value)}
placeholder={strings.newUserNameFieldPlaceholder}
/>
</div>

11
app/login/page.tsx Normal file
View File

@ -0,0 +1,11 @@
import { LoginForm } from "@/components/login-form"
export default function Page() {
return (
<div className="flex min-h-svh w-full items-center justify-center p-6 md:p-10">
<div className="w-full max-w-sm">
<LoginForm />
</div>
</div>
)
}

88
components/login-form.tsx Normal file
View File

@ -0,0 +1,88 @@
'use client';
import { useState } from "react"
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
export function LoginForm({
className,
...props
}: React.ComponentPropsWithoutRef<"div">) {
const [username, setUsername] = useState("")
const [password, setPassword] = useState("")
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
try {
const response = await fetch('http://localhost:3001/api/admin/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ username, password }),
})
if (!response.ok) {
console.log('Failed to login');
}
const data = await response.json()
console.log('Success:', data)
} catch (error) {
console.error('Error:', error)
}
}
return (
<div className={cn("flex flex-col gap-6", className)} {...props}>
<Card>
<CardHeader>
<CardTitle className="text-2xl">Administration Panel</CardTitle>
<CardDescription>
Please authenticate with your credentials.
</CardDescription>
</CardHeader>
<CardContent>
<form onSubmit={handleSubmit}>
<div className="flex flex-col gap-6">
<div className="grid gap-2">
<Label htmlFor="username">Username</Label>
<Input
id="username"
type="text"
value={username}
onChange={(e) => setUsername(e.target.value)}
required
/>
</div>
<div className="grid gap-2">
<div className="flex items-center">
<Label htmlFor="password">Password</Label>
</div>
<Input
id="password"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
/>
</div>
<Button type="submit" className="w-full">
Login
</Button>
</div>
</form>
</CardContent>
</Card>
</div>
)
}

77
server/index.js Normal file
View File

@ -0,0 +1,77 @@
import express from 'express';
import sqlite3 from 'sqlite3';
import { open } from 'sqlite';
import cors from 'cors';
const app = express();
const port = 3001;
app.use(cors(
{
origin: 'http://localhost:3000',
credentials: true
}
));
let db;
(async () => {
db = await open({
filename: './db.sqlite',
driver: sqlite3.Database
});
await db.exec(`
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT,
email TEXT,
password TEXT
);
`);
await db.exec(`
CREATE TABLE IF NOT EXISTS posts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT,
description TEXT,
category TEXT,
slug TEXT,
content TEXT,
date TEXT
);
`);
})();
app.use(express.json());
app.post('/api/admin/login', async (req, res) => {
const { username, password } = req.body;
try {
const user = await db.get('SELECT * FROM users WHERE username = ? AND password = ?', [username, password]);
if (user) {
res.json({ success: true, user });
} else {
res.status(401).json({ success: false, message: 'Invalid credentials' });
}
} catch (error) {
console.error('Error logging in:', error);
res.status(500).json({ success: false, message: 'Server error' });
}
});
app.post('/api/users/add', async (req, res) => {
const { username, email, password } = req.body;
const result = await db.run('INSERT INTO users (username, email, password) VALUES (?, ?, ?)', [username, email, password]);
res.json({ id: result.lastID });
});
app.post('/api/posts/new', async (req, res) => {
const { title, description, category, slug, content, date } = req.body;
const result = await db.run('INSERT INTO posts (title, description, category, slug, content, date) VALUES (?, ?, ?, ?, ?, ?)', [title, description, category, slug, content, date]);
res.json({ id: result.lastID });
});
app.listen(port, () => {
console.log(`Server running on http://localhost:${port}`);
});

30
server/package.json Normal file
View File

@ -0,0 +1,30 @@
{
"name": "server",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/ihatenodejs/blogpop.git"
},
"author": "ihatenodejs",
"license": "GPL-3.0-or-later",
"bugs": {
"url": "https://github.com/ihatenodejs/blogpop/issues"
},
"homepage": "https://github.com/ihatenodejs/blogpop#readme",
"description": "",
"dependencies": {
"cors": "^2.8.5",
"ejs": "^3.1.10",
"express": "^4.21.2",
"sqlite": "^5.1.1",
"sqlite3": "^5.1.7"
},
"devDependencies": {
"@types/express": "^5.0.0"
},
"type": "module"
}