rename the app

This commit is contained in:
Smigz 2025-03-24 04:06:46 -04:00
parent ce35924a54
commit f65bf959b1
27 changed files with 1832 additions and 0 deletions

53
messages/src/app.js Normal file
View file

@ -0,0 +1,53 @@
const fs = require("node:fs");
const { logging } = require("./middlewares/logging");
const path = require("node:path");
const express = require("express");
const passport = require("passport");
const session = require("express-session");
const SQLiteStore = require("connect-sqlite3")(session);
const { indexRouter } = require("./routes/indexRouter");
const { authRouter } = require("./routes/authRouter");
const app = express();
const assetsPath = path.join(path.dirname(__dirname), "public");
app.use(express.static(assetsPath));
app.set("views", path.join(__dirname, "views"));
app.set("view engine", "ejs");
app.use(
session({
store: new SQLiteStore({ dir: "./src/db/", db: "keynotes.db" }),
secret: "keynotes.app",
cookie: { maxAge: 7 * 24 * 60 * 60 * 1000 },
saveUninitialized: false,
resave: false,
}),
);
app.use(passport.session());
app.use(express.urlencoded({ extended: false }));
app.use(logging);
app.use((req, res, next) => {
res.locals.currentUser = req.user;
next();
});
app.use("/", indexRouter);
app.use("/auth", authRouter);
const server = app.listen(5173, () => console.log(`App running on port: 5173`));
const gracefulShutdownHandler = (signal) => {
console.log(`\n${signal} received.`);
server.close(() => {
console.log("Shutting down the server process...");
process.exit();
});
};
process.on("SIGINT", gracefulShutdownHandler);
process.on("SIGTERM", gracefulShutdownHandler);

0
messages/src/config.js Normal file
View file

View file

@ -0,0 +1,46 @@
const bcryptjs = require("bcryptjs");
const db = require("../models/query");
const { validationResult } = require("express-validator");
function loginGet(req, res, next) {
res.render("login", {
pageTitle: "InspiredCliches | Login",
errors: req.session.messages,
});
}
function logOut(req, res, next) {
req.logout((err) => {
if (err) return next(err);
});
res.redirect("/");
}
function signUpGet(req, res, next) {
res.render("register", {
pageTitle: "InspiredCliches | Register",
errors: null,
});
}
async function signUpPost(req, res, next) {
const result = validationResult(req);
if (result.isEmpty()) {
const { username, password } = req.body;
const hashedPassword = await bcryptjs.hash(password, 10);
db.insertUser(username, hashedPassword);
res.redirect("/auth/login");
} else {
res.status(400).render("register", {
errors: result.array(),
pageTitle: "InspiredCliches | Register",
});
}
}
module.exports = {
loginGet,
logOut,
signUpPost,
signUpGet,
};

View file

@ -0,0 +1,90 @@
const db = require("../models/query");
function indexGet(req, res, next) {
if (res.locals.currentUser) {
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: "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 deleteNote(req, res) {
const { messageId, userId } = req.query;
if (res.locals.currentUser) {
const message = db.getNoteById(messageId);
if (message.user_id === Number(userId)) {
db.deleteNote(Number(messageId));
}
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.getSumNotesByUserId(userId);
const allPosts = db.getAllNotesByUserId(userId);
res.render("view-profile", {
pageTitle: "InspiredCliches | Edit Profile",
user: userExists,
totalPosts: sumUserPosts,
allPosts: allPosts,
});
} else {
res.redirect("/");
}
}
}
module.exports = {
indexGet,
addNoteGet,
addNotePost,
deleteNote,
addLike,
getProfile,
};

View file

@ -0,0 +1,50 @@
const bcryptjs = require("bcryptjs");
const db = require("../models/db");
const LocalStrategy = require("passport-local");
const passport = require("passport");
passport.use(
new LocalStrategy(
{ passReqToCallback: true },
async (req, username, password, done) => {
try {
req.session.messages = [];
const query = db.query(`SELECT * FROM users WHERE username = $1`);
const user = query.get({ $1: username });
if (!user) {
return done(null, false, {
message: "Incorrect username or password.",
});
}
const match = await bcryptjs.compare(password, user.password);
if (!match) {
return done(null, false, {
message: "Incorrect username or password.",
});
}
return done(null, user);
} catch (err) {
return done(err);
}
},
),
);
passport.serializeUser((user, done) => {
done(null, user.user_id);
});
passport.deserializeUser(async (id, done) => {
try {
const query = db.query("SELECT * FROM users WHERE user_id = $userId");
const results = query.get({ $userId: id });
done(null, results);
} catch (err) {
done(err);
}
});
module.exports = passport;

View file

@ -0,0 +1,8 @@
function logging(req, res, next) {
req.time = new Date(Date.now()).toISOString();
const clientIp = req.header("cf-connecting-ip") || req.socket.remoteAddress;
console.log(req.time, req.method, req.hostname, req.path, clientIp);
next();
}
module.exports = { logging };

35
messages/src/models/db.js Normal file
View file

@ -0,0 +1,35 @@
const { Database } = require("bun:sqlite");
const fs = require("node:fs");
const path = require("node:path");
const { mkdir } = require("node:fs/promises");
const { stat } = require("node:fs");
const dbDirPath = path.join(path.dirname(__dirname), "/db");
const dbPath = path.join(dbDirPath, "/keynotes.db");
async function makeDirectory(path) {
const dirCreation = await mkdir(path);
return dirCreation;
}
// Make sure DB path exists
stat(dbDirPath, (err, stats) => {
if (err !== null) {
makeDirectory(dbDirPath);
}
});
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) => {
if (err) {
console.error(err);
return;
}
db.exec(data);
});
module.exports = db;

View file

@ -0,0 +1,73 @@
CREATE TABLE IF NOT EXISTS users (
user_id INTEGER PRIMARY KEY ASC,
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 IGNORE INTO messages (message_id, message, media, date, user_id)
VALUES
(1, "When life gives you lemons, make lemonade.", NULL, strftime('%s', 'now')*1000, 1),
(2, "Rome wasnt built in a day take your time.", NULL, strftime('%s', 'now')*1000, 1),
(3, "The grass is always greener on the other side.", NULL, strftime('%s', 'now')*1000, 1),
(4, "If it aint broke, dont fix it.", NULL, strftime('%s', 'now')*1000, 1),
(5, "You cant have your cake and eat it too.", NULL, strftime('%s', 'now')*1000, 1),
(6, "Better late than never.", NULL, strftime('%s', 'now')*1000, 1),
(7, "What doesnt kill you makes you stronger.", NULL, strftime('%s', 'now')*1000, 1),
(8, "Every cloud has a silver lining.", NULL, strftime('%s', 'now')*1000, 1),
(9, "Dont bite the hand that feeds you.", NULL, strftime('%s', 'now')*1000, 1),
(10, "Absence makes the heart grow fonder.", NULL, strftime('%s', 'now')*1000, 1),
(11, "Always trust your gut feeling its usually right.", NULL, strftime('%s', 'now')*1000, 2),
(12, "Money cant buy happiness, but it sure helps.", NULL, strftime('%s', 'now')*1000, 2),
(13, "Everything happens for a reason.", NULL, strftime('%s', 'now')*1000, 2),
(14, "Dont go to bed angry resolve conflicts before sleeping.", NULL, strftime('%s', 'now')*1000, 2),
(15, "The early bird catches the worm.", NULL, strftime('%s', 'now')*1000, 2),
(16, "You only live once make the most of it.", NULL, strftime('%s', 'now')*1000, 2),
(17, "Hard work pays off in the end.", NULL, strftime('%s', 'now')*1000, 2),
(18, "Family is everything cherish them.", NULL, strftime('%s', 'now')*1000, 2),
(19, "Dont put off until tomorrow what you can do today.", NULL, strftime('%s', 'now')*1000, 2),
(20, "A smile costs nothing but gives so much.", NULL, strftime('%s', 'now')*1000, 2),
(21, "You cant please everyone focus on yourself.", NULL, strftime('%s', 'now')*1000, 2),
(22, "Health is wealth take care of your body.", NULL, strftime('%s', 'now')*1000, 2),
(23, "Patience is a virtue good things take time.", NULL, strftime('%s', 'now')*1000, 2),
(24, "Dont judge a book by its cover.", NULL, strftime('%s', 'now')*1000, 2),
(25, "Actions speak louder than words.", NULL, strftime('%s', 'now')*1000, 2),
(26, "Learn from your mistakes they make you wiser.", NULL, strftime('%s', 'now')*1000, 2),
(27, "Kindness costs nothing but means everything.", NULL, strftime('%s', 'now')*1000, 2),
(28, "Life is short eat the cake.", NULL, strftime('%s', 'now')*1000, 2),
(29, "Comparison is the thief of joy focus on your journey.", NULL, strftime('%s', 'now')*1000, 2),
(30, "The best things in life are free.", NULL, strftime('%s', 'now')*1000, 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;

View file

@ -0,0 +1,138 @@
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
ORDER BY date DESC
`,
);
const data = query.all();
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 deleteNote(messageId) {
try {
const query = db.query(`DELETE FROM likes WHERE message_id = $1`);
query.run({ $1: messageId });
const queryTwo = db.query(`DELETE FROM messages WHERE message_id = $1`);
queryTwo.run({ $1: messageId });
} catch (e) {
console.log(e);
}
}
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, user_id 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 getSumNotesByUserId(userId) {
const query = db.query(`SELECT COUNT(1) FROM messages WHERE user_id = $1`);
const res = query.get({ $1: userId });
return res["COUNT(1)"];
}
function getAllNotesByUserId(userId) {
const query = db.query(`SELECT * FROM messages WHERE user_id = $1`);
const res = query.all({ $1: userId });
return res;
}
module.exports = {
getEveryNote,
getNoteById,
putNewNote,
insertLike,
deleteLike,
deleteNote,
checkIfNotedLiked,
insertUser,
getLikesByUser,
getTotalLikesByMessageId,
getUserById,
getSumNotesByUserId,
getAllNotesByUserId,
};

View file

@ -0,0 +1,32 @@
const { Router } = require("express");
const authController = require("../controllers/authController");
const passport = require("../middlewares/auth");
const { body } = require("express-validator");
const authRouter = Router();
//authRouter.get("/", (req, res) => res.redirect("/auth/login"));
authRouter.get("/login", authController.loginGet);
authRouter.post(
"/login",
passport.authenticate("local", {
successRedirect: "/",
failureRedirect: "/auth/login",
failureMessage: true,
}),
);
authRouter.get("/logout", authController.logOut);
authRouter.get("/register", authController.signUpGet);
authRouter.post(
"/register",
body("password").isLength({ min: 5 }).withMessage("Password is too short"),
body("password-confirmation").custom((value, { req }) => {
if (value !== req.body.password) {
throw new Error("Passwords do not match!");
}
return true;
}),
authController.signUpPost,
);
module.exports = { authRouter };

View file

@ -0,0 +1,13 @@
const { Router } = require("express");
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);
indexRouter.get("/delete", indexController.deleteNote);
module.exports = { indexRouter };

View file

View file

@ -0,0 +1,31 @@
<%- include('header') %>
<div class="grid">
<% keynotes.forEach((note) => { %>
<% const likedPost = userLikedPosts.find((post) => post.message_id == note.message_id) %>
<% let extraMessageClass = "" %>
<% if (note.message.length > 250) { %>
<% extraMessageClass = " grid__span_two"; %>
<% }; %>
<div class="grid-item card<%= extraMessageClass %>">
<p class="card__message"><%= note.message %></p>
<div class="container grid__card__metadata">
<div class="flex author__meta">
<p class="card__author"><a href="/profile?userId=<%= note.user_id %>"><%= note.user %></a></p>
<p class="card__likes">&bull;</p>
<p class="card__likes timestamp"><%= new Date(note.date).toLocaleString() %></p>
</div>
<% if (currentUser.user_id === note.user_id) { %>
<a href="/delete?userId=<%= currentUser.user_id %>&messageId=<%= note.message_id %>">delete post</a>
<% }; %>
<div class="likes">
<a href="/like?noteId=<%= note.message_id %>">
<% if (likedPost) { %><i class="fa-solid fa-heart red"></i><% } else { %><i class="fa-regular fa-heart red"></i><% }; %>
</a>
<p class="card__likes"><%= note.likes_count %></p>
</div>
</div>
</div>
<% }); %>
</div>
<%- include('footer') %>

View file

@ -0,0 +1,12 @@
<footer class="container">
<h2 class="foooter-heading">Inspired Cliches</h2>
<p>Making unsolicited advice inspiring.</p>
<div class="social-icons flex">
<a href="https://github.com/smiggiddy/odin-codeprojects" target="_blank"><i class="fa-brands fa-github"></i></a>
<a href="https://smig.tech" target="_blank"><i class="fa-solid fa-link"></i></a>
</div>
<p> © Copyright <%= new Date(Date.now()).getFullYear() %></p>
</footer>
<script src="/js/script.js"></script>
</body>
</html>

View file

@ -0,0 +1,36 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Rokkitt:ital,wght@0,100..900;1,100..900&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Lato:ital,wght@0,100;0,300;0,400;0,700;0,900;1,100;1,300;1,400;1,700;1,900&family=Rokkitt:ital,wght@0,100..900;1,100..900&display=swap" rel="stylesheet">
<title><%= pageTitle %></title>
<link href="/css/style.css" rel="stylesheet">
<link rel="icon" href="/images/favicon.png">
<!--<script src="https://kit.fontawesome.com/24f16b96cf.js" crossorigin="anonymous"></script>-->
<script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/js/all.min.js"></script>
</head>
<body>
<nav>
<ul class="container flex">
<li><a class="hover__underline" href="/">Home</a></li>
<% if (currentUser) { %>
<li class="nav__profile"><%= currentUser.username %>
<div class="hidden nav__hover">
<ul class="container flex col">
<li><a class="hover__underline" href="/profile?userId=<%= currentUser.user_id %>">view profile</a></li>
<li><a class="hover__underline" href="/new" class="new-link">new cliche</a></li>
<li><a class="hover__underline" href="/auth/logout">logout</a></li>
</ul>
</div>
</li>
<% } else { %>
<li><a class="hover__underline" href="/auth/login">Login</a></li>
<% }; %>
</ul>
</nav>
<div class="divider"></div>

View file

@ -0,0 +1,49 @@
<%- include('header') %>
<% if (currentUser) { %>
<h1> Hi <%= currentUser.username %> </h1>
<% } %>
<div class="container flex hero">
<div class="container hero">
<h1 class="hero__heading">Unlock Your Potential, One cliche at a time.</h1>
<p class="hero__subheading">Read a cliche a day, to keep the brain at bay</p>
<a href="/auth/login" type="button" class="btn btn__link">Start Today</a>
</div>
</div>
<section id="main">
<div class="container flex">
<div class="card">
<h2>Lorem</h2>
<p>Lorem ipsum dolor sit amet consectetur, adipisicing elit. Nesciunt laboriosam, aliquam non ex consequuntur deleniti ut illo voluptatibus aperiam necessitatibus doloribus, quasi minima officia, inventore sed minus sit dolorem possimus.</p>
</div>
<div class="img">
<p>This is an image</p>
</div>
</div>
<div class="container flex">
<div class="img">
<p>This is an image</p>
</div>
<div class="card">
<h2>Lorem</h2>
<p>Lorem ipsum dolor sit amet consectetur, adipisicing elit. Nesciunt laboriosam, aliquam non ex consequuntur deleniti ut illo voluptatibus aperiam necessitatibus doloribus, quasi minima officia, inventore sed minus sit dolorem possimus.</p>
</div>
</div>
<div class="container flex">
<div class="card">
<h2>Lorem</h2>
<p>Lorem ipsum dolor sit amet consectetur, adipisicing elit. Nesciunt laboriosam, aliquam non ex consequuntur deleniti ut illo voluptatibus aperiam necessitatibus doloribus, quasi minima officia, inventore sed minus sit dolorem possimus.</p>
</div>
<div class="img">
<p>This is an image</p>
</div>
</div>
</section>
<%- include('footer') %>

View file

@ -0,0 +1,30 @@
<%- include('header') %>
<% if (locals.errors) {%>
<div class="center-horizontal">
<ul>
<% errors.forEach(function(error) { %>
<li><%= error %></li>
<% }); %>
</ul>
</div>
<% }; %>
<main class="container full-page">
<form class="center-horizontal" method="POST" action="/auth/login">
<div class="form-item flex">
<label for="username">Username</label>
<input type="text" name="username" required/>
</div>
<div class="form-item flex">
<label for="password">Password</label>
<input type="password" name="password" required/>
</div>
<div class="form-item-row flex">
<button type="submit" class="btn btn__form">Login</button>
</div>
<p>Need an account?
<a href="/auth/register">Click here!</a>
</p>
</form>
</main>
<%- include('footer') %>

View file

@ -0,0 +1,19 @@
<%- include("header") %>
<form action="/new" method="POST" class="center-horizontal">
<div class="form-item flex">
<label for="message">Message</label>
<textarea type="text" name="message" required></textarea>
</div>
<div class="form-item flex">
<label for="media">Media</label>
<input type="text" name="media" />
</div>
<div class="form-item flex col">
<button type="submit" class="btn">Post</button>
<button type="button" class="btn" onClick="document.location.href='/'">Back</button>
</div>
</form>
<%- include("footer") %>

View file

@ -0,0 +1,35 @@
<%- include('header') %>
<!-- views/partials/errors.ejs -->
<% if (locals.errors) {%>
<div class="center-horizontal">
<ul>
<% errors.forEach(function(error) { %>
<li><%= error.msg %></li>
<% }); %>
</ul>
</div>
<% }; %>
<main class="container full-page">
<form class="register-form center-horizontal" method="POST" action="/auth/register">
<div class="form-item flex">
<label for="username">username</label>
<input type="text" name="username" required/>
<div class="error"></div>
</div>
<div class="form-item flex">
<label for="password">password</label>
<input type="password" name="password" required/>
<div class="error flex col"></div>
</div>
<div class="form-item flex">
<label for="password-confirmation">confirm password</label>
<input type="password" name="password-confirmation" required/>
<div class="error"></div>
</div>
<div class="form-item flex">
<button type="submit" class="btn btn__form">register</button>
</div>
</form>
</main>
<%- include('footer') %>

View file

@ -0,0 +1,20 @@
<%- include("header") %>
<div class="full-page container center-horizontal">
<div class="container">
<h1 class="username"><%= user.username %></h1>
<% if(currentUser.user_id === user.user_id ) { %>
<p>This is your profile page.</p>
<% }; %>
</div>
<div class="container">
<h2><%= user.username %>'s <%= totalPosts %> post(s): </h2>
<% allPosts.forEach((post) => { %>
<div class="container">
<p>"<%= post.message %>"</p>
</div>
<% }); %>
</div>
</div>
<%- include("footer") %>