Add lots of things
This commit is contained in:
66
drill-and-practice/routes/controllers/authController.js
Normal file
66
drill-and-practice/routes/controllers/authController.js
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import * as authServices from "../services/authServices.js";
|
||||||
|
import { bcrypt, validasaur } from "../../deps";
|
||||||
|
|
||||||
|
const validationRules = {
|
||||||
|
email: [validasaur.isEmail, validasaur.required],
|
||||||
|
password: [validasaur.minLength(4), validasaur.required],
|
||||||
|
};
|
||||||
|
|
||||||
|
const showLogin = ({ render }) => {
|
||||||
|
render("login.eta");
|
||||||
|
};
|
||||||
|
|
||||||
|
const showRegister = ({ render }) => {
|
||||||
|
render("register.eta");
|
||||||
|
};
|
||||||
|
|
||||||
|
const createUser = async ({ request, response, render }) => {
|
||||||
|
const body = request.body({ type: "form-data" });
|
||||||
|
const params = await body.value;
|
||||||
|
const userData = {
|
||||||
|
email: params.get("email"),
|
||||||
|
password: params.get("password"),
|
||||||
|
};
|
||||||
|
const [passes, errors] = await validasaur.validate(userData, validationRules);
|
||||||
|
|
||||||
|
if (!passes) {
|
||||||
|
response.status = 422;
|
||||||
|
userData.errors = errors;
|
||||||
|
render("register.eta", userData);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
const hashedPassword = await bcrypt.hash(userData.password);
|
||||||
|
const user = await authServices.createUser(userData.email, hashedPassword);
|
||||||
|
response.direct("/auth/login");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const login = async ({ request, response, state, render }) => {
|
||||||
|
const body = request.body({ type: "form" });
|
||||||
|
const params = await body.value;
|
||||||
|
|
||||||
|
const userDatabase = await authServices.findUser(params.get("email"));
|
||||||
|
|
||||||
|
if (userDatabase.length < 1) {
|
||||||
|
response.status = 422;
|
||||||
|
render("login.eta", { error: "User not found!" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = userDatabase[0];
|
||||||
|
const passwordCorrect = await bcrypt.compare(
|
||||||
|
params.get("password"),
|
||||||
|
user.password
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!passwordCorrect) {
|
||||||
|
response.status = 422;
|
||||||
|
render("login.eta", { error: "Incorrect password!" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await state.session.set("authenticated", user);
|
||||||
|
response.direct("/topics");
|
||||||
|
};
|
||||||
|
|
||||||
|
export { showLogin, showRegister, createUser, login };
|
||||||
53
drill-and-practice/routes/controllers/topicController.js
Normal file
53
drill-and-practice/routes/controllers/topicController.js
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import * as topicService from "../../services/topicService.js";
|
||||||
|
import { validasaur } from "../../deps";
|
||||||
|
|
||||||
|
const validationRules = {
|
||||||
|
name: [validasaur.required, validasaur.minLength(1)],
|
||||||
|
};
|
||||||
|
|
||||||
|
const addTopic = async ({ request, response, render, state }) => {
|
||||||
|
const userId = (await state.session.get("user")).id;
|
||||||
|
const admin = (await state.session.get("user")).admin;
|
||||||
|
const body = request.body({ type: "form-data" });
|
||||||
|
const params = await body.value;
|
||||||
|
const topicData = {
|
||||||
|
admin: admin,
|
||||||
|
name: params.get("name"),
|
||||||
|
};
|
||||||
|
const [passes, errors] = await validasaur.validate(
|
||||||
|
topicData,
|
||||||
|
validationRules
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!passes || !admin) {
|
||||||
|
response.status = 422;
|
||||||
|
topicData.errors = errors;
|
||||||
|
if (!admin) {
|
||||||
|
topicData.errors = { admin: { error: "You are not an admin!" } };
|
||||||
|
}
|
||||||
|
topicData.allTopics = await topicService.getAllTopics();
|
||||||
|
render("topics.eta", topicData);
|
||||||
|
} else {
|
||||||
|
await topicService.addTopic(userId, topicData.name);
|
||||||
|
response.redirect("/topics");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteTopic = async ({ params, response, state }) => {
|
||||||
|
const id = params.id;
|
||||||
|
const admin = (await state.session.get("user")).admin;
|
||||||
|
if (admin) {
|
||||||
|
await topicService.deleteTopic(id);
|
||||||
|
}
|
||||||
|
response.redirect("/topics");
|
||||||
|
};
|
||||||
|
|
||||||
|
const listTopics = async ({ render, state }) => {
|
||||||
|
const user = await state.session.get("user");
|
||||||
|
render("topics.eta", {
|
||||||
|
admin: user.admin,
|
||||||
|
allTopics: await topicService.getAllTopics(),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export { addTopic, deleteTopic, listTopics };
|
||||||
@@ -1,8 +1,12 @@
|
|||||||
import { Router } from "../deps.js";
|
import { Router } from "../deps.js";
|
||||||
import * as mainController from "./controllers/mainController.js";
|
import * as mainController from "./controllers/mainController.js";
|
||||||
|
import * as authController from "./controllers/authController.js";
|
||||||
|
|
||||||
const router = new Router();
|
const router = new Router();
|
||||||
|
|
||||||
router.get("/", mainController.showMain);
|
router.get("/", mainController.showMain);
|
||||||
|
router.get("/auth/login", authController.showLogin);
|
||||||
|
router.get("/auth/register", authController.showRegister);
|
||||||
|
router.post("/auth/login", authController.login);
|
||||||
|
|
||||||
export { router };
|
export { router };
|
||||||
|
|||||||
0
drill-and-practice/services/answerService.js
Normal file
0
drill-and-practice/services/answerService.js
Normal file
11
drill-and-practice/services/authService.js
Normal file
11
drill-and-practice/services/authService.js
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { sql } from "../database/database.js";
|
||||||
|
|
||||||
|
const findUser = async (email) => {
|
||||||
|
return await sql`SELECT * FROM users WHERE email = ${email}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const createUser = async (email, password) => {
|
||||||
|
return await sql`INSERT INTO users (email, password) VALUES (${email}, ${password})`;
|
||||||
|
};
|
||||||
|
|
||||||
|
export { findUser, createUser };
|
||||||
8
drill-and-practice/services/questionService.js
Normal file
8
drill-and-practice/services/questionService.js
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { sql } from "../database/database.js";
|
||||||
|
|
||||||
|
const countQuestions = async () => {
|
||||||
|
const result = await sql`SELECT COUNT(id) FROM questions`;
|
||||||
|
return result.rows[0].count;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getQuestionsByTopicId = async (topicId) => {
|
||||||
39
drill-and-practice/services/topicService.js
Normal file
39
drill-and-practice/services/topicService.js
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import { sql } from "../database/database.js";
|
||||||
|
|
||||||
|
const addTopic = async (userId, name) => {
|
||||||
|
await sql`INSERT INTO topics (user_id, name) VALUES (${userId}, ${name})`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const countTopics = async () => {
|
||||||
|
const result = await sql`SELECT COUNT(id) FROM topics`;
|
||||||
|
return result.rows[0].count;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getAllTopics = async () => {
|
||||||
|
await sql`SELECT * FROM topics ORDER BY name ASC`;
|
||||||
|
return result.rows;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getTopicsByUserId = async (userId) => {
|
||||||
|
const result =
|
||||||
|
await sql`SELECT * FROM topics WHERE user_id = ${userId} ORDER BY name ASC`;
|
||||||
|
return result.rows;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getTopicByTopicId = async (topicId) => {
|
||||||
|
const result = await sql`SELECT * FROM topics WHERE id = ${topicId}`;
|
||||||
|
return result.rows[0];
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteTopic = async (topicId) => {
|
||||||
|
await sql`DELETE FROM topics WHERE id = ${topicId}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
addTopic,
|
||||||
|
countTopics,
|
||||||
|
getAllTopics,
|
||||||
|
getTopicsByUserId,
|
||||||
|
getTopicByTopicId,
|
||||||
|
deleteTopic,
|
||||||
|
};
|
||||||
18
drill-and-practice/views/login.eta
Normal file
18
drill-and-practice/views/login.eta
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<% layout("./layouts/layout.eta") %>
|
||||||
|
|
||||||
|
<h1>Login</h1>
|
||||||
|
|
||||||
|
<form method="POST" action="/auth/login">
|
||||||
|
Email:<br/>
|
||||||
|
<input type="email" name="email" /><br/>
|
||||||
|
Password:<br/>
|
||||||
|
<input type="password" name="password" /><br/>
|
||||||
|
<input type="submit" value="Login" />
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<% if(it.error){ %>
|
||||||
|
<b><%= it.error %></b>
|
||||||
|
<br/>
|
||||||
|
<% } %>
|
||||||
|
|
||||||
|
<a href="/auth/register">Are you a new user?</a>
|
||||||
@@ -2,11 +2,13 @@
|
|||||||
|
|
||||||
<h1>MCQ Application</h1>
|
<h1>MCQ Application</h1>
|
||||||
|
|
||||||
<P>This is a web application that is used for repeated practice of learned content. The application provides a list of topics and allows creating multiple-choice questions into those topics that are then answered by self and others. The application also shows basic statistics: the total number of available questions and the total number of question answers. In addition, the application also provides an API for retrieving and answering random questions.</p>
|
<P>This is a web application that is used for repeated practice of learned content. The application provides a list of topics and allows creating multiple-choice questions into those topics that are then answered by self and others.</p>
|
||||||
|
|
||||||
<p>Are you already registered? <a href="/login">Login</a></p>
|
<p>The application also shows basic statistics: the total number of available questions and the total number of question answers. In addition, the application also provides an API for retrieving and answering random questions.</p>
|
||||||
|
|
||||||
<p>Not registered yet? <a href="/register">Register</a></p>
|
<p>Are you already registered? <a href="/auth/login">Login</a></p>
|
||||||
|
|
||||||
|
<p>Not registered yet? <a href="/auth/register">Register</a></p>
|
||||||
|
|
||||||
<h2>Statistics</h2>
|
<h2>Statistics</h2>
|
||||||
<p>Total number of:</p>
|
<p>Total number of:</p>
|
||||||
|
|||||||
21
drill-and-practice/views/register.eta
Normal file
21
drill-and-practice/views/register.eta
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<% layout("./layouts/layout.eta") %>
|
||||||
|
|
||||||
|
<h1>New user registration</h1>
|
||||||
|
|
||||||
|
<form method="POST" action="/auth/register">
|
||||||
|
Email:<br/>
|
||||||
|
<input type="email" name="email" /><br/>
|
||||||
|
Password:<br/>
|
||||||
|
<input type="password" name="password" /><br/>
|
||||||
|
<input type="submit" value="Register" />
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<% if (it.errors) { %>
|
||||||
|
<% Object.keys(it.errors).forEach((error) => { %>
|
||||||
|
<% Object.values(it.errors[error]).forEach((err) => { %>
|
||||||
|
<%= err %>
|
||||||
|
<% }); %>
|
||||||
|
<% }); %>
|
||||||
|
<% } %>
|
||||||
|
<br/>
|
||||||
|
<a href="/auth/login">Are you already registered?</a>
|
||||||
Reference in New Issue
Block a user