diff --git a/.gitignore b/.gitignore
index a3bd578..7d04f08 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
+*.db-*
*.db
# Logs
logs
diff --git a/keynotes.app/public/css/style.css b/keynotes.app/public/css/style.css
index 6ead252..280cd66 100644
--- a/keynotes.app/public/css/style.css
+++ b/keynotes.app/public/css/style.css
@@ -75,10 +75,44 @@ h6 {
isolation: isolate;
}
+a {
+ text-decoration: none;
+}
+
+nav {
+ font-size: 1.5rem;
+ margin: 1em 0 2em;
+}
+
+nav > ul {
+ list-style: none;
+}
+
+ul {
+ padding: 0;
+ margin: auto;
+ justify-content: space-around;
+ max-width: 80vw;
+}
+
+ul > li {
+ display: inline-block;
+ text-align: center;
+}
+
+form {
+ width: 100%;
+ max-width: 50vw;
+}
+
.flex {
display: flex;
}
+.col {
+ flex-direction: column;
+}
+
.container.flex.hero {
align-items: center;
justify-content: center;
@@ -98,6 +132,14 @@ h6 {
padding: 1em;
}
+.center-horizontal {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ margin: 0 auto;
+}
+
.hero__heading {
margin: 0;
font-size: var(--mobile-heading-font-size);
@@ -138,7 +180,84 @@ h6 {
flex-wrap: wrap;
}
+.feed-heading {
+ margin-left: 1em;
+ text-transform: capitalize;
+}
+
+.feed-heading span {
+ text-transform: initial;
+}
+
+.grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
+ gap: 1em;
+ padding: 2em;
+ grid-auto-flow: dense;
+}
+
+.grid-item {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+}
+
+.grid-item.card {
+ border: 1px solid black;
+ text-align: center;
+ padding: 1.5em 1.5em 0.5em;
+}
+
+.grid__card__metadata {
+ font-size: 0.8em;
+ justify-content: space-between;
+}
+
+.card .card__message {
+ margin-bottom: 1em;
+}
+
+.form-item {
+ flex-direction: column;
+ width: 100%;
+}
+
+/*.active__underline {*/
+/**/
+/*}*/
+
+.hover__underline {
+ /*display: inline-block;*/
+ position: relative;
+}
+
+.red {
+ color: red;
+}
+
+.hover__underline::after {
+ content: "";
+ position: absolute;
+ width: 100%;
+ transform: scaleX(0);
+ height: 2px;
+ bottom: 0;
+ left: 0;
+ background-color: black;
+ transform-origin: bottom left;
+ transition: transform 0.25s ease-out;
+}
+
+.hover__underline:hover::after {
+ transform: scaleX(1);
+ transform-origin: bottom left;
+}
+
@media only screen and (min-width: 750px) {
+ ul {
+ max-width: 60vw;
+ }
.hero__heading {
margin: 0;
font-size: var(--desktop-heading-font-size);
@@ -159,8 +278,11 @@ h6 {
#main > .container.flex {
gap: 10em;
}
-
- .form-item > .btn {
- max-width: 200px;
+ .grid__span_two {
+ grid-column-end: span 2;
}
+
+ /*.form-item > .btn {*/
+ /* max-width: 200px;*/
+ /*}*/
}
diff --git a/keynotes.app/public/images/favicon.png b/keynotes.app/public/images/favicon.png
new file mode 100644
index 0000000..e98cbc1
Binary files /dev/null and b/keynotes.app/public/images/favicon.png differ
diff --git a/keynotes.app/public/js/script.js b/keynotes.app/public/js/script.js
new file mode 100644
index 0000000..e69de29
diff --git a/keynotes.app/src/controllers/authController.js b/keynotes.app/src/controllers/authController.js
index ef50411..3a17b13 100644
--- a/keynotes.app/src/controllers/authController.js
+++ b/keynotes.app/src/controllers/authController.js
@@ -1,9 +1,9 @@
const bcryptjs = require("bcryptjs");
-const db = require("../models/db");
-const passport = require("../middlewares/auth");
+const db = require("../models/query");
+const { validationResult } = require("express-validator");
function loginGet(req, res, next) {
- res.render("login", { pageTitle: "KeyNotes.App | Login" });
+ res.render("login", { pageTitle: "InspiredCliches | Login" });
}
function logOut(req, res, next) {
@@ -14,20 +14,24 @@ function logOut(req, res, next) {
}
function signUpGet(req, res, next) {
- res.render("register", { pageTitle: "KeyNotes.App | Register" });
+ res.render("register", {
+ pageTitle: "InspiredCliches | Register",
+ errors: null,
+ });
}
async function signUpPost(req, res, next) {
- const { username, password } = req.body;
- try {
+ const result = validationResult(req);
+ if (result.isEmpty()) {
+ const { username, password } = req.body;
const hashedPassword = await bcryptjs.hash(password, 10);
- const query = db.query(
- "INSERT INTO users (username, password) VALUES ($username, $password)",
- );
- query.run({ $username: username, $password: hashedPassword });
- res.redirect("/");
- } catch (err) {
- next(err);
+ db.insertUser(username, hashedPassword);
+ res.redirect("/auth/login");
+ } else {
+ res.status(400).render("register", {
+ errors: result.array(),
+ pageTitle: "InspiredCliches | Register",
+ });
}
}
diff --git a/keynotes.app/src/controllers/indexController.js b/keynotes.app/src/controllers/indexController.js
index d554ca1..51c94b3 100644
--- a/keynotes.app/src/controllers/indexController.js
+++ b/keynotes.app/src/controllers/indexController.js
@@ -1,11 +1,74 @@
+const db = require("../models/query");
+
function indexGet(req, res, next) {
if (res.locals.currentUser) {
- res.send(
- `Thanks for stopping by ${res.locals.currentUser.username.toUpperCase()}
Log Out`,
- );
+ const keynotes = db.getEveryNote();
+ const userLikedPosts = db.getLikesByUser(res.locals.currentUser.user_id);
+
+ res.render("feed", {
+ pageTitle: "InspiredCliches | Feed",
+ keynotes: keynotes,
+ userLikedPosts: userLikedPosts,
+ });
}
- res.render("home", { pageTitle: "KeyNotes.App" });
+ res.render("home", { pageTitle: "InspiredCliches" });
}
+
+function addLike(req, res, next) {
+ const { noteId } = req.query;
+
+ const noteExits = noteId ? db.getNoteById(noteId) : null;
+
+ if (res.locals.currentUser && noteExits) {
+ const userId = res.locals.currentUser.user_id;
+ if (db.checkIfNotedLiked(noteId, userId)) {
+ db.deleteLike(noteId, userId);
+ } else {
+ db.insertLike(Number(noteId), userId);
+ }
+ }
+ res.redirect("/");
+}
+
+function addNotePost(req, res, next) {
+ const { message, media } = req.body;
+
+ if (res.locals.currentUser) {
+ const userId = res.locals.currentUser.user_id;
+
+ db.putNewNote({ message: message, media: null, userId: userId });
+ }
+
+ res.redirect("/");
+}
+
+function addNoteGet(req, res, next) {
+ res.render("note-form", { pageTitle: "InspiredCliches | New Note" });
+}
+
+function getProfile(req, res, next) {
+ const { userId } = req.query;
+
+ const userExists = userId ? db.getUserById(userId) : null;
+
+ if (res.locals.currentUser) {
+ if (userExists) {
+ const sumUserPosts = db.getAllNotesByUserId(userId);
+ res.render("view-profile", {
+ pageTitle: "InspiredCliches | Edit Profile",
+ user: userExists,
+ totalPosts: sumUserPosts,
+ });
+ } else {
+ res.redirect("/");
+ }
+ }
+}
+
module.exports = {
indexGet,
+ addNoteGet,
+ addNotePost,
+ addLike,
+ getProfile,
};
diff --git a/keynotes.app/src/models/db.js b/keynotes.app/src/models/db.js
index d9ddd94..0394ecd 100644
--- a/keynotes.app/src/models/db.js
+++ b/keynotes.app/src/models/db.js
@@ -20,6 +20,8 @@ stat(dbDirPath, (err, stats) => {
});
const db = new Database(dbPath);
+// Enable WAL Mode
+db.exec("PRAGMA journal_mode = WAL;");
const initSQL = path.join(__dirname, "./init.sql");
fs.readFile(initSQL, "utf8", (err, data) => {
@@ -27,7 +29,7 @@ fs.readFile(initSQL, "utf8", (err, data) => {
console.error(err);
return;
}
- db.query(data).get();
+ db.exec(data);
});
module.exports = db;
diff --git a/keynotes.app/src/models/init.sql b/keynotes.app/src/models/init.sql
index e0a79e8..a2d792e 100644
--- a/keynotes.app/src/models/init.sql
+++ b/keynotes.app/src/models/init.sql
@@ -1,5 +1,73 @@
CREATE TABLE IF NOT EXISTS users (
user_id INTEGER PRIMARY KEY ASC,
- username VARCHAR(50),
- password TEXT
+ username VARCHAR(50) UNIQUE NOT NULL,
+ password TEXT NOT NULL
);
+
+CREATE TABLE IF NOT EXISTS user_profile_info (
+ user_info_id INTEGER PRIMARY KEY ASC,
+ email VARCHAR(50),
+ user_id INTEGER REFERENCES users(user_id)
+);
+
+CREATE TABLE IF NOT EXISTS messages (
+ message_id INTEGER PRIMARY KEY ASC,
+ message TEXT,
+ media VARCHAR(100),
+ date NUMBER,
+ user_id INTEGER REFERENCES users(user_id)
+);
+
+CREATE TABLE IF NOT EXISTS likes (
+ likes_id INTEGER PRIMARY KEY ASC,
+ message_id INTEGER REFERENCES messages(message_id),
+ user_id INTEGER REFERENCES users(user_id),
+ liked_at NUMBER
+);
+
+
+
+INSERT OR IGNORE INTO users (username, password)
+VALUES ("jim", "$2a$10$Bmjre5WSpSSAi.nWBfLZFOlhQhbIAoY/MM7ikJz3Ho9tqeXCExaB6"),
+ ("demo", "$2a$10$Vo.XmsVQVx9gGojRIdewpOap5SnhrgZ21/Im5IxhH3PC4FycM5uwC");
+
+INSERT OR REPLACE INTO messages (message_id, message, media, date, user_id)
+VALUES
+ (1, "When life gives you lemons, make lemonade.", NULL, strftime('%s', 'now'), 1),
+ (2, "Rome wasn’t built in a day – take your time.", NULL, strftime('%s', 'now'), 1),
+ (3, "The grass is always greener on the other side.", NULL, strftime('%s', 'now'), 1),
+ (4, "If it ain’t broke, don’t fix it.", NULL, strftime('%s', 'now'), 1),
+ (5, "You can’t have your cake and eat it too.", NULL, strftime('%s', 'now'), 1),
+ (6, "Better late than never.", NULL, strftime('%s', 'now'), 1),
+ (7, "What doesn’t kill you makes you stronger.", NULL, strftime('%s', 'now'), 1),
+ (8, "Every cloud has a silver lining.", NULL, strftime('%s', 'now'), 1),
+ (9, "Don’t bite the hand that feeds you.", NULL, strftime('%s', 'now'), 1),
+ (10, "Absence makes the heart grow fonder.", NULL, strftime('%s', 'now'), 1),
+ (11, "Always trust your gut feeling – it’s usually right.", NULL, strftime('%s', 'now'), 2),
+ (12, "Money can’t buy happiness, but it sure helps.", NULL, strftime('%s', 'now'), 2),
+ (13, "Everything happens for a reason.", NULL, strftime('%s', 'now'), 2),
+ (14, "Don’t go to bed angry – resolve conflicts before sleeping.", NULL, strftime('%s', 'now'), 2),
+ (15, "The early bird catches the worm.", NULL, strftime('%s', 'now'), 2),
+ (16, "You only live once – make the most of it.", NULL, strftime('%s', 'now'), 2),
+ (17, "Hard work pays off in the end.", NULL, strftime('%s', 'now'), 2),
+ (18, "Family is everything – cherish them.", NULL, strftime('%s', 'now'), 2),
+ (19, "Don’t put off until tomorrow what you can do today.", NULL, strftime('%s', 'now'), 2),
+ (20, "A smile costs nothing but gives so much.", NULL, strftime('%s', 'now'), 2),
+ (21, "You can’t please everyone – focus on yourself.", NULL, strftime('%s', 'now'), 2),
+ (22, "Health is wealth – take care of your body.", NULL, strftime('%s', 'now'), 2),
+ (23, "Patience is a virtue – good things take time.", NULL, strftime('%s', 'now'), 2),
+ (24, "Don’t judge a book by its cover.", NULL, strftime('%s', 'now'), 2),
+ (25, "Actions speak louder than words.", NULL, strftime('%s', 'now'), 2),
+ (26, "Learn from your mistakes – they make you wiser.", NULL, strftime('%s', 'now'), 2),
+ (27, "Kindness costs nothing but means everything.", NULL, strftime('%s', 'now'), 2),
+ (28, "Life is short – eat the cake.", NULL, strftime('%s', 'now'), 2),
+ (29, "Comparison is the thief of joy – focus on your journey.", NULL, strftime('%s', 'now'), 2),
+ (30, "The best things in life are free.", NULL, strftime('%s', 'now'), 2);
+
+-- ALTER TABLE likes
+-- ADD COLUMN user_id INTEGER REFERENCES users(user_id);
+
+-- ALTER TABLE likes
+-- ADD COLUMN liked_at NUMBER;
+
+-- ALTER TABLE likes DROP COLUMN sum_likes;
diff --git a/keynotes.app/src/models/query.js b/keynotes.app/src/models/query.js
new file mode 100644
index 0000000..8b234ea
--- /dev/null
+++ b/keynotes.app/src/models/query.js
@@ -0,0 +1,117 @@
+const db = require("./db");
+
+function getEveryNote() {
+ try {
+ const query = db.query(
+ `SELECT messages.message_id, message, media, date, users.username AS user, messages.user_id, COUNT(likes.likes_id) AS likes_count
+ FROM messages LEFT JOIN users ON messages.user_id = users.user_id
+ LEFT JOIN likes ON messages.message_id = likes.message_id
+ GROUP BY messages.message_id, message, media, date, users.username
+ `,
+ );
+
+ const data = query.all();
+ console.log(data);
+ return data;
+ } catch (err) {
+ return err;
+ }
+}
+
+function putNewNote(note) {
+ const { message, media, userId } = note;
+ const date = Date.now();
+ const query = db.query(
+ `INSERT INTO messages (message, media, user_id, date) VALUES ($1, $2, $3, $4)`,
+ );
+ query.run({ $1: message, $2: media, $3: userId, $4: date });
+}
+
+function getLikesByUser(userId) {
+ const query = db.query(`SELECT message_id FROM likes WHERE user_id = $1`);
+
+ return query.all({ $1: userId });
+}
+
+function getTotalLikesByMessageId(messageId) {
+ const query = db.query(`SELECT COUNT(1) FROM likes WHERE message_id = $1`);
+ return query.get({ $1: messageId });
+}
+
+function getNoteById(noteId) {
+ const query = db.query(`SELECT message from messages WHERE message_id = $1`);
+ return query.get({ $1: noteId });
+}
+
+function checkIfNotedLiked(messageId, userId) {
+ const query = db.query(
+ `SELECT * FROM likes WHERE message_id = $1 and user_id = $2`,
+ );
+
+ const res = query.get({ $1: messageId, $2: userId });
+ return res;
+}
+
+function insertLike(messageId, userId) {
+ try {
+ const query = db.query(
+ `INSERT INTO likes (message_id, user_id, liked_at) VALUES ($1, $2, $3)`,
+ );
+ query.run({ $1: messageId, $2: userId, $3: Date.now() });
+ } catch (err) {
+ console.log(err);
+ return err;
+ }
+}
+
+function deleteLike(messageId, userId) {
+ try {
+ const query = db.query(
+ `DELETE FROM likes WHERE message_id = $1 and user_id = $2`,
+ );
+ query.run({ $1: messageId, $2: userId });
+ } catch (err) {
+ console.log(err);
+ return err;
+ }
+}
+
+function insertUser(username, password) {
+ try {
+ const query = db.query(
+ "INSERT INTO users (username, password) VALUES ($username, $password)",
+ );
+ query.run({ $username: username, $password: password });
+ } catch (err) {
+ return err;
+ }
+}
+function getUserById(userId) {
+ const query = db.query(
+ `SELECT username, user_id FROM users WHERE user_id = $1`,
+ );
+ const res = query.get({ $1: userId });
+
+ return res;
+}
+
+function getAllNotesByUserId(userId) {
+ const query = db.query(`SELECT COUNT(1) FROM messages WHERE user_id = $1`);
+ const res = query.get({ $1: userId });
+
+ return res["COUNT(1)"];
+}
+
+module.exports = {
+ getEveryNote,
+ getNoteById,
+ putNewNote,
+ insertLike,
+ deleteLike,
+ checkIfNotedLiked,
+ insertUser,
+ getLikesByUser,
+ getTotalLikesByMessageId,
+ getUserById,
+ getAllNotesByUserId,
+};
diff --git a/keynotes.app/src/routes/authRouter.js b/keynotes.app/src/routes/authRouter.js
index 427dfe7..b7a6431 100644
--- a/keynotes.app/src/routes/authRouter.js
+++ b/keynotes.app/src/routes/authRouter.js
@@ -1,6 +1,7 @@
const { Router } = require("express");
const authController = require("../controllers/authController");
const passport = require("../middlewares/auth");
+const { body } = require("express-validator");
const authRouter = Router();
@@ -15,6 +16,16 @@ authRouter.post(
);
authRouter.get("/logout", authController.logOut);
authRouter.get("/register", authController.signUpGet);
-authRouter.post("/register", authController.signUpPost);
+authRouter.post(
+ "/register",
+ body("password").isLength({ min: 5 }).withMessage("Password is too short"),
+ body("confirm-password").custom((value, { req }) => {
+ if (value !== req.body.password) {
+ throw new Error("Passwords do not match!");
+ }
+ return true;
+ }),
+ authController.signUpPost,
+);
module.exports = { authRouter };
diff --git a/keynotes.app/src/routes/indexRouter.js b/keynotes.app/src/routes/indexRouter.js
index 6e30c0c..363aa25 100644
--- a/keynotes.app/src/routes/indexRouter.js
+++ b/keynotes.app/src/routes/indexRouter.js
@@ -4,5 +4,9 @@ const indexController = require("../controllers/indexController");
const indexRouter = Router();
indexRouter.get("/", indexController.indexGet);
+indexRouter.get("/like", indexController.addLike);
+indexRouter.get("/new", indexController.addNoteGet);
+indexRouter.post("/new", indexController.addNotePost);
+indexRouter.get("/profile", indexController.getProfile);
module.exports = { indexRouter };
diff --git a/keynotes.app/src/views/feed.ejs b/keynotes.app/src/views/feed.ejs
new file mode 100644
index 0000000..74a21ea
--- /dev/null
+++ b/keynotes.app/src/views/feed.ejs
@@ -0,0 +1,27 @@
+<%- include('header') %>
+