add initial user and post APIs for login/posting/adding users
This commit is contained in:
parent
5f4f8ed144
commit
cb02089224
1
.env.example
Normal file
1
.env.example
Normal file
@ -0,0 +1 @@
|
|||||||
|
BLOG_NAME=Blog
|
10
.gitignore
vendored
10
.gitignore
vendored
@ -31,7 +31,7 @@ yarn-error.log*
|
|||||||
.pnpm-debug.log*
|
.pnpm-debug.log*
|
||||||
|
|
||||||
# env files (can opt-in for committing if needed)
|
# env files (can opt-in for committing if needed)
|
||||||
.env*
|
.env
|
||||||
|
|
||||||
# vercel
|
# vercel
|
||||||
.vercel
|
.vercel
|
||||||
@ -39,3 +39,11 @@ yarn-error.log*
|
|||||||
# typescript
|
# typescript
|
||||||
*.tsbuildinfo
|
*.tsbuildinfo
|
||||||
next-env.d.ts
|
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
11
app/admin/login/page.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
@ -30,10 +30,28 @@ export default function CreatePost() {
|
|||||||
setContent(value || "")
|
setContent(value || "")
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSubmit = (e: React.FormEvent) => {
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
|
const date = Math.floor(Date.now() / 1000);
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
// TODO: handle form submission here!
|
try {
|
||||||
console.log({ title, description, category, slug, content })
|
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 (
|
return (
|
||||||
|
@ -8,14 +8,31 @@ import { Button } from "@/components/ui/button"
|
|||||||
import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
|
import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
|
||||||
|
|
||||||
export default function CreateUser() {
|
export default function CreateUser() {
|
||||||
const [name, setName] = useState("")
|
const [username, setUsername] = useState("")
|
||||||
const [email, setEmail] = useState("")
|
const [email, setEmail] = useState("")
|
||||||
const [password, setPassword] = useState("")
|
const [password, setPassword] = useState("")
|
||||||
|
|
||||||
const handleSubmit = (e: React.FormEvent) => {
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
// TODO: handle form submission here!
|
try {
|
||||||
console.log({ name, email, password })
|
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 (
|
return (
|
||||||
@ -28,11 +45,11 @@ export default function CreateUser() {
|
|||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-4">
|
<CardContent className="space-y-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="name">{strings.newUserNameFieldLabel}</Label>
|
<Label htmlFor="username">{strings.newUserNameFieldLabel}</Label>
|
||||||
<Input
|
<Input
|
||||||
id="name"
|
id="username"
|
||||||
value={name}
|
value={username}
|
||||||
onChange={(e) => setName(e.target.value)}
|
onChange={(e) => setUsername(e.target.value)}
|
||||||
placeholder={strings.newUserNameFieldPlaceholder}
|
placeholder={strings.newUserNameFieldPlaceholder}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
11
app/login/page.tsx
Normal file
11
app/login/page.tsx
Normal 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
88
components/login-form.tsx
Normal 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
77
server/index.js
Normal 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
30
server/package.json
Normal 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"
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user