Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Model Layer Refactor #1

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
3 changes: 2 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"rules": {
"@typescript-eslint/lines-between-class-members": "off",
"no-underscore-dangle": "off",
"import/prefer-default-export": "off"
"import/prefer-default-export": "off",
"max-len": ["warn", 120]
}
}
19 changes: 15 additions & 4 deletions src/@types/Entities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,23 @@ import {

export type Entity = z.infer<typeof entityZodType>;

export type User = z.infer<typeof userZodType>;
export type User = Omit<z.infer<typeof userZodType>, 'company'> & {
company: string | Record<string, unknown>;
};

export type Company = z.infer<typeof companyZodType>;
export type Company = Omit<z.infer<typeof companyZodType>, 'employees' | 'assets' | 'units'> & {
employees: string[] | Record<string, unknown>[];
assets: string[] | Record<string, unknown>[];
units: string[] | Record<string, unknown>[];
};

export type Unit = z.infer<typeof unitZodType>;
export type Unit = Omit<z.infer<typeof unitZodType>, 'assets' | 'owner'> & {
assets: string[] | Record<string, unknown>[];
owner: string | Record<string, unknown>;
};

export type Asset = z.infer<typeof assetZodType>;
export type Asset = Omit<z.infer<typeof assetZodType>, 'owner'> & {
owner: string | Record<string, unknown>;
};

export type Archive = z.infer<typeof archiveZodType>;
14 changes: 0 additions & 14 deletions src/database/models/AssetModel.ts

This file was deleted.

12 changes: 12 additions & 0 deletions src/database/models/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { model } from 'mongoose';
import archiveSchema from '../schemas/archiveSchema';
import assetSchema from '../schemas/assetSchema';
import companySchema from '../schemas/companySchema';
import unitSchema from '../schemas/unitSchema';
import userSchema from '../schemas/userSchema';

export const User = model('User', userSchema);
export const Asset = model('Asset', assetSchema);
export const Company = model('Company', companySchema);
export const Unit = model('Unit', unitSchema);
export const Archive = model('Archive', archiveSchema);
8 changes: 2 additions & 6 deletions src/database/schemas/assetSchema.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
import { ObjectId, Schema } from 'mongoose';
import { Schema } from 'mongoose';
import { Asset } from '../../@types/Entities';

type SchemaCompatibleAsset = Omit<Asset, 'owner'> & {
owner: ObjectId;
};

const assetSchema = new Schema<SchemaCompatibleAsset>({
const assetSchema = new Schema<Asset>({
name: {
type: String,
require: [true, 'Asset name field is required.'],
Expand Down
10 changes: 2 additions & 8 deletions src/database/schemas/companySchema.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
import { ObjectId, Schema } from 'mongoose';
import { Schema } from 'mongoose';
import { Company } from '../../@types/Entities';

type SchemaCompatibleCompany = Omit<Company, 'assets' | 'employees' | 'units'> & {
assets: ObjectId[];
employees: ObjectId[];
units: ObjectId[];
};

const companySchema = new Schema<SchemaCompatibleCompany>({
const companySchema = new Schema<Company>({
name: {
type: String,
required: [true, 'Company name field is required.'],
Expand Down
9 changes: 2 additions & 7 deletions src/database/schemas/unitSchema.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
import { ObjectId, Schema } from 'mongoose';
import { Schema } from 'mongoose';
import { Unit } from '../../@types/Entities';

type SchemaCompatibleUnit = Omit<Unit, 'owner' | 'assets'> & {
assets: ObjectId[];
owner: ObjectId;
};

const unitSchema = new Schema<SchemaCompatibleUnit>({
const unitSchema = new Schema<Unit>({
name: {
type: String,
required: [true, 'Unit name field is required.'],
Expand Down
8 changes: 2 additions & 6 deletions src/database/schemas/userSchema.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
import { ObjectId, Schema } from 'mongoose';
import { Schema } from 'mongoose';
import { User } from '../../@types/Entities';

type SchemaCompatibleUser = Omit<User, 'company'> & {
company: ObjectId;
};

const userSchema = new Schema<SchemaCompatibleUser>({
const userSchema = new Schema<User>({
name: {
type: String,
required: [true, 'User name is required.'],
Expand Down
20 changes: 7 additions & 13 deletions src/database/seeders/index.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,28 @@
/* eslint-disable no-console */
import AssetModel from '../models/AssetModel';
import CompanyModel from '../models/CompanyModel';
import UnitModel from '../models/UnitModel';
import UserModel from '../models/UserModel';
import assets from './Assets';
import companies from './Companies';
import units from './Units';
import users from './Users';
import { connect, disconnect, MONGO_URI_DEV } from '..';
import {
Asset, Company, User, Unit,
} from '../models';

const seed = async (mongoURI = MONGO_URI_DEV) => {
const userModel = new UserModel();
const companyModel = new CompanyModel();
const assetModel = new AssetModel();
const unitModel = new UnitModel();

try {
await connect(mongoURI);

console.log('Seeding users collection.');
await userModel.seed(users);
await User.insertMany(users);

console.log('Seeding companies collection.');
await companyModel.seed(companies);
await Company.insertMany(companies);

console.log('Seeding assets collection.');
await assetModel.seed(assets);
await Asset.insertMany(assets);

console.log('Seeding units collection.');
await unitModel.seed(units);
await Unit.insertMany(units);

console.log('Database seeding completed successfully.');
await disconnect();
Expand Down
14 changes: 14 additions & 0 deletions src/models/AssetModel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Asset as AssetType } from '../@types/Entities';
import { IAssetModel } from '../interfaces/IAssetModel';
import Model from './Model';
import { Asset } from '../database/models';

class AssetModel extends Model<AssetType> implements IAssetModel<AssetType> {
protected _populate = 'owner';

constructor() {
super(Asset);
}
}

export default AssetModel;
22 changes: 11 additions & 11 deletions src/database/models/CompanyModel.ts → src/models/CompanyModel.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import { Company } from '../../@types/Entities';
import { ICompanyModel } from '../../interfaces/ICompanyModel';
import companySchema from '../schemas/companySchema';
import { Company as CompanyType } from '../@types/Entities';
import { Company } from '../database/models';
import { ICompanyModel } from '../interfaces/ICompanyModel';
import Model from './Model';

class CompanyModel extends Model<Company> implements ICompanyModel<Company> {
class CompanyModel extends Model<CompanyType> implements ICompanyModel<CompanyType> {
protected _populate = 'employees assets units';

constructor() {
super('Company', companySchema);
super(Company);
}

async editEmployeeList(id: string, employeeId: string, add: boolean): Promise<Company | null> {
async editEmployeeList(id: string, employeeId: string, add: boolean): Promise<CompanyType | null> {
const edited = add
? await this._model.findByIdAndUpdate(id, {
$push: { employees: employeeId },
Expand All @@ -19,10 +19,10 @@ class CompanyModel extends Model<Company> implements ICompanyModel<Company> {
$pull: { employees: employeeId },
}, { new: true });

return edited?.toObject() as Company;
return edited?.toObject() as CompanyType;
}

async editAssetList(id: string, assetId: string, add: boolean): Promise<Company | null> {
async editAssetList(id: string, assetId: string, add: boolean): Promise<CompanyType | null> {
const edited = add
? await this._model.findByIdAndUpdate(id, {
$push: { assets: assetId },
Expand All @@ -31,10 +31,10 @@ class CompanyModel extends Model<Company> implements ICompanyModel<Company> {
$pull: { assets: assetId },
}, { new: true });

return edited?.toObject() as Company;
return edited?.toObject() as CompanyType;
}

async editUnitList(id: string, unitId: string, add: boolean): Promise<Company | null> {
async editUnitList(id: string, unitId: string, add: boolean): Promise<CompanyType | null> {
const edited = add
? await this._model.findByIdAndUpdate(id, {
$push: { units: unitId },
Expand All @@ -43,7 +43,7 @@ class CompanyModel extends Model<Company> implements ICompanyModel<Company> {
$pull: { units: unitId },
}, { new: true });

return edited?.toObject() as Company;
return edited?.toObject() as CompanyType;
}
}

Expand Down
23 changes: 8 additions & 15 deletions src/database/models/Model.ts → src/models/Model.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
import { model, Schema } from 'mongoose';
import { IModel } from '../../interfaces/IModel';
import { Entity, Archive } from '../../@types/Entities';
import archiveSchema from '../schemas/archiveSchema';
import { Model as MongoModel } from 'mongoose';
import { IModel } from '../interfaces/IModel';
import { Entity, Archive as ArchiveType } from '../@types/Entities';
import { Archive } from '../database/models';

abstract class Model<T extends Entity> implements IModel<T> {
protected abstract _populate: string;
private _archive;
protected _model;

constructor(entityName: string, schema: Schema) {
this._model = model(entityName, schema);
this._archive = model('Archive', archiveSchema);
constructor(model: MongoModel<T>) {
this._model = model;
}

async createOne(object: T): Promise<T> {
Expand Down Expand Up @@ -51,20 +49,15 @@ abstract class Model<T extends Entity> implements IModel<T> {

// This saves the object in an archive that is only accessible by the database admins.
// With this, data is not lost forever and can be consulted if necessary.
const archived: Archive = {
const archived: ArchiveType = {
collectionName: this._model.collection.collectionName,
document: deleted,
};

await this._archive.create(archived);
await Archive.create(archived);

return deleted as T;
}

async seed(objects: T[], reset = true): Promise<void> {
if (reset) await this._model.deleteMany();
await this._model.insertMany(objects);
}
}

export default Model;
14 changes: 7 additions & 7 deletions src/database/models/UnitModel.ts → src/models/UnitModel.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import { Unit } from '../../@types/Entities';
import { IUnitModel } from '../../interfaces/IUnitModel';
import unitSchema from '../schemas/unitSchema';
import { Unit as UnitType } from '../@types/Entities';
import { IUnitModel } from '../interfaces/IUnitModel';
import Model from './Model';
import { Unit } from '../database/models';

class UnitModel extends Model<Unit> implements IUnitModel<Unit> {
class UnitModel extends Model<UnitType> implements IUnitModel<UnitType> {
protected _populate = 'owner assets';

constructor() {
super('Unit', unitSchema);
super(Unit);
}

async editAssetList(id: string, assetId: string, add: boolean): Promise<Unit | null> {
async editAssetList(id: string, assetId: string, add: boolean): Promise<UnitType | null> {
const edited = add
? await this._model.findByIdAndUpdate(id, {
$push: { assets: assetId },
Expand All @@ -19,7 +19,7 @@ class UnitModel extends Model<Unit> implements IUnitModel<Unit> {
$pull: { assets: assetId },
}, { new: true });

return edited?.toObject() as Unit;
return edited?.toObject() as UnitType;
}
}

Expand Down
18 changes: 9 additions & 9 deletions src/database/models/UserModel.ts → src/models/UserModel.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,33 @@
import { User } from '../../@types/Entities';
import { IUserModel } from '../../interfaces/IUserModel';
import userSchema from '../schemas/userSchema';
import { User as UserType } from '../@types/Entities';
import { IUserModel } from '../interfaces/IUserModel';
import Model from './Model';
import { User } from '../database/models';

class UserModel extends Model<User> implements IUserModel<User> {
class UserModel extends Model<UserType> implements IUserModel<UserType> {
protected _populate = 'company';
constructor() {
super('User', userSchema);
super(User);
}

// Override for deleting the user password.
async createOne(user: User): Promise<User> {
async createOne(user: UserType): Promise<UserType> {
const created = await (await this._model.create(user)).toObject();
delete created.password;
return created as User;
return created as UserType;
}

/**
* Finds a user with matching email in the database.
* @param email The user's email.
* @returns The user with the passed email. Null if no user found.
*/
async findByEmail(email: string, login = false): Promise<User | null> {
async findByEmail(email: string, login = false): Promise<UserType | null> {
const select = login ? '+password' : '';
const found = await this._model.findOne({ email })
.populate(this._populate)
.select(select);

return found as User | null;
return found as UserType | null;
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/services/AssetService.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { AssetStatus } from '../@types/AssetStatus';
import { Asset } from '../@types/Entities';
import AssetModel from '../database/models/AssetModel';
import AssetModel from '../models/AssetModel';
import Service from './Service';

class AssetService extends Service<Asset> {
Expand Down