mirror of
https://github.com/OneUptime/oneuptime
synced 2024-11-21 14:49:07 +00:00
fix lint.
This commit is contained in:
parent
6ecc400c3e
commit
712ccc8e88
@ -1,7 +1,7 @@
|
||||
import ApiBase from 'CommonUI/src/Reducers/ApiBase';
|
||||
|
||||
export default class ChangePassword extends ApiBase{
|
||||
export default class ChangePassword extends ApiBase {
|
||||
constructor() {
|
||||
super("ChangePassword");
|
||||
super('ChangePassword');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import ApiBase from 'CommonUI/src/Reducers/ApiBase';
|
||||
|
||||
export default class ChangePassword extends ApiBase{
|
||||
export default class ChangePassword extends ApiBase {
|
||||
constructor() {
|
||||
super("Login");
|
||||
super('Login');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import ApiBase from 'CommonUI/src/Reducers/ApiBase';
|
||||
|
||||
export default class ChangePassword extends ApiBase{
|
||||
export default class ChangePassword extends ApiBase {
|
||||
constructor() {
|
||||
super("Register");
|
||||
super('Register');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import ApiBase from 'CommonUI/src/Reducers/ApiBase';
|
||||
|
||||
export default class ResendVerifyEmail extends ApiBase{
|
||||
export default class ResendVerifyEmail extends ApiBase {
|
||||
constructor() {
|
||||
super("ResendVerifyEmail");
|
||||
super('ResendVerifyEmail');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,6 @@ import ApiBase from 'CommonUI/src/Reducers/ApiBase';
|
||||
|
||||
export default class ResetPassword extends ApiBase {
|
||||
constructor() {
|
||||
super("ResetPassword");
|
||||
super('ResetPassword');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -59,7 +59,6 @@ const store: $TSFixMe = createStore(
|
||||
composedEnhancers
|
||||
);
|
||||
|
||||
|
||||
export type RootState = ReturnType<typeof store.getState>;
|
||||
|
||||
export default store;
|
||||
|
@ -12,8 +12,6 @@ import { JSONObject } from '../Types/JSON';
|
||||
import ObjectID from '../Types/ObjectID';
|
||||
|
||||
export default class BaseModel extends BaseEntity {
|
||||
|
||||
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
public _id!: string;
|
||||
|
||||
@ -29,7 +27,6 @@ export default class BaseModel extends BaseEntity {
|
||||
@VersionColumn()
|
||||
public version!: number;
|
||||
|
||||
|
||||
private encryptedColumns: Columns = new Columns([]);
|
||||
private uniqueColumns: Columns = new Columns([]);
|
||||
private requiredColumns: Columns = new Columns([]);
|
||||
@ -59,31 +56,30 @@ export default class BaseModel extends BaseEntity {
|
||||
private viewerDeleteableColumns: Columns = new Columns([]);
|
||||
private publicDeleteableColumns: Columns = new Columns([]);
|
||||
|
||||
private canAdminCreateRecord = false;
|
||||
private canAdminDeleteRecord = false;
|
||||
private canAdminUpdateRecord = false;
|
||||
private canAdminReadRecord = false;
|
||||
private canAdminCreateRecord = false;
|
||||
private canAdminDeleteRecord = false;
|
||||
private canAdminUpdateRecord = false;
|
||||
private canAdminReadRecord = false;
|
||||
|
||||
private canPublicCreateRecord = false;
|
||||
private canPublicDeleteRecord = false;
|
||||
private canPublicUpdateRecord = false;
|
||||
private canPublicReadRecord = false;
|
||||
private canPublicCreateRecord = false;
|
||||
private canPublicDeleteRecord = false;
|
||||
private canPublicUpdateRecord = false;
|
||||
private canPublicReadRecord = false;
|
||||
|
||||
private canOwnerCreateRecord = false;
|
||||
private canOwnerDeleteRecord = false;
|
||||
private canOwnerUpdateRecord = false;
|
||||
private canOwnerReadRecord = false;
|
||||
private canOwnerCreateRecord = false;
|
||||
private canOwnerDeleteRecord = false;
|
||||
private canOwnerUpdateRecord = false;
|
||||
private canOwnerReadRecord = false;
|
||||
|
||||
private canMemberCreateRecord = false;
|
||||
private canMemberDeleteRecord = false;
|
||||
private canMemberUpdateRecord = false;
|
||||
private canMemberReadRecord = false;
|
||||
|
||||
private canViewerCreateRecord = false;
|
||||
private canViewerDeleteRecord = false;
|
||||
private canViewerUpdateRecord = false;
|
||||
private canViewerReadRecord = false;
|
||||
private canMemberCreateRecord = false;
|
||||
private canMemberDeleteRecord = false;
|
||||
private canMemberUpdateRecord = false;
|
||||
private canMemberReadRecord = false;
|
||||
|
||||
private canViewerCreateRecord = false;
|
||||
private canViewerDeleteRecord = false;
|
||||
private canViewerUpdateRecord = false;
|
||||
private canViewerReadRecord = false;
|
||||
|
||||
private slugifyColumn!: string | null;
|
||||
private saveSlugToColumn!: string | null;
|
||||
@ -341,8 +337,6 @@ export default class BaseModel extends BaseEntity {
|
||||
return this.viewerDeleteableColumns;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public setSlugifyColumn(columnName: string): void {
|
||||
this.slugifyColumn = columnName;
|
||||
}
|
||||
@ -384,300 +378,335 @@ export default class BaseModel extends BaseEntity {
|
||||
private static _fromJSON<T extends BaseModel>(json: JSONObject): T {
|
||||
const baseModel = new BaseModel();
|
||||
|
||||
for (let key of Object.keys(json)) {
|
||||
|
||||
for (const key of Object.keys(json)) {
|
||||
(baseModel as any)[key] = json[key];
|
||||
|
||||
}
|
||||
|
||||
return baseModel as T;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static fromJSON<T extends BaseModel>(json: JSONObject): T {
|
||||
return this._fromJSON<T>(json);
|
||||
}
|
||||
|
||||
private static keepColumns<T extends BaseModel>(data: T, columnsToKeep: Columns): T {
|
||||
private static keepColumns<T extends BaseModel>(
|
||||
data: T,
|
||||
columnsToKeep: Columns
|
||||
): T {
|
||||
const baseModel = new BaseModel();
|
||||
|
||||
for (let key of Object.keys(data)) {
|
||||
|
||||
for (const key of Object.keys(data)) {
|
||||
if (!columnsToKeep) {
|
||||
(baseModel as any)[key] = (data as any)[key];
|
||||
}
|
||||
|
||||
if (columnsToKeep && columnsToKeep.columns.length > 0 && columnsToKeep.columns.includes(key)) {
|
||||
if (
|
||||
columnsToKeep &&
|
||||
columnsToKeep.columns.length > 0 &&
|
||||
columnsToKeep.columns.includes(key)
|
||||
) {
|
||||
(baseModel as any)[key] = (data as any)[key];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return baseModel as T;
|
||||
}
|
||||
|
||||
public static asPublicCreateable<T extends BaseModel>(data: JSONObject | T) {
|
||||
|
||||
public static asPublicCreateable<T extends BaseModel>(
|
||||
data: JSONObject | T
|
||||
): T {
|
||||
if (!(data instanceof BaseModel)) {
|
||||
data = this._fromJSON<T>(data);
|
||||
}
|
||||
|
||||
if (!data.canPublicCreateRecord) {
|
||||
throw new BadRequestException("A user of role public cannot create this record.")
|
||||
throw new BadRequestException(
|
||||
'A user of role public cannot create this record.'
|
||||
);
|
||||
}
|
||||
|
||||
return this.keepColumns(data, data.getPublicCreateableColumns());
|
||||
return this.keepColumns(data, data.getPublicCreateableColumns());
|
||||
}
|
||||
|
||||
public static asPublicUpdateable<T extends BaseModel>(data: JSONObject | T) {
|
||||
|
||||
public static asPublicUpdateable<T extends BaseModel>(
|
||||
data: JSONObject | T
|
||||
): T {
|
||||
if (!(data instanceof BaseModel)) {
|
||||
data = this._fromJSON<T>(data);
|
||||
}
|
||||
|
||||
if (!data.canPublicUpdateRecord) {
|
||||
throw new BadRequestException("A user of role public cannot update this record.")
|
||||
throw new BadRequestException(
|
||||
'A user of role public cannot update this record.'
|
||||
);
|
||||
}
|
||||
|
||||
return this.keepColumns(data, data.getPublicUpdateableColumns());
|
||||
return this.keepColumns(data, data.getPublicUpdateableColumns());
|
||||
}
|
||||
|
||||
public static asPublicReadable<T extends BaseModel>(data: JSONObject | T) {
|
||||
|
||||
public static asPublicReadable<T extends BaseModel>(data: JSONObject | T): T {
|
||||
if (!(data instanceof BaseModel)) {
|
||||
data = this._fromJSON<T>(data);
|
||||
}
|
||||
|
||||
if (!data.canPublicReadRecord) {
|
||||
throw new BadRequestException("A user of role public cannot read this record.")
|
||||
throw new BadRequestException(
|
||||
'A user of role public cannot read this record.'
|
||||
);
|
||||
}
|
||||
|
||||
return this.keepColumns(data, data.getPublicReadableColumns());
|
||||
return this.keepColumns(data, data.getPublicReadableColumns());
|
||||
}
|
||||
|
||||
public static asPublicDeleteable<T extends BaseModel>(data: JSONObject | T) {
|
||||
|
||||
public static asPublicDeleteable<T extends BaseModel>(
|
||||
data: JSONObject | T
|
||||
): T {
|
||||
if (!(data instanceof BaseModel)) {
|
||||
data = this._fromJSON<T>(data);
|
||||
}
|
||||
|
||||
if (!data.canPublicDeleteRecord) {
|
||||
throw new BadRequestException("A user of role public cannot delete this record.")
|
||||
throw new BadRequestException(
|
||||
'A user of role public cannot delete this record.'
|
||||
);
|
||||
}
|
||||
|
||||
return this.keepColumns(data, data.getPublicDeleteableColumns());
|
||||
return this.keepColumns(data, data.getPublicDeleteableColumns());
|
||||
}
|
||||
|
||||
public static asOwnerCreateable<T extends BaseModel>(data: JSONObject | T) {
|
||||
|
||||
public static asOwnerCreateable<T extends BaseModel>(data: JSONObject | T): T {
|
||||
if (!(data instanceof BaseModel)) {
|
||||
data = this._fromJSON<T>(data);
|
||||
}
|
||||
|
||||
if (!data.canOwnerCreateRecord) {
|
||||
throw new BadRequestException("A user of role owner cannot create this record.")
|
||||
throw new BadRequestException(
|
||||
'A user of role owner cannot create this record.'
|
||||
);
|
||||
}
|
||||
|
||||
return this.keepColumns(data, data.getOwnerCreateableColumns());
|
||||
return this.keepColumns(data, data.getOwnerCreateableColumns());
|
||||
}
|
||||
|
||||
public static asOwnerUpdateable<T extends BaseModel>(data: JSONObject | T) {
|
||||
|
||||
public static asOwnerUpdateable<T extends BaseModel>(data: JSONObject | T): T {
|
||||
if (!(data instanceof BaseModel)) {
|
||||
data = this._fromJSON<T>(data);
|
||||
}
|
||||
|
||||
if (!data.canOwnerUpdateRecord) {
|
||||
throw new BadRequestException("A user of role owner cannot update this record.")
|
||||
throw new BadRequestException(
|
||||
'A user of role owner cannot update this record.'
|
||||
);
|
||||
}
|
||||
|
||||
return this.keepColumns(data, data.getOwnerUpdateableColumns());
|
||||
return this.keepColumns(data, data.getOwnerUpdateableColumns());
|
||||
}
|
||||
|
||||
public static asOwnerReadable<T extends BaseModel>(data: JSONObject | T) {
|
||||
|
||||
public static asOwnerReadable<T extends BaseModel>(data: JSONObject | T): T {
|
||||
if (!(data instanceof BaseModel)) {
|
||||
data = this._fromJSON<T>(data);
|
||||
}
|
||||
|
||||
if (!data.canOwnerReadRecord) {
|
||||
throw new BadRequestException("A user of role owner cannot delete this record.")
|
||||
throw new BadRequestException(
|
||||
'A user of role owner cannot delete this record.'
|
||||
);
|
||||
}
|
||||
|
||||
return this.keepColumns(data, data.getOwnerReadableColumns());
|
||||
return this.keepColumns(data, data.getOwnerReadableColumns());
|
||||
}
|
||||
|
||||
public static asOwnerDeleteable<T extends BaseModel>(data: JSONObject | T) {
|
||||
|
||||
public static asOwnerDeleteable<T extends BaseModel>(data: JSONObject | T): T {
|
||||
if (!(data instanceof BaseModel)) {
|
||||
data = this._fromJSON<T>(data);
|
||||
}
|
||||
|
||||
if (!data.canOwnerDeleteRecord) {
|
||||
throw new BadRequestException("A user of role owner cannot delete this record.")
|
||||
throw new BadRequestException(
|
||||
'A user of role owner cannot delete this record.'
|
||||
);
|
||||
}
|
||||
|
||||
return this.keepColumns(data, data.getOwnerDeleteableColumns());
|
||||
return this.keepColumns(data, data.getOwnerDeleteableColumns());
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static asViewerCreateable<T extends BaseModel>(data: JSONObject | T) {
|
||||
|
||||
public static asViewerCreateable<T extends BaseModel>(
|
||||
data: JSONObject | T
|
||||
): T {
|
||||
if (!(data instanceof BaseModel)) {
|
||||
data = this._fromJSON<T>(data);
|
||||
}
|
||||
|
||||
if (!data.canViewerCreateRecord) {
|
||||
throw new BadRequestException("A user of role viewer cannot create this record.")
|
||||
throw new BadRequestException(
|
||||
'A user of role viewer cannot create this record.'
|
||||
);
|
||||
}
|
||||
|
||||
return this.keepColumns(data, data.getViewerCreateableColumns());
|
||||
return this.keepColumns(data, data.getViewerCreateableColumns());
|
||||
}
|
||||
|
||||
public static asViewerUpdateable<T extends BaseModel>(data: JSONObject | T) {
|
||||
|
||||
public static asViewerUpdateable<T extends BaseModel>(
|
||||
data: JSONObject | T
|
||||
): T {
|
||||
if (!(data instanceof BaseModel)) {
|
||||
data = this._fromJSON<T>(data);
|
||||
}
|
||||
|
||||
if (!data.canViewerUpdateRecord) {
|
||||
throw new BadRequestException("A user of role viewer cannot update this record.")
|
||||
throw new BadRequestException(
|
||||
'A user of role viewer cannot update this record.'
|
||||
);
|
||||
}
|
||||
|
||||
return this.keepColumns(data, data.getViewerUpdateableColumns());
|
||||
return this.keepColumns(data, data.getViewerUpdateableColumns());
|
||||
}
|
||||
|
||||
public static asViewerReadable<T extends BaseModel>(data: JSONObject | T) {
|
||||
|
||||
public static asViewerReadable<T extends BaseModel>(data: JSONObject | T): T {
|
||||
if (!(data instanceof BaseModel)) {
|
||||
data = this._fromJSON<T>(data);
|
||||
}
|
||||
|
||||
if (!data.canViewerReadRecord) {
|
||||
throw new BadRequestException("A user of role viewer cannot read this record.")
|
||||
throw new BadRequestException(
|
||||
'A user of role viewer cannot read this record.'
|
||||
);
|
||||
}
|
||||
|
||||
return this.keepColumns(data, data.getViewerReadableColumns());
|
||||
return this.keepColumns(data, data.getViewerReadableColumns());
|
||||
}
|
||||
|
||||
public static asViewerDeleteable<T extends BaseModel>(data: JSONObject | T) {
|
||||
|
||||
public static asViewerDeleteable<T extends BaseModel>(
|
||||
data: JSONObject | T
|
||||
): T {
|
||||
if (!(data instanceof BaseModel)) {
|
||||
data = this._fromJSON<T>(data);
|
||||
}
|
||||
|
||||
if (!data.canViewerDeleteRecord) {
|
||||
throw new BadRequestException("A user of role viewer cannot delete this record.")
|
||||
throw new BadRequestException(
|
||||
'A user of role viewer cannot delete this record.'
|
||||
);
|
||||
}
|
||||
|
||||
return this.keepColumns(data, data.getViewerDeleteableColumns());
|
||||
return this.keepColumns(data, data.getViewerDeleteableColumns());
|
||||
}
|
||||
|
||||
|
||||
public static asMemberCreateable<T extends BaseModel>(data: JSONObject | T) {
|
||||
|
||||
public static asMemberCreateable<T extends BaseModel>(
|
||||
data: JSONObject | T
|
||||
): T {
|
||||
if (!(data instanceof BaseModel)) {
|
||||
data = this._fromJSON<T>(data);
|
||||
}
|
||||
|
||||
if (!data.canMemberCreateRecord) {
|
||||
throw new BadRequestException("A user of role member cannot create this record.")
|
||||
throw new BadRequestException(
|
||||
'A user of role member cannot create this record.'
|
||||
);
|
||||
}
|
||||
|
||||
return this.keepColumns(data, data.getMemberCreateableColumns());
|
||||
return this.keepColumns(data, data.getMemberCreateableColumns());
|
||||
}
|
||||
|
||||
public static asMemberUpdateable<T extends BaseModel>(data: JSONObject | T) {
|
||||
|
||||
public static asMemberUpdateable<T extends BaseModel>(
|
||||
data: JSONObject | T
|
||||
): T {
|
||||
if (!(data instanceof BaseModel)) {
|
||||
data = this._fromJSON<T>(data);
|
||||
}
|
||||
|
||||
if (!data.canMemberUpdateRecord) {
|
||||
throw new BadRequestException("A user of role member cannot update this record.")
|
||||
throw new BadRequestException(
|
||||
'A user of role member cannot update this record.'
|
||||
);
|
||||
}
|
||||
|
||||
return this.keepColumns(data, data.getMemberUpdateableColumns());
|
||||
return this.keepColumns(data, data.getMemberUpdateableColumns());
|
||||
}
|
||||
|
||||
public static asMemberReadable<T extends BaseModel>(data: JSONObject | T) {
|
||||
|
||||
public static asMemberReadable<T extends BaseModel>(data: JSONObject | T): T {
|
||||
if (!(data instanceof BaseModel)) {
|
||||
data = this._fromJSON<T>(data);
|
||||
}
|
||||
|
||||
if (!data.canMemberReadRecord) {
|
||||
throw new BadRequestException("A user of role member cannot read this record.")
|
||||
throw new BadRequestException(
|
||||
'A user of role member cannot read this record.'
|
||||
);
|
||||
}
|
||||
|
||||
return this.keepColumns(data, data.getMemberReadableColumns());
|
||||
return this.keepColumns(data, data.getMemberReadableColumns());
|
||||
}
|
||||
|
||||
public static asMemberDeleteable<T extends BaseModel>(data: JSONObject | T) {
|
||||
|
||||
public static asMemberDeleteable<T extends BaseModel>(
|
||||
data: JSONObject | T
|
||||
): T {
|
||||
if (!(data instanceof BaseModel)) {
|
||||
data = this._fromJSON<T>(data);
|
||||
}
|
||||
|
||||
if (!data.canMemberDeleteRecord) {
|
||||
throw new BadRequestException("A user of role member cannot delete this record.")
|
||||
throw new BadRequestException(
|
||||
'A user of role member cannot delete this record.'
|
||||
);
|
||||
}
|
||||
|
||||
return this.keepColumns(data, data.getMemberDeleteableColumns());
|
||||
return this.keepColumns(data, data.getMemberDeleteableColumns());
|
||||
}
|
||||
|
||||
|
||||
public static asAdminCreateable<T extends BaseModel>(data: JSONObject | T) {
|
||||
|
||||
public static asAdminCreateable<T extends BaseModel>(data: JSONObject | T): T {
|
||||
if (!(data instanceof BaseModel)) {
|
||||
data = this._fromJSON<T>(data);
|
||||
}
|
||||
|
||||
if (!data.canAdminCreateRecord) {
|
||||
throw new BadRequestException("A user of role admin cannot create this record.")
|
||||
throw new BadRequestException(
|
||||
'A user of role admin cannot create this record.'
|
||||
);
|
||||
}
|
||||
|
||||
return this.keepColumns(data, data.getAdminCreateableColumns());
|
||||
return this.keepColumns(data, data.getAdminCreateableColumns());
|
||||
}
|
||||
|
||||
public static asAdminUpdateable<T extends BaseModel>(data: JSONObject | T) {
|
||||
|
||||
public static asAdminUpdateable<T extends BaseModel>(data: JSONObject | T): T {
|
||||
if (!(data instanceof BaseModel)) {
|
||||
data = this._fromJSON<T>(data);
|
||||
}
|
||||
|
||||
if (!data.canAdminUpdateRecord) {
|
||||
throw new BadRequestException("A user of role admin cannot update this record.")
|
||||
throw new BadRequestException(
|
||||
'A user of role admin cannot update this record.'
|
||||
);
|
||||
}
|
||||
|
||||
return this.keepColumns(data, data.getAdminUpdateableColumns());
|
||||
return this.keepColumns(data, data.getAdminUpdateableColumns());
|
||||
}
|
||||
|
||||
public static asAdminReadable<T extends BaseModel>(data: JSONObject | T) {
|
||||
|
||||
public static asAdminReadable<T extends BaseModel>(data: JSONObject | T): T {
|
||||
if (!(data instanceof BaseModel)) {
|
||||
data = this._fromJSON<T>(data);
|
||||
}
|
||||
|
||||
if (!data.canAdminReadRecord) {
|
||||
throw new BadRequestException("A user of role admin cannot read this record.")
|
||||
throw new BadRequestException(
|
||||
'A user of role admin cannot read this record.'
|
||||
);
|
||||
}
|
||||
|
||||
return this.keepColumns(data, data.getAdminReadableColumns());
|
||||
return this.keepColumns(data, data.getAdminReadableColumns());
|
||||
}
|
||||
|
||||
public static asAdminDeleteable<T extends BaseModel>(data: JSONObject | T) {
|
||||
|
||||
public static asAdminDeleteable<T extends BaseModel>(data: JSONObject | T): T {
|
||||
if (!(data instanceof BaseModel)) {
|
||||
data = this._fromJSON<T>(data);
|
||||
}
|
||||
|
||||
if (!data.canAdminDeleteRecord) {
|
||||
throw new BadRequestException("A user of role admin cannot delete this record.")
|
||||
throw new BadRequestException(
|
||||
'A user of role admin cannot delete this record.'
|
||||
);
|
||||
}
|
||||
|
||||
return this.keepColumns(data, data.getAdminDeleteableColumns());
|
||||
return this.keepColumns(data, data.getAdminDeleteableColumns());
|
||||
}
|
||||
}
|
||||
|
@ -42,7 +42,6 @@ export default class OneUptimeDate {
|
||||
.toDate();
|
||||
}
|
||||
|
||||
|
||||
public static getOneMinAfter(): Date {
|
||||
return this.getSomeMinutesAfter(new PositiveNumber(1));
|
||||
}
|
||||
@ -53,29 +52,28 @@ export default class OneUptimeDate {
|
||||
|
||||
public static getSomeMinutesAfter(minutes: PositiveNumber): Date {
|
||||
return this.getCurrentMomentDate()
|
||||
.add( minutes.toNumber(), 'minutes')
|
||||
.add(minutes.toNumber(), 'minutes')
|
||||
.toDate();
|
||||
}
|
||||
|
||||
public static getSomeHoursAfter(hours: PositiveNumber): Date {
|
||||
return this.getCurrentMomentDate()
|
||||
.add( hours.toNumber(), 'hours')
|
||||
.add(hours.toNumber(), 'hours')
|
||||
.toDate();
|
||||
}
|
||||
|
||||
public static getSomeDaysAfter(days: PositiveNumber): Date {
|
||||
return this.getCurrentMomentDate()
|
||||
.add( days.toNumber(), 'days')
|
||||
.add(days.toNumber(), 'days')
|
||||
.toDate();
|
||||
}
|
||||
|
||||
public static getSomeSecondsAfter(seconds: PositiveNumber): Date {
|
||||
return this.getCurrentMomentDate()
|
||||
.add( seconds.toNumber(), 'days')
|
||||
.add(seconds.toNumber(), 'days')
|
||||
.toDate();
|
||||
}
|
||||
|
||||
|
||||
public static momentToDate(moment: moment.Moment): Date {
|
||||
return moment.toDate();
|
||||
}
|
||||
|
@ -1,23 +1,26 @@
|
||||
import Dictionary from "../Dictionary";
|
||||
import BadOperationException from "../Exception/BadOperationException";
|
||||
import EmailTemplateType from "./EmailTemplateType";
|
||||
|
||||
class EmailSubjects {
|
||||
import Dictionary from '../Dictionary';
|
||||
import BadOperationException from '../Exception/BadOperationException';
|
||||
import EmailTemplateType from './EmailTemplateType';
|
||||
|
||||
class EmailSubjects {
|
||||
private subjectMap: Dictionary<string> = {};
|
||||
|
||||
constructor() {
|
||||
this.subjectMap[EmailTemplateType.SIGNUP_WELCOME_EMAIL] = "Welcome to OneUptime.";
|
||||
this.subjectMap[EmailTemplateType.SIGNUP_VERIFICATION_EMAIL] = "Welcome to OneUptime. Please verify your email.";
|
||||
public constructor() {
|
||||
this.subjectMap[EmailTemplateType.SIGNUP_WELCOME_EMAIL] =
|
||||
'Welcome to OneUptime.';
|
||||
this.subjectMap[EmailTemplateType.SIGNUP_VERIFICATION_EMAIL] =
|
||||
'Welcome to OneUptime. Please verify your email.';
|
||||
}
|
||||
|
||||
getSubjectByType(emailTemplateType: EmailTemplateType): string {
|
||||
public getSubjectByType(emailTemplateType: EmailTemplateType): string {
|
||||
if (this.subjectMap[emailTemplateType]) {
|
||||
return this.subjectMap[emailTemplateType] as string;
|
||||
}
|
||||
|
||||
throw new BadOperationException(`Subject for ${emailTemplateType} not found.`);
|
||||
throw new BadOperationException(
|
||||
`Subject for ${emailTemplateType} not found.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default new EmailSubjects();
|
||||
export default new EmailSubjects();
|
||||
|
@ -1,6 +1,5 @@
|
||||
enum EncryptionAlgorithm{
|
||||
SHA256 = "SHA-256"
|
||||
enum EncryptionAlgorithm {
|
||||
SHA256 = 'SHA-256',
|
||||
}
|
||||
|
||||
|
||||
export default EncryptionAlgorithm;
|
||||
export default EncryptionAlgorithm;
|
||||
|
@ -6,7 +6,6 @@ enum ExceptionCode {
|
||||
BadOperationException = 5,
|
||||
BadDataException = 400,
|
||||
BadRequestException = 400,
|
||||
|
||||
}
|
||||
|
||||
export default ExceptionCode;
|
||||
|
@ -4,9 +4,9 @@ import DatabaseProperty from './Database/DatabaseProperty';
|
||||
import BadOperationException from './Exception/BadOperationException';
|
||||
import EncryptionAlgorithm from './EncryptionAlgorithm';
|
||||
import ObjectID from './ObjectID';
|
||||
import { arrayBuffer } from 'stream/consumers';
|
||||
|
||||
export default class HashedString extends DatabaseProperty {
|
||||
|
||||
private isHashed: boolean = false;
|
||||
|
||||
private _value: string = '';
|
||||
@ -41,45 +41,55 @@ export default class HashedString extends DatabaseProperty {
|
||||
return null;
|
||||
}
|
||||
|
||||
public isValueHashed(): boolean {
|
||||
public isValueHashed(): boolean {
|
||||
return this.isHashed;
|
||||
}
|
||||
|
||||
public async hashValue(encryptionSecret: ObjectID | null): Promise<string> {
|
||||
|
||||
if (!this.value) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (this.isHashed) {
|
||||
throw new BadOperationException("Value is alredy hashed");
|
||||
throw new BadOperationException('Value is alredy hashed');
|
||||
}
|
||||
|
||||
const valueToHash: string = (encryptionSecret || '') + this.value;
|
||||
this.isHashed = true;
|
||||
|
||||
// encode as UTF-8
|
||||
const msgBuffer = new TextEncoder().encode(valueToHash);
|
||||
const msgBuffer: Uint8Array = new TextEncoder().encode(valueToHash);
|
||||
|
||||
// hash the message
|
||||
const hashBuffer = await crypto.subtle.digest(EncryptionAlgorithm.SHA256, msgBuffer);
|
||||
|
||||
const hashBuffer: ArrayBuffer = await crypto.subtle.digest(
|
||||
EncryptionAlgorithm.SHA256,
|
||||
msgBuffer
|
||||
);
|
||||
|
||||
// convert ArrayBuffer to Array
|
||||
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
||||
const hashArray: Uint8Array = Array.from(new Uint8Array(hashBuffer));
|
||||
|
||||
// convert bytes to hex string
|
||||
const hashHex: string = hashArray.map((b) => b.toString(16).padStart(2, '0')).join('');
|
||||
// convert bytes to hex string
|
||||
const hashHex: string = hashArray
|
||||
.map((b) => {
|
||||
return b.toString(16).padStart(2, '0');
|
||||
})
|
||||
.join('');
|
||||
this.value = hashHex;
|
||||
return hashHex;
|
||||
|
||||
}
|
||||
|
||||
public static async hashValue(value: string, encryptionSecret: ObjectID | null): Promise<string> {
|
||||
public static async hashValue(
|
||||
value: string,
|
||||
encryptionSecret: ObjectID | null
|
||||
): Promise<string> {
|
||||
const hashstring: HashedString = new HashedString(value, false);
|
||||
return await hashstring.hashValue(encryptionSecret);
|
||||
}
|
||||
|
||||
protected static override fromDatabase(_value: string): HashedString | null {
|
||||
protected static override fromDatabase(
|
||||
_value: string
|
||||
): HashedString | null {
|
||||
if (_value) {
|
||||
return new HashedString(_value, true);
|
||||
}
|
||||
|
@ -1,9 +1,8 @@
|
||||
import { JSONObject } from "./JSON";
|
||||
import ObjectID from "./ObjectID";
|
||||
import Role from "./Role";
|
||||
import { JSONObject } from './JSON';
|
||||
import ObjectID from './ObjectID';
|
||||
import Role from './Role';
|
||||
|
||||
export default class UserRole {
|
||||
|
||||
public projectId!: ObjectID;
|
||||
public userId!: ObjectID;
|
||||
public role!: Role;
|
||||
@ -16,17 +15,17 @@ export default class UserRole {
|
||||
|
||||
public toJSON(): JSONObject {
|
||||
return {
|
||||
"userId": this.userId.toString(),
|
||||
"projectId": this.projectId.toString(),
|
||||
"role": this.role
|
||||
}
|
||||
userId: this.userId.toString(),
|
||||
projectId: this.projectId.toString(),
|
||||
role: this.role,
|
||||
};
|
||||
}
|
||||
|
||||
public static fromJSON(data: JSONObject): UserRole{
|
||||
public static fromJSON(data: JSONObject): UserRole {
|
||||
return new UserRole(
|
||||
new ObjectID(data["projectId"] as string),
|
||||
new ObjectID(data["userId"] as string),
|
||||
data["role"] as Role
|
||||
)
|
||||
new ObjectID(data['projectId'] as string),
|
||||
new ObjectID(data['userId'] as string),
|
||||
data['role'] as Role
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,9 +3,9 @@ import ObjectID from 'Common/Types/ObjectID';
|
||||
import Port from 'Common/Types/Port';
|
||||
import Hostname from 'Common/Types/API/Hostname';
|
||||
|
||||
export const DisableSignup: boolean = !!process.env['DISABLE_SIGNUP'];
|
||||
export const DisableSignup: boolean = Boolean(process.env['DISABLE_SIGNUP']);
|
||||
|
||||
export const IsSaaSService: boolean = !!process.env['IS_SAAS_SERVICE'];
|
||||
export const IsSaaSService: boolean = Boolean(process.env['IS_SAAS_SERVICE']);
|
||||
|
||||
export const DatabaseHost: Hostname = new Hostname(
|
||||
process.env['DATABASE_HOST'] || ''
|
||||
@ -21,7 +21,9 @@ export const DatabasePassword: string = process.env['DATABASE_PASSWORD'] || '';
|
||||
export const DatabaseName: string =
|
||||
process.env['DATABASE_NAME'] || 'oneuptimedb';
|
||||
|
||||
export const EncryptionSecret: ObjectID = new ObjectID(process.env['ENCRYPTIOJN_SECRET'] || '');
|
||||
export const EncryptionSecret: ObjectID = new ObjectID(
|
||||
process.env['ENCRYPTIOJN_SECRET'] || ''
|
||||
);
|
||||
|
||||
export const AirtableApiKey: string = process.env['AIRTABLE_API_KEY'] || '';
|
||||
|
||||
|
@ -14,18 +14,13 @@ import PositiveNumber from 'Common/Types/PositiveNumber';
|
||||
const ProjectService: ProjectServiceType = Services.ProjectService;
|
||||
|
||||
export default class ProjectMiddleware {
|
||||
|
||||
public static getProjectId(req: ExpressRequest): ObjectID | null {
|
||||
|
||||
let projectId: ObjectID | null = null;
|
||||
if (req.params && req.params['projectId']) {
|
||||
projectId = new ObjectID(req.params['projectId']);
|
||||
} else if (req.query && req.query['projectId']) {
|
||||
projectId = new ObjectID(req.query['projectId'] as string);
|
||||
} else if (
|
||||
req.headers &&
|
||||
req.headers['projectid']
|
||||
) {
|
||||
} else if (req.headers && req.headers['projectid']) {
|
||||
// Header keys are automatically transformed to lowercase
|
||||
projectId = new ObjectID(req.headers['projectid'] as string);
|
||||
} else if (req.body && req.body.projectId) {
|
||||
@ -40,11 +35,8 @@ export default class ProjectMiddleware {
|
||||
|
||||
if (req.query && req.query['apiKey']) {
|
||||
apiKey = new ObjectID(req.query['apiKey'] as string);
|
||||
} else if (
|
||||
req.headers &&
|
||||
req.headers['apikey']
|
||||
) {
|
||||
apiKey = new ObjectID(req.headers['apikey'] as string)
|
||||
} else if (req.headers && req.headers['apikey']) {
|
||||
apiKey = new ObjectID(req.headers['apikey'] as string);
|
||||
} else if (req.body && req.body.apiKey) {
|
||||
apiKey = req.body.apiKey;
|
||||
}
|
||||
@ -53,35 +45,34 @@ export default class ProjectMiddleware {
|
||||
}
|
||||
|
||||
public static hasApiKey(req: ExpressRequest): boolean {
|
||||
return !!this.getApiKey(req);
|
||||
return Boolean(this.getApiKey(req));
|
||||
}
|
||||
|
||||
public static hasProjectID(req: ExpressRequest): boolean {
|
||||
return !!this.getProjectId(req);
|
||||
}
|
||||
return Boolean(this.getProjectId(req));
|
||||
}
|
||||
|
||||
public static async isValidProjectIdAndApiKeyMiddleware(
|
||||
req: ExpressRequest,
|
||||
_res: ExpressResponse,
|
||||
next: NextFunction
|
||||
): Promise<void> {
|
||||
|
||||
let projectId: ObjectID | null = this.getProjectId(req);
|
||||
let apiKey: ObjectID | null = this.getApiKey(req);
|
||||
const projectId: ObjectID | null = this.getProjectId(req);
|
||||
const apiKey: ObjectID | null = this.getApiKey(req);
|
||||
|
||||
if (!projectId) {
|
||||
throw new BadDataException("ProjectID not found in the request");
|
||||
throw new BadDataException('ProjectID not found in the request');
|
||||
}
|
||||
|
||||
if (!apiKey) {
|
||||
throw new BadDataException("ApiKey not found in the request");
|
||||
throw new BadDataException('ApiKey not found in the request');
|
||||
}
|
||||
|
||||
const projectCount: PositiveNumber = await ProjectService.countBy({
|
||||
query: {
|
||||
_id: projectId.toString(),
|
||||
apiKey: apiKey,
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
if (projectCount.toNumber() > 0) {
|
||||
@ -89,8 +80,6 @@ export default class ProjectMiddleware {
|
||||
return next();
|
||||
}
|
||||
|
||||
throw new BadDataException("Invalid Project ID or API Key");
|
||||
|
||||
throw new BadDataException('Invalid Project ID or API Key');
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,6 @@ import JSONWebToken from '../Utils/JsonWebToken';
|
||||
const UserService: UserServiceType = Service.UserService;
|
||||
|
||||
export default class UserMiddleware {
|
||||
|
||||
/*
|
||||
* Description: Checking if user is authorized to access the page and decode jwt to get user data.
|
||||
* Params:
|
||||
@ -24,7 +23,6 @@ export default class UserMiddleware {
|
||||
*/
|
||||
|
||||
public static getAccessToken(req: ExpressRequest): string | null {
|
||||
|
||||
let accessToken: string | null = null;
|
||||
|
||||
if (req.headers['authorization']) {
|
||||
@ -35,7 +33,7 @@ export default class UserMiddleware {
|
||||
accessToken = req.query['accessToken'] as string;
|
||||
}
|
||||
|
||||
if (accessToken?.includes(" ")) {
|
||||
if (accessToken?.includes(' ')) {
|
||||
accessToken = accessToken.split(' ')[1] || '';
|
||||
}
|
||||
|
||||
@ -51,20 +49,23 @@ export default class UserMiddleware {
|
||||
|
||||
if (projectId) {
|
||||
if (ProjectMiddleware.hasApiKey(req)) {
|
||||
return await ProjectMiddleware.isValidProjectIdAndApiKeyMiddleware(req, res, next);
|
||||
return await ProjectMiddleware.isValidProjectIdAndApiKeyMiddleware(
|
||||
req,
|
||||
res,
|
||||
next
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const accessToken: string | null = this.getAccessToken(req);
|
||||
|
||||
if (!accessToken) {
|
||||
throw new BadDataException("AccessToken not found in request");
|
||||
throw new BadDataException('AccessToken not found in request');
|
||||
}
|
||||
|
||||
const oneuptimeRequest = (req as OneUptimeRequest);
|
||||
const oneuptimeRequest: OneUptimeRequest = req as OneUptimeRequest;
|
||||
oneuptimeRequest.userAuthorization = JSONWebToken.decode(accessToken);
|
||||
|
||||
|
||||
if (oneuptimeRequest.userAuthorization.isMasterAdmin) {
|
||||
oneuptimeRequest.authorizationType = AuthorizationType.MasterAdmin;
|
||||
} else {
|
||||
@ -72,11 +73,12 @@ export default class UserMiddleware {
|
||||
}
|
||||
|
||||
UserService.updateOneBy({
|
||||
query: { _id: oneuptimeRequest.userAuthorization.userId.toString() },
|
||||
data: { lastActive: Date.now() }
|
||||
query: {
|
||||
_id: oneuptimeRequest.userAuthorization.userId.toString(),
|
||||
},
|
||||
data: { lastActive: Date.now() },
|
||||
});
|
||||
|
||||
return next();
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ class DatabaseService<TBaseModel extends BaseModel> {
|
||||
private database!: PostgresDatabase;
|
||||
|
||||
public constructor(
|
||||
type: { new(): TBaseModel },
|
||||
type: { new (): TBaseModel },
|
||||
database: PostgresDatabase
|
||||
) {
|
||||
this.entityName = type.name;
|
||||
@ -101,13 +101,12 @@ class DatabaseService<TBaseModel extends BaseModel> {
|
||||
}
|
||||
|
||||
protected async hash(data: TBaseModel): Promise<TBaseModel> {
|
||||
|
||||
for (const key of data.getHashedColumns().columns) {
|
||||
|
||||
if (!((data as any)[key] as HashedString).isValueHashed) {
|
||||
await ((data as any)[key] as HashedString).hashValue(EncryptionSecret);
|
||||
await ((data as any)[key] as HashedString).hashValue(
|
||||
EncryptionSecret
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return data;
|
||||
@ -243,7 +242,7 @@ class DatabaseService<TBaseModel extends BaseModel> {
|
||||
(data as any)[data.getSaveSlugToColumn() as string] =
|
||||
Slug.getSlug(
|
||||
(data as any)[
|
||||
data.getSlugifyColumn() as string
|
||||
data.getSlugifyColumn() as string
|
||||
] as string
|
||||
);
|
||||
}
|
||||
@ -454,15 +453,12 @@ class DatabaseService<TBaseModel extends BaseModel> {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public async findOneById(
|
||||
id: ObjectID
|
||||
): Promise<TBaseModel | null> {
|
||||
public async findOneById(id: ObjectID): Promise<TBaseModel | null> {
|
||||
return await this.findOneBy({
|
||||
query: {
|
||||
_id: id.toString()
|
||||
}
|
||||
})
|
||||
_id: id.toString(),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private async _updateBy({
|
||||
@ -507,21 +503,24 @@ class DatabaseService<TBaseModel extends BaseModel> {
|
||||
return await this._updateBy({ query, data });
|
||||
}
|
||||
|
||||
public async updateOneById(updateById: UpdateByID<TBaseModel>): Promise<void> {
|
||||
public async updateOneById(
|
||||
updateById: UpdateByID<TBaseModel>
|
||||
): Promise<void> {
|
||||
await this.updateOneBy({
|
||||
query: {
|
||||
_id: updateById.id.toString()
|
||||
_id: updateById.id.toString(),
|
||||
},
|
||||
data: updateById.data
|
||||
data: updateById.data,
|
||||
});
|
||||
}
|
||||
|
||||
public async updateOneByIdAndFetch(updateById: UpdateByID<TBaseModel>): Promise<TBaseModel | null> {
|
||||
public async updateOneByIdAndFetch(
|
||||
updateById: UpdateByID<TBaseModel>
|
||||
): Promise<TBaseModel | null> {
|
||||
await this.updateOneById(updateById);
|
||||
return this.findOneById(updateById.id);
|
||||
}
|
||||
|
||||
|
||||
public async searchBy({
|
||||
skip,
|
||||
limit,
|
||||
|
@ -13,7 +13,7 @@ const postgresDatabase: PostgresDatabase = new PostgresDatabase();
|
||||
await postgresDatabase.connect(postgresDatabase.getDatasourceOptions());
|
||||
|
||||
export default {
|
||||
//Database Services
|
||||
//Database Services
|
||||
ProbeService: new ProbeService(postgresDatabase),
|
||||
UserService: new UserService(postgresDatabase),
|
||||
GlobalConfigService: new GlobalConfigService(postgresDatabase),
|
||||
@ -21,7 +21,9 @@ export default {
|
||||
EmailLogService: new EmailLogService(postgresDatabase),
|
||||
MonitorService: new MonitorService(postgresDatabase),
|
||||
ProjectService: new ProjectService(postgresDatabase),
|
||||
EmailVerificationTokenService: new EmailVerificationTokenService(postgresDatabase),
|
||||
EmailVerificationTokenService: new EmailVerificationTokenService(
|
||||
postgresDatabase
|
||||
),
|
||||
|
||||
// Other Services.
|
||||
MailService: new MailService(),
|
||||
|
@ -1,42 +1,50 @@
|
||||
import HTTPResponse from "Common/Types/API/Response";
|
||||
import Route from "Common/Types/API/Route";
|
||||
import URL from "Common/Types/API/URL";
|
||||
import Dictionary from "Common/Types/Dictionary";
|
||||
import Email from "Common/Types/Email";
|
||||
import EmailTemplateType from "Common/Types/Email/EmailTemplateType";
|
||||
import { JSONObject } from "Common/Types/JSON";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
import API from "Common/Utils/API";
|
||||
import { ClusterKey, HttpProtocol, MailHostname } from "../Config";
|
||||
import HTTPResponse from 'Common/Types/API/Response';
|
||||
import Route from 'Common/Types/API/Route';
|
||||
import URL from 'Common/Types/API/URL';
|
||||
import Dictionary from 'Common/Types/Dictionary';
|
||||
import Email from 'Common/Types/Email';
|
||||
import EmailTemplateType from 'Common/Types/Email/EmailTemplateType';
|
||||
import { JSONObject } from 'Common/Types/JSON';
|
||||
import ObjectID from 'Common/Types/ObjectID';
|
||||
import API from 'Common/Utils/API';
|
||||
import { ClusterKey, HttpProtocol, MailHostname } from '../Config';
|
||||
|
||||
export default class MailService {
|
||||
|
||||
public async sendMail(to: Email, subject: string, template: EmailTemplateType, vars: Dictionary<string>, options?: {
|
||||
projectId?: ObjectID
|
||||
forceSendFromGlobalMailServer?: boolean
|
||||
}): Promise<HTTPResponse> {
|
||||
|
||||
public async sendMail(
|
||||
to: Email,
|
||||
subject: string,
|
||||
template: EmailTemplateType,
|
||||
vars: Dictionary<string>,
|
||||
options?: {
|
||||
projectId?: ObjectID;
|
||||
forceSendFromGlobalMailServer?: boolean;
|
||||
}
|
||||
): Promise<HTTPResponse> {
|
||||
const body: JSONObject = {
|
||||
toEmail: to.toString(),
|
||||
subject,
|
||||
vars: vars,
|
||||
}
|
||||
};
|
||||
|
||||
if (options?.projectId) {
|
||||
body["projectId"] = options.projectId;
|
||||
body['projectId'] = options.projectId;
|
||||
}
|
||||
|
||||
if (options?.forceSendFromGlobalMailServer) {
|
||||
body["forceSendFromGlobalMailServer"] = options.forceSendFromGlobalMailServer;
|
||||
body['forceSendFromGlobalMailServer'] =
|
||||
options.forceSendFromGlobalMailServer;
|
||||
}
|
||||
|
||||
return await API.post(
|
||||
new URL(HttpProtocol, MailHostname, new Route('/email/' + template)),
|
||||
new URL(
|
||||
HttpProtocol,
|
||||
MailHostname,
|
||||
new Route('/email/' + template)
|
||||
),
|
||||
body,
|
||||
{
|
||||
"clusterkey": ClusterKey.toString()
|
||||
clusterkey: ClusterKey.toString(),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ import BaseModel from 'Common/Models/BaseModel';
|
||||
import ObjectID from 'Common/Types/ObjectID';
|
||||
import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity';
|
||||
|
||||
export default interface UpdateBy<TBaseModel extends BaseModel>{
|
||||
export default interface UpdateBy<TBaseModel extends BaseModel> {
|
||||
id: ObjectID;
|
||||
data: QueryDeepPartialEntity<TBaseModel>;
|
||||
}
|
||||
|
@ -19,10 +19,10 @@ export type ExpressResponse = express.Response;
|
||||
export type ExpressApplication = express.Application;
|
||||
export type ExpressRouter = express.Router;
|
||||
|
||||
export enum AuthorizationType {
|
||||
API = "API",
|
||||
User = "User",
|
||||
MasterAdmin = "MasterAdmin"
|
||||
export enum AuthorizationType {
|
||||
API = 'API',
|
||||
User = 'User',
|
||||
MasterAdmin = 'MasterAdmin',
|
||||
}
|
||||
|
||||
export interface OneUptimeRequest extends express.Request {
|
||||
|
@ -7,40 +7,48 @@ import jwt from 'jsonwebtoken';
|
||||
import { EncryptionSecret } from '../Config';
|
||||
|
||||
export interface JSONWebTokenData {
|
||||
userId: ObjectID,
|
||||
email: Email,
|
||||
roles: Array<UserRole>,
|
||||
isMasterAdmin: boolean
|
||||
userId: ObjectID;
|
||||
email: Email;
|
||||
roles: Array<UserRole>;
|
||||
isMasterAdmin: boolean;
|
||||
}
|
||||
|
||||
class JSONWebToken {
|
||||
|
||||
public static sign(data: JSONWebTokenData, expiresIn: Date): string {
|
||||
return jwt.sign({
|
||||
userId: data.userId.toString(),
|
||||
email: data.email.toString(),
|
||||
roles: data.roles.map((role: UserRole): JSONObject => role.toJSON()),
|
||||
isMasterAdmin: data.isMasterAdmin
|
||||
}, EncryptionSecret.toString(), {
|
||||
expiresIn: String(expiresIn),
|
||||
});
|
||||
return jwt.sign(
|
||||
{
|
||||
userId: data.userId.toString(),
|
||||
email: data.email.toString(),
|
||||
roles: data.roles.map((role: UserRole): JSONObject => {
|
||||
return role.toJSON();
|
||||
}),
|
||||
isMasterAdmin: data.isMasterAdmin,
|
||||
},
|
||||
EncryptionSecret.toString(),
|
||||
{
|
||||
expiresIn: String(expiresIn),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public static decode(token: string): JSONWebTokenData {
|
||||
try {
|
||||
|
||||
const decoded: JSONObject = JSON.parse(jwt.verify(token, EncryptionSecret.toString()) as string);
|
||||
const decoded: JSONObject = JSON.parse(
|
||||
jwt.verify(token, EncryptionSecret.toString()) as string
|
||||
);
|
||||
|
||||
return {
|
||||
userId: new ObjectID(decoded["userId"] as string),
|
||||
email: new Email(decoded["email"] as string),
|
||||
roles: (decoded["roles"] as JSONArray).map((obj: JSONObject): UserRole => {
|
||||
return UserRole.fromJSON(obj);
|
||||
}),
|
||||
isMasterAdmin: !!decoded["isMasterAdmin"]
|
||||
}
|
||||
userId: new ObjectID(decoded['userId'] as string),
|
||||
email: new Email(decoded['email'] as string),
|
||||
roles: (decoded['roles'] as JSONArray).map(
|
||||
(obj: JSONObject): UserRole => {
|
||||
return UserRole.fromJSON(obj);
|
||||
}
|
||||
),
|
||||
isMasterAdmin: Boolean(decoded['isMasterAdmin']),
|
||||
};
|
||||
} catch (e) {
|
||||
throw new BadDataException("AccessToken is invalid or expired");
|
||||
throw new BadDataException('AccessToken is invalid or expired');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -21,25 +21,26 @@ export default class Response {
|
||||
): void {
|
||||
const oneUptimeRequest: OneUptimeRequest = req as OneUptimeRequest;
|
||||
const oneUptimeResponse: OneUptimeResponse = res as OneUptimeResponse;
|
||||
|
||||
|
||||
const requestEndedAt: Date = new Date();
|
||||
const method: string = oneUptimeRequest.method;
|
||||
const url: URL = URL.fromString(oneUptimeRequest.url);
|
||||
|
||||
|
||||
const duration_info: string = `OUTGOING RESPONSE ID: ${
|
||||
oneUptimeRequest.id
|
||||
} -- POD NAME: ${
|
||||
process.env['POD_NAME'] || 'NONE'
|
||||
} -- METHOD: ${method} -- URL: ${url.toString()} -- DURATION: ${(
|
||||
requestEndedAt.getTime() - oneUptimeRequest.requestStartedAt.getTime()
|
||||
requestEndedAt.getTime() -
|
||||
oneUptimeRequest.requestStartedAt.getTime()
|
||||
).toString()}ms -- STATUS: ${oneUptimeResponse.statusCode}`;
|
||||
|
||||
|
||||
const body_info: string = `OUTGOING RESPONSE ID: ${
|
||||
oneUptimeRequest.id
|
||||
} -- RESPONSE BODY: ${
|
||||
responsebody ? JSON.stringify(responsebody, null, 2) : 'EMPTY'
|
||||
}`;
|
||||
|
||||
|
||||
if (oneUptimeResponse.statusCode > 299) {
|
||||
logger.error(duration_info);
|
||||
logger.error(body_info);
|
||||
@ -48,74 +49,80 @@ export default class Response {
|
||||
logger.info(body_info);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static sendEmptyResponse(
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse
|
||||
): void {
|
||||
const oneUptimeRequest: OneUptimeRequest = req as OneUptimeRequest;
|
||||
const oneUptimeResponse: OneUptimeResponse = res as OneUptimeResponse;
|
||||
|
||||
oneUptimeResponse.set('ExpressRequest-Id', oneUptimeRequest.id.toString());
|
||||
|
||||
oneUptimeResponse.set(
|
||||
'ExpressRequest-Id',
|
||||
oneUptimeRequest.id.toString()
|
||||
);
|
||||
oneUptimeResponse.set('Pod-Id', process.env['POD_NAME']);
|
||||
|
||||
|
||||
oneUptimeResponse.status(200).send();
|
||||
|
||||
|
||||
return this.logResponse(req, res, undefined);
|
||||
}
|
||||
|
||||
public static async sendFileResponse (
|
||||
|
||||
public static async sendFileResponse(
|
||||
req: ExpressRequest | ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
file: File
|
||||
): Promise<void> {
|
||||
/** Create read stream */
|
||||
|
||||
|
||||
const oneUptimeResponse: OneUptimeResponse = res as OneUptimeResponse;
|
||||
|
||||
|
||||
/*
|
||||
* const gfs: GridFSBucket = new GridFSBucket(await Database.getDatabase(), {
|
||||
* bucketName: 'uploads',
|
||||
* });
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* const readstream: GridFSBucketReadStream = gfs.openDownloadStreamByName(
|
||||
* file.name
|
||||
* );
|
||||
*/
|
||||
|
||||
|
||||
/** Set the proper content type */
|
||||
oneUptimeResponse.set('Content-Type', file.contentType);
|
||||
oneUptimeResponse.status(200);
|
||||
/** Return response */
|
||||
// readstream.pipe(res);
|
||||
|
||||
|
||||
this.logResponse(req, res);
|
||||
};
|
||||
|
||||
public static sendErrorResponse (
|
||||
}
|
||||
|
||||
public static sendErrorResponse(
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
error: Exception
|
||||
): void {
|
||||
): void {
|
||||
const oneUptimeRequest: OneUptimeRequest = req as OneUptimeRequest;
|
||||
const oneUptimeResponse: OneUptimeResponse = res as OneUptimeResponse;
|
||||
|
||||
|
||||
oneUptimeResponse.logBody = { message: error.message }; // To be used in 'auditLog' middleware to log reponse data;
|
||||
const status: number = error.code || 500;
|
||||
const message: string = error.message || 'Server Error';
|
||||
|
||||
|
||||
logger.error(error);
|
||||
|
||||
oneUptimeResponse.set('ExpressRequest-Id', oneUptimeRequest.id.toString());
|
||||
|
||||
oneUptimeResponse.set(
|
||||
'ExpressRequest-Id',
|
||||
oneUptimeRequest.id.toString()
|
||||
);
|
||||
oneUptimeResponse.set('Pod-Id', process.env['POD_NAME']);
|
||||
|
||||
|
||||
oneUptimeResponse.status(status).send({ message });
|
||||
return this.logResponse(req, res, { message });
|
||||
};
|
||||
|
||||
public static sendListResponse (
|
||||
}
|
||||
|
||||
public static sendListResponse(
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
list: JSONArray,
|
||||
@ -123,43 +130,46 @@ export default class Response {
|
||||
): void {
|
||||
const oneUptimeRequest: OneUptimeRequest = req as OneUptimeRequest;
|
||||
const oneUptimeResponse: OneUptimeResponse = res as OneUptimeResponse;
|
||||
|
||||
oneUptimeResponse.set('ExpressRequest-Id', oneUptimeRequest.id.toString());
|
||||
|
||||
oneUptimeResponse.set(
|
||||
'ExpressRequest-Id',
|
||||
oneUptimeRequest.id.toString()
|
||||
);
|
||||
oneUptimeResponse.set('Pod-Id', process.env['POD_NAME']);
|
||||
|
||||
|
||||
const listData: ListData = new ListData({
|
||||
data: [],
|
||||
count: new PositiveNumber(0),
|
||||
skip: new PositiveNumber(0),
|
||||
limit: new PositiveNumber(0),
|
||||
});
|
||||
|
||||
|
||||
if (!list) {
|
||||
list = [];
|
||||
}
|
||||
|
||||
|
||||
if (list) {
|
||||
listData.data = list;
|
||||
}
|
||||
|
||||
|
||||
if (count) {
|
||||
listData.count = count;
|
||||
} else if (list) {
|
||||
listData.count = new PositiveNumber(list.length);
|
||||
}
|
||||
|
||||
|
||||
if (oneUptimeRequest.query['skip']) {
|
||||
listData.skip = new PositiveNumber(
|
||||
parseInt(oneUptimeRequest.query['skip'].toString())
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
if (oneUptimeRequest.query['limit']) {
|
||||
listData.limit = new PositiveNumber(
|
||||
parseInt(oneUptimeRequest.query['limit'].toString())
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
if (oneUptimeRequest.query['output-type'] === 'csv') {
|
||||
const csv: string = JsonToCsv.ToCsv(listData.data);
|
||||
oneUptimeResponse.status(200).send(csv);
|
||||
@ -169,29 +179,31 @@ export default class Response {
|
||||
oneUptimeResponse.status(200).send(listData);
|
||||
this.logResponse(req, res, listData.toJSON());
|
||||
}
|
||||
};
|
||||
|
||||
public static sendItemResponse (
|
||||
}
|
||||
|
||||
public static sendItemResponse(
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
item: JSONObject
|
||||
): void {
|
||||
const oneUptimeRequest: OneUptimeRequest = req as OneUptimeRequest;
|
||||
const oneUptimeResponse: OneUptimeResponse = res as OneUptimeResponse;
|
||||
|
||||
oneUptimeResponse.set('ExpressRequest-Id', oneUptimeRequest.id.toString());
|
||||
|
||||
oneUptimeResponse.set(
|
||||
'ExpressRequest-Id',
|
||||
oneUptimeRequest.id.toString()
|
||||
);
|
||||
oneUptimeResponse.set('Pod-Id', process.env['POD_NAME']);
|
||||
|
||||
|
||||
if (oneUptimeRequest.query['output-type'] === 'csv') {
|
||||
const csv: string = JsonToCsv.ToCsv([item]);
|
||||
oneUptimeResponse.status(200).send(csv);
|
||||
this.logResponse(req, res);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
oneUptimeResponse.logBody = item;
|
||||
oneUptimeResponse.status(200).send(item);
|
||||
this.logResponse(req, res, item);
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -11,14 +11,13 @@ import { Dispatch } from 'redux';
|
||||
import HTTPErrorResponse from 'Common/Types/API/ErrorResponse';
|
||||
|
||||
export default class ActionBase {
|
||||
|
||||
private _name: string;
|
||||
private apiBaseConstants: ApiBaseConstants;
|
||||
public get name(): string {
|
||||
return this._name;
|
||||
}
|
||||
|
||||
constructor(name: string) {
|
||||
public constructor(name: string) {
|
||||
this._name = name;
|
||||
this.apiBaseConstants = new ApiBaseConstants(name);
|
||||
}
|
||||
@ -54,24 +53,26 @@ export default class ActionBase {
|
||||
|
||||
public requestData(apiRequest: Promise<HTTPResponse>): Function {
|
||||
return async (dispatch: Dispatch): Promise<void> => {
|
||||
|
||||
dispatch(this.request({
|
||||
requesting: true,
|
||||
httpResponsePromise: apiRequest
|
||||
}));
|
||||
dispatch(
|
||||
this.request({
|
||||
requesting: true,
|
||||
httpResponsePromise: apiRequest,
|
||||
})
|
||||
);
|
||||
|
||||
try {
|
||||
|
||||
const response: HTTPResponse = await apiRequest;
|
||||
|
||||
dispatch(this.success({
|
||||
response: response
|
||||
}));
|
||||
dispatch(
|
||||
this.success({
|
||||
response: response,
|
||||
})
|
||||
);
|
||||
} catch (e) {
|
||||
const errorResponse = e as HTTPErrorResponse;
|
||||
const errorResponse: HTTPErrorResponse = e as HTTPErrorResponse;
|
||||
this.error({
|
||||
errorResponse: errorResponse
|
||||
})
|
||||
errorResponse: errorResponse,
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -1,23 +1,22 @@
|
||||
class ApiBaseConstant {
|
||||
|
||||
private _name: string;
|
||||
|
||||
public get name(): string {
|
||||
return this._name;
|
||||
}
|
||||
|
||||
constructor(name: string) {
|
||||
public constructor(name: string) {
|
||||
this._name = name;
|
||||
this.REQUEST += name;
|
||||
this.ERROR += name;
|
||||
this.REQUEST += name;
|
||||
this.ERROR += name;
|
||||
this.SUCCESS += name;
|
||||
this.RESET += name;
|
||||
}
|
||||
|
||||
public REQUEST: string = "Request";
|
||||
public ERROR: string = "Error";
|
||||
public SUCCESS: string = "Success";
|
||||
public RESET: string = "Reset";
|
||||
|
||||
public REQUEST: string = 'Request';
|
||||
public ERROR: string = 'Error';
|
||||
public SUCCESS: string = 'Success';
|
||||
public RESET: string = 'Reset';
|
||||
}
|
||||
|
||||
export default ApiBaseConstant;
|
||||
|
@ -18,13 +18,12 @@ export interface InitialStateType {
|
||||
export default class ApiBaseReducer {
|
||||
private constants: ApiBaseConstants;
|
||||
private _name: string;
|
||||
|
||||
|
||||
public get name(): string {
|
||||
return this._name;
|
||||
}
|
||||
|
||||
constructor(name: string) {
|
||||
public constructor(name: string) {
|
||||
this._name = name;
|
||||
this.constants = new ApiBaseConstants(this.name);
|
||||
}
|
||||
@ -65,4 +64,4 @@ export default class ApiBaseReducer {
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,11 @@
|
||||
import { AccountsHostname, DashboardHostname, DisableSignup, HomeHostname, HttpProtocol, IsSaaSService } from 'CommonServer/Config';
|
||||
import {
|
||||
AccountsHostname,
|
||||
DashboardHostname,
|
||||
DisableSignup,
|
||||
HomeHostname,
|
||||
HttpProtocol,
|
||||
IsSaaSService,
|
||||
} from 'CommonServer/Config';
|
||||
import Express, {
|
||||
ExpressRequest,
|
||||
ExpressResponse,
|
||||
@ -23,106 +30,124 @@ import OneUptimeDate from 'Common/Types/Date';
|
||||
import PositiveNumber from 'Common/Types/PositiveNumber';
|
||||
|
||||
const UserService: UserServiceType = Service.UserService;
|
||||
const EmailVerificationTokenService: EmailVerificationTokenServiceType = Service.EmailVerificationTokenService;
|
||||
const EmailVerificationTokenService: EmailVerificationTokenServiceType =
|
||||
Service.EmailVerificationTokenService;
|
||||
const MailService: MailServiceType = Service.MailService;
|
||||
|
||||
const router: ExpressRouter = Express.getRouter();
|
||||
|
||||
router.post('/signup', async (req: ExpressRequest, res: ExpressResponse): Promise<void> => {
|
||||
router.post(
|
||||
'/signup',
|
||||
async (req: ExpressRequest, res: ExpressResponse): Promise<void> => {
|
||||
if (DisableSignup) {
|
||||
throw new BadRequestException('Sign up is disabled.');
|
||||
}
|
||||
|
||||
const data: JSONObject = req.body;
|
||||
const user: User = User.fromJSON<User>(data['user'] as JSONObject);
|
||||
|
||||
if (
|
||||
DisableSignup
|
||||
) {
|
||||
throw new BadRequestException("Sign up is disabled.");
|
||||
}
|
||||
if (IsSaaSService) {
|
||||
//ALERT: Delete data.role so user don't accidently sign up as master-admin from the API.
|
||||
user.isMasterAdmin = false;
|
||||
user.isEmailVerified = false;
|
||||
}
|
||||
|
||||
const data: JSONObject = req.body;
|
||||
const user: User = User.fromJSON<User>(data["user"] as JSONObject);
|
||||
let verificationToken: ObjectID | null = null;
|
||||
let emailVerificationToken: EmailVerificationToken | null = null;
|
||||
if (req.query['token']) {
|
||||
verificationToken = new ObjectID(req.query['token'] as string);
|
||||
emailVerificationToken =
|
||||
await EmailVerificationTokenService.findOneBy({
|
||||
query: {
|
||||
token: verificationToken,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (IsSaaSService) {
|
||||
//ALERT: Delete data.role so user don't accidently sign up as master-admin from the API.
|
||||
user.isMasterAdmin = false;
|
||||
user.isEmailVerified = false;
|
||||
}
|
||||
|
||||
let verificationToken: ObjectID | null = null;
|
||||
let emailVerificationToken: EmailVerificationToken | null = null;
|
||||
if (req.query['token']) {
|
||||
verificationToken = new ObjectID(req.query['token'] as string);
|
||||
emailVerificationToken = await EmailVerificationTokenService.findOneBy({
|
||||
query: {
|
||||
token: verificationToken,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
const alreadySavedUser = await UserService.findOneBy({
|
||||
query: { email: user.email },
|
||||
select: {
|
||||
_id: true,
|
||||
password: true
|
||||
},
|
||||
});
|
||||
|
||||
if (emailVerificationToken && user && user.id.toString() === emailVerificationToken.userId.toString()) {
|
||||
user.isEmailVerified = true;
|
||||
}
|
||||
|
||||
if (alreadySavedUser && alreadySavedUser.password) {
|
||||
throw new BadDataException(`User with email ${user.email} already exists.`);
|
||||
}
|
||||
|
||||
|
||||
let savedUser: User | null = null;
|
||||
if (alreadySavedUser) {
|
||||
savedUser = await UserService.updateOneByIdAndFetch({
|
||||
id: alreadySavedUser.id, data: user
|
||||
const alreadySavedUser: User | null = await UserService.findOneBy({
|
||||
query: { email: user.email },
|
||||
select: {
|
||||
_id: true,
|
||||
password: true,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
savedUser = await UserService.create({ data: user });
|
||||
|
||||
if (
|
||||
emailVerificationToken &&
|
||||
user &&
|
||||
user.id.toString() === emailVerificationToken.userId.toString()
|
||||
) {
|
||||
user.isEmailVerified = true;
|
||||
}
|
||||
|
||||
if (alreadySavedUser && alreadySavedUser.password) {
|
||||
throw new BadDataException(
|
||||
`User with email ${user.email} already exists.`
|
||||
);
|
||||
}
|
||||
|
||||
let savedUser: User | null = null;
|
||||
if (alreadySavedUser) {
|
||||
savedUser = await UserService.updateOneByIdAndFetch({
|
||||
id: alreadySavedUser.id,
|
||||
data: user,
|
||||
});
|
||||
} else {
|
||||
savedUser = await UserService.create({ data: user });
|
||||
}
|
||||
|
||||
if (alreadySavedUser) {
|
||||
// Send Welcome Mail
|
||||
MailService.sendMail(
|
||||
user.email,
|
||||
EmailSubjects.getSubjectByType(
|
||||
EmailTemplateType.SIGNUP_WELCOME_EMAIL
|
||||
),
|
||||
EmailTemplateType.SIGNUP_WELCOME_EMAIL,
|
||||
{
|
||||
name: user.name.toString(),
|
||||
dashboardUrl: new URL(
|
||||
HttpProtocol,
|
||||
DashboardHostname
|
||||
).toString(),
|
||||
homeUrl: new URL(HttpProtocol, HomeHostname).toString(),
|
||||
}
|
||||
);
|
||||
} else {
|
||||
// Send EmailVerification Link because this is a new user.
|
||||
MailService.sendMail(
|
||||
user.email,
|
||||
EmailSubjects.getSubjectByType(
|
||||
EmailTemplateType.SIGNUP_WELCOME_EMAIL
|
||||
),
|
||||
EmailTemplateType.SIGNUP_WELCOME_EMAIL,
|
||||
{
|
||||
name: user.name.toString(),
|
||||
emailVerificationUrl: new URL(
|
||||
HttpProtocol,
|
||||
AccountsHostname
|
||||
).toString(),
|
||||
homeUrl: new URL(HttpProtocol, HomeHostname).toString(),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if (savedUser) {
|
||||
const token: string = JSONWebToken.sign(
|
||||
{
|
||||
userId: savedUser?.id,
|
||||
email: savedUser?.email,
|
||||
isMasterAdmin: savedUser?.isMasterAdmin,
|
||||
roles: [],
|
||||
},
|
||||
OneUptimeDate.getSomeDaysAfter(new PositiveNumber(30))
|
||||
);
|
||||
|
||||
return Response.sendItemResponse(req, res, { token: token });
|
||||
}
|
||||
|
||||
throw new BadRequestException('Failed to create a user');
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
if (alreadySavedUser) {
|
||||
// Send Welcome Mail
|
||||
MailService.sendMail(
|
||||
user.email,
|
||||
EmailSubjects.getSubjectByType(EmailTemplateType.SIGNUP_WELCOME_EMAIL),
|
||||
EmailTemplateType.SIGNUP_WELCOME_EMAIL,
|
||||
{
|
||||
"name": user.name.toString(),
|
||||
"dashboardUrl": new URL(HttpProtocol, DashboardHostname).toString(),
|
||||
"homeUrl": new URL(HttpProtocol, HomeHostname).toString()
|
||||
}
|
||||
)
|
||||
} else {
|
||||
// Send EmailVerification Link because this is a new user.
|
||||
MailService.sendMail(
|
||||
user.email,
|
||||
EmailSubjects.getSubjectByType(EmailTemplateType.SIGNUP_WELCOME_EMAIL),
|
||||
EmailTemplateType.SIGNUP_WELCOME_EMAIL,
|
||||
{
|
||||
"name": user.name.toString(),
|
||||
"emailVerificationUrl": new URL(HttpProtocol, AccountsHostname).toString(),
|
||||
"homeUrl": new URL(HttpProtocol, HomeHostname).toString()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if (savedUser) {
|
||||
const token = JSONWebToken.sign({
|
||||
userId: savedUser?.id,
|
||||
email: savedUser?.email,
|
||||
isMasterAdmin: savedUser?.isMasterAdmin,
|
||||
roles: []
|
||||
}, OneUptimeDate.getSomeDaysAfter(new PositiveNumber(30)));
|
||||
|
||||
return Response.sendItemResponse(req, res, { token: token });
|
||||
}
|
||||
|
||||
throw new BadRequestException("Failed to create a user");
|
||||
});
|
||||
|
||||
export default router;
|
||||
export default router;
|
||||
|
@ -18,7 +18,6 @@ router.post(
|
||||
'/:template-name',
|
||||
ClusterKeyAuthorization.isAuthorizedServiceMiddleware,
|
||||
async (req: ExpressRequest, res: ExpressResponse) => {
|
||||
|
||||
const body: JSONObject = req.body;
|
||||
|
||||
const mail: Mail = {
|
||||
|
Loading…
Reference in New Issue
Block a user