Skip to content

Commit

Permalink
Merge pull request #1 from jdf18/switching-to-json
Browse files Browse the repository at this point in the history
Switching to json instead of sqlite for storing data
  • Loading branch information
jdf18 authored Jan 4, 2025
2 parents a0a6b76 + dd15459 commit 1c58564
Show file tree
Hide file tree
Showing 7 changed files with 363 additions and 336 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -134,3 +134,6 @@ dist

# Runtime data
data/

# Testing
test-data/
5 changes: 5 additions & 0 deletions nodemon.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"watch": ["src"],
"ext": "js",
"ignore": ["data", "test-data", "coverage", "node_modules", "static", "test"]
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"scripts": {
"prepare": "husky",
"pretest": "npx eslint .",
"test": "jest --verbose --coverage --runInBand --silent=true --workerThreads=false -t none",
"test": "jest --verbose --coverage --silent=true",
"start": "node ./src/server.js",
"dev": "nodemon ./src/server.js"
},
Expand Down
24 changes: 10 additions & 14 deletions src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ app.use('/node_modules/bootstrap/dist', express.static('node_modules/bootstrap/d
// Import database objects from ./db.js
const db = require('./db');

db.connect('./data/forkedflavors.db').then(() => {});
const datastore = new db.Datastore('./data/');

// Login system functions

Expand Down Expand Up @@ -65,14 +65,14 @@ app.get('/account', (req, res) => {

// Other API Endpoints

app.post('/api/login', async (req, res) => {
app.post('/api/login', (req, res) => {
const { username, password } = req.body;

if (!username || !password) {
return res.status(400).json({ message: 'Username or password is missing' });
}

const user = await db.getUserFromUsername(username);
const user = datastore.getUserFromUsername(username);
if (!user) { // If the username does not exist in the database
return res.status(401).json({ message: 'Invalid username or password' });
}
Expand All @@ -94,22 +94,21 @@ app.all('/api/logout', requireLogin, (req, res) => {
});
});

app.get('/api/is_logged_in', async (req, res) => {
app.get('/api/is_logged_in', (req, res) => {
if (req.session.user) {
return res.status(200).json(true);
}
return res.status(200).json(false);
});

app.get('/api/profile/:username', async (req, res) => {
app.get('/api/profile/:username', (req, res) => {
let user;
if (req.params.username === 'self') {
if (!req.session.user) return res.status(401).json({ message: 'Invalid username or password' });
user = await db.getUserFromUserId(req.session.user.user_id);
user = datastore.getUserFromUserId(req.session.user.user_id);
} else {
user = await db.getUserFromUsername(req.params.username);
user = datastore.getUserFromUsername(req.params.username);
}
console.log(user);

return res.status(200).json({
displayName: user.display_name,
Expand All @@ -118,18 +117,15 @@ app.get('/api/profile/:username', async (req, res) => {
});
});

app.post('/api/modify_profile', requireLogin, async (req, res) => {
console.log('/api/modify_profile');
const user = await db.getUserFromUserId(req.session.user.user_id);
app.post('/api/modify_profile', requireLogin, (req, res) => {
const user = datastore.getUserFromUserId(req.session.user.user_id);
const { displayName, bio } = req.body;

console.log(user.user_id, displayName, bio);

if (!displayName) {
return res.status(400).json({ message: 'Display name is missing' });
}

await db.modifyUser(user.user_id, displayName, bio);
datastore.modifyUser(user.user_id, displayName, bio);

return res.status(200).send('');
});
Expand Down
217 changes: 79 additions & 138 deletions src/db.js
Original file line number Diff line number Diff line change
@@ -1,162 +1,103 @@
const fs = require('fs');

const sqlite3 = require('sqlite3').verbose();
function returnUserFromRow(user) {
return {
user_id: user.user_id,
username: user.username,
display_name: user.display_name,
bio: user.bio,
pfp_link: user.pfp_link,
password_hash: user.password_hash,
};
}

let db;
class Datastore {
constructor(path) {
if (!fs.existsSync(path)) {
throw new Error(`Database directory "${path}" does not exist.`);
}
this.path = path;

// Try to connect to the database file
function connect(databasePath) {
// Check that the file exists
if (!(fs.existsSync(databasePath) || databasePath === ':memory:')) {
return new Promise((resolve, reject) => {
reject(new Error(`Database file "${databasePath}" does not exist.`));
});
this.users = null;
this.recipes = null;
this.comments = null;

this.initialised = false;

this.init_datastore();
}

return new Promise((resolve, reject) => {
// Connect to the database
db = new sqlite3.Database(databasePath, sqlite3.OPEN_READWRITE, (err) => {
if (err) {
console.log('Error connecting to database:', err.message);
return reject(new Error(`Error connecting to database: ${err.message}`));
}
console.log(`Connected to the ForkedFlavors database. ${databasePath}`);
return resolve();
});

// Create tables if they don’t exist
db.serialize(() => {
// Users Table
db.run(`
CREATE TABLE IF NOT EXISTS users (
user_id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE NOT NULL,
display_name TEXT UNIQUE NOT NULL,
bio TEXT,
pfp_link TEXT,
password_hash TEXT NOT NULL
);
`);

// Recipes Table with JSON column
db.run(`
CREATE TABLE IF NOT EXISTS recipes (
recipe_id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
title TEXT NOT NULL,
recipe_data TEXT NOT NULL, -- JSON stored as TEXT
forked_from INTEGER DEFAULT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(user_id),
FOREIGN KEY (forked_from) REFERENCES recipes(forked_from)
);
`);

// Comments Table
db.run(`
CREATE TABLE IF NOT EXISTS comments (
comment_id INTEGER PRIMARY KEY AUTOINCREMENT,
recipe_id INTEGER NOT NULL,
user_id INTEGER NOT NULL,
comment_text TEXT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (recipe_id) REFERENCES recipes(recipe_id),
FOREIGN KEY (user_id) REFERENCES users(user_id)
);
`);
resolve();
});
});
}
load() {
const usersJson = fs.readFileSync(
`${this.path}users.json`,
);
this.users = JSON.parse(usersJson.toString('utf8'));
}

function getDb() {
if (!db) {
throw new Error('Database not yet initialized. Call `connect() first.`');
init_datastore() {
// Check required files all exist, load into object
if (!fs.existsSync(`${this.path}users.json`)) {
console.log('Datastore users.json does not exist: Creating');
fs.writeFileSync(`${this.path}users.json`, JSON.stringify([]));
}
this.load();
this.initialised = true;
}
return db;
}

function close() {
if (!db) {
throw new Error('Database not yet initialized. Call `connect() first.`');
save() {
const usersJson = JSON.stringify(this.users);
fs.writeFileSync(
`${this.path}users.json`,
usersJson,
);
}
db.close();
db = undefined;
}

function returnUserFromRow(row) {
return {
user_id: row.user_id,
username: row.username,
display_name: row.display_name,
bio: row.bio,
pfp_link: row.pfp_link,
password_hash: row.password_hash,
};
}
close() {
if (!this.initialised) throw new Error('Database not initialized.');

async function getUserFromUserId(userId) {
return new Promise((resolve, reject) => {
if (!db) {
reject(new Error('Database not yet initialized. Call `connect() first.`'));
return;
}
this.save();
this.initialised = false;
delete this;
}

const query = 'SELECT * FROM users WHERE user_id = ?';

db.get(query, [userId], (err, row) => {
if (err) {
reject(new Error(`Error querying the database: ${err.message}`));
} else if (!row) { // If no user is found, `row` will be null
resolve(null);
} else {
resolve(returnUserFromRow(row));
}
});
});
}
getUserFromUserId(userId) {
if (!this.initialised) {
throw new Error('Database not initialized.');
}
const index = this.users.findIndex((user) => user.user_id === userId);
if (index === -1) return null;

async function getUserFromUsername(username) {
if (!db) {
throw new Error('Database not yet initialized. Call `connect() first.`');
return returnUserFromRow(this.users[index]);
}

return new Promise((resolve, reject) => {
const query = 'SELECT * FROM users WHERE username = ?';

db.get(query, [username], (err, row) => {
if (err) {
return reject(new Error(`Error querying the database: ${err.message}`));
} if (!row) { // If no user is found, `row` will be null
return resolve(null);
}
return resolve(returnUserFromRow(row)); // If no user is found, `row` will be null
});
});
}
getUserFromUsername(username) {
if (!this.initialised) {
throw new Error('Database not initialized.');
}

async function modifyUser(userId, displayName, bio) {
if (!db) {
throw new Error('Database not yet initialized. Call `connect() first.`');
const index = this.users.findIndex((user) => user.username === username);
if (index === -1) return null;

return returnUserFromRow(this.users[index]);
}

return new Promise((resolve, reject) => {
const query = 'UPDATE users SET display_name = ?, bio = ? WHERE user_id = ?';
modifyUser(userId, displayName, bio) {
if (!this.initialised) {
throw new Error('Database not initialized.');
}

const index = this.users.findIndex((user) => user.user_id === userId);
if (index === -1) return false;

db.run(query, [displayName, bio, userId], (err, res) => {
if (err) {
return reject(new Error(`Error querying the database: ${err.message}`));
}
return resolve(res);
});
});
this.users[index].display_name = displayName;
this.users[index].bio = bio;

this.save();

return true;
}
}

module.exports = {
connect,
getDb,
close,
getUserFromUserId,
getUserFromUsername,
modifyUser,
Datastore,
};
Loading

0 comments on commit 1c58564

Please sign in to comment.