Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
DATABASE_DIALECT = "postgres"
DATABASE_HOST = "127.0.0.1"
DATABASE_PORT = "5432"
DATABASE_DATABASE = "defaultdb"
DATABASE_USERNAME = "postgres"
DATABASE_PASSWORD = "postgres"
DATABASE_SSL = "false"
DATABASE_SSL_CERT = ""
DATABASE_LOGGING = "false"

HOST_URL = "http://localhost:3000"
BASE_PATH = ""
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ yarn-debug.log*
yarn-error.log*

# local env files
.env
.env*.local

# vercel
Expand Down
29 changes: 20 additions & 9 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "portfolio",
"description": "portfolio",
"version": "0.1.1",
"version": "0.1.2",
"private": true,
"engines": {
"node": ">=18",
Expand All @@ -24,12 +24,23 @@
"@tsparticles/engine": "^3.3.0",
"@tsparticles/react": "^3.0.0",
"@tsparticles/slim": "^3.3.0",
"@types/bcrypt": "^5.0.2",
"@types/react-vertical-timeline-component": "^3.3.6",
"next": "14.1.0",
"react": "^18",
"react-dom": "^18",
"bcrypt": "^5.1.1",
"cli-highlight": "^2.1.11",
"lodash": "^4.17.21",
"mariadb": "^3.3.0",
"mysql2": "^3.9.2",
"next": "^14.1.0",
"pg": "^8.11.3",
"pg-hstore": "^2.3.4",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-tooltip": "^5.26.3",
"react-vertical-timeline-component": "^3.6.0"
"react-vertical-timeline-component": "^3.6.0",
"sequelize": "^6.37.1",
"sequelize-cli": "^6.6.2",
"sqlite": "^5.1.1"
},
"devDependencies": {
"@commitlint/cli": "^19.0.3",
Expand All @@ -47,17 +58,17 @@
"@icongo/vl": "^1.2.0",
"@next/bundle-analyzer": "^14.1.2",
"@types/node": "^20.11.24",
"@types/react": "^18",
"@types/react": "^18.2.60",
"autoprefixer": "^10.0.1",
"cross-env": "^7.0.3",
"eslint": "^8",
"eslint": "^8.57.0",
"eslint-config-next": "14.1.0",
"husky": "^9.0.11",
"postcss": "^8",
"postcss": "^8.4.23",
"prettier": "^3.2.5",
"prettier-plugin-tailwindcss": "^0.5.11",
"tailwindcss": "^3.3.0",
"typescript": "^5"
"typescript": "^5.4.3"
},
"nextBundleAnalysis": {
"budget": 358400,
Expand Down
6 changes: 4 additions & 2 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { getConfig } from '@/config';
import RootLayout from '@/components/layouts/RootLayout';

import type { Metadata, Viewport } from 'next';

const config = getConfig();

export const metadata: Metadata = {
metadataBase: new URL('https://james-gates-portfolio.vercel.app'),
metadataBase: new URL(config.common.host),
alternates: {
canonical: '/',
},
Expand Down
5 changes: 4 additions & 1 deletion src/app/sitemap.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { getConfig } from '@/config';
import { MetadataRoute } from 'next';

const config = getConfig();

export default function sitemap(): MetadataRoute.Sitemap {
const host = 'https://james-gates-portfolio.vercel.app' + (process.env.BASE_PATH || '');
const host = config.common.host + config.common.basePath;
return [
{
url: host,
Expand Down
57 changes: 57 additions & 0 deletions src/config/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/**
* This method returns the server config.
* By default, it returns the Environment Variables.
*/

type TConfig = {
common: {
host: string;
basePath: string;
};
database?: {
dialect: string;
host: string;
port: number;
db: string;
user: string;
password: string;
ssl?: boolean;
ssl_cert?: string;
logging: boolean;
};
node: {
env: string;
};
};

const parseBoolean = (value) => ['true', '1'].includes((value || '').toLowerCase());

export function getConfig(): TConfig {
const env = process.env || {};
const config: TConfig = {
common: {
host: env.HOST_URL || 'http://localhost:3000',
basePath: env.BASE_PATH || '',
},
node: {
env: env.NODE_ENV || 'production',
},
};

if (env.DATABASE_HOST && env.DATABASE_DIALECT) {
// database environment variables
config.database = {
dialect: env.DATABASE_DIALECT || 'postgres',
host: env.DATABASE_HOST || '',
port: parseInt(env.DATABASE_PORT || '0'),
db: env.DATABASE_DATABASE || '',
user: env.DATABASE_USERNAME || '',
password: env.DATABASE_PASSWORD || '',
ssl: parseBoolean(env.DATABASE_SSL),
ssl_cert: env.DATABASE_SSL_CERT,
logging: parseBoolean(env.DATABASE_LOGGING),
};
}

return config;
}
14 changes: 14 additions & 0 deletions src/database/databaseConnection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import models from '@/database/models';

let cached;

/**
* Initializes the connection to the Database
*/
export default async function databaseInit() {
if (!cached) {
cached = await models();
}

return cached;
}
74 changes: 74 additions & 0 deletions src/database/models/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/**
* This module creates the Sequelize to the database and
* exports all the models.
*/
import * as pg from 'pg';
import * as mysql from 'mysql2';
import * as mariadb from 'mariadb';
import * as sqlite from 'sqlite';
import { Sequelize } from 'sequelize';
import { getConfig } from '@/config';

// import models for tables of your database

const dataModels: Array<Function> = [];

const databaseModules = {
postgres: pg,
mysql: mysql,
mariadb: mariadb,
sqlite: sqlite,
};

const highlight = require('cli-highlight').highlight;

async function models() {
const config = getConfig();

const database = {} as any;

if (!config.database) {
return null;
}

let sequelize = new (<any>Sequelize)(config.database.db, config.database.user, config.database.password, {
host: config.database.host,
port: config.database.port,
dialect: config.database.dialect,
dialectModule: databaseModules[config.database.dialect],
dialectOptions: {
ssl: config.database.ssl && {
ssl: true,
rejectUnauthorized: false,
ca: config.database.ssl_cert,
},
},
logging: config.database.logging
? (log) =>
console.log(
highlight(log, {
language: 'sql',
ignoreIllegals: true,
}),
)
: false,
});

dataModels.forEach((dataModel) => {
const model = dataModel(sequelize);
database[model.name] = model;
});

Object.keys(database).forEach(function (modelName) {
if (database[modelName].associate) {
database[modelName].associate(database);
}
});

database.sequelize = sequelize;
database.Sequelize = Sequelize;

return database;
}

export default models;
6 changes: 6 additions & 0 deletions src/database/repositories/IRepositoryOptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export interface IRepositoryOptions {
language: string;
currentUser: any;
database: any;
transaction?: any;
}
74 changes: 74 additions & 0 deletions src/database/repositories/sequelizeRepostiory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { getConfig } from '@/config';
import { IRepositoryOptions } from './IRepositoryOptions';
import { UniqueConstraintError } from 'sequelize';
import Error400 from '@/errors/Error400';
import lodash from 'lodash';

/**
* Abstracts some basic Sequelize operations.
* See https://sequelize.org/v5/index.html to learn how to customize it.
*/
export default class SequelizeRepository {
/**
* Cleans the database.
*/
static async cleanDatabase(database) {
if (getConfig().node.env !== 'test') {
throw new Error('Clean database only allowed for test!');
}

await database.sequelize.sync({ force: true });
}

/**
* Returns the currentUser if it exists on the options.
*/
static getCurrentUser(options: IRepositoryOptions) {
return (options && options.currentUser) || { id: null };
}

/**
* Returns the transaction if it exists on the options.
*/
static getTransaction(options: IRepositoryOptions) {
return (options && options.transaction) || undefined;
}

/**
* Creates a database transaction.
*/
static async createTransaction(database) {
return database.sequelize.transaction();
}

/**
* Commits a database transaction.
*/
static async commitTransaction(transaction) {
return transaction.commit();
}

/**
* Rolls back a database transaction.
*/
static async rollbackTransaction(transaction) {
return transaction.rollback();
}

static handleUniqueFieldError(error, language, entityName) {
if (!(error instanceof UniqueConstraintError)) {
return;
}

const fieldName = lodash.get(error, 'errors[0].path');
throw new Error400(language, `entities.${entityName}.errors.unique.${fieldName}`);
}

static handleNotEmptyField(data, fields, language, entityName) {
(fields || []).forEach((field) => {
if (!data[field] || data[field] === '') {
throw new Error400(language, `entities.${entityName}.errors.notEmpty.${field}`);
}
});
}
}
17 changes: 17 additions & 0 deletions src/database/utils/orderByUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { IRepositoryOptions } from '@/database/repositories/IRepositoryOptions';

export function orderByUtils(orderBy, options: IRepositoryOptions, alias = {}) {
const [, model, field, order] =
/^([\w_]*)[.]?([\w_]+)[_]{1}([\w]+)$/.exec(/[.]+/.test(orderBy) ? orderBy : `.${orderBy}`) || [];

const nameAs = alias[model] || null;

return [
model.length > 0 && {
model: options.database[model],
...(nameAs ? { as: nameAs } : {}),
},
field,
order,
].filter(Boolean);
}
Loading