From cb02089224014b4b64ea5ccda9fa44749e6d09aa Mon Sep 17 00:00:00 2001 From: Aidan Date: Wed, 15 Jan 2025 09:31:23 -0500 Subject: [PATCH] add initial user and post APIs for login/posting/adding users --- .env.example | 1 + .gitignore | 10 +++- app/admin/login/page.tsx | 11 +++++ app/admin/post/page.tsx | 24 ++++++++-- app/admin/users/new/page.tsx | 33 ++++++++++---- app/login/page.tsx | 11 +++++ components/login-form.tsx | 88 ++++++++++++++++++++++++++++++++++++ server/index.js | 77 +++++++++++++++++++++++++++++++ server/package.json | 30 ++++++++++++ 9 files changed, 273 insertions(+), 12 deletions(-) create mode 100644 .env.example create mode 100644 app/admin/login/page.tsx create mode 100644 app/login/page.tsx create mode 100644 components/login-form.tsx create mode 100644 server/index.js create mode 100644 server/package.json diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..ce2badc --- /dev/null +++ b/.env.example @@ -0,0 +1 @@ +BLOG_NAME=Blog \ No newline at end of file diff --git a/.gitignore b/.gitignore index 5ef6a52..a23e4f8 100644 --- a/.gitignore +++ b/.gitignore @@ -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 \ No newline at end of file diff --git a/app/admin/login/page.tsx b/app/admin/login/page.tsx new file mode 100644 index 0000000..e73f93e --- /dev/null +++ b/app/admin/login/page.tsx @@ -0,0 +1,11 @@ +import { LoginForm } from "@/components/login-form" + +export default function Page() { + return ( +
+
+ +
+
+ ) +} \ No newline at end of file diff --git a/app/admin/post/page.tsx b/app/admin/post/page.tsx index 57a19b1..e878fa1 100644 --- a/app/admin/post/page.tsx +++ b/app/admin/post/page.tsx @@ -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 ( diff --git a/app/admin/users/new/page.tsx b/app/admin/users/new/page.tsx index e828d66..347d946 100644 --- a/app/admin/users/new/page.tsx +++ b/app/admin/users/new/page.tsx @@ -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() {
- + setName(e.target.value)} + id="username" + value={username} + onChange={(e) => setUsername(e.target.value)} placeholder={strings.newUserNameFieldPlaceholder} />
diff --git a/app/login/page.tsx b/app/login/page.tsx new file mode 100644 index 0000000..8266323 --- /dev/null +++ b/app/login/page.tsx @@ -0,0 +1,11 @@ +import { LoginForm } from "@/components/login-form" + +export default function Page() { + return ( +
+
+ +
+
+ ) +} diff --git a/components/login-form.tsx b/components/login-form.tsx new file mode 100644 index 0000000..a22d606 --- /dev/null +++ b/components/login-form.tsx @@ -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 ( +
+ + + Administration Panel + + Please authenticate with your credentials. + + + +
+
+
+ + setUsername(e.target.value)} + required + /> +
+
+
+ +
+ setPassword(e.target.value)} + required + /> +
+ +
+
+
+
+
+ ) +} diff --git a/server/index.js b/server/index.js new file mode 100644 index 0000000..1ed2f60 --- /dev/null +++ b/server/index.js @@ -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}`); +}); \ No newline at end of file diff --git a/server/package.json b/server/package.json new file mode 100644 index 0000000..aa947ac --- /dev/null +++ b/server/package.json @@ -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" +}