-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit df2bc1b
Showing
54 changed files
with
3,804 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
# Logs | ||
logs | ||
*.log | ||
|
||
# Runtime data | ||
# pids | ||
# *.pid | ||
# *.seed | ||
|
||
venv/ | ||
|
||
# Directory for instrumented libs generated by jscoverage/JSCover | ||
#lib-cov | ||
|
||
# Coverage directory used by tools like istanbul | ||
#coverage | ||
|
||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) | ||
#.grunt | ||
|
||
# node-waf configuration | ||
# .lock-wscript | ||
|
||
# Compiled binary addons (http://nodejs.org/api/addons.html) | ||
# build/Release | ||
|
||
# Dependency directory | ||
# https://docs.npmjs.com/cli/shrinkwrap#caveats | ||
node_modules | ||
|
||
# Distribution directory | ||
dist | ||
|
||
# Debug log from npm | ||
npm-debug.log | ||
|
||
.DS_Store | ||
|
||
.env |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
import Main from "./src/main"; | ||
|
||
Main(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
{ | ||
"name": "gifty-backend", | ||
"version": "1.0.0", | ||
"description": "Backend for Gifty", | ||
"main": "app.js", | ||
"author": "ruishanteo, mfjkri", | ||
"license": "MIT", | ||
"dependencies": { | ||
"bcrypt": "^5.1.0", | ||
"cors": "^2.8.5", | ||
"dotenv": "^16.3.1", | ||
"express": "^4.18.2", | ||
"jsonwebtoken": "^9.0.1", | ||
"nodemailer": "^6.9.4", | ||
"pg": "^8.11.1", | ||
"pgtools": "^1.0.0", | ||
"sequelize": "^6.32.1" | ||
}, | ||
"scripts": { | ||
"build": "npx tsc", | ||
"start": "node dist/app.js", | ||
"dev": "concurrently \"npx tsc --watch\" \"nodemon -q dist/app.js\"", | ||
"dropDB": "yarn tsc; node dist/cmd/db.js drop", | ||
"seedDB": "yarn tsc; node dist/cmd/db.js seed", | ||
"backupDB": "yarn tsc; node dist/cmd/db.js backup" | ||
}, | ||
"devDependencies": { | ||
"@types/bcrypt": "^5.0.0", | ||
"@types/cors": "^2.8.13", | ||
"@types/express": "^4.17.17", | ||
"@types/jsonwebtoken": "^9.0.2", | ||
"@types/node": "^20.4.5", | ||
"@types/nodemailer": "^6.4.9", | ||
"@types/pg": "^8.10.2", | ||
"commander": "^11.0.0", | ||
"concurrently": "^8.2.0", | ||
"nodemon": "^3.0.1", | ||
"typescript": "^5.1.6" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
import dotenv from "dotenv"; | ||
|
||
type DBConfig = { | ||
DBHostname: string; | ||
DBPort: number; | ||
DBName: string; | ||
DBUsername: string; | ||
DBPassword: string; | ||
}; | ||
|
||
type Config = DBConfig & { | ||
ServerPort: number; | ||
JWTSecretKey: string; | ||
|
||
UseLocalDB: boolean; | ||
|
||
EmailUsername: string; | ||
EmailPassword: string; | ||
}; | ||
|
||
export function loadEnv() { | ||
dotenv.config(); | ||
validateEnv(); | ||
} | ||
|
||
export function getConfig(): Config { | ||
const { | ||
SERVER_PORT, | ||
|
||
JWT_SECRET_KEY, | ||
|
||
USE_LOCAL_DB, | ||
DB_HOSTNAME, | ||
DB_PORT, | ||
DB_NAME, | ||
DB_USERNAME, | ||
DB_PASSWORD, | ||
LOCAL_DB_HOSTNAME, | ||
LOCAL_DB_PORT, | ||
LOCAL_DB_NAME, | ||
LOCAL_DB_USERNAME, | ||
LOCAL_DB_PASSWORD, | ||
|
||
EMAIL_USERNAME, | ||
EMAIL_PASSWORD, | ||
} = process.env; | ||
const dbConfig: DBConfig = | ||
USE_LOCAL_DB === "true" | ||
? { | ||
DBHostname: LOCAL_DB_HOSTNAME || "localhost", | ||
DBPort: parseInt(LOCAL_DB_PORT || "5432"), | ||
DBName: LOCAL_DB_NAME || "postgres", | ||
DBUsername: LOCAL_DB_USERNAME || "postgres", | ||
DBPassword: LOCAL_DB_PASSWORD || "postgres", | ||
} | ||
: { | ||
DBHostname: DB_HOSTNAME || "localhost", | ||
DBPort: parseInt(DB_PORT || "5432"), | ||
DBName: DB_NAME || "postgres", | ||
DBUsername: DB_USERNAME || "postgres", | ||
DBPassword: DB_PASSWORD || "postgres", | ||
}; | ||
|
||
return { | ||
ServerPort: parseInt(SERVER_PORT || "8080"), | ||
|
||
JWTSecretKey: JWT_SECRET_KEY || "secret", | ||
|
||
UseLocalDB: USE_LOCAL_DB === "true", | ||
...dbConfig, | ||
|
||
EmailUsername: EMAIL_USERNAME || "", | ||
EmailPassword: EMAIL_PASSWORD || "", | ||
}; | ||
} | ||
|
||
function validateEnv() { | ||
const { | ||
SERVER_PORT, | ||
DB_HOSTNAME, | ||
DB_PORT, | ||
DB_NAME, | ||
DB_USERNAME, | ||
DB_PASSWORD, | ||
JWT_SECRET_KEY, | ||
} = process.env; | ||
|
||
if (!SERVER_PORT) { | ||
throw new Error("SERVER_PORT is not defined"); | ||
} | ||
|
||
if (!DB_HOSTNAME) { | ||
throw new Error("DB_HOSTNAME is not defined"); | ||
} | ||
if (!DB_PORT) { | ||
throw new Error("DB_PORT is not defined"); | ||
} | ||
if (!DB_NAME) { | ||
throw new Error("DB_NAME is not defined"); | ||
} | ||
if (!DB_USERNAME) { | ||
throw new Error("DB_USERNAME is not defined"); | ||
} | ||
if (!DB_PASSWORD) { | ||
throw new Error("DB_PASSWORD is not defined"); | ||
} | ||
if (!JWT_SECRET_KEY) { | ||
throw new Error("JWT_SECRET_KEY is not defined"); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
import { Client } from "pg"; | ||
import { Sequelize } from "sequelize"; | ||
|
||
import pgtools from "pgtools"; | ||
|
||
import { getConfig } from "../config/config"; | ||
|
||
const connectedDBs: Map<string, Sequelize> = new Map(); | ||
|
||
export async function connectDB(dbName?: string) { | ||
const config = getConfig(); | ||
const name = dbName || config.DBName; | ||
const db = connect({ | ||
Hostname: config.DBHostname, | ||
Port: config.DBPort, | ||
Name: name, | ||
User: config.DBUsername, | ||
Password: config.DBPassword, | ||
}); | ||
|
||
try { | ||
await db.authenticate(); | ||
connectedDBs.set(name, db); | ||
console.log(`Connection to ${name} DB has been established successfully.`); | ||
} catch (error) { | ||
console.error(`Unable to connect to ${name} DB:`, error); | ||
} | ||
|
||
return db; | ||
} | ||
|
||
export function getDB(dbName?: string) { | ||
const config = getConfig(); | ||
dbName = dbName || config.DBName; | ||
const targetDb = connectedDBs.get(dbName); | ||
if (connectedDBs.size === 0 || !targetDb) { | ||
throw new Error("Database not connected"); | ||
} | ||
return targetDb; | ||
} | ||
|
||
type Config = { | ||
Hostname: string; | ||
Port: number; | ||
Name: string; | ||
User: string; | ||
Password: string; | ||
}; | ||
|
||
function connect(config: Config) { | ||
const { Hostname, Port, Name, User, Password } = config; | ||
return new Sequelize(Name, User, Password, { | ||
host: Hostname, | ||
port: Port, | ||
dialect: "postgres", | ||
}); | ||
} | ||
export async function createBackupDB() { | ||
const config = getConfig(); | ||
const backupDBName = "gifty_backup"; | ||
|
||
try { | ||
const client = new Client({ | ||
user: config.DBUsername, | ||
password: config.DBPassword, | ||
host: config.DBHostname, | ||
port: config.DBPort, | ||
database: "postgres", | ||
}); | ||
|
||
await client.connect(); | ||
const res = await client.query( | ||
`SELECT 1 FROM pg_database WHERE datname = $1`, | ||
[backupDBName] | ||
); | ||
await client.end(); | ||
|
||
if (res.rows.length === 0) { | ||
await pgtools.createdb( | ||
{ | ||
user: config.DBUsername, | ||
password: config.DBPassword, | ||
host: config.DBHostname, | ||
port: config.DBPort, | ||
}, | ||
backupDBName | ||
); | ||
const backupDB = await connectDB(backupDBName); | ||
await backupDB.authenticate(); | ||
console.log(`Created backup database: ${backupDBName}`); | ||
return backupDB; | ||
} else { | ||
const backupDB = await connectDB(backupDBName); | ||
await backupDB.authenticate(); | ||
console.log(`Backup database already exists: ${backupDBName}`); | ||
return backupDB; | ||
} | ||
} catch (err) { | ||
console.error(err); | ||
throw err; | ||
} | ||
} | ||
|
||
export default getDB; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
export type ValidationError = { | ||
error: boolean; | ||
message: string; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import { Request, Response } from "express"; | ||
|
||
import User from "../../models/user"; | ||
import { DeleteUserParams } from "../../params/auth/deleteUser"; | ||
|
||
const SUCCESS_DELETED_USER = "Deleted user successfully"; | ||
|
||
const ERROR_USER_DOES_NOT_EXIST = "User does not exist"; | ||
const ERROR_FAILED_TO_DELETE_USER = "Failed to delete user"; | ||
|
||
export default async function handleDeleteUser( | ||
req: Request, | ||
res: Response, | ||
params: DeleteUserParams | ||
) { | ||
try { | ||
const user: User = req.body.user; | ||
|
||
if (!user) { | ||
return res.status(404).json({ message: ERROR_USER_DOES_NOT_EXIST }); | ||
} | ||
|
||
await user.destroy(); | ||
|
||
return res.status(201).json({ | ||
message: SUCCESS_DELETED_USER, | ||
}); | ||
} catch (error) { | ||
res.status(500).json({ message: ERROR_FAILED_TO_DELETE_USER, error }); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import { Request, Response } from "express"; | ||
|
||
import ResetPasswordToken from "../../models/resetPasswordToken"; | ||
import User from "../../models/user"; | ||
import { sendEmailWithOTP } from "../../utilities/mail"; | ||
import { GetOTPParams } from "../../params/auth/getOTP"; | ||
|
||
const SUCCESS_OTP_GENERATED = "OTP token generated successfully"; | ||
|
||
const ERROR_USER_NOT_FOUND = "User not found"; | ||
const ERROR_FAILED_TO_GENERATE_OTP = "Failed to generate OTP token"; | ||
|
||
export default async function handleGenerateOTP( | ||
req: Request, | ||
res: Response, | ||
params: GetOTPParams | ||
) { | ||
try { | ||
const otp = Math.floor(100000 + Math.random() * 900000).toString(); | ||
const expirationTime = 900; | ||
const expireAt = new Date(Date.now() + expirationTime * 1000); | ||
|
||
const user = await User.findOne({ | ||
where: { email: params.email.toLowerCase() }, | ||
}); | ||
if (!user) { | ||
return res.status(400).json({ message: ERROR_USER_NOT_FOUND }); | ||
} | ||
|
||
let resetPasswordToken = await ResetPasswordToken.findOne({ | ||
where: { userId: user.id }, | ||
}); | ||
|
||
if (!resetPasswordToken) { | ||
resetPasswordToken = await ResetPasswordToken.create({ | ||
userId: user.id, | ||
otp, | ||
expireAt, | ||
}); | ||
} else { | ||
await resetPasswordToken.update({ otp, expireAt }); | ||
} | ||
|
||
await sendEmailWithOTP(user.email, otp); | ||
res.status(201).json({ message: SUCCESS_OTP_GENERATED }); | ||
} catch (error) { | ||
res.status(500).json({ message: ERROR_FAILED_TO_GENERATE_OTP, error }); | ||
} | ||
} |
Oops, something went wrong.