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

upgrade mongo and mongoose packages #3950

Merged
merged 30 commits into from
Oct 8, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
13555cd
Basic package updates required
RafaPolit Sep 22, 2021
eb53c13
Apparently missing type, double-check
RafaPolit Sep 22, 2021
85a2900
useCache already supported, removed comment
RafaPolit Sep 22, 2021
b83ae1c
Adhered to standard index definition
RafaPolit Sep 22, 2021
ffdcf9e
Fixed TenantDocument return of .model
RafaPolit Sep 22, 2021
99e1e90
createSpyObject was not returning correct TS
RafaPolit Sep 22, 2021
7f7854e
extra key was failiing validation, double-check
RafaPolit Sep 22, 2021
c254f51
Added react router new type version
RafaPolit Sep 29, 2021
3e06258
Typed the MultiTenantModel with new mongoose types
RafaPolit Sep 29, 2021
4182aa2
Retyped ViewDocumentLink
RafaPolit Sep 29, 2021
c88c4f9
Fixed QueryForEach types
RafaPolit Sep 29, 2021
ff85983
Typed argument
RafaPolit Sep 29, 2021
ca6bac2
Upgraded recharts to newest minor version
RafaPolit Sep 29, 2021
0d033db
Fixed package owner
RafaPolit Sep 29, 2021
aae95ec
createCollection no longer works if collection extists
RafaPolit Sep 29, 2021
d7c2b92
Cleaned up a bit where the types belong in
RafaPolit Sep 30, 2021
7230999
Unified DataType on Model
RafaPolit Oct 1, 2021
3b2008b
Typed doc, this may not be ideal
RafaPolit Oct 1, 2021
3189379
This is testing an error, added ignore
RafaPolit Oct 1, 2021
366bcb2
Moved models to model
RafaPolit Oct 1, 2021
658b3a2
Simple type now works again.
RafaPolit Oct 1, 2021
52037e0
Minor refactor
RafaPolit Oct 1, 2021
7c3ea8e
export let replaced with const. Fingers crossed!
RafaPolit Oct 1, 2021
0f1e588
Merge branch 'development' into 3393-upgrade-mongoose-working
RafaPolit Oct 1, 2021
75e69fe
Mostly working changes
RafaPolit Oct 5, 2021
c09fa6a
Removed casting issues
RafaPolit Oct 5, 2021
17f0547
Replaced getWithoutDocuments query
RafaPolit Oct 6, 2021
cf4602d
Removed unneded casts
RafaPolit Oct 6, 2021
8727829
Fixed es-lint async requirement
RafaPolit Oct 6, 2021
bc4d2da
Merge branch 'development' into 3393-upgrade-mongoose-working
fnocetti Oct 8, 2021
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
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');
}
});

daneryl marked this conversation as resolved.
Show resolved Hide resolved
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[] }>) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this was wrong to start with, the & permissions should have been OUTSIDE of the bracket.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

im not sure this is the case with the current implementation, lets take a look together.

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>> };
Copy link
Member Author

@RafaPolit RafaPolit Oct 1, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the first iteration, DataType included an & Document clause. This is no longer true. This means that dbs no longer explicitly has the mongoose.Document type extended, but I think that T should already have that passed along on instance.


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) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was confused that this had any | string | number... any is already 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