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 * as mainController from "./controllers/mainController.js";
|
||||
import * as authController from "./controllers/authController.js";
|
||||
|
||||
const router = new Router();
|
||||
|
||||
router.get("/", mainController.showMain);
|
||||
router.get("/auth/login", authController.showLogin);
|
||||
router.get("/auth/register", authController.showRegister);
|
||||
router.post("/auth/login", authController.login);
|
||||
|
||||
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>
|
||||
|
||||
<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>
|
||||
<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