mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-18 05:40:31 +00:00
fix: mark payment license as used after usage.
fix: fix system models with createdAt and updatedAt fields. fix: reset password token expiration time.
This commit is contained in:
8
server/src/exceptions/NoPaymentModelWithPricedPlan.ts
Normal file
8
server/src/exceptions/NoPaymentModelWithPricedPlan.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
|
||||
|
||||
export default class NoPaymentModelWithPricedPlan {
|
||||
|
||||
constructor() {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,8 @@
|
||||
|
||||
|
||||
export default class NotAllowedChangeSubscriptionPlan extends Error{
|
||||
export default class NotAllowedChangeSubscriptionPlan {
|
||||
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
this.name = "NotAllowedChangeSubscriptionPlan";
|
||||
}
|
||||
}
|
||||
7
server/src/exceptions/PaymentAmountInvalidWithPlan.ts
Normal file
7
server/src/exceptions/PaymentAmountInvalidWithPlan.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
|
||||
|
||||
export default class PaymentAmountInvalidWithPlan{
|
||||
constructor() {
|
||||
|
||||
}
|
||||
}
|
||||
5
server/src/exceptions/PaymentInputInvalid.ts
Normal file
5
server/src/exceptions/PaymentInputInvalid.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
|
||||
|
||||
export default class PaymentInputInvalid {
|
||||
constructor() {}
|
||||
}
|
||||
@@ -1,9 +1,15 @@
|
||||
import NotAllowedChangeSubscriptionPlan from './NotAllowedChangeSubscriptionPlan';
|
||||
import ServiceError from './ServiceError';
|
||||
import ServiceErrors from './ServiceErrors';
|
||||
import NoPaymentModelWithPricedPlan from './NoPaymentModelWithPricedPlan';
|
||||
import PaymentInputInvalid from './PaymentInputInvalid';
|
||||
import PaymentAmountInvalidWithPlan from './PaymentAmountInvalidWithPlan';
|
||||
|
||||
export {
|
||||
NotAllowedChangeSubscriptionPlan,
|
||||
NoPaymentModelWithPricedPlan,
|
||||
PaymentAmountInvalidWithPlan,
|
||||
ServiceError,
|
||||
ServiceErrors,
|
||||
PaymentInputInvalid
|
||||
};
|
||||
@@ -204,7 +204,7 @@ export default class AuthenticationController extends BaseController{
|
||||
})
|
||||
} catch(error) {
|
||||
if (error instanceof ServiceError) {
|
||||
if (error.errorType === 'token_invalid') {
|
||||
if (error.errorType === 'token_invalid' || error.errorType === 'token_expired') {
|
||||
return res.boom.badRequest(null, {
|
||||
errors: [{ type: 'TOKEN_INVALID', code: 100 }],
|
||||
});
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { Router, Request, Response } from 'express';
|
||||
import { check, param, query, ValidationSchema } from 'express-validator';
|
||||
import { License, Plan } from '@/system/models';
|
||||
import { check } from 'express-validator';
|
||||
import validateMiddleware from '@/http/middleware/validateMiddleware';
|
||||
import asyncMiddleware from '@/http/middleware/asyncMiddleware';
|
||||
import PaymentMethodController from '@/http/controllers/Subscription/PaymentMethod';
|
||||
import {
|
||||
NotAllowedChangeSubscriptionPlan
|
||||
NotAllowedChangeSubscriptionPlan,
|
||||
NoPaymentModelWithPricedPlan,
|
||||
PaymentAmountInvalidWithPlan,
|
||||
PaymentInputInvalid,
|
||||
} from '@/exceptions';
|
||||
import { ILicensePaymentModel } from '@/interfaces';
|
||||
|
||||
@Service()
|
||||
export default class PaymentViaLicenseController extends PaymentMethodController {
|
||||
@@ -24,9 +27,7 @@ export default class PaymentViaLicenseController extends PaymentMethodController
|
||||
'/payment',
|
||||
this.paymentViaLicenseSchema,
|
||||
validateMiddleware,
|
||||
asyncMiddleware(this.validateLicenseCodeExistance.bind(this)),
|
||||
asyncMiddleware(this.validatePlanSlugExistance.bind(this)),
|
||||
asyncMiddleware(this.validateLicenseAndPlan.bind(this)),
|
||||
asyncMiddleware(this.paymentViaLicense.bind(this)),
|
||||
);
|
||||
return router;
|
||||
@@ -38,54 +39,10 @@ export default class PaymentViaLicenseController extends PaymentMethodController
|
||||
get paymentViaLicenseSchema() {
|
||||
return [
|
||||
check('plan_slug').exists().trim().escape(),
|
||||
check('license_code').exists().trim().escape(),
|
||||
check('license_code').optional().trim().escape(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the given license code exists on the storage.
|
||||
* @async
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async validateLicenseCodeExistance(req: Request, res: Response, next: Function) {
|
||||
const { licenseCode } = this.matchedBodyData(req);
|
||||
this.logger.info('[license_payment] trying to validate license code.', { licenseCode });
|
||||
|
||||
const foundLicense = await License.query()
|
||||
.modify('filterActiveLicense')
|
||||
.where('license_code', licenseCode)
|
||||
.first();
|
||||
|
||||
if (!foundLicense) {
|
||||
return res.status(400).send({
|
||||
errors: [{ type: 'LICENSE.CODE.IS.INVALID', code: 120 }],
|
||||
});
|
||||
}
|
||||
next();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the license period and plan period.
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {Function} next
|
||||
*/
|
||||
async validateLicenseAndPlan(req: Request, res: Response, next: Function) {
|
||||
const { planSlug, licenseCode } = this.matchedBodyData(req);
|
||||
this.logger.info('[license_payment] trying to validate license with the plan.', { licenseCode });
|
||||
|
||||
const license = await License.query().findOne('license_code', licenseCode);
|
||||
const plan = await Plan.query().findOne('slug', planSlug);
|
||||
|
||||
if (license.planId !== plan.id) {
|
||||
return res.status(400).send({
|
||||
errors: [{ type: 'LICENSE.NOT.FOR.GIVEN.PLAN' }],
|
||||
});
|
||||
}
|
||||
next();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the subscription payment via license code.
|
||||
* @param {Request} req
|
||||
@@ -97,8 +54,11 @@ export default class PaymentViaLicenseController extends PaymentMethodController
|
||||
const { tenant } = req;
|
||||
|
||||
try {
|
||||
const licenseModel: ILicensePaymentModel|null = licenseCode
|
||||
? { licenseCode } : null;
|
||||
|
||||
await this.subscriptionService
|
||||
.subscriptionViaLicense(tenant.id, planSlug, licenseCode);
|
||||
.subscriptionViaLicense(tenant.id, planSlug, licenseModel);
|
||||
|
||||
return res.status(200).send({
|
||||
type: 'success',
|
||||
@@ -108,7 +68,13 @@ export default class PaymentViaLicenseController extends PaymentMethodController
|
||||
} catch (exception) {
|
||||
const errorReasons = [];
|
||||
|
||||
if (exception.name === 'NotAllowedChangeSubscriptionPlan') {
|
||||
if (exception instanceof NoPaymentModelWithPricedPlan) {
|
||||
errorReasons.push({
|
||||
type: 'NO_PAYMENT_WITH_PRICED_PLAN',
|
||||
code: 140,
|
||||
});
|
||||
}
|
||||
if (exception instanceof NotAllowedChangeSubscriptionPlan) {
|
||||
errorReasons.push({
|
||||
type: 'NOT.ALLOWED.RENEW.SUBSCRIPTION.WHILE.ACTIVE',
|
||||
code: 120,
|
||||
@@ -117,6 +83,16 @@ export default class PaymentViaLicenseController extends PaymentMethodController
|
||||
if (errorReasons.length > 0) {
|
||||
return res.status(400).send({ errors: errorReasons });
|
||||
}
|
||||
if (exception instanceof PaymentInputInvalid) {
|
||||
return res.status(400).send({
|
||||
errors: [{ type: 'LICENSE.CODE.IS.INVALID', code: 120 }],
|
||||
});
|
||||
}
|
||||
if (exception instanceof PaymentAmountInvalidWithPlan) {
|
||||
return res.status(400).send({
|
||||
errors: [{ type: 'LICENSE.NOT.FOR.GIVEN.PLAN' }],
|
||||
});
|
||||
}
|
||||
next(exception);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,11 @@ export default class AccountTransaction extends mixin(TenantModel, [CachableMode
|
||||
*/
|
||||
static get modifiers() {
|
||||
return {
|
||||
/**
|
||||
* Filters accounts by the given ids.
|
||||
* @param {Query} query
|
||||
* @param {number[]} accountsIds
|
||||
*/
|
||||
filterAccounts(query, accountsIds) {
|
||||
if (accountsIds.length > 0) {
|
||||
query.whereIn('account_id', accountsIds);
|
||||
|
||||
@@ -203,7 +203,7 @@ export default class AuthenticationService {
|
||||
|
||||
// Delete all stored tokens of reset password that associate to the give email.
|
||||
this.logger.info('[send_reset_password] trying to delete all tokens by email.');
|
||||
await PasswordReset.query().where('email', email).delete();
|
||||
this.deletePasswordResetToken(email);
|
||||
|
||||
const token = uniqid();
|
||||
|
||||
@@ -230,28 +230,44 @@ export default class AuthenticationService {
|
||||
this.logger.info('[reset_password] token invalid.');
|
||||
throw new ServiceError('token_invalid');
|
||||
}
|
||||
// Different between tokne creation datetime and current time.
|
||||
if (moment().diff(tokenModel.createdAt, 'seconds') > config.resetPasswordSeconds) {
|
||||
this.logger.info('[reset_password] token expired.');
|
||||
|
||||
// Deletes the expired token by expired token email.
|
||||
await this.deletePasswordResetToken(tokenModel.email);
|
||||
throw new ServiceError('token_expired');
|
||||
}
|
||||
const user = await SystemUser.query().findOne('email', tokenModel.email)
|
||||
|
||||
if (!user) {
|
||||
throw new ServiceError('user_not_found');
|
||||
}
|
||||
const hashedPassword = await hashPassword(password);
|
||||
|
||||
|
||||
this.logger.info('[reset_password] saving a new hashed password.');
|
||||
await SystemUser.query()
|
||||
.where('email', tokenModel.email)
|
||||
.update({
|
||||
password: hashedPassword,
|
||||
});
|
||||
// Delete the reset password token.
|
||||
await PasswordReset.query().where('email', user.email).delete();
|
||||
.update({ password: hashedPassword });
|
||||
|
||||
// Deletes the used token.
|
||||
await this.deletePasswordResetToken(tokenModel.email);
|
||||
|
||||
// Triggers `onResetPassword` event.
|
||||
this.eventDispatcher.dispatch(events.auth.resetPassword, { user, token, password });
|
||||
|
||||
this.logger.info('[reset_password] reset password success.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the password reset token by the given email.
|
||||
* @param {string} email
|
||||
* @returns {Promise}
|
||||
*/
|
||||
private async deletePasswordResetToken(email: string) {
|
||||
this.logger.info('[reset_password] trying to delete all tokens by email.');
|
||||
return PasswordReset.query().where('email', email).delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates JWT token for the given user.
|
||||
* @param {ISystemUser} user
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
EventDispatcher,
|
||||
EventDispatcherInterface,
|
||||
} from '@/decorators/eventDispatcher';
|
||||
import { ServiceError, ServiceErrors } from "@/exceptions";
|
||||
import { ServiceError } from "@/exceptions";
|
||||
import { SystemUser, Invite, Tenant } from "@/system/models";
|
||||
import { Option } from '@/models';
|
||||
import { hashPassword } from '@/utils';
|
||||
@@ -49,8 +49,6 @@ export default class InviteUserService {
|
||||
this.logger.info('[aceept_invite] trying to hash the user password.');
|
||||
const hashedPassword = await hashPassword(inviteUserInput.password);
|
||||
|
||||
console.log(inviteToken);
|
||||
|
||||
this.logger.info('[accept_invite] trying to update user details.');
|
||||
const updateUserOper = SystemUser.query()
|
||||
.where('email', inviteToken.email)
|
||||
@@ -60,7 +58,7 @@ export default class InviteUserService {
|
||||
invite_accepted_at: moment().format('YYYY-MM-DD'),
|
||||
password: hashedPassword,
|
||||
});
|
||||
|
||||
|
||||
this.logger.info('[accept_invite] trying to delete the given token.');
|
||||
const deleteInviteTokenOper = Invite.query().where('token', inviteToken.token).delete();
|
||||
|
||||
|
||||
@@ -42,7 +42,6 @@ export default class LicenseService {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number} loop
|
||||
@@ -52,7 +51,7 @@ export default class LicenseService {
|
||||
*/
|
||||
async generateLicenses(
|
||||
loop = 1,
|
||||
licensePeriod: numner,
|
||||
licensePeriod: number,
|
||||
periodInterval: string = 'days',
|
||||
planId: number,
|
||||
) {
|
||||
@@ -67,7 +66,7 @@ export default class LicenseService {
|
||||
|
||||
/**
|
||||
* Disables the given license id on the storage.
|
||||
* @param {number} licenseId
|
||||
* @param {number} licenseId
|
||||
* @return {Promise}
|
||||
*/
|
||||
async disableLicense(licenseId: number) {
|
||||
|
||||
@@ -1,14 +1,47 @@
|
||||
import { License } from "@/system/models";
|
||||
import PaymentMethod from '@/services/Payment/PaymentMethod';
|
||||
import { Plan } from '@/system/models';
|
||||
import { IPaymentMethod, ILicensePaymentModel } from '@/interfaces';
|
||||
import { ILicensePaymentModel } from "@/interfaces";
|
||||
import { PaymentInputInvalid, PaymentAmountInvalidWithPlan } from '@/exceptions';
|
||||
|
||||
export default class VocuherPaymentMethod extends PaymentMethod implements IPaymentMethod {
|
||||
export default class LicensePaymentMethod extends PaymentMethod implements IPaymentMethod {
|
||||
/**
|
||||
* Payment subscription of organization via license code.
|
||||
* @param {ILicensePaymentModel}
|
||||
* @param {ILicensePaymentModel} licensePaymentModel -
|
||||
*/
|
||||
async payment(licensePaymentModel: ILicensePaymentModel) {
|
||||
async payment(licensePaymentModel: ILicensePaymentModel, plan: Plan) {
|
||||
const license = await this.getLicenseOrThrowInvalid(licensePaymentModel);
|
||||
this.validatePaymentAmountWithPlan(license, plan);
|
||||
|
||||
// Mark the license code as used.
|
||||
return License.markLicenseAsUsed(licensePaymentModel.licenseCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the license code activation on the storage.
|
||||
* @param {ILicensePaymentModel} licensePaymentModel -
|
||||
*/
|
||||
async getLicenseOrThrowInvalid(licensePaymentModel: ILicensePaymentModel) {
|
||||
const foundLicense = await License.query()
|
||||
.modify('filterActiveLicense')
|
||||
.where('license_code', licensePaymentModel.licenseCode)
|
||||
.first();
|
||||
|
||||
if (!foundLicense) {
|
||||
throw new PaymentInputInvalid();
|
||||
}
|
||||
return foundLicense;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the payment amount with given plan price.
|
||||
* @param {License} license
|
||||
* @param {Plan} plan
|
||||
*/
|
||||
validatePaymentAmountWithPlan(license: License, plan: Plan) {
|
||||
if (license.planId !== plan.id) {
|
||||
throw new PaymentAmountInvalidWithPlan();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { IPaymentMethod, IPaymentContext } from "@/interfaces";
|
||||
import { Plan } from '@/system/models';
|
||||
|
||||
export default class PaymentContext<PaymentModel> implements IPaymentContext{
|
||||
paymentMethod: IPaymentMethod;
|
||||
@@ -15,7 +16,7 @@ export default class PaymentContext<PaymentModel> implements IPaymentContext{
|
||||
*
|
||||
* @param {<PaymentModel>} paymentModel
|
||||
*/
|
||||
makePayment(paymentModel: PaymentModel) {
|
||||
this.paymentMethod.makePayment(paymentModel);
|
||||
makePayment(paymentModel: PaymentModel, plan: Plan) {
|
||||
return this.paymentMethod.payment(paymentModel, plan);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Inject } from 'typedi';
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { Tenant, Plan } from '@/system/models';
|
||||
import { IPaymentContext } from '@/interfaces';
|
||||
import { NotAllowedChangeSubscriptionPlan } from '@/exceptions';
|
||||
import { NoPaymentModelWithPricedPlan } from '@/exceptions';
|
||||
|
||||
export default class Subscription<PaymentModel> {
|
||||
paymentContext: IPaymentContext|null;
|
||||
@@ -19,7 +20,7 @@ export default class Subscription<PaymentModel> {
|
||||
|
||||
/**
|
||||
* Subscripe to the given plan.
|
||||
* @param {Plan} plan
|
||||
* @param {Plan} plan
|
||||
* @throws {NotAllowedChangeSubscriptionPlan}
|
||||
*/
|
||||
async subscribe(
|
||||
@@ -28,8 +29,11 @@ export default class Subscription<PaymentModel> {
|
||||
paymentModel?: PaymentModel,
|
||||
subscriptionSlug: string = 'main',
|
||||
) {
|
||||
if (plan.price < 0) {
|
||||
await this.paymentContext.makePayment(paymentModel);
|
||||
this.validateIfPlanHasPriceNoPayment(plan, paymentModel);
|
||||
|
||||
// @todo
|
||||
if (plan.price > 0) {
|
||||
await this.paymentContext.makePayment(paymentModel, plan);
|
||||
}
|
||||
const subscription = await tenant.$relatedQuery('subscriptions')
|
||||
.modify('subscriptionBySlug', subscriptionSlug)
|
||||
@@ -39,8 +43,7 @@ export default class Subscription<PaymentModel> {
|
||||
if (subscription && subscription.active()) {
|
||||
throw new NotAllowedChangeSubscriptionPlan;
|
||||
|
||||
// In case there is already subscription associated to the given tenant.
|
||||
// renew it.
|
||||
// In case there is already subscription associated to the given tenant renew it.
|
||||
} else if(subscription && subscription.inactive()) {
|
||||
await subscription.renew(plan);
|
||||
|
||||
@@ -49,4 +52,15 @@ export default class Subscription<PaymentModel> {
|
||||
await tenant.newSubscription(subscriptionSlug, plan);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Throw error in plan has price and no payment model.
|
||||
* @param {Plan} plan -
|
||||
* @param {PaymentModel} paymentModel - payment input.
|
||||
*/
|
||||
validateIfPlanHasPriceNoPayment(plan: Plan, paymentModel: PaymentMode) {
|
||||
if (plan.price > 0 && !paymentModel) {
|
||||
throw new NoPaymentModelWithPricedPlan();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
import { Service, Inject } from 'typedi';
|
||||
import { Plan, Tenant, License } from '@/system/models';
|
||||
import { Plan, Tenant } from '@/system/models';
|
||||
import Subscription from '@/services/Subscription/Subscription';
|
||||
import VocuherPaymentMethod from '@/services/Payment/LicensePaymentMethod';
|
||||
import LicensePaymentMethod from '@/services/Payment/LicensePaymentMethod';
|
||||
import PaymentContext from '@/services/Payment';
|
||||
import SubscriptionSMSMessages from '@/services/Subscription/SMSMessages';
|
||||
import SubscriptionMailMessages from '@/services/Subscription/MailMessages';
|
||||
import { ILicensePaymentModel } from '@/interfaces';
|
||||
|
||||
@Service()
|
||||
export default class SubscriptionService {
|
||||
@@ -14,31 +15,37 @@ export default class SubscriptionService {
|
||||
@Inject()
|
||||
mailMessages: SubscriptionMailMessages;
|
||||
|
||||
@Inject('logger')
|
||||
logger: any;
|
||||
|
||||
/**
|
||||
* Handles the payment process via license code and than subscribe to
|
||||
* the given tenant.
|
||||
*
|
||||
* @param {number} tenantId
|
||||
* @param {String} planSlug
|
||||
* @param {string} licenseCode
|
||||
*
|
||||
* @return {Promise}
|
||||
*/
|
||||
async subscriptionViaLicense(
|
||||
tenantId: number,
|
||||
planSlug: string,
|
||||
licenseCode: string,
|
||||
paymentModel?: ILicensePaymentModel,
|
||||
subscriptionSlug: string = 'main',
|
||||
) {
|
||||
this.logger.info('[subscription_via_license] try to subscribe via given license.', {
|
||||
tenantId, paymentModel
|
||||
});
|
||||
const plan = await Plan.query().findOne('slug', planSlug);
|
||||
const tenant = await Tenant.query().findById(tenantId);
|
||||
const licenseModel = await License.query().findOne('license_code', licenseCode);
|
||||
|
||||
const paymentViaLicense = new VocuherPaymentMethod();
|
||||
const paymentViaLicense = new LicensePaymentMethod();
|
||||
const paymentContext = new PaymentContext(paymentViaLicense);
|
||||
|
||||
const subscription = new Subscription(paymentContext);
|
||||
|
||||
return subscription.subscribe(tenant, plan, licenseModel, subscriptionSlug);
|
||||
await subscription.subscribe(tenant, plan, paymentModel, subscriptionSlug);
|
||||
this.logger.info('[subscription_via_license] payment via license done successfully.', {
|
||||
tenantId, paymentModel
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ exports.up = function(knex) {
|
||||
table.string('email');
|
||||
table.string('token').unique();
|
||||
table.integer('tenant_id').unsigned();
|
||||
table.timestamps();
|
||||
table.datetime('created_at');
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -7,4 +7,11 @@ export default class UserInvite extends SystemModel {
|
||||
static get tableName() {
|
||||
return 'user_invites';
|
||||
}
|
||||
|
||||
/**
|
||||
* Timestamps columns.
|
||||
*/
|
||||
get timestamps() {
|
||||
return ['createdAt'];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,4 +7,11 @@ export default class PasswordResets extends SystemModel {
|
||||
static get tableName() {
|
||||
return 'password_resets';
|
||||
}
|
||||
|
||||
/**
|
||||
* Timestamps columns.
|
||||
*/
|
||||
get timestamps() {
|
||||
return ['createdAt'];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ export default class Plan extends mixin(SystemModel) {
|
||||
/**
|
||||
* Timestamps columns.
|
||||
*/
|
||||
static get timestamps() {
|
||||
get timestamps() {
|
||||
return ['createdAt', 'updatedAt'];
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ export default class PlanSubscription extends mixin(SystemModel) {
|
||||
/**
|
||||
* Timestamps columns.
|
||||
*/
|
||||
static get timestamps() {
|
||||
get timestamps() {
|
||||
return ['createdAt', 'updatedAt'];
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ export default class SystemUser extends mixin(SystemModel) {
|
||||
/**
|
||||
* Timestamps columns.
|
||||
*/
|
||||
static get timestamps() {
|
||||
get timestamps() {
|
||||
return ['createdAt', 'updatedAt'];
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,13 @@ export default class Tenant extends BaseModel {
|
||||
return 'tenants';
|
||||
}
|
||||
|
||||
/**
|
||||
* Timestamps columns.
|
||||
*/
|
||||
get timestamps() {
|
||||
return ['createdAt', 'updatedAt'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Query modifiers.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user