split routes into separate files and added authentication
This commit is contained in:
parent
32913d582a
commit
5a7021b674
@ -16,3 +16,29 @@ a {
|
|||||||
a:visited {
|
a:visited {
|
||||||
color: slategray;
|
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 PageEdit from '/src/page/PageEdit.jsx';
|
||||||
import LogIn from '/src/login/LogIn.jsx';
|
import LogIn from '/src/login/LogIn.jsx';
|
||||||
import Register from '/src/login/Register.jsx';
|
import Register from '/src/login/Register.jsx';
|
||||||
|
import Profile from '/src/login/Profile.jsx';
|
||||||
|
import AuthProvider from '/src/AuthProvider.jsx';
|
||||||
|
|
||||||
import './App.css';
|
import './App.css';
|
||||||
|
|
||||||
@ -13,15 +15,18 @@ const queryClient = new QueryClient();
|
|||||||
function App() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
<BrowserRouter>
|
<AuthProvider>
|
||||||
<Routes>
|
<BrowserRouter>
|
||||||
<Route path="/" element={<Landing/>}/>
|
<Routes>
|
||||||
<Route path="/login" element={<LogIn/>}/>
|
<Route path="/" element={<Landing/>}/>
|
||||||
<Route path="/register" element={<Register/>}/>
|
<Route path="/login" element={<LogIn/>}/>
|
||||||
<Route path="/:pagenumber" element={<PageView/>}/>
|
<Route path="/register" element={<Register/>}/>
|
||||||
<Route path="/:pagenumber/edit" element={<PageEdit/>}/>
|
<Route path="/:pagenumber" element={<PageView/>}/>
|
||||||
</Routes>
|
<Route path="/:pagenumber/edit" element={<PageEdit/>}/>
|
||||||
</BrowserRouter>
|
<Route path="/profile" element={<Profile/>}/>
|
||||||
|
</Routes>
|
||||||
|
</BrowserRouter>
|
||||||
|
</AuthProvider>
|
||||||
</QueryClientProvider>
|
</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 Graph from 'graphology';
|
||||||
|
import { LogInStatusUpdateEscapeTool } from './AuthProvider.jsx';
|
||||||
|
|
||||||
// This is wrapper functtions to do requests to the api, from the frontend.
|
// 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
|
//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) => {
|
.then(async (res) => {
|
||||||
|
if (res.status == 401) {
|
||||||
|
LogInStatusUpdateEscapeTool.setLoggedIn(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
localStorage.setItem("session", res.session);
|
||||||
|
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
throw new Error(`got an error from the server: ${await res.text()}`);
|
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}) {
|
export async function postPage({id, title, description}) {
|
||||||
return fetch(`${apiUrl}/page/${id}`, {
|
return shoofetch(`${apiUrl}/page/${id}`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify({id: id, title: title, description: description}),
|
body: JSON.stringify({id: id, title: title, description: description}),
|
||||||
...defaults
|
...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`, {
|
return shoofetch(`${apiUrl}/login`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify({name: name, password: password})
|
body: JSON.stringify({name: name, password: password})
|
||||||
|
}).then((res) => {
|
||||||
|
LogInStatusUpdateEscapeTool.setLoggedIn(true);
|
||||||
|
return res;
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function logOut() {
|
export async function postLogOut() {
|
||||||
return shoofetch(`${apiUrl}/logout`, { method: 'POST' });
|
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.
|
// 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() {
|
export function useFixLinks() {
|
||||||
// spread this return value on <a/> elements in order to make them navigate
|
// spread this return value on <a/> elements in order to make them navigate
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
@ -1,21 +1,12 @@
|
|||||||
|
|
||||||
.landing-page {
|
.landing-column {
|
||||||
width: 76ch;
|
width: 80%;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
padding-top: 3rem;
|
padding-top: 3rem;
|
||||||
color: white;
|
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 {
|
.landing-container {
|
||||||
display: grid;
|
display: grid;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@ -29,3 +20,7 @@
|
|||||||
background: rgba(10,66,30, 0.75);
|
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 { 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 { useFixLinks } from '../clientStuff.jsx';
|
||||||
|
|
||||||
|
import { useLoggedIn } from '../AuthProvider.jsx';
|
||||||
|
|
||||||
import PageList from './PageList.jsx';
|
import PageList from './PageList.jsx';
|
||||||
import GraphRender from './GraphRender.jsx';
|
import GraphRender from './GraphRender.jsx';
|
||||||
|
|
||||||
@ -23,8 +26,10 @@ function Landing() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const loggedIn = useLoggedIn();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="landing-page">
|
<div className="landing-column">
|
||||||
<header>
|
<header>
|
||||||
<h1>Welcome to the forest.</h1>
|
<h1>Welcome to the forest.</h1>
|
||||||
<hr/>
|
<hr/>
|
||||||
@ -37,10 +42,20 @@ function Landing() {
|
|||||||
<PageList />
|
<PageList />
|
||||||
</section>
|
</section>
|
||||||
<section className="landing-section">
|
<section className="landing-section">
|
||||||
<button onClick={() => navigate('/register')}>Sign up</button><br/>
|
{ loggedIn
|
||||||
<button onClick={() => navigate('/login')}>Log in</button><br/>
|
?
|
||||||
<button onClick={makeNewPage.mutate}>Dig new room!</button><br/>
|
<>
|
||||||
<button onClick={logOut}>Log Out</button><br/>
|
<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>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,31 +1,31 @@
|
|||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { logIn } from '../apiTools.jsx';
|
import { postLogIn } from '../apiTools.jsx';
|
||||||
|
|
||||||
function LogIn() {
|
function LogIn() {
|
||||||
const { register, handleSubmit } = useForm();
|
const { register, handleSubmit } = useForm();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const onSubmit = (d) => {
|
const onSubmit = (d) => {
|
||||||
logIn(d)
|
postLogIn(d).then( () => navigate('/') );
|
||||||
.then(() => navigate('/'))
|
|
||||||
.catch((error) => console.log('error logging in: ', error));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={handleSubmit(onSubmit)}>
|
<div className="main-column">
|
||||||
<label>
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
User name:
|
<label>
|
||||||
<input {...register("name")} />
|
User name:
|
||||||
<br/>
|
<input {...register("name")} />
|
||||||
</label>
|
<br/>
|
||||||
<label>
|
</label>
|
||||||
Password:
|
<label>
|
||||||
<input {...register("password")} />
|
Password:
|
||||||
<br/>
|
<input type="password" {...register("password")} />
|
||||||
</label>
|
<br/>
|
||||||
<button type="submit">Log right in, buckaroo!</button>
|
</label>
|
||||||
</form>
|
<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 (
|
return (
|
||||||
<form onSubmit={handleSubmit(onSubmit)}>
|
<div className="main-column">
|
||||||
<label>
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
User name:
|
<label>
|
||||||
<input {...register("name")} />
|
User name:
|
||||||
<br/>
|
<input {...register("name")} />
|
||||||
</label>
|
<br/>
|
||||||
<label>
|
</label>
|
||||||
Password:
|
<label>
|
||||||
<input {...register("password")} />
|
Password:
|
||||||
<br/>
|
<input {...register("password")} />
|
||||||
</label>
|
<br/>
|
||||||
<label>
|
</label>
|
||||||
Nonce password from shoofle:
|
<label>
|
||||||
<input {...register("nonce")} />
|
Nonce password from shoofle:
|
||||||
<br/>
|
<input {...register("nonce")} />
|
||||||
</label>
|
<br/>
|
||||||
<button type="submit">Request Account!</button>
|
</label>
|
||||||
</form>
|
<button type="submit">Request Account!</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ import { apiUrl, fetchPage, postPage, deletePage } from '../apiTools.jsx';
|
|||||||
|
|
||||||
import './Pages.css';
|
import './Pages.css';
|
||||||
|
|
||||||
function PageView() {
|
function PageEdit() {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { pagenumber } = useParams();
|
const { pagenumber } = useParams();
|
||||||
@ -32,10 +32,9 @@ function PageView() {
|
|||||||
|
|
||||||
const ready = !(fetchQuery.error || fetchQuery.isPending);
|
const ready = !(fetchQuery.error || fetchQuery.isPending);
|
||||||
|
|
||||||
let the_id, page_title, page_text, page_html;
|
let {id, title, description, html} = fetchQuery.data || {};
|
||||||
if (ready) [the_id, page_title, page_text, page_html] = fetchQuery.data;
|
if (!title) title = " ";
|
||||||
if (!page_title) page_title = " ";
|
if (!description) description = " ";
|
||||||
if (!page_text) page_text = " ";
|
|
||||||
|
|
||||||
function submitChanges(e) {
|
function submitChanges(e) {
|
||||||
const newTitle = document.querySelector('span').innerHTML;
|
const newTitle = document.querySelector('span').innerHTML;
|
||||||
@ -45,7 +44,7 @@ function PageView() {
|
|||||||
title: newTitle,
|
title: newTitle,
|
||||||
description: newText,
|
description: newText,
|
||||||
});
|
});
|
||||||
navigate(`/${pagenumber}`)
|
navigate(`/${pagenumber}`, {replace: true})
|
||||||
}
|
}
|
||||||
|
|
||||||
function submitDelete(e) {
|
function submitDelete(e) {
|
||||||
@ -55,13 +54,14 @@ function PageView() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="page-container">
|
<div className="main-column">
|
||||||
<header>
|
<header>
|
||||||
<h1>
|
<h1>
|
||||||
|
<a href="/">🌳</a>
|
||||||
{pagenumber}.
|
{pagenumber}.
|
||||||
<span
|
<span
|
||||||
contentEditable="true"
|
contentEditable="true"
|
||||||
dangerouslySetInnerHTML={{__html: ready ? page_title : "..." }} />
|
dangerouslySetInnerHTML={{__html: ready ? title : "..." }} />
|
||||||
</h1>
|
</h1>
|
||||||
<hr/>
|
<hr/>
|
||||||
</header>
|
</header>
|
||||||
@ -71,7 +71,7 @@ function PageView() {
|
|||||||
<div className="page-contents">
|
<div className="page-contents">
|
||||||
<pre
|
<pre
|
||||||
contentEditable="true"
|
contentEditable="true"
|
||||||
dangerouslySetInnerHTML={{__html: page_text}} />
|
dangerouslySetInnerHTML={{__html: description}} />
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
disabled={postMutation.isPending}
|
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);
|
const ready = !(error || isPending);
|
||||||
|
|
||||||
let the_id, page_title, page_text, page_html;
|
let {id, title, description, html} = data || {};
|
||||||
if (data) [the_id, page_title, page_text, page_html] = data;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="page-container">
|
<div className="main-column">
|
||||||
<div>
|
<div>
|
||||||
<header>
|
<header>
|
||||||
<h1>{pagenumber}. {ready ? (page_title || " ") : "..."}</h1>
|
<h1>
|
||||||
|
<a href="/" {...noLoad}>🌳</a>{pagenumber}. {ready ? (title || " ") : "..."}</h1>
|
||||||
<hr/>
|
<hr/>
|
||||||
</header>
|
</header>
|
||||||
<section>
|
<section>
|
||||||
{ ready ?
|
{ ready ?
|
||||||
<div
|
<div
|
||||||
className="page-contents"
|
className="page-contents"
|
||||||
dangerouslySetInnerHTML={{ __html: (page_html || " ") }}
|
dangerouslySetInnerHTML={{ __html: (html || " ") }}
|
||||||
{...noLoad}
|
{...noLoad}
|
||||||
/>
|
/>
|
||||||
:
|
:
|
||||||
(isPending ? "Loading..." : JSON.stringify(error))
|
(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>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</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 {
|
.page-contents {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
background: rgba(17,66,0);
|
background: rgba(17,66,0);
|
||||||
@ -25,7 +7,7 @@
|
|||||||
border-radius: 1rem;
|
border-radius: 1rem;
|
||||||
|
|
||||||
}
|
}
|
||||||
.page-contents pre {
|
pre {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
white-space: normal;
|
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 express = require('express');
|
||||||
const session = require('express-session');
|
const app = express();
|
||||||
const sqlite = require('better-sqlite3');
|
|
||||||
|
|
||||||
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 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 db = new sqlite('the_big_db.db', { verbose: console.log });
|
||||||
const SqliteStore = require('better-sqlite3-session-store')(session);
|
const SqliteStore = require('better-sqlite3-session-store')(session);
|
||||||
|
|
||||||
const app = express();
|
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
|
|
||||||
app.use(session({
|
app.use(session({
|
||||||
|
Loading…
Reference in New Issue
Block a user