From 96af150813a7933ad08e0657c6f63e36187a3a04 Mon Sep 17 00:00:00 2001 From: AndrewTrieu Date: Thu, 9 Mar 2023 13:37:03 +0200 Subject: [PATCH] Add lots of things --- .../routes/controllers/authController.js | 66 +++++++++++++++++++ .../routes/controllers/topicController.js | 53 +++++++++++++++ drill-and-practice/routes/routes.js | 4 ++ drill-and-practice/services/answerService.js | 0 drill-and-practice/services/authService.js | 11 ++++ .../services/questionService.js | 8 +++ drill-and-practice/services/topicService.js | 39 +++++++++++ drill-and-practice/views/login.eta | 18 +++++ drill-and-practice/views/main.eta | 8 ++- drill-and-practice/views/register.eta | 21 ++++++ 10 files changed, 225 insertions(+), 3 deletions(-) create mode 100644 drill-and-practice/routes/controllers/authController.js create mode 100644 drill-and-practice/routes/controllers/topicController.js create mode 100644 drill-and-practice/services/answerService.js create mode 100644 drill-and-practice/services/authService.js create mode 100644 drill-and-practice/services/questionService.js create mode 100644 drill-and-practice/services/topicService.js create mode 100644 drill-and-practice/views/login.eta create mode 100644 drill-and-practice/views/register.eta diff --git a/drill-and-practice/routes/controllers/authController.js b/drill-and-practice/routes/controllers/authController.js new file mode 100644 index 0000000..a1f9173 --- /dev/null +++ b/drill-and-practice/routes/controllers/authController.js @@ -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 }; diff --git a/drill-and-practice/routes/controllers/topicController.js b/drill-and-practice/routes/controllers/topicController.js new file mode 100644 index 0000000..444ef4f --- /dev/null +++ b/drill-and-practice/routes/controllers/topicController.js @@ -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 }; diff --git a/drill-and-practice/routes/routes.js b/drill-and-practice/routes/routes.js index 44cd48f..ec6020a 100644 --- a/drill-and-practice/routes/routes.js +++ b/drill-and-practice/routes/routes.js @@ -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 }; diff --git a/drill-and-practice/services/answerService.js b/drill-and-practice/services/answerService.js new file mode 100644 index 0000000..e69de29 diff --git a/drill-and-practice/services/authService.js b/drill-and-practice/services/authService.js new file mode 100644 index 0000000..aea9662 --- /dev/null +++ b/drill-and-practice/services/authService.js @@ -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 }; diff --git a/drill-and-practice/services/questionService.js b/drill-and-practice/services/questionService.js new file mode 100644 index 0000000..7db5949 --- /dev/null +++ b/drill-and-practice/services/questionService.js @@ -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) => { diff --git a/drill-and-practice/services/topicService.js b/drill-and-practice/services/topicService.js new file mode 100644 index 0000000..2a1c870 --- /dev/null +++ b/drill-and-practice/services/topicService.js @@ -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, +}; diff --git a/drill-and-practice/views/login.eta b/drill-and-practice/views/login.eta new file mode 100644 index 0000000..449c8e2 --- /dev/null +++ b/drill-and-practice/views/login.eta @@ -0,0 +1,18 @@ +<% layout("./layouts/layout.eta") %> + +

Login

+ +
+ Email:
+
+ Password:
+
+ +
+ +<% if(it.error){ %> + <%= it.error %> +
+<% } %> + +Are you a new user? \ No newline at end of file diff --git a/drill-and-practice/views/main.eta b/drill-and-practice/views/main.eta index e80598d..64fd128 100644 --- a/drill-and-practice/views/main.eta +++ b/drill-and-practice/views/main.eta @@ -2,11 +2,13 @@

MCQ Application

-

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.

+

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.

-

Are you already registered? Login

+

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.

-

Not registered yet? Register

+

Are you already registered? Login

+ +

Not registered yet? Register

Statistics

Total number of:

diff --git a/drill-and-practice/views/register.eta b/drill-and-practice/views/register.eta new file mode 100644 index 0000000..bd0f094 --- /dev/null +++ b/drill-and-practice/views/register.eta @@ -0,0 +1,21 @@ +<% layout("./layouts/layout.eta") %> + +

New user registration

+ +
+ Email:
+
+ Password:
+
+ +
+ +<% if (it.errors) { %> + <% Object.keys(it.errors).forEach((error) => { %> + <% Object.values(it.errors[error]).forEach((err) => { %> + <%= err %> + <% }); %> + <% }); %> +<% } %> +
+Are you already registered? \ No newline at end of file