-
Notifications
You must be signed in to change notification settings - Fork 21
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #365 from commons-stack/feat/user_event_log
Feat/user event log
- Loading branch information
Showing
46 changed files
with
1,081 additions
and
95 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
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
56 changes: 56 additions & 0 deletions
56
packages/api/src/database/migrations/06_event_log_types.ts
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,56 @@ | ||
import { EventLogTypeModel } from '../../eventlog/entities'; | ||
import { EventLogTypeKey } from '../../eventlog/types'; | ||
|
||
const eventLogTypes = [ | ||
{ | ||
key: EventLogTypeKey.PERMISSION, | ||
label: 'User Permissions', | ||
description: 'An action that changes user permissions', | ||
}, | ||
{ | ||
key: EventLogTypeKey.AUTHENTICATION, | ||
label: 'User Authentication', | ||
description: 'An action to authenticate or register a user', | ||
}, | ||
{ | ||
key: EventLogTypeKey.PERIOD, | ||
label: 'Period', | ||
description: 'An action on a period', | ||
}, | ||
{ | ||
key: EventLogTypeKey.PRAISE, | ||
label: 'Praise', | ||
description: 'An action to give praise', | ||
}, | ||
{ | ||
key: EventLogTypeKey.SETTING, | ||
label: 'Setting', | ||
description: 'An action that changes a setting', | ||
}, | ||
{ | ||
key: EventLogTypeKey.QUANTIFICATION, | ||
label: 'Quantification', | ||
description: 'An action to quantify praise', | ||
}, | ||
]; | ||
|
||
const up = async (): Promise<void> => { | ||
const upsertQueries = eventLogTypes.map((s) => ({ | ||
updateOne: { | ||
filter: { key: s.key }, | ||
|
||
// Insert item if not found, otherwise continue | ||
update: { $setOnInsert: { ...s } }, | ||
upsert: true, | ||
}, | ||
})); | ||
|
||
await EventLogTypeModel.bulkWrite(upsertQueries); | ||
}; | ||
|
||
const down = async (): Promise<void> => { | ||
const allKeys = eventLogTypes.map((s) => s.key); | ||
await EventLogTypeModel.deleteMany({ key: { $in: allKeys } }); | ||
}; | ||
|
||
export { up, down }; |
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,45 @@ | ||
import { getQuerySort } from '@shared/functions'; | ||
import { | ||
PaginatedResponseBody, | ||
QueryInputParsedQs, | ||
TypedRequestQuery, | ||
TypedResponse, | ||
} from '@shared/types'; | ||
import { BadRequestError } from '@error/errors'; | ||
import { StatusCodes } from 'http-status-codes'; | ||
import { EventLogModel } from './entities'; | ||
import { EventLogDto } from './types'; | ||
import { eventLogListTransformer } from './transformers'; | ||
|
||
/** | ||
* Fetch a paginated list of EventLogs | ||
*/ | ||
export const all = async ( | ||
req: TypedRequestQuery<QueryInputParsedQs>, | ||
res: TypedResponse<PaginatedResponseBody<EventLogDto>> | ||
): Promise<void> => { | ||
if (!req.query.limit || !req.query.page) | ||
throw new BadRequestError('limit and page are required'); | ||
|
||
const paginateQuery = { | ||
query: {}, | ||
limit: parseInt(req.query.limit), | ||
page: parseInt(req.query.page), | ||
sort: getQuerySort(req.query), | ||
}; | ||
|
||
const response = await EventLogModel.paginate(paginateQuery); | ||
|
||
if (!response) throw new BadRequestError('Failed to query event logs'); | ||
|
||
const docs = response.docs ? response.docs : []; | ||
const docsTransfomed = await eventLogListTransformer( | ||
docs, | ||
res.locals.currentUser.roles | ||
); | ||
|
||
res.status(StatusCodes.OK).json({ | ||
...response, | ||
docs: docsTransfomed, | ||
}); | ||
}; |
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,69 @@ | ||
import mongoose, { Schema } from 'mongoose'; | ||
import { mongoosePagination, Pagination } from 'mongoose-paginate-ts'; | ||
import { | ||
EventLogDocument, | ||
EventLogTypeDocument, | ||
EventLogTypeKey, | ||
} from './types'; | ||
|
||
export const eventLogSchema = new mongoose.Schema( | ||
{ | ||
user: { | ||
type: Schema.Types.ObjectId, | ||
ref: 'User', | ||
index: true, | ||
}, | ||
useraccount: { | ||
type: Schema.Types.ObjectId, | ||
ref: 'UserAccount', | ||
index: true, | ||
}, | ||
|
||
// "Related Period" of an eventlog - only used for quantification events | ||
// which are restricted to ADMIN users when period is active | ||
period: { | ||
type: Schema.Types.ObjectId, | ||
ref: 'Period', | ||
index: true, | ||
}, | ||
|
||
type: { | ||
type: Schema.Types.ObjectId, | ||
ref: 'EventLogType', | ||
required: true, | ||
index: true, | ||
}, | ||
description: { type: String, required: true }, | ||
}, | ||
{ | ||
timestamps: true, | ||
} | ||
); | ||
|
||
eventLogSchema.plugin(mongoosePagination); | ||
|
||
export const EventLogModel = mongoose.model< | ||
EventLogDocument, | ||
Pagination<EventLogDocument> | ||
>('EventLog', eventLogSchema); | ||
|
||
export const eventLogTypeSchema = new mongoose.Schema( | ||
{ | ||
key: { | ||
type: String, | ||
required: true, | ||
unique: true, | ||
enum: Object.values(EventLogTypeKey), | ||
}, | ||
label: { type: String, required: true }, | ||
description: { type: String, required: true }, | ||
}, | ||
{ | ||
timestamps: true, | ||
} | ||
); | ||
|
||
export const EventLogTypeModel = mongoose.model< | ||
EventLogTypeDocument, | ||
Pagination<EventLogTypeDocument> | ||
>('EventLogType', eventLogTypeSchema); |
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,9 @@ | ||
import { Router } from '@awaitjs/express'; | ||
import * as controller from './controllers'; | ||
|
||
// Period-routes | ||
const eventLogRouter = Router(); | ||
|
||
eventLogRouter.getAsync('/all', controller.all); | ||
|
||
export { eventLogRouter }; |
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,86 @@ | ||
import { PeriodModel } from '@period/entities'; | ||
import { PeriodStatusType } from '@period/types'; | ||
import { UserRole } from '@user/types'; | ||
import { UserAccountModel } from '@useraccount/entities'; | ||
import { userAccountTransformer } from '@useraccount/transformers'; | ||
import { EventLogTypeModel } from './entities'; | ||
import { | ||
EventLogDocument, | ||
EventLogDto, | ||
EventLogTypeDocument, | ||
EventLogTypeDto, | ||
} from './types'; | ||
|
||
const eventLogTypeTransformer = ( | ||
eventLogType: EventLogTypeDocument | ||
): EventLogTypeDto => { | ||
const { key, label, description } = eventLogType; | ||
|
||
return { | ||
key, | ||
label, | ||
description, | ||
} as EventLogTypeDto; | ||
}; | ||
|
||
export const eventLogTransformer = async ( | ||
eventLog: EventLogDocument, | ||
currentUserRoles: UserRole[] = [UserRole.USER] | ||
): Promise<EventLogDto> => { | ||
const eventLogType = await EventLogTypeModel.findOne({ | ||
_id: eventLog.type, | ||
}).orFail(); | ||
|
||
const _id = eventLog._id.toString(); | ||
const createdAt = eventLog.createdAt.toISOString(); | ||
const updatedAt = eventLog.updatedAt.toISOString(); | ||
const eventLogTypeDto = eventLogTypeTransformer(eventLogType); | ||
const user = eventLog.user ? eventLog.user : undefined; | ||
|
||
let useraccount = undefined; | ||
if (eventLog.useraccount) { | ||
const userAccountDocument = await UserAccountModel.findOne({ | ||
_id: eventLog.useraccount, | ||
}).orFail(); | ||
useraccount = userAccountTransformer(userAccountDocument); | ||
} | ||
|
||
// Hide eventLog contents if related to a period | ||
// and period has status QUANTIFY | ||
// and user is not ADMIN | ||
const period = eventLog.period | ||
? await PeriodModel.findOne({ _id: eventLog.period }).orFail() | ||
: undefined; | ||
|
||
let hidden = false; | ||
let description = eventLog.description; | ||
if ( | ||
period?.status === PeriodStatusType.QUANTIFY && | ||
!currentUserRoles.includes(UserRole.ADMIN) | ||
) { | ||
description = ''; | ||
hidden = true; | ||
} | ||
|
||
return { | ||
_id, | ||
user, | ||
useraccount, | ||
type: eventLogTypeDto, | ||
description, | ||
hidden, | ||
createdAt, | ||
updatedAt, | ||
} as EventLogDto; | ||
}; | ||
|
||
export const eventLogListTransformer = async ( | ||
eventLogs: EventLogDocument[], | ||
currentUserRoles: UserRole[] = [UserRole.USER] | ||
): Promise<EventLogDto[]> => { | ||
const eventLogDtos = await Promise.all( | ||
eventLogs.map((eventLog) => eventLogTransformer(eventLog, currentUserRoles)) | ||
); | ||
|
||
return eventLogDtos; | ||
}; |
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 { UserAccountDto } from '../useraccount/types'; | ||
import { Document, Types } from 'mongoose'; | ||
|
||
export enum EventLogTypeKey { | ||
PERMISSION = 'PERMISSION', | ||
AUTHENTICATION = 'AUTHENTICATION', | ||
PERIOD = 'PERIOD', | ||
PRAISE = 'PRAISE', | ||
QUANTIFICATION = 'QUANTIFICATION', | ||
SETTING = 'SETTING', | ||
} | ||
|
||
export interface EventLog { | ||
user?: Types.ObjectId; | ||
useraccount?: Types.ObjectId; | ||
period?: Types.ObjectId; | ||
type: Types.ObjectId; | ||
description: string; | ||
createdAt: Date; | ||
updatedAt: Date; | ||
} | ||
|
||
export interface EventLogDocument extends EventLog, Document {} | ||
|
||
export interface EventLogDto { | ||
user?: string; | ||
useraccount?: UserAccountDto; | ||
type: EventLogTypeDto; | ||
description: string; | ||
hidden: boolean; | ||
createdAt: string; | ||
updatedAt: string; | ||
} | ||
|
||
export interface EventLogType { | ||
key: string; | ||
label: string; | ||
description: string; | ||
createdAt: Date; | ||
updatedAt: Date; | ||
} | ||
|
||
export interface EventLogTypeDocument extends EventLogType, Document {} | ||
|
||
export interface EventLogTypeDto { | ||
key: string; | ||
label: string; | ||
description: 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,29 @@ | ||
import { Types } from 'mongoose'; | ||
import { EventLogModel, EventLogTypeModel } from './entities'; | ||
import { EventLogTypeKey } from './types'; | ||
|
||
interface UserInfo { | ||
userId?: Types.ObjectId; | ||
userAccountId?: Types.ObjectId; | ||
} | ||
|
||
export const logEvent = async ( | ||
typeKey: EventLogTypeKey, | ||
description: string, | ||
userInfo: UserInfo = {}, | ||
periodId: Types.ObjectId | undefined = undefined | ||
): Promise<void> => { | ||
const type = await EventLogTypeModel.findOne({ | ||
key: typeKey.toString(), | ||
}).orFail(); | ||
|
||
const data = { | ||
type: type._id, | ||
description, | ||
user: userInfo.userId ? userInfo.userId : undefined, | ||
useraccount: userInfo.userAccountId ? userInfo.userAccountId : undefined, | ||
period: periodId, | ||
}; | ||
|
||
await EventLogModel.create(data); | ||
}; |
Oops, something went wrong.