added login
This commit is contained in:
1 parent
171657d616
commit
0cb2d25a62
3 files changed
+137
-26
No files matched your search
+29
-2
@@ -12,17 +12,44 @@
|
|||||||
<nav class="navbar" role="navigation" aria-label="main navigation">
|
<nav class="navbar" role="navigation" aria-label="main navigation">
|
||||||
<div class="navbar-menu">
|
<div class="navbar-menu">
|
||||||
<div class="navbar-start">
|
<div class="navbar-start">
|
||||||
<a class="navbar-item" href="https://bulma.io">
|
<a class="navbar-item" href="#" hx-post="/api/v1/logout">
|
||||||
<img src="images/js_logo.svg" alt="js logo" style="height: 100%">
|
<img src="images/js_logo.svg" alt="js logo" style="height: 100%">
|
||||||
<h2 class="title is-3 ml-1">Reading Exercices</h2>
|
<h2 class="title is-3 ml-1">Reading Exercices</h2>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
<div hx-get="/api/v1/random-questions/dsdsddsf/4"
|
<div hx-get="/api/v1/random-questions/:seed/:defaultIndex"
|
||||||
hx-trigger="load"
|
hx-trigger="load"
|
||||||
id="question-body">
|
id="question-body">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function getDefaultIndex(){
|
||||||
|
if(document.location.hash.length > 1){
|
||||||
|
return document.location.hash.substr(1)
|
||||||
|
}
|
||||||
|
return localStorage.getItem("defaultIndex") || '0';
|
||||||
|
}
|
||||||
|
document.body.addEventListener('htmx:configRequest', function(evt) {
|
||||||
|
evt.detail.headers.session = localStorage.getItem("seed");
|
||||||
|
evt.detail.path = evt.detail.path.replace(":seed", localStorage.getItem("seed"));
|
||||||
|
|
||||||
|
evt.detail.path = evt.detail.path.replace(":defaultIndex", getDefaultIndex());
|
||||||
|
});
|
||||||
|
document.body.addEventListener('logout', function (evt) {
|
||||||
|
window.localStorage.clear();
|
||||||
|
document.location.href = "/login.html";
|
||||||
|
});
|
||||||
|
|
||||||
|
document.body.addEventListener('set-question-id', function (evt) {
|
||||||
|
if(isNaN(evt.detail.value)){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
document.location.hash = evt.detail.value
|
||||||
|
localStorage.setItem("defaultIndex", evt.detail.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||||
|
<title>JS Reading App</title>
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.4/css/bulma.min.css">
|
||||||
|
<script src="https://unpkg.com/htmx.org@1.9.9"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<nav class="navbar" role="navigation" aria-label="main navigation">
|
||||||
|
<div class="navbar-menu">
|
||||||
|
<div class="navbar-start">
|
||||||
|
<a class="navbar-item" href="#">
|
||||||
|
<img src="images/js_logo.svg" alt="js logo" style="height: 100%">
|
||||||
|
<h2 class="title is-3 ml-1">Reading Exercices</h2>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<div class="columns is-centered">
|
||||||
|
<div class="column is-half">
|
||||||
|
<form hx-post="/api/v1/login" hx-swap="none">
|
||||||
|
|
||||||
|
<div class="field has-addons">
|
||||||
|
<div class="control">
|
||||||
|
<input class="input" id="username" type="text" placeholder="bob.l.eponge@ocean.com" name="username" required>
|
||||||
|
</div>
|
||||||
|
<div class="control">
|
||||||
|
<input type="submit" class="button" value="Login">
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.body.addEventListener('set-seed', function (evt) {
|
||||||
|
const seed = document.getElementById("username").value;
|
||||||
|
window.localStorage.setItem("seed", seed);
|
||||||
|
document.location.href = "/";
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
+59
-24
@@ -8,10 +8,16 @@ import {QuestionSource, QuestionMeta} from './shared.ts';
|
|||||||
|
|
||||||
type Question = [QuestionSource, QuestionMeta];
|
type Question = [QuestionSource, QuestionMeta];
|
||||||
|
|
||||||
|
type QuestionURLS = {
|
||||||
|
nextQuestionUrl: string,
|
||||||
|
validationUrl: string,
|
||||||
|
answerQuestionUrl: string
|
||||||
|
};
|
||||||
|
|
||||||
const questions: Question = Object.values(rawQuestions).flatMap(s => s.questions.map( q => [s, q]));
|
const questions: Question = Object.values(rawQuestions).flatMap(s => s.questions.map( q => [s, q]));
|
||||||
|
|
||||||
const router = new Router();
|
const apiRouter = new Router();
|
||||||
|
const loginRouter = new Router();
|
||||||
const app = new Application();
|
const app = new Application();
|
||||||
|
|
||||||
function hashString(str: string): number {
|
function hashString(str: string): number {
|
||||||
@@ -45,7 +51,7 @@ function getRandomQuestion<T>(seed: string, index: number, questions: Question[
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getQuestionPage(question: Question, nextQuestionUrl: string, validationUrl: string, answerQuestionUrl: string){
|
function getQuestionPage(question: Question, urls: QuestionURLS){
|
||||||
return (<div>
|
return (<div>
|
||||||
<pre> { question[0].toString() }</pre>
|
<pre> { question[0].toString() }</pre>
|
||||||
<div class="content mt-2 mb-2">
|
<div class="content mt-2 mb-2">
|
||||||
@@ -64,15 +70,15 @@ function getQuestionPage(question: Question, nextQuestionUrl: string, validation
|
|||||||
<button class="button is-info"
|
<button class="button is-info"
|
||||||
hx-trigger="click"
|
hx-trigger="click"
|
||||||
hx-target="#message"
|
hx-target="#message"
|
||||||
hx-post={validationUrl} >
|
hx-post={urls.validationUrl} >
|
||||||
Verify Answer
|
Verify Answer
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<a class="button is-info"
|
<a class="button is-info"
|
||||||
href="#"
|
href="#"
|
||||||
hx-trigger="next-question from:body delay:1500ms, click"
|
hx-trigger="next-question from:body delay:500ms, click"
|
||||||
hx-get={nextQuestionUrl}
|
hx-get={urls.nextQuestionUrl}
|
||||||
hx-params="none"
|
hx-params="none"
|
||||||
hx-target="#question-body" >
|
hx-target="#question-body" >
|
||||||
Next Question
|
Next Question
|
||||||
@@ -83,7 +89,7 @@ function getQuestionPage(question: Question, nextQuestionUrl: string, validation
|
|||||||
<a class="button is-info"
|
<a class="button is-info"
|
||||||
href="#"
|
href="#"
|
||||||
hx-trigger="click"
|
hx-trigger="click"
|
||||||
hx-get={answerQuestionUrl}
|
hx-get={urls.answerQuestionUrl}
|
||||||
hx-params="none"
|
hx-params="none"
|
||||||
hx-target="#message" >
|
hx-target="#message" >
|
||||||
Show Answer
|
Show Answer
|
||||||
@@ -120,43 +126,72 @@ function processValidAnswer(ctx, answer: string): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
router.all("/api/v1/random-questions/:seed/:idx/validate", async (ctx)=> {
|
function getQuestion(ctx): Question{
|
||||||
|
const seed = ctx.params.seed;
|
||||||
|
const index = Number.parseInt(ctx.params.idx??"0");
|
||||||
|
return getRandomQuestion(seed, index, questions);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getUrls(ctx): QuestionURLS{
|
||||||
const seed = ctx.params.seed;
|
const seed = ctx.params.seed;
|
||||||
const index = Number.parseInt(ctx.params.idx??"0");
|
const index = Number.parseInt(ctx.params.idx??"0");
|
||||||
const answer = (await ctx.request.body().value).get("answer");
|
|
||||||
const nextQuestionUrl = `/api/v1/random-questions/${seed}/${index + 1}`;
|
const nextQuestionUrl = `/api/v1/random-questions/${seed}/${index + 1}`;
|
||||||
const q = getRandomQuestion(seed, index, questions);
|
const validationUrl = `/api/v1/random-questions/${seed}/${index}/validate`;
|
||||||
|
const answerQuestionUrl = `/api/v1/random-questions/${seed}/${index}/answer`
|
||||||
|
return {nextQuestionUrl, validationUrl, answerQuestionUrl};
|
||||||
|
}
|
||||||
|
|
||||||
|
apiRouter.post("/api/v1/random-questions/:seed/:idx/validate", async (ctx)=> {
|
||||||
|
const answer = (await ctx.request.body().value).get("answer");
|
||||||
|
const q = getQuestion(ctx);
|
||||||
if(isValidAnswer(q, answer)){
|
if(isValidAnswer(q, answer)){
|
||||||
processValidAnswer(ctx, nextQuestionUrl)
|
processValidAnswer(ctx, getUrls(ctx).nextQuestionUrl)
|
||||||
}else{
|
}else{
|
||||||
processInvalidAnswer(ctx, nextQuestionUrl)
|
processInvalidAnswer(ctx, getUrls(ctx).nextQuestionUrl)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
router.get("/api/v1/random-questions/:seed/:idx/answer", (ctx)=> {
|
apiRouter.get("/api/v1/random-questions/:seed/:idx/answer", (ctx)=> {
|
||||||
const seed = ctx.params.seed;
|
const q = getQuestion(ctx);
|
||||||
const index = Number.parseInt(ctx.params.idx??"0");
|
|
||||||
const q = getRandomQuestion(seed, index, questions);
|
|
||||||
ctx.response.body = renderToString(
|
ctx.response.body = renderToString(
|
||||||
<div class="notification is-warning">
|
<div class="notification is-warning">
|
||||||
The answer is {q[1].answer}
|
The answer is {q[1].answer}
|
||||||
</div>);
|
</div>);
|
||||||
})
|
})
|
||||||
|
|
||||||
router.get("/api/v1/random-questions/:seed/:idx", (ctx)=> {
|
apiRouter.get("/api/v1/random-questions/:seed/:idx", (ctx)=> {
|
||||||
const seed = ctx.params.seed;
|
const q = getQuestion(ctx);
|
||||||
const index = Number.parseInt(ctx.params.idx??"0");
|
const urls = getUrls(ctx);
|
||||||
const q = getRandomQuestion(seed, index, questions);
|
|
||||||
const nextQuestionUrl = `/api/v1/random-questions/${seed}/${index + 1}`;
|
|
||||||
const validationUrl = `/api/v1/random-questions/${seed}/${index}/validate`;
|
|
||||||
const answerQuestionUrl = `/api/v1/random-questions/${seed}/${index}/answer`
|
|
||||||
|
|
||||||
ctx.response.body = renderToString(getQuestionPage(q, nextQuestionUrl, validationUrl, answerQuestionUrl));
|
ctx.response.body = renderToString(getQuestionPage(q, urls));
|
||||||
|
ctx.response.headers.append("HX-Trigger", JSON.stringify({'set-question-id': ctx.params.idx}));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
app.use(router.routes());
|
loginRouter.post("/api/v1/login", async (ctx)=> {
|
||||||
|
const username = (await ctx.request.body().value).get("username");
|
||||||
|
ctx.response.headers.append("HX-Trigger", JSON.stringify({'set-seed': username}));
|
||||||
|
ctx.response.body = username;
|
||||||
|
});
|
||||||
|
|
||||||
|
loginRouter.post("/api/v1/logout", (ctx)=> {
|
||||||
|
ctx.response.headers.append("HX-Trigger", "logout");
|
||||||
|
ctx.response.body = "";
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
app.use(loginRouter.routes());
|
||||||
app.use(staticFiles("public", {brotli: true, fetch: true}));
|
app.use(staticFiles("public", {brotli: true, fetch: true}));
|
||||||
|
app.use(async (ctx, next) => {
|
||||||
|
if(!ctx.request.headers.get('session')){
|
||||||
|
console.log("user not logged in.")
|
||||||
|
ctx.response.headers.append("HX-Redirect", "/login.html");
|
||||||
|
return
|
||||||
|
}
|
||||||
|
await next();
|
||||||
|
})
|
||||||
|
app.use(apiRouter.routes());
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
await app.listen({ port: 8000 });
|
await app.listen({ port: 8000 });
|
||||||
Reference in new issue
Block a user