split routes into separate files and added authentication
This commit is contained in:
parent
32913d582a
commit
5a7021b674
@ -16,3 +16,29 @@ a {
|
||||
a:visited {
|
||||
color: slategray;
|
||||
}
|
||||
|
||||
.main-column {
|
||||
width: 76ch;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
padding-top: 3rem;
|
||||
color: white;
|
||||
}
|
||||
header {
|
||||
text-align: right;
|
||||
width: 100%;
|
||||
}
|
||||
header hr {
|
||||
width: 60%;
|
||||
margin-inline-start: auto;
|
||||
margin-inline-end: 0;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.page-contents {
|
||||
text-align: left;
|
||||
background: rgba(10,66,30, 0.75);
|
||||
margin: 1rem;
|
||||
padding: 1rem;
|
||||
border-radius: 1rem;
|
||||
}
|
@ -5,6 +5,8 @@ import PageView from '/src/page/PageView.jsx';
|
||||
import PageEdit from '/src/page/PageEdit.jsx';
|
||||
import LogIn from '/src/login/LogIn.jsx';
|
||||
import Register from '/src/login/Register.jsx';
|
||||
import Profile from '/src/login/Profile.jsx';
|
||||
import AuthProvider from '/src/AuthProvider.jsx';
|
||||
|
||||
import './App.css';
|
||||
|
||||
@ -13,15 +15,18 @@ const queryClient = new QueryClient();
|
||||
function App() {
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<BrowserRouter>
|
||||
<Routes>
|
||||
<Route path="/" element={<Landing/>}/>
|
||||
<Route path="/login" element={<LogIn/>}/>
|
||||
<Route path="/register" element={<Register/>}/>
|
||||
<Route path="/:pagenumber" element={<PageView/>}/>
|
||||
<Route path="/:pagenumber/edit" element={<PageEdit/>}/>
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
<AuthProvider>
|
||||
<BrowserRouter>
|
||||
<Routes>
|
||||
<Route path="/" element={<Landing/>}/>
|
||||
<Route path="/login" element={<LogIn/>}/>
|
||||
<Route path="/register" element={<Register/>}/>
|
||||
<Route path="/:pagenumber" element={<PageView/>}/>
|
||||
<Route path="/:pagenumber/edit" element={<PageEdit/>}/>
|
||||
<Route path="/profile" element={<Profile/>}/>
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
</AuthProvider>
|
||||
</QueryClientProvider>
|
||||
);
|
||||
}
|
||||
|
29
client/src/AuthProvider.jsx
Normal file
29
client/src/AuthProvider.jsx
Normal file
@ -0,0 +1,29 @@
|
||||
import { useState, createContext, useContext } from "react";
|
||||
import { postLogIn, postLogOut } from './apiTools.jsx';
|
||||
|
||||
const LogInContext = createContext(false);
|
||||
export const LogInStatusUpdateEscapeTool = {};
|
||||
|
||||
function AuthProvider({children}) {
|
||||
let [loggedIn, setLoggedIn] = useState(localStorage.getItem("loggedIn") == "true");
|
||||
|
||||
function logOneWay (yeah) {
|
||||
localStorage.setItem("loggedIn", yeah);
|
||||
setLoggedIn(yeah);
|
||||
}
|
||||
|
||||
LogInStatusUpdateEscapeTool.loggedIn = loggedIn;
|
||||
LogInStatusUpdateEscapeTool.setLoggedIn = logOneWay;
|
||||
|
||||
return (
|
||||
<LogInContext.Provider value={ loggedIn }>
|
||||
{children}
|
||||
</LogInContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useLoggedIn() {
|
||||
return useContext(LogInContext);
|
||||
}
|
||||
|
||||
export default AuthProvider;
|
@ -1,4 +1,5 @@
|
||||
import Graph from 'graphology';
|
||||
import { LogInStatusUpdateEscapeTool } from './AuthProvider.jsx';
|
||||
|
||||
// This is wrapper functtions to do requests to the api, from the frontend.
|
||||
|
||||
@ -6,8 +7,14 @@ export const apiUrl = `${window.location.origin}/api`;
|
||||
|
||||
|
||||
//lil helper to throw errorrs from the promise when we get not-ok results
|
||||
export const shoofetch = (url, config) => fetch(url, {...config, ...defaults})
|
||||
export const shoofetch = (url, config) => fetch(url, {...defaults, ...config})
|
||||
.then(async (res) => {
|
||||
if (res.status == 401) {
|
||||
LogInStatusUpdateEscapeTool.setLoggedIn(false);
|
||||
}
|
||||
|
||||
localStorage.setItem("session", res.session);
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(`got an error from the server: ${await res.text()}`);
|
||||
}
|
||||
@ -42,7 +49,7 @@ export async function fetchPage(id) {
|
||||
}
|
||||
|
||||
export async function postPage({id, title, description}) {
|
||||
return fetch(`${apiUrl}/page/${id}`, {
|
||||
return shoofetch(`${apiUrl}/page/${id}`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({id: id, title: title, description: description}),
|
||||
...defaults
|
||||
@ -75,13 +82,25 @@ export async function createAccount({name, password, nonce}) {
|
||||
})
|
||||
}
|
||||
|
||||
export async function logIn({name, password}) {
|
||||
export async function postLogIn({name, password}) {
|
||||
return shoofetch(`${apiUrl}/login`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({name: name, password: password})
|
||||
}).then((res) => {
|
||||
LogInStatusUpdateEscapeTool.setLoggedIn(true);
|
||||
return res;
|
||||
})
|
||||
}
|
||||
|
||||
export async function logOut() {
|
||||
return shoofetch(`${apiUrl}/logout`, { method: 'POST' });
|
||||
export async function postLogOut() {
|
||||
return shoofetch(`${apiUrl}/logout`, {
|
||||
method: 'POST'
|
||||
}).then((res) => {
|
||||
LogInStatusUpdateEscapeTool.setLoggedIn(false);
|
||||
return res;
|
||||
})
|
||||
}
|
||||
|
||||
export async function fetchProfile() {
|
||||
return shoofetch(`${apiUrl}/user`, {method: 'GET'});
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { postLogOut } from './apiTools.jsx';
|
||||
|
||||
// New hook to make links use the react-router `navigate` method instead of default browser behavior.
|
||||
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
|
||||
export function useFixLinks() {
|
||||
// spread this return value on <a/> elements in order to make them navigate
|
||||
const navigate = useNavigate();
|
||||
|
@ -1,21 +1,12 @@
|
||||
|
||||
.landing-page {
|
||||
width: 76ch;
|
||||
.landing-column {
|
||||
width: 80%;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
padding-top: 3rem;
|
||||
color: white;
|
||||
}
|
||||
.landing-page header {
|
||||
text-align: right;
|
||||
width: 100%;
|
||||
}
|
||||
.landing-page header hr {
|
||||
width: 60%;
|
||||
margin-inline-start: auto;
|
||||
margin-inline-end: 0;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.landing-container {
|
||||
display: grid;
|
||||
width: 100%;
|
||||
@ -29,3 +20,7 @@
|
||||
background: rgba(10,66,30, 0.75);
|
||||
}
|
||||
|
||||
ol li {
|
||||
list-style-type: circle;
|
||||
}
|
||||
|
||||
|
@ -1,9 +1,12 @@
|
||||
import { useNavigate, Link } from 'react-router-dom'
|
||||
import { useState } from 'react';
|
||||
import { useNavigate, Link } from 'react-router-dom';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
|
||||
import { fetchPageList, postNewPage, logOut } from '../apiTools.jsx';
|
||||
import { fetchPageList, postNewPage, postLogOut } from '../apiTools.jsx';
|
||||
import { useFixLinks } from '../clientStuff.jsx';
|
||||
|
||||
import { useLoggedIn } from '../AuthProvider.jsx';
|
||||
|
||||
import PageList from './PageList.jsx';
|
||||
import GraphRender from './GraphRender.jsx';
|
||||
|
||||
@ -23,8 +26,10 @@ function Landing() {
|
||||
},
|
||||
});
|
||||
|
||||
const loggedIn = useLoggedIn();
|
||||
|
||||
return (
|
||||
<div className="landing-page">
|
||||
<div className="landing-column">
|
||||
<header>
|
||||
<h1>Welcome to the forest.</h1>
|
||||
<hr/>
|
||||
@ -37,10 +42,20 @@ function Landing() {
|
||||
<PageList />
|
||||
</section>
|
||||
<section className="landing-section">
|
||||
<button onClick={() => navigate('/register')}>Sign up</button><br/>
|
||||
<button onClick={() => navigate('/login')}>Log in</button><br/>
|
||||
<button onClick={makeNewPage.mutate}>Dig new room!</button><br/>
|
||||
<button onClick={logOut}>Log Out</button><br/>
|
||||
{ loggedIn
|
||||
?
|
||||
<>
|
||||
<button onClick={makeNewPage.mutate}>Dig new room!</button><br/>
|
||||
<button onClick={postLogOut}>Log Out</button><br/>
|
||||
<button onClick={() => navigate('/profile')}>Profile</button><br/>
|
||||
</>
|
||||
:
|
||||
<>
|
||||
<button onClick={() => navigate('/register')}>Sign up</button><br/>
|
||||
<button onClick={() => navigate('/login')}>Log in</button><br/>
|
||||
<button onClick={() => navigate('/profile')}>Profile (but you're logged out, so it shouldn't work!)</button><br/>
|
||||
</>
|
||||
}
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,31 +1,31 @@
|
||||
import { useForm } from "react-hook-form";
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { logIn } from '../apiTools.jsx';
|
||||
import { postLogIn } from '../apiTools.jsx';
|
||||
|
||||
function LogIn() {
|
||||
const { register, handleSubmit } = useForm();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const onSubmit = (d) => {
|
||||
logIn(d)
|
||||
.then(() => navigate('/'))
|
||||
.catch((error) => console.log('error logging in: ', error));
|
||||
postLogIn(d).then( () => navigate('/') );
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<label>
|
||||
User name:
|
||||
<input {...register("name")} />
|
||||
<br/>
|
||||
</label>
|
||||
<label>
|
||||
Password:
|
||||
<input {...register("password")} />
|
||||
<br/>
|
||||
</label>
|
||||
<button type="submit">Log right in, buckaroo!</button>
|
||||
</form>
|
||||
<div className="main-column">
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<label>
|
||||
User name:
|
||||
<input {...register("name")} />
|
||||
<br/>
|
||||
</label>
|
||||
<label>
|
||||
Password:
|
||||
<input type="password" {...register("password")} />
|
||||
<br/>
|
||||
</label>
|
||||
<button type="submit">Log right in, buckaroo!</button>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
52
client/src/login/Profile.jsx
Normal file
52
client/src/login/Profile.jsx
Normal file
@ -0,0 +1,52 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useForm } from "react-hook-form";
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { fetchProfile } from '../apiTools.jsx';
|
||||
import { useLoggedIn } from '../AuthProvider.jsx';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
function Profile() {
|
||||
const { register, handleSubmit } = useForm();
|
||||
const navigate = useNavigate();
|
||||
const loggedIn = useLoggedIn();
|
||||
|
||||
const { isPending, isError, error, data } = useQuery({ // fetch the currrent values
|
||||
queryKey: ['profile'],
|
||||
queryFn: fetchProfile,
|
||||
retry: 1
|
||||
});
|
||||
|
||||
//useEffect(() => { if (!loggedIn) navigate('/'); }, [loggedIn]);
|
||||
|
||||
const { name, favoriteColor, leastFavoriteColor } = (data || {});
|
||||
|
||||
return (
|
||||
<div className="main-column">
|
||||
<section className="page-contents">
|
||||
<p>the page {isPending ? "is" : "isn't"} pending</p>
|
||||
<p>there {isError ? "is" : "isn't"} an error</p>
|
||||
<p>error is {error?.message}</p>
|
||||
<form onSubmit={handleSubmit(() => {})}>
|
||||
<label>
|
||||
User name:
|
||||
<input {...register("name")} value={name}/>
|
||||
<br/>
|
||||
</label>
|
||||
<label>
|
||||
Favorite Color:
|
||||
<input {...register("favoriteColor")} value={favoriteColor}/>
|
||||
<br/>
|
||||
</label>
|
||||
<label>
|
||||
Least Favorite Color:
|
||||
<input {...register("leastFavoriteColor")} value={leastFavoriteColor}/>
|
||||
<br/>
|
||||
</label>
|
||||
<button type="submit">Change those settings, bucko!!</button>
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Profile;
|
@ -13,24 +13,26 @@ function Register() {
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<label>
|
||||
User name:
|
||||
<input {...register("name")} />
|
||||
<br/>
|
||||
</label>
|
||||
<label>
|
||||
Password:
|
||||
<input {...register("password")} />
|
||||
<br/>
|
||||
</label>
|
||||
<label>
|
||||
Nonce password from shoofle:
|
||||
<input {...register("nonce")} />
|
||||
<br/>
|
||||
</label>
|
||||
<button type="submit">Request Account!</button>
|
||||
</form>
|
||||
<div className="main-column">
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<label>
|
||||
User name:
|
||||
<input {...register("name")} />
|
||||
<br/>
|
||||
</label>
|
||||
<label>
|
||||
Password:
|
||||
<input {...register("password")} />
|
||||
<br/>
|
||||
</label>
|
||||
<label>
|
||||
Nonce password from shoofle:
|
||||
<input {...register("nonce")} />
|
||||
<br/>
|
||||
</label>
|
||||
<button type="submit">Request Account!</button>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,7 @@ import { apiUrl, fetchPage, postPage, deletePage } from '../apiTools.jsx';
|
||||
|
||||
import './Pages.css';
|
||||
|
||||
function PageView() {
|
||||
function PageEdit() {
|
||||
const queryClient = useQueryClient();
|
||||
const navigate = useNavigate();
|
||||
const { pagenumber } = useParams();
|
||||
@ -32,10 +32,9 @@ function PageView() {
|
||||
|
||||
const ready = !(fetchQuery.error || fetchQuery.isPending);
|
||||
|
||||
let the_id, page_title, page_text, page_html;
|
||||
if (ready) [the_id, page_title, page_text, page_html] = fetchQuery.data;
|
||||
if (!page_title) page_title = " ";
|
||||
if (!page_text) page_text = " ";
|
||||
let {id, title, description, html} = fetchQuery.data || {};
|
||||
if (!title) title = " ";
|
||||
if (!description) description = " ";
|
||||
|
||||
function submitChanges(e) {
|
||||
const newTitle = document.querySelector('span').innerHTML;
|
||||
@ -45,7 +44,7 @@ function PageView() {
|
||||
title: newTitle,
|
||||
description: newText,
|
||||
});
|
||||
navigate(`/${pagenumber}`)
|
||||
navigate(`/${pagenumber}`, {replace: true})
|
||||
}
|
||||
|
||||
function submitDelete(e) {
|
||||
@ -55,13 +54,14 @@ function PageView() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="page-container">
|
||||
<div className="main-column">
|
||||
<header>
|
||||
<h1>
|
||||
<a href="/">🌳</a>
|
||||
{pagenumber}.
|
||||
<span
|
||||
contentEditable="true"
|
||||
dangerouslySetInnerHTML={{__html: ready ? page_title : "..." }} />
|
||||
dangerouslySetInnerHTML={{__html: ready ? title : "..." }} />
|
||||
</h1>
|
||||
<hr/>
|
||||
</header>
|
||||
@ -71,7 +71,7 @@ function PageView() {
|
||||
<div className="page-contents">
|
||||
<pre
|
||||
contentEditable="true"
|
||||
dangerouslySetInnerHTML={{__html: page_text}} />
|
||||
dangerouslySetInnerHTML={{__html: description}} />
|
||||
</div>
|
||||
<button
|
||||
disabled={postMutation.isPending}
|
||||
@ -94,4 +94,4 @@ function PageView() {
|
||||
);
|
||||
}
|
||||
|
||||
export default PageView;
|
||||
export default PageEdit;
|
@ -18,27 +18,27 @@ function PageView() {
|
||||
|
||||
const ready = !(error || isPending);
|
||||
|
||||
let the_id, page_title, page_text, page_html;
|
||||
if (data) [the_id, page_title, page_text, page_html] = data;
|
||||
let {id, title, description, html} = data || {};
|
||||
|
||||
return (
|
||||
<div className="page-container">
|
||||
<div className="main-column">
|
||||
<div>
|
||||
<header>
|
||||
<h1>{pagenumber}. {ready ? (page_title || " ") : "..."}</h1>
|
||||
<h1>
|
||||
<a href="/" {...noLoad}>🌳</a>{pagenumber}. {ready ? (title || " ") : "..."}</h1>
|
||||
<hr/>
|
||||
</header>
|
||||
<section>
|
||||
{ ready ?
|
||||
<div
|
||||
className="page-contents"
|
||||
dangerouslySetInnerHTML={{ __html: (page_html || " ") }}
|
||||
dangerouslySetInnerHTML={{ __html: (html || " ") }}
|
||||
{...noLoad}
|
||||
/>
|
||||
:
|
||||
(isPending ? "Loading..." : JSON.stringify(error))
|
||||
}
|
||||
<button onClick={(e) => { e.preventDefault(); navigate(`/${pagenumber}/edit`)}}>Edit this page!</button>
|
||||
<button onClick={(e) => { e.preventDefault(); navigate(`/${pagenumber}/edit`, {replace: true})}}>Edit this page!</button>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
|
0
client/src/page/PageWithSidebar.jsx
Normal file
0
client/src/page/PageWithSidebar.jsx
Normal file
@ -1,22 +1,4 @@
|
||||
|
||||
.page-container {
|
||||
width: 76ch;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
padding-top: 3rem;
|
||||
color: white;
|
||||
}
|
||||
.page-container header {
|
||||
text-align: right;
|
||||
width: 100%;
|
||||
}
|
||||
.page-container header hr {
|
||||
width: 60%;
|
||||
margin-inline-start: auto;
|
||||
margin-inline-end: 0;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.page-contents {
|
||||
text-align: left;
|
||||
background: rgba(17,66,0);
|
||||
@ -25,7 +7,7 @@
|
||||
border-radius: 1rem;
|
||||
|
||||
}
|
||||
.page-contents pre {
|
||||
pre {
|
||||
width: 100%;
|
||||
white-space: normal;
|
||||
}
|
||||
|
122
server/api.js
122
server/api.js
@ -1,122 +0,0 @@
|
||||
const express = require('express');
|
||||
const showdown = require('showdown');
|
||||
const argon2 = require('argon2');
|
||||
|
||||
const { query } = require('./dbHelper.js');
|
||||
const { graphFromList } = require('./graphStuff.js');
|
||||
|
||||
const app = express.Router();
|
||||
const converter = new showdown.Converter();
|
||||
|
||||
const sqlite = require("better-sqlite3");
|
||||
const db = new sqlite('the_big_db.db', { verbose: console.log });
|
||||
console.log(db);
|
||||
|
||||
app.get('/pages', (req, res) => {
|
||||
try {
|
||||
const pages = db.prepare('select id, title from pages').all();
|
||||
res.status(200).json(pages);
|
||||
} catch (error) {
|
||||
res.status(500).json({"error": error});
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/page/new', (req, res) => {
|
||||
try {
|
||||
const newPage = db.prepare('insert into pages (title, description) values (?, ?) returning id')
|
||||
.get("new page", "this page is new!");
|
||||
res.status(200).json(newPage);
|
||||
} catch (error) {
|
||||
res.status(500).json({"error": error});
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/page/:id', (req, res) => {
|
||||
try {
|
||||
const page = db.prepare('select * from pages where id=:id').get(req.params);
|
||||
if (page === undefined) res.status(404).json({"error": "page not found"});
|
||||
else res.status(200).json(page);
|
||||
} catch (error) {
|
||||
res.status(500).json({"error": error});
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/page/:id', (req, res) => {
|
||||
try {
|
||||
const html = converter.makeHtml(req.body.description);
|
||||
const changes = db.prepare('replace into pages (id, title, descriptioon, html) values (?, ?, ?, ?)')
|
||||
.run(req.params.id, req.body.title, req.body.description, html);
|
||||
res.status(200).json(changes);
|
||||
} catch (error) {
|
||||
res.status(500).json({"error": error});
|
||||
}
|
||||
});
|
||||
|
||||
app.delete('/page/:id', (req, res) => {
|
||||
try {
|
||||
const changes = db.prepare('delete from pages where id = ?').run(req.params.id);
|
||||
res.status(200).json({id: req.params.id});
|
||||
} catch (error) {
|
||||
res.status(500).json({"error": error});
|
||||
}
|
||||
})
|
||||
|
||||
app.get('/graph', (req, res) => {
|
||||
try {
|
||||
const rows = db.prepare('select id, html from pages').all();
|
||||
const graph = graphFromList(rows);
|
||||
res.status(200).json(graph);
|
||||
} catch (error) {
|
||||
res.status(500).json({"error": error});
|
||||
}
|
||||
});
|
||||
|
||||
// auth stuff
|
||||
app.post('/register', async (req, res) => {
|
||||
const {name, password, nonce} = req.body;
|
||||
|
||||
const oldUser = db.prepare('select name from users where name=?').get(name);
|
||||
if (oldUser) return res.status(500).json({"error": "user name already in use"});
|
||||
|
||||
// check if the nonce password is correctt
|
||||
if (nonce != "a softer birdsong") return res.status(500).json({"error": "wrong nonce"});
|
||||
|
||||
try {
|
||||
// i'm told argon2 is the good one nowatimes
|
||||
const hash = await argon2.hash(password);
|
||||
const inserted = db.prepare('insert into users (name, password) values (?, ?)').run(name, hash);
|
||||
res.status(200).json(inserted);
|
||||
} catch (error) {
|
||||
res.status(500).json({"error": error});
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/login', async (req, res) => {
|
||||
if (req.session.name) {
|
||||
return res.status(200).json({message: "already logged in", name: req.session.name});
|
||||
}
|
||||
|
||||
const {name, password} = req.body;
|
||||
|
||||
// fetch username and passswords from the db
|
||||
const storedUser = db.prepare('select name, password from users where name = ?').get(name);
|
||||
if (!storedUser) {
|
||||
return res.status(401).json({"error": "password/username combo not found in database"});
|
||||
}
|
||||
|
||||
//check if the passss hashes mattch and log in
|
||||
if (!(await argon2.verify(storedUser.password, password))) {
|
||||
return res.status(401).json({"error": "password/username combo not found in database"});
|
||||
}
|
||||
|
||||
// set the session cookie and rreturn 200!
|
||||
req.session.name = name;
|
||||
return res.status(200).json({message: "successfully logged in!", name: name});
|
||||
});
|
||||
|
||||
app.post('/logout', (req, res) => {
|
||||
req.session.destroy();
|
||||
res.status(200).json({message: "successfully logged out"});
|
||||
});
|
||||
|
||||
module.exports = app;
|
10
server/authStuff.js
Normal file
10
server/authStuff.js
Normal file
@ -0,0 +1,10 @@
|
||||
function loginRequired(req, res, next) {
|
||||
console.log("checkinig on req.session for auhetnticaion: ", req.session);
|
||||
if (!req.session.name) {
|
||||
return res.status(401).json({"error": "need to be logged in for that bucko"});
|
||||
}
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
module.exports = { loginRequired };
|
25
server/routes/api.js
Normal file
25
server/routes/api.js
Normal file
@ -0,0 +1,25 @@
|
||||
const { graphFromList } = require('../graphStuff.js');
|
||||
|
||||
const sqlite = require('better-sqlite3');
|
||||
const db = new sqlite('the_big_db.db', { verbose: console.log });
|
||||
|
||||
const express = require('express');
|
||||
const app = express.Router();
|
||||
|
||||
const page_routes = require('./pages.js');
|
||||
app.use(page_routes);
|
||||
|
||||
const user_routes = require('./users.js');
|
||||
app.use(user_routes);
|
||||
|
||||
app.get('/graph', (req, res) => {
|
||||
try {
|
||||
const rows = db.prepare('select id, html from pages').all();
|
||||
const graph = graphFromList(rows);
|
||||
res.status(200).json(graph);
|
||||
} catch (error) {
|
||||
res.status(500).json({"error": error});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = app;
|
62
server/routes/pages.js
Normal file
62
server/routes/pages.js
Normal file
@ -0,0 +1,62 @@
|
||||
const express = require('express');
|
||||
const app = express.Router();
|
||||
|
||||
const sqlite = require('better-sqlite3');
|
||||
const db = new sqlite('../the_big_db.db', { verbose: console.log });
|
||||
|
||||
const { loginRequired } = require('../authStuff.js');
|
||||
|
||||
const showdown = require('showdown');
|
||||
const converter = new showdown.Converter();
|
||||
|
||||
app.get('/pages', (req, res) => {
|
||||
try {
|
||||
const pages = db.prepare('select id, title from pages').all();
|
||||
res.status(200).json(pages);
|
||||
} catch (error) {
|
||||
res.status(500).json({"error": error});
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/page/new', loginRequired, (req, res) => {
|
||||
try {
|
||||
const newPage = db.prepare('insert into pages (title, description) values (?, ?) returning id')
|
||||
.get("new page", "this page is new!");
|
||||
res.status(200).json(newPage);
|
||||
} catch (error) {
|
||||
res.status(500).json({"error": error});
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/page/:id', (req, res) => {
|
||||
try {
|
||||
const page = db.prepare('select * from pages where id=:id').get(req.params);
|
||||
if (page === undefined) res.status(404).json({"error": "page not found"});
|
||||
else res.status(200).json(page);
|
||||
} catch (error) {
|
||||
res.status(500).json({"error": error});
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/page/:id', loginRequired, (req, res) => {
|
||||
try {
|
||||
const html = converter.makeHtml(req.body.description);
|
||||
const changes = db.prepare('replace into pages (id, title, description, html) values (?, ?, ?, ?)')
|
||||
.run(req.params.id, req.body.title, req.body.description, html);
|
||||
res.status(200).json(changes);
|
||||
} catch (error) {
|
||||
res.status(500).json({"error": error});
|
||||
}
|
||||
});
|
||||
|
||||
app.delete('/page/:id', loginRequired, (req, res) => {
|
||||
try {
|
||||
const changes = db.prepare('delete from pages where id = ?').run(req.params.id);
|
||||
res.status(200).json({id: req.params.id});
|
||||
} catch (error) {
|
||||
res.status(500).json({"error": error});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
module.exports = app;
|
68
server/routes/users.js
Normal file
68
server/routes/users.js
Normal file
@ -0,0 +1,68 @@
|
||||
const express = require('express');
|
||||
const app = express.Router();
|
||||
|
||||
const sqlite = require('better-sqlite3');
|
||||
const db = new sqlite('../the_big_db.db', { verbose: console.log });
|
||||
|
||||
const argon2 = require('argon2');
|
||||
|
||||
const { loginRequired } = require('../authStuff.js');
|
||||
|
||||
// auth stuff
|
||||
app.post('/register', async (req, res) => {
|
||||
const {name, password, nonce} = req.body;
|
||||
|
||||
const oldUser = db.prepare('select name from users where name=?').get(name);
|
||||
if (oldUser) return res.status(500).json({"error": "user name already in use"});
|
||||
|
||||
// check if the nonce password is correctt
|
||||
if (nonce != "a softer birdsong") return res.status(500).json({"error": "wrong nonce"});
|
||||
|
||||
try {
|
||||
// i'm told argon2 is the good one nowatimes
|
||||
const hash = await argon2.hash(password);
|
||||
const inserted = db.prepare('insert into users (name, password) values (?, ?)').run(name, hash);
|
||||
res.status(200).json(inserted);
|
||||
} catch (error) {
|
||||
res.status(500).json({"error": error});
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/login', async (req, res) => {
|
||||
if (req.session.name) {
|
||||
return res.status(200).json({message: "already logged in", name: req.session.name});
|
||||
}
|
||||
|
||||
const {name, password} = req.body;
|
||||
|
||||
// fetch username and passswords from the db
|
||||
const storedUser = db.prepare('select name, password from users where name = ?').get(name);
|
||||
if (!storedUser) {
|
||||
return res.status(401).json({"error": "password/username combo not found in database"});
|
||||
}
|
||||
|
||||
//check if the passss hashes mattch and log in
|
||||
if (!(await argon2.verify(storedUser.password, password))) {
|
||||
return res.status(401).json({"error": "password/username combo not found in database"});
|
||||
}
|
||||
|
||||
// set the session cookie and rreturn 200!
|
||||
req.session.name = name;
|
||||
console.log('setting req.session.name! : ', req.session);
|
||||
return res.status(200).json({message: "successfully logged in!", name: name});
|
||||
});
|
||||
|
||||
app.post('/logout', (req, res) => {
|
||||
req.session.destroy();
|
||||
res.status(200).json({message: "successfully logged out"});
|
||||
});
|
||||
|
||||
app.get('/user', loginRequired, (req, res) => {
|
||||
res.status(200).json({
|
||||
"name": req.session.name,
|
||||
"favoriteColor": "red",
|
||||
"leastFavoriteColor": "also red"
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = app;
|
@ -1,14 +1,16 @@
|
||||
const express = require('express');
|
||||
const session = require('express-session');
|
||||
const sqlite = require('better-sqlite3');
|
||||
const app = express();
|
||||
|
||||
const apiRoutes = require('./api.js');
|
||||
const session = require('express-session');
|
||||
|
||||
const apiRoutes = require('./routes/api.js');
|
||||
|
||||
const port = process.env.PORT || 3001; // Use the port provided by the host or default to 3000
|
||||
|
||||
const sqlite = require('better-sqlite3');
|
||||
const db = new sqlite('the_big_db.db', { verbose: console.log });
|
||||
const SqliteStore = require('better-sqlite3-session-store')(session);
|
||||
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
|
||||
app.use(session({
|
||||
|
Loading…
Reference in New Issue
Block a user