aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authors-ol <s+removethis@s-ol.nu>2022-01-14 15:53:47 +0000
committers-ol <s+removethis@s-ol.nu>2022-01-14 15:53:47 +0000
commit569d322f516ddb7271f89222f11ede68caf7fefb (patch)
treed826b0bab30595af161f588fc4b47068ce8be3c7
parentsmall fixes (diff)
downloadfedidag-569d322f516ddb7271f89222f11ede68caf7fefb.tar.gz
fedidag-569d322f516ddb7271f89222f11ede68caf7fefb.zip
login/logout
-rw-r--r--client/src/index.js34
-rw-r--r--client/src/ui/Menu.js109
-rw-r--r--client/src/user.js71
-rw-r--r--server/main.ts5
4 files changed, 162 insertions, 57 deletions
diff --git a/client/src/index.js b/client/src/index.js
index a30dbf1..0ca8b1c 100644
--- a/client/src/index.js
+++ b/client/src/index.js
@@ -4,6 +4,7 @@ import 'regenerator-runtime/runtime';
import { h, Fragment, Component, render } from 'preact';
import { GraphContainerJSONLD, GraphContainerMastodon } from './graph';
import { Menu, Discussion, Selection } from './ui';
+import { UserContainer } from './user';
class SelectionContainer extends Component {
state = {
@@ -134,29 +135,24 @@ const search = new URLSearchParams(window.location.search);
const graph = search.has('graph') ? search.get('graph') : 'lib/graph.json';
let app;
-if (search.has('document')) {
- app = (
- <GraphContainerJSONLD
- url={search.get('document')}
- render={graphRender}
- />
- );
-} else if (search.has('note')) {
+if (search.has('document') || search.has('note') || search.has('graph')) {
+ const Container = search.has('document') ? GraphContainerJSONLD : GraphContainerMastodon;
+ const url = search.get('document') || search.get('note') || search.get('graph');
+
app = (
- <GraphContainerMastodon
- url={search.get('note')}
- render={graphRender}
- />
+ <UserContainer>
+ <Container
+ url={url}
+ render={graphRender}
+ />
+ </UserContainer>
);
-} else if (search.has('graph')) {
+} else {
app = (
- <GraphContainerMastodon
- url={search.get('graph')}
- render={graphRender}
- />
+ <UserContainer>
+ <Menu />
+ </UserContainer>
);
-} else {
- app = <Menu />;
}
render(app, document.body);
diff --git a/client/src/ui/Menu.js b/client/src/ui/Menu.js
index 6f32280..f3e912d 100644
--- a/client/src/ui/Menu.js
+++ b/client/src/ui/Menu.js
@@ -1,17 +1,9 @@
import { h, Fragment } from 'preact';
-import { useState, useEffect } from 'preact/hooks';
+import { useState } from 'preact/hooks';
import cn from 'classnames';
-import { API_PREFIX } from '../config';
+import { useUser, useUserCtx } from '../user';
import css from './css';
-const fetchJSON = async (url) => {
- const res = await fetch(API_PREFIX + url, { credentials: 'include' });
- if (res.status !== 200)
- throw new Error("wrong status");
-
- return res.json();
-};
-
css`
a.discussion {
display: flex;
@@ -52,8 +44,9 @@ a.discussion > a:hover {
}
`;
-const DiscussionLink = ({ id, name, url, attributedTo, user }) => {
- const writable = attributedTo && attributedTo.indexOf(user) > -1;
+const DiscussionLink = ({ id, name, url, attributedTo }) => {
+ const { user } = useUser();
+ const writable = user && attributedTo && attributedTo.indexOf(user.id) > -1;
return (
<a class="discussion" href={`?document=${id}`}>
@@ -79,12 +72,12 @@ ul.discussions > li {
}
`;
-const DiscussionList = ({ discussions, user }) => {
+const DiscussionList = ({ discussions }) => {
return (
<ul class="discussions">
{discussions.map((discussion) => (
<li>
- <DiscussionLink {...discussion} user={user} />
+ <DiscussionLink {...discussion} />
</li>
))}
</ul>
@@ -92,21 +85,73 @@ const DiscussionList = ({ discussions, user }) => {
};
css`
+div.menu div.user {
+ display: flex;
+ align-items: baseline;
+ gap: 1em;
+}
+`;
+const UserMenu = () => {
+ const [showLogin, setLogin] = useState(false);
+ const { state: { user }, dispatch } = useUserCtx();
+
+ if (showLogin) {
+ return (
+ <form class="user" onSubmit={(e) => {
+ e.preventDefault();
+
+ const elem = e.target.elements;
+ const username = elem.namedItem('username').value;
+ const password = elem.namedItem('password').value;
+
+ dispatch({ type: 'login', username, password })
+ .then(
+ () => setLogin(false),
+ (err) => setLogin("error - wrong username/password?"),
+ );
+
+ }}>
+ {showLogin !== true ? <p>{showLogin}</p> : null}
+ <div>
+ <label>username</label>
+ <input name="username" autocomplete="username" required />
+ </div>
+ <div>
+ <label>password</label>
+ <input name="password" autocomplete="password" type="password" required />
+ </div>
+ <input type="submit" value="login" />
+ <button onClick={(e) => { e.preventDefault(); setLogin(false)}}>cancel</button>
+ </form>
+ );
+ }
+
+ return (
+ <div class="user">
+ {user && user.name !== 'Guest'
+ ? (
+ <>
+ <span>logged in as {user.name}</span>
+ <button onClick={() => dispatch({ type: 'logout' })}>log out</button>
+ </>
+ )
+ : (
+ <>
+ <span>not logged in</span>
+ <button onClick={() => setLogin(true)}>log in</button>
+ </>
+ )}
+ </div>
+ );
+};
+
+css`
div.menu {
margin: 1rem 2rem;
}
`;
export const Menu = () => {
- const [user, setUser] = useState(null);
- const [discussions, setDiscussions] = useState([]);
-
- useEffect(() => {
- fetchJSON('/discdag/list')
- .then(({ user, discussions }) => {
- setUser(user ?? null);
- setDiscussions(discussions);
- });
- }, []);
+ const { user, discussions } = useUser();
if (user === null) {
return (
@@ -120,20 +165,8 @@ export const Menu = () => {
return (
<div class="menu">
<h2>Discussions</h2>
- {/*user && user.name !== 'Guest'
- ? (
- <>
- <span>logged in as {user.name}</span>
- <button>log out</button>
- </>
- )
- : (
- <>
- <span>not logged in</span>
- <button>log in</button>
- </>
- )*/}
- <DiscussionList discussions={discussions} user={user?.id} />
+ <UserMenu />
+ <DiscussionList discussions={discussions} />
</div>
);
};
diff --git a/client/src/user.js b/client/src/user.js
new file mode 100644
index 0000000..dfa6384
--- /dev/null
+++ b/client/src/user.js
@@ -0,0 +1,71 @@
+import { h, createContext } from 'preact';
+import { useRef, useState, useCallback, useEffect, useContext } from 'preact/hooks';
+import { API_PREFIX } from './config';
+
+const fetchJSON = async (url, method='GET', data=undefined) => {
+ const res = await fetch(API_PREFIX + url, {
+ method,
+ credentials: 'include',
+ headers: { 'content-type': data && 'application/json' },
+ body: data && JSON.stringify(data),
+ });
+
+ if (res.status !== 200)
+ throw new Error("wrong status");
+
+ return res.json();
+};
+
+const initialState = { user: null, discussions: [] };
+const reducer = async (action, ref) => {
+ switch (action.type) {
+ case 'login': {
+ const { username, password } = action;
+ await fetchJSON('/discdag/login', 'POST', { username, password });
+ return await reducer({ type: 'refresh' }, ref);
+ }
+
+ case 'logout': {
+ await fetchJSON('/discdag/logout', 'POST');
+ return await reducer({ type: 'refresh' }, ref);
+ }
+
+ case 'refresh': {
+ const { user, discussions } = await fetchJSON('/discdag/list');
+ return { ...ref.current, user, discussions };
+ }
+
+ default:
+ throw new Error('Unexpected action');
+ }
+};
+
+const UserContext = createContext({
+ state: initialState,
+ dispatch: () => {
+ throw new Error("no UserContext Provider!");
+ },
+});
+
+export const UserContainer = ({ children }) => {
+ const ref = useRef();
+ const [state, setState] = useState(initialState);
+ ref.current = state;
+
+ const dispatch = useCallback(async (action) => {
+ const nextState = await reducer(action, ref);
+ setState(nextState);
+ }, [ref]);
+
+ useEffect(() => dispatch({ type: 'refresh' }), []);
+
+ return (
+ <UserContext.Provider value={{ state, dispatch }}>
+ {children}
+ </UserContext.Provider>
+ );
+};
+
+export const useUser = () => useContext(UserContext).state;
+export const useDispatch = () => useContext(UserContext).dispatch;
+export const useUserCtx = () => useContext(UserContext);
diff --git a/server/main.ts b/server/main.ts
index 9dc6191..3a28ee9 100644
--- a/server/main.ts
+++ b/server/main.ts
@@ -62,6 +62,8 @@ const discdag = express()
});
}))
+ .post('/logout', wrap(async ($req, $res) => $res.cookie('digest', '').json({})))
+
.get('/list', wrap(async ($req, $res) => {
const { digest } = $req.cookies;
@@ -131,6 +133,9 @@ const discdag = express()
const doc = cheerio.load(await res.text());
+ if (doc.text().indexOf("Invalid discussion ID:") > -1)
+ return $res.status(404).end();
+
type Note = {
type: 'Note';
published: string;