Skip to content

Commit

Permalink
Merge pull request #3950 from huridocs/3393-upgrade-mongoose-working
Browse files Browse the repository at this point in the history
upgrade mongo and mongoose packages
  • Loading branch information
fnocetti authored Oct 8, 2021
2 parents 5761084 + bc4d2da commit 438d763
Show file tree
Hide file tree
Showing 28 changed files with 366 additions and 283 deletions.
2 changes: 1 addition & 1 deletion app/api/entities/entities.js
Original file line number Diff line number Diff line change
Expand Up @@ -496,7 +496,7 @@ export default {
await process(0, totalRows);
},

getWithoutDocuments(query, select, options = {}) {
async getWithoutDocuments(query, select, options = {}) {
return model.getUnrestricted(query, select, options);
},

Expand Down
4 changes: 3 additions & 1 deletion app/api/entities/entitiesModel.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { instanceModelWithPermissions } from 'api/odm/ModelWithPermissions';
import mongoose from 'mongoose';
import { instanceModelWithPermissions } from 'api/odm/ModelWithPermissions';
import { MetadataObjectSchema, PropertyValueSchema } from 'shared/types/commonTypes';
import { EntitySchema } from 'shared/types/entityType';

Expand Down Expand Up @@ -31,6 +31,8 @@ const mongoSchema = new mongoose.Schema(
{ emitIndexErrors: true, minimize: false }
);

//mongodb types not updated yet for language_override?
//@ts-ignore
mongoSchema.index({ title: 'text' }, { language_override: 'mongoLanguage' });

const Model = instanceModelWithPermissions<EntitySchema>('entities', mongoSchema);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,19 @@ describe('migration rename-uploads-to-files', () => {
});

describe('when files already exists', () => {
beforeEach(async () => {
const collections = await testingDB.mongodb.listCollections().toArray();
if (!collections.find(c => c.name === 'files')) {
await testingDB.mongodb.createCollection('files');
}
});

it('should not fail', async () => {
await testingDB.mongodb.createCollection('files');
await migration.up(testingDB.mongodb);
});

it('should not delete files when uploads does not exists (migration already ran)', async () => {
await testingDB.mongodb.collection('uploads').drop();
await testingDB.mongodb.createCollection('files');

await migration.up(testingDB.mongodb);
expect((await testingDB.mongodb.listCollections({ name: 'files' }).toArray()).length).toBe(1);
Expand Down
2 changes: 0 additions & 2 deletions app/api/odm/DB.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@ const DB = {
},

connectionForDB(dbName: string) {
//mongoose types not updated yet for useCache
//@ts-ignore
return this.getConnection().useDb(dbName, { useCache: true });
},

Expand Down
100 changes: 62 additions & 38 deletions app/api/odm/ModelWithPermissions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,24 @@ import { UserSchema } from 'shared/types/userType';
import { PermissionSchema } from 'shared/types/permissionType';
import { ObjectIdSchema } from 'shared/types/commonTypes';
import { createUpdateLogHelper } from './logHelper';
import { DataType, OdmModel } from './model';
import { models, UwaziFilterQuery, WithId } from './models';

export type PermissionsUwaziFilterQuery<T> = UwaziFilterQuery<T> & {
published?: boolean;
permissions?: PermissionSchema[];
};

const appendPermissionData = <T>(data: DataType<T>, user: UserSchema | undefined) => {
import {
DataType,
OdmModel,
models,
UwaziFilterQuery,
UwaziQueryOptions,
EnforcedWithId,
} from './model';

type WithPermissions<T> = T & { published?: boolean; permissions?: PermissionSchema[] };
type WithPermissionsDataType<T> = DataType<WithPermissions<T>>;

export type PermissionsUwaziFilterQuery<T> = UwaziFilterQuery<DataType<WithPermissions<T>>>;

const appendPermissionData = <T>(
data: WithPermissionsDataType<T>,
user: DataType<UserSchema> | undefined
) => {
if (!user) {
return data;
}
Expand All @@ -33,13 +42,15 @@ const appendPermissionData = <T>(data: DataType<T>, user: UserSchema | undefined
const requestingPermissions = (select: any) =>
select && ((select.includes && select.includes('+permissions')) || select.permissions === 1);

export const getUserPermissionIds = (user: WithId<UserSchema>) => {
export const getUserPermissionIds = (user: DataType<UserSchema>) => {
const userIds = user.groups ? user.groups.map(group => group._id.toString()) : [];
userIds.push(user._id.toString());
if (user._id) {
userIds.push(user._id.toString());
}
return userIds;
};

const addPermissionsCondition = (user: WithId<UserSchema>, level: AccessLevels) => {
const addPermissionsCondition = (user: DataType<UserSchema>, level: AccessLevels) => {
let permissionCond = {};
if (!['admin', 'editor'].includes(user.role)) {
const userIds = getUserPermissionIds(user);
Expand All @@ -59,19 +70,19 @@ const addPermissionsCondition = (user: WithId<UserSchema>, level: AccessLevels)
const appendPermissionQuery = <T>(
query: PermissionsUwaziFilterQuery<T>,
level: AccessLevels,
user: UserSchema | undefined
) => {
user: DataType<UserSchema> | undefined
): UwaziFilterQuery<DataType<WithPermissions<T>>> => {
let permissionCond: {} = { _id: null };
if (user) {
permissionCond = addPermissionsCondition(user as WithId<UserSchema>, level);
permissionCond = addPermissionsCondition(user, level);
} else if (level === AccessLevels.READ) {
permissionCond = { published: true };
}
return { ...query, ...permissionCond };
};

function checkPermissionAccess<T>(
elem: T & { permissions?: PermissionSchema[] },
elem: EnforcedWithId<WithPermissions<T>>,
userIds: ObjectIdSchema[],
level: AccessLevels = AccessLevels.WRITE
) {
Expand All @@ -87,75 +98,88 @@ function checkPermissionAccess<T>(
return elem;
}

const filterPermissionsData = <T>(data: T[], user: UserSchema | undefined) => {
const filterPermissionsData = <T>(
data: EnforcedWithId<WithPermissions<T>>[] = [],
user: DataType<UserSchema> | undefined
) => {
let filteredData = data;
if (user && !['admin', 'editor'].includes(user.role)) {
if (Array.isArray(data) && data.length > 0) {
const userIds = getUserPermissionIds(user as WithId<UserSchema>);
if (data.length > 0) {
const userIds = getUserPermissionIds(user);
filteredData = data.map(elem => checkPermissionAccess(elem, userIds));
}
}
return filteredData;
};

const controlPermissionsData = <T>(data: T & { published?: boolean }, user?: UserSchema) => {
const controlPermissionsData = <T>(
data: EnforcedWithId<WithPermissions<T>>,
user?: DataType<UserSchema>
) => {
if (user) {
if (['admin', 'editor'].includes(user.role)) {
return data;
}

return checkPermissionAccess(
data,
getUserPermissionIds(user as WithId<UserSchema>),
AccessLevels.WRITE
);
return checkPermissionAccess(data, getUserPermissionIds(user), AccessLevels.WRITE);
}

return { ...data, permissions: undefined };
};

export class ModelWithPermissions<T> extends OdmModel<T> {
async save(data: DataType<T & { permissions?: PermissionSchema[] }>) {
export class ModelWithPermissions<T> extends OdmModel<WithPermissions<T>> {
async save(data: WithPermissionsDataType<T>) {
const user = permissionsContext.getUserInContext();
const query = { _id: data._id };
return data._id || data.permissions
? super.save(data, appendPermissionQuery({ _id: data._id }, AccessLevels.WRITE, user))
? super.save(data, appendPermissionQuery(query, AccessLevels.WRITE, user))
: super.save(appendPermissionData(data, user));
}

async saveUnrestricted(data: DataType<T & { permissions?: PermissionSchema[] }>) {
async saveUnrestricted(data: WithPermissionsDataType<T>) {
return data._id || data.permissions ? super.save(data, { _id: data._id }) : super.save(data);
}

get(query: UwaziFilterQuery<T> = {}, select: any = '', options: {} = {}) {
async get(
query: UwaziFilterQuery<WithPermissions<DataType<T>>> = {},
select: any = '',
options: UwaziQueryOptions = {}
) {
const user = permissionsContext.getUserInContext();
const results = super.get(
appendPermissionQuery(query, AccessLevels.READ, user),
const results = await super.get(
appendPermissionQuery<WithPermissions<T>>(query, AccessLevels.READ, user),
select,
options
);
return requestingPermissions(select)
? results.map(data => filterPermissionsData(data, user))
const returnResult = requestingPermissions(select)
? filterPermissionsData<T>(results, user)
: results;
return returnResult;
}

async count(query: UwaziFilterQuery<T> = {}) {
async count(query: UwaziFilterQuery<WithPermissions<DataType<T>>> = {}) {
const user = permissionsContext.getUserInContext();
return super.count(appendPermissionQuery(query, AccessLevels.READ, user));
}

getUnrestricted(query: UwaziFilterQuery<T> = {}, select: any = '', options: {} = {}) {
async getUnrestricted(
query: UwaziFilterQuery<WithPermissions<DataType<T>>> = {},
select: any = '',
options: {} = {}
) {
return super.get(query, select, options);
}

async getById(id: any, select?: string) {
const user = permissionsContext.getUserInContext();
const query = { _id: id || null };
const doc = await this.db.findOne(
appendPermissionQuery({ _id: id || null }, AccessLevels.READ, user),
appendPermissionQuery<T>(query, AccessLevels.READ, user),
select
);

if (doc && requestingPermissions(select)) {
return controlPermissionsData(doc, user);
return controlPermissionsData<T>(doc, user);
}

return doc;
Expand Down
52 changes: 30 additions & 22 deletions app/api/odm/MultiTenantMongooseModel.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import mongoose, { Schema, UpdateQuery, ModelUpdateOptions } from 'mongoose';
import { WithId, UwaziFilterQuery } from './models';
import mongoose, { Schema } from 'mongoose';
import {
DataType,
UwaziFilterQuery,
UwaziUpdateQuery,
UwaziQueryOptions,
EnforcedWithId,
} from './model';
import { tenants } from '../tenants/tenantContext';
import { DB } from './DB';

class MultiTenantMongooseModel<T> {
dbs: { [k: string]: mongoose.Model<WithId<T> & mongoose.Document> };
dbs: { [k: string]: mongoose.Model<DataType<T>> };

collectionName: string;

Expand All @@ -20,67 +26,69 @@ class MultiTenantMongooseModel<T> {
const currentTenant = tenants.current();

if (!this.dbs[currentTenant.name]) {
this.dbs[currentTenant.name] = DB.connectionForDB(currentTenant.dbName).model<
WithId<T> & mongoose.Document
>(this.collectionName, this.schema);
this.dbs[currentTenant.name] = DB.connectionForDB(currentTenant.dbName).model<DataType<T>>(
this.collectionName,
this.schema
);
}

return this.dbs[currentTenant.name];
}

findById(id: any | string | number, select?: any) {
findById(id: any, select?: any) {
return this.dbForCurrentTenant().findById(id, select, { lean: true });
}

find(query: UwaziFilterQuery<T>, select = '', options = {}) {
find(query: UwaziFilterQuery<DataType<T>>, select = '', options = {}) {
return this.dbForCurrentTenant().find(query, select, options);
}

async findOneAndUpdate(
query: UwaziFilterQuery<T>,
update: Readonly<Partial<T>> & { _id?: any },
options: any = {}
query: UwaziFilterQuery<DataType<T>>,
update: UwaziUpdateQuery<DataType<T>>,
options: UwaziQueryOptions
) {
return this.dbForCurrentTenant().findOneAndUpdate(query, update, options);
}

async create(data: Readonly<Partial<T>> & { _id?: any }) {
async create(data: Partial<DataType<T>>) {
return this.dbForCurrentTenant().create(data);
}

async _updateMany(
conditions: UwaziFilterQuery<T>,
doc: UpdateQuery<T>,
options: ModelUpdateOptions = {}
conditions: UwaziFilterQuery<DataType<T>>,
doc: UwaziUpdateQuery<DataType<T>>,
options: UwaziQueryOptions
) {
return this.dbForCurrentTenant().updateMany(conditions, doc, options);
}

async findOne(conditions: UwaziFilterQuery<T>, projection: any) {
return this.dbForCurrentTenant().findOne(conditions, projection, { lean: true });
async findOne(conditions: UwaziFilterQuery<DataType<T>>, projection: any) {
const result = await this.dbForCurrentTenant().findOne(conditions, projection, { lean: true });
return result as EnforcedWithId<T> | null;
}

async replaceOne(conditions: UwaziFilterQuery<T>, replacement: any) {
async replaceOne(conditions: UwaziFilterQuery<DataType<T>>, replacement: any) {
return this.dbForCurrentTenant().replaceOne(conditions, replacement);
}

async countDocuments(query: UwaziFilterQuery<T> = {}) {
async countDocuments(query: UwaziFilterQuery<DataType<T>> = {}) {
return this.dbForCurrentTenant().countDocuments(query);
}

async distinct(field: string, query: UwaziFilterQuery<T> = {}) {
async distinct(field: string, query: UwaziFilterQuery<DataType<T>> = {}) {
return this.dbForCurrentTenant().distinct(field, query);
}

async deleteMany(query: UwaziFilterQuery<T>) {
async deleteMany(query: UwaziFilterQuery<DataType<T>>) {
return this.dbForCurrentTenant().deleteMany(query);
}

async aggregate(aggregations?: any[]) {
return this.dbForCurrentTenant().aggregate(aggregations);
}

async updateOne(conditions: UwaziFilterQuery<T>, doc: UpdateQuery<T>) {
async updateOne(conditions: UwaziFilterQuery<DataType<T>>, doc: UwaziUpdateQuery<T>) {
return this.dbForCurrentTenant().updateOne(conditions, doc);
}
}
Expand Down
13 changes: 8 additions & 5 deletions app/api/odm/logHelper.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
/* eslint-disable max-classes-per-file */
//@ts-ignore
import PromisePool from '@supercharge/promise-pool';
import { models, UwaziFilterQuery } from 'api/odm/models';
import mongoose from 'mongoose';
import { model as updatelogsModel } from 'api/updatelogs';
import { OdmModel } from 'api/odm/model';
import { OdmModel, models, UwaziFilterQuery, EnforcedWithId, DataType } from './model';

const getBatchSteps = async <T>(
model: OdmModel<T>,
query: UwaziFilterQuery<T>,
query: UwaziFilterQuery<DataType<T>>,
batchSize: number
): Promise<T[]> => {
): Promise<EnforcedWithId<T>[]> => {
const allIds = await model.get(query, '_id', { sort: { _id: 1 } });

const steps = [];
Expand Down Expand Up @@ -43,7 +43,7 @@ export class UpdateLogHelper<T> implements UpdateLogger<T> {
}

async upsertLogMany(
query: UwaziFilterQuery<T>,
query: UwaziFilterQuery<DataType<T>>,
deleted = false,
batchSize = UpdateLogHelper.batchSizeUpsertMany
) {
Expand All @@ -67,14 +67,17 @@ export class UpdateLogHelper<T> implements UpdateLogger<T> {
}

export class NoLogger<T> implements UpdateLogger<T> {
// eslint-disable-next-line class-methods-use-this
async getAffectedIds() {
return Promise.resolve();
}

// eslint-disable-next-line class-methods-use-this
async upsertLogOne() {
return Promise.resolve();
}

// eslint-disable-next-line class-methods-use-this
async upsertLogMany() {
return Promise.resolve();
}
Expand Down
Loading

0 comments on commit 438d763

Please sign in to comment.