Compare commits

...

22 Commits

Author SHA1 Message Date
copilot-swe-agent[bot]
d035bcc9a3 Fix throttle TTL to use milliseconds per NestJS v6 docs
Co-authored-by: abouolia <2197422+abouolia@users.noreply.github.com>
2025-10-30 20:05:18 +00:00
copilot-swe-agent[bot]
d9e07fdd99 Initial plan 2025-10-30 20:02:23 +00:00
Ahmed Bouhuolia
8c5a359610 feat: api endpoints throttle 2025-10-30 21:56:41 +02:00
Ahmed Bouhuolia
844a050c9a Merge pull request #836 from bigcapitalhq/auth-pages-errors-handler
fix: auth pages errors handler
2025-10-30 19:29:41 +02:00
Ahmed Bouhuolia
0588a30c88 fix: auth pages errors handler 2025-10-30 19:27:29 +02:00
Ahmed Bouhuolia
4a0091d3f8 Merge pull request #835 from bigcapitalhq/fix-edit-payment-transaction
fix: edit payment transaction
2025-10-29 12:57:24 +02:00
Ahmed Bouhuolia
fc89cfb14a fix: edit payment transaction 2025-10-29 12:54:12 +02:00
Ahmed Bouhuolia
98401b5a01 Merge pull request #438 from bigcapitalhq/BIG-166
feat: one-command setup script
2025-10-29 00:39:45 +02:00
Ahmed Bouhuolia
3afe4f470f Update setup.sh
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-10-29 00:39:26 +02:00
Ahmed Bouhuolia
b4281a71d4 Apply suggestion from @Copilot
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-10-29 00:39:15 +02:00
Ahmed Bouhuolia
87c127fabd Merge branch 'develop' into BIG-166 2025-10-29 00:35:33 +02:00
Ahmed Bouhuolia
50d9e8d375 Merge pull request #833 from bigcapitalhq/fix-more-bugs
fix: issues related to PUT operations
2025-10-28 18:14:00 +02:00
Ahmed Bouhuolia
4839a6dea8 Update packages/server/src/modules/Bills/commands/EditBill.service.ts
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-10-28 18:13:52 +02:00
Ahmed Bouhuolia
f736c3f9eb fix: issues related to PUT operations 2025-10-28 18:12:08 +02:00
Ahmed Bouhuolia
368c85a01a Merge pull request #832 from bigcapitalhq/validate-tenant-existance-in-guards
fix: validate request org id existance in guards
2025-10-25 15:20:42 +02:00
Ahmed Bouhuolia
5d792fea26 fix 2025-10-25 15:19:10 +02:00
Ahmed Bouhuolia
1bccba572a fix: validate request org id existance in guards 2025-10-25 15:15:13 +02:00
Ahmed Bouhuolia
900921e6ba Merge pull request #831 from bigcapitalhq/fix-tenant-build
fix: organization build db connection error
2025-10-25 14:59:28 +02:00
Ahmed Bouhuolia
2b4772a070 fix: organization build db connection error 2025-10-25 14:57:38 +02:00
Ahmed Bouhuolia
8852a4a0f8 Merge pull request #830 from bigcapitalhq/fix-system-tenant-migration
fix: seed migration issue
2025-10-25 14:02:19 +02:00
Ahmed Bouhuolia
8e2cd98689 fix: the menu labels 2024-05-12 18:32:19 +02:00
Ahmed Bouhuolia
f934797929 feat: one-command setup script 2024-05-12 18:07:38 +02:00
62 changed files with 743 additions and 318 deletions

View File

@@ -39,6 +39,7 @@
"@casl/ability": "^5.4.3",
"@lemonsqueezy/lemonsqueezy.js": "^2.2.0",
"@liaoliaots/nestjs-redis": "^10.0.0",
"@nest-lab/throttler-storage-redis": "^1.1.0",
"@nestjs/bull": "^10.2.1",
"@nestjs/bullmq": "^10.2.2",
"@nestjs/cache-manager": "^2.2.2",

View File

@@ -14,6 +14,7 @@ import jwt from './jwt';
import mail from './mail';
import loops from './loops';
import bankfeed from './bankfeed';
import throttle from './throttle';
export const config = [
systemDatabase,
@@ -32,4 +33,5 @@ export const config = [
mail,
loops,
bankfeed,
throttle,
];

View File

@@ -0,0 +1,16 @@
import { registerAs } from '@nestjs/config';
export default registerAs('throttle', () => ({
global: {
// TTL in milliseconds (NestJS throttler v6+)
ttl: parseInt(process.env.THROTTLE_GLOBAL_TTL ?? '60000', 10),
limit: parseInt(process.env.THROTTLE_GLOBAL_LIMIT ?? '100', 10),
},
auth: {
// TTL in milliseconds (NestJS throttler v6+)
ttl: parseInt(process.env.THROTTLE_AUTH_TTL ?? '60000', 10),
limit: parseInt(process.env.THROTTLE_AUTH_LIMIT ?? '10', 10),
},
}));

View File

@@ -1,7 +1,7 @@
import { MiddlewareConsumer, Module, RequestMethod } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { EventEmitterModule } from '@nestjs/event-emitter';
import { APP_INTERCEPTOR } from '@nestjs/core';
import { APP_GUARD, APP_INTERCEPTOR } from '@nestjs/core';
import { join } from 'path';
import { ServeStaticModule } from '@nestjs/serve-static';
import { RedisModule } from '@liaoliaots/nestjs-redis';
@@ -95,6 +95,8 @@ import { BankingCategorizeModule } from '../BankingCategorize/BankingCategorize.
import { TenantModelsInitializeModule } from '../Tenancy/TenantModelsInitialize.module';
import { BillLandedCostsModule } from '../BillLandedCosts/BillLandedCosts.module';
import { SocketModule } from '../Socket/Socket.module';
import { ThrottlerGuard } from '@nestjs/throttler';
import { AppThrottleModule } from './AppThrottle.module';
@Module({
imports: [
@@ -126,6 +128,7 @@ import { SocketModule } from '../Socket/Socket.module';
],
}),
PassportModule,
AppThrottleModule,
BullModule.forRootAsync({
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => ({
@@ -231,6 +234,10 @@ import { SocketModule } from '../Socket/Socket.module';
],
controllers: [AppController],
providers: [
{
provide: APP_GUARD,
useClass: ThrottlerGuard,
},
{
provide: APP_INTERCEPTOR,
useClass: SerializeInterceptor,

View File

@@ -0,0 +1,48 @@
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { ThrottlerModule } from '@nestjs/throttler';
import { ThrottlerStorageRedisService } from '@nest-lab/throttler-storage-redis';
@Module({
imports: [
ThrottlerModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (configService: ConfigService) => {
const host = configService.get<string>('redis.host') || 'localhost';
const port = Number(configService.get<number>('redis.port') || 6379);
const password = configService.get<string>('redis.password');
const db = configService.get<number>('redis.db');
const globalTtl = configService.get<number>('throttle.global.ttl');
const globalLimit = configService.get<number>('throttle.global.limit');
const authTtl = configService.get<number>('throttle.auth.ttl');
const authLimit = configService.get<number>('throttle.auth.limit');
return {
throttlers: [
{
name: 'default',
ttl: globalTtl,
limit: globalLimit,
},
{
name: 'auth',
ttl: authTtl,
limit: authLimit,
},
],
storage: new ThrottlerStorageRedisService({
host,
port,
password,
db,
}),
};
},
}),
],
})
export class AppThrottleModule { }

View File

@@ -8,6 +8,7 @@ import {
Request,
UseGuards,
} from '@nestjs/common';
import { Throttle } from '@nestjs/throttler';
import {
ApiTags,
ApiOperation,
@@ -28,6 +29,7 @@ import { SystemUser } from '../System/models/SystemUser';
@ApiTags('Auth')
@ApiExcludeController()
@PublicRoute()
@Throttle({ auth: {} })
export class AuthController {
constructor(
private readonly authApp: AuthenticationApplication,

View File

@@ -6,6 +6,7 @@ import {
} from '@nestjs/swagger';
import { GetAuthenticatedAccount } from './queries/GetAuthedAccount.service';
import { Controller, Get, Post } from '@nestjs/common';
import { Throttle } from '@nestjs/throttler';
import { IgnoreTenantSeededRoute } from '../Tenancy/EnsureTenantIsSeeded.guards';
import { IgnoreTenantInitializedRoute } from '../Tenancy/EnsureTenantIsInitialized.guard';
import { AuthenticationApplication } from './AuthApplication.sevice';
@@ -18,11 +19,12 @@ import { IgnoreUserVerifiedRoute } from './guards/EnsureUserVerified.guard';
@IgnoreTenantSeededRoute()
@IgnoreTenantInitializedRoute()
@IgnoreUserVerifiedRoute()
@Throttle({ auth: {} })
export class AuthedController {
constructor(
private readonly getAuthedAccountService: GetAuthenticatedAccount,
private readonly authApp: AuthenticationApplication,
) {}
) { }
@Post('/signup/verify/resend')
@ApiOperation({ summary: 'Resend the signup confirmation message' })

View File

@@ -24,7 +24,7 @@ export class AuthSendResetPasswordService {
@Inject(SystemUser.name)
private readonly systemUserModel: typeof SystemUser,
) {}
) { }
/**
* Sends the given email reset password email.
@@ -33,8 +33,9 @@ export class AuthSendResetPasswordService {
async sendResetPassword(email: string): Promise<void> {
const user = await this.systemUserModel
.query()
.findOne({ email })
.throwIfNotFound();
.findOne({ email });
if (!user) return;
const token: string = uniqid();
@@ -48,10 +49,8 @@ export class AuthSendResetPasswordService {
this.deletePasswordResetToken(email);
// Creates a new password reset row with unique token.
const passwordReset = await this.resetPasswordModel.query().insert({
email,
token,
});
await this.resetPasswordModel.query().insert({ email, token });
// Triggers sent reset password event.
await this.eventPublisher.emitAsync(events.auth.sendResetPassword, {
user,

View File

@@ -1,9 +1,11 @@
import { ClsService } from 'nestjs-cls';
import { Inject, Injectable, UnauthorizedException } from '@nestjs/common';
import { Inject, Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { SystemUser } from '@/modules/System/models/SystemUser';
import { ModelObject } from 'objection';
import { JwtPayload } from '../Auth.interfaces';
import { InvalidEmailPasswordException } from '../exceptions/InvalidEmailPassword.exception';
import { UserNotFoundException } from '../exceptions/UserNotFound.exception';
@Injectable()
export class AuthSigninService {
@@ -12,7 +14,7 @@ export class AuthSigninService {
private readonly systemUserModel: typeof SystemUser,
private readonly jwtService: JwtService,
private readonly clsService: ClsService,
) {}
) { }
/**
* Validates the given email and password.
@@ -32,14 +34,10 @@ export class AuthSigninService {
.findOne({ email })
.throwIfNotFound();
} catch (err) {
throw new UnauthorizedException(
`There isn't any user with email: ${email}`,
);
throw new InvalidEmailPasswordException(email);
}
if (!(await user.checkPassword(password))) {
throw new UnauthorizedException(
`Wrong password for user with email: ${email}`,
);
throw new InvalidEmailPasswordException(email);
}
return user;
}
@@ -61,9 +59,7 @@ export class AuthSigninService {
this.clsService.set('tenantId', user.tenantId);
this.clsService.set('userId', user.id);
} catch (error) {
throw new UnauthorizedException(
`There isn't any user with email: ${payload.sub}`,
);
throw new UserNotFoundException(String(payload.sub));
}
return payload;
}

View File

@@ -32,7 +32,7 @@ export class AuthSignupService {
@Inject(SystemUser.name)
private readonly systemUserModel: typeof SystemUser,
) {}
) { }
/**
* Registers a new tenant with user from user input.
@@ -121,7 +121,6 @@ export class AuthSignupService {
const isAllowedDomain = signupRestrictions.allowedDomains.some(
(domain) => emailDomain === domain,
);
if (!isAllowedEmail && !isAllowedDomain) {
throw new ServiceError(
ERRORS.SIGNUP_RESTRICTED_NOT_ALLOWED,

View File

@@ -0,0 +1,15 @@
import { UnauthorizedException } from '@nestjs/common';
import { ERRORS } from '../Auth.constants';
export class InvalidEmailPasswordException extends UnauthorizedException {
constructor(email: string) {
super({
statusCode: 401,
error: 'Unauthorized',
message: `Invalid email or password for ${email}`,
code: ERRORS.INVALID_DETAILS,
});
}
}

View File

@@ -0,0 +1,13 @@
import { UnauthorizedException } from '@nestjs/common';
import { ERRORS } from '../Auth.constants';
export class UserNotFoundException extends UnauthorizedException {
constructor(identifier: string) {
super({
statusCode: 401,
error: 'Unauthorized',
message: `User not found: ${identifier}`,
code: ERRORS.USER_NOT_FOUND,
});
}
}

View File

@@ -28,7 +28,12 @@ export class GetBankRulesTransformer extends Transformer {
* @returns {string}
*/
protected assignCategoryFormatted(bankRule: any) {
return getCashflowTransactionFormattedType(bankRule.assignCategory);
const translationKey = getCashflowTransactionFormattedType(
bankRule.assignCategory,
);
return translationKey
? this.context.i18n.t(translationKey)
: bankRule.assignCategory;
}
/**

View File

@@ -19,7 +19,7 @@ export class BillPaymentsPages {
@Inject(BillPayment.name)
private readonly billPaymentModel: TenantModelProxy<typeof BillPayment>,
) {}
) { }
/**
* Retrieve bill payment with associated metadata.
@@ -46,7 +46,8 @@ export class BillPaymentsPages {
paymentAmount: entry.paymentAmount,
}));
const resPayableBills = await Bill.query()
const resPayableBills = await this.billModel()
.query()
.modify('opened')
.modify('dueBills')
.where('vendor_id', billPayment.vendorId)

View File

@@ -28,8 +28,8 @@ export class EditBillService {
private transformerDTO: BillDTOTransformer,
@Inject(Bill.name) private billModel: TenantModelProxy<typeof Bill>,
@Inject(Vendor.name) private contactModel: TenantModelProxy<typeof Vendor>,
) {}
@Inject(Vendor.name) private vendorModel: TenantModelProxy<typeof Vendor>,
) { }
/**
* Edits details of the given bill id with associated entries.
@@ -58,10 +58,9 @@ export class EditBillService {
this.validators.validateBillExistance(oldBill);
// Retrieve vendor details or throw not found service error.
const vendor = await this.contactModel()
const vendor = await this.vendorModel()
.query()
.findById(billDTO.vendorId)
.modify('vendor')
.throwIfNotFound();
// Validate bill number uniqiness on the storage.

View File

@@ -1,6 +1,7 @@
import { ToNumber } from '@/common/decorators/Validators';
import { ItemEntryDto } from '@/modules/TransactionItemEntry/dto/ItemEntry.dto';
import { Type } from 'class-transformer';
import { Type, Transform } from 'class-transformer';
import { parseBoolean } from '@/utils/parse-boolean';
import { ApiProperty } from '@nestjs/swagger';
import {
ArrayMinSize,
@@ -31,6 +32,7 @@ export class BillEntryDto extends ItemEntryDto {
})
@IsOptional()
@IsBoolean()
@Transform(({ value }) => parseBoolean(value, false))
landedCost?: boolean;
}
@@ -211,5 +213,5 @@ export class CommandBillDto {
adjustment?: number;
}
export class CreateBillDto extends CommandBillDto {}
export class EditBillDto extends CommandBillDto {}
export class CreateBillDto extends CommandBillDto { }
export class EditBillDto extends CommandBillDto { }

View File

@@ -10,6 +10,7 @@ import { BranchTransactionDTOTransformer } from '@/modules/Branches/integrations
import { WarehouseTransactionDTOTransform } from '@/modules/Warehouses/Integrations/WarehouseTransactionDTOTransform';
import { BrandingTemplateDTOTransformer } from '../../PdfTemplate/BrandingTemplateDTOTransformer';
import { assocItemEntriesDefaultIndex } from '@/utils/associate-item-entries-index';
import { formatDateFields } from '@/utils/format-date-fields';
import { CreditNoteAutoIncrementService } from './CreditNoteAutoIncrement.service';
import { CreditNote } from '../models/CreditNote';
import {
@@ -33,7 +34,7 @@ export class CommandCreditNoteDTOTransform {
private readonly warehouseDTOTransform: WarehouseTransactionDTOTransform,
private readonly brandingTemplatesTransformer: BrandingTemplateDTOTransformer,
private readonly creditNoteAutoIncrement: CreditNoteAutoIncrementService,
) {}
) { }
/**
* Transforms the credit/edit DTO to model.
@@ -70,7 +71,10 @@ export class CommandCreditNoteDTOTransform {
autoNextNumber;
const initialDTO = {
...omit(creditNoteDTO, ['open', 'attachments']),
...formatDateFields(
omit(creditNoteDTO, ['open', 'attachments']),
['creditNoteDate'],
),
creditNoteNumber,
amount,
currencyCode: customerCurrencyCode,
@@ -78,8 +82,8 @@ export class CommandCreditNoteDTOTransform {
entries,
...(creditNoteDTO.open &&
!oldCreditNote?.openedAt && {
openedAt: moment().toMySqlDateTime(),
}),
openedAt: moment().toMySqlDateTime(),
}),
refundedAmount: 0,
invoicesAmount: 0,
};

View File

@@ -185,5 +185,5 @@ export class CommandExpenseDto {
attachments?: AttachmentDto[];
}
export class CreateExpenseDto extends CommandExpenseDto {}
export class EditExpenseDto extends CommandExpenseDto {}
export class CreateExpenseDto extends CommandExpenseDto { }
export class EditExpenseDto extends CommandExpenseDto { }

View File

@@ -26,6 +26,7 @@ import { GetCurrentOrganizationService } from './queries/GetCurrentOrganization.
import { UpdateOrganizationService } from './commands/UpdateOrganization.service';
import { IgnoreTenantInitializedRoute } from '../Tenancy/EnsureTenantIsInitialized.guard';
import { IgnoreTenantSeededRoute } from '../Tenancy/EnsureTenantIsSeeded.guards';
import { IgnoreTenantModelsInitialize } from '../Tenancy/TenancyInitializeModels.guard';
import { GetBuildOrganizationBuildJob } from './commands/GetBuildOrganizationJob.service';
import { OrganizationBaseCurrencyLocking } from './Organization/OrganizationBaseCurrencyLocking.service';
import {
@@ -39,6 +40,7 @@ import { ApiCommonHeaders } from '@/common/decorators/ApiCommonHeaders';
@Controller('organization')
@IgnoreTenantInitializedRoute()
@IgnoreTenantSeededRoute()
@IgnoreTenantModelsInitialize()
@ApiExtraModels(GetCurrentOrganizationResponseDto)
@ApiCommonHeaders()
export class OrganizationController {
@@ -48,7 +50,7 @@ export class OrganizationController {
private readonly updateOrganizationService: UpdateOrganizationService,
private readonly getBuildOrganizationJobService: GetBuildOrganizationBuildJob,
private readonly orgBaseCurrencyLockingService: OrganizationBaseCurrencyLocking,
) {}
) { }
@Post('build')
@HttpCode(200)

View File

@@ -18,7 +18,7 @@ export class PaymentsReceivedPagesService {
@Inject(PaymentReceived.name)
private readonly paymentReceived: TenantModelProxy<typeof PaymentReceived>,
) {}
) { }
/**
* Retrive page invoices entries from the given sale invoices models.
@@ -58,11 +58,10 @@ export class PaymentsReceivedPagesService {
/**
* Retrieve the payment receive details of the given id.
* @param {number} tenantId - Tenant id.
* @param {Integer} paymentReceiveId - Payment receive id.
* @param {number} paymentReceiveId - Payment receive id.
*/
public async getPaymentReceiveEditPage(paymentReceiveId: number): Promise<{
paymentReceive: Omit<PaymentReceived, 'entries'>;
data: Omit<PaymentReceived, 'entries'>;
entries: IPaymentReceivePageEntry[];
}> {
// Retrieve payment receive.
@@ -100,7 +99,7 @@ export class PaymentsReceivedPagesService {
const entries = [...paymentEntries, ...restReceivableEntries];
return {
paymentReceive: omit(paymentReceive, ['entries']),
data: omit(paymentReceive, ['entries']),
entries,
};
}

View File

@@ -17,12 +17,11 @@ import { TenantUser } from '../Tenancy/TenancyModels/models/TenantUser.model';
@Injectable()
export class AuthorizationGuard implements CanActivate {
constructor(
private reflector: Reflector,
private readonly clsService: ClsService,
@Inject(TenantUser.name)
private readonly tenantUserModel: TenantModelProxy<typeof TenantUser>,
) {}
) { }
/**
* Checks if the user has the required abilities to access the route
@@ -31,7 +30,7 @@ export class AuthorizationGuard implements CanActivate {
*/
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest<Request>();
const { tenantId, user } = request as any;
const { user } = request as any;
if (ABILITIES_CACHE.has(user.id)) {
(request as any).ability = ABILITIES_CACHE.get(user.id);
@@ -40,7 +39,6 @@ export class AuthorizationGuard implements CanActivate {
(request as any).ability = ability;
ABILITIES_CACHE.set(user.id, ability);
}
return true;
}

View File

@@ -21,7 +21,7 @@ enum DiscountType {
Amount = 'amount',
}
class SaleEstimateEntryDto extends ItemEntryDto {}
class SaleEstimateEntryDto extends ItemEntryDto { }
class AttachmentDto {
@IsString()
@@ -140,6 +140,7 @@ export class CommandSaleEstimateDto {
},
],
})
attachments?: AttachmentDto[];
@IsOptional()
@ToNumber()
@@ -177,5 +178,5 @@ export class CommandSaleEstimateDto {
adjustment?: number;
}
export class CreateSaleEstimateDto extends CommandSaleEstimateDto {}
export class EditSaleEstimateDto extends CommandSaleEstimateDto {}
export class CreateSaleEstimateDto extends CommandSaleEstimateDto { }
export class EditSaleEstimateDto extends CommandSaleEstimateDto { }

View File

@@ -24,7 +24,7 @@ import { ApiCommonHeaders } from '@/common/decorators/ApiCommonHeaders';
@ApiExtraModels(TaxRateResponseDto)
@ApiCommonHeaders()
export class TaxRatesController {
constructor(private readonly taxRatesApplication: TaxRatesApplication) {}
constructor(private readonly taxRatesApplication: TaxRatesApplication) { }
@Post()
@ApiOperation({ summary: 'Create a new tax rate.' })

View File

@@ -18,7 +18,7 @@ export class EnsureTenantIsInitializedGuard implements CanActivate {
constructor(
private readonly tenancyContext: TenancyContext,
private reflector: Reflector,
) {}
) { }
/**
* Validate the tenant of the current request is initialized..
@@ -41,6 +41,12 @@ export class EnsureTenantIsInitializedGuard implements CanActivate {
}
const tenant = await this.tenancyContext.getTenant();
if (!tenant) {
throw new UnauthorizedException({
message: 'Tenant not found.',
errors: [{ type: 'TENANT.NOT.FOUND' }],
});
}
if (!tenant?.initializedAt) {
throw new UnauthorizedException({
statusCode: 400,

View File

@@ -19,7 +19,7 @@ export class EnsureTenantIsSeededGuard implements CanActivate {
constructor(
private readonly tenancyContext: TenancyContext,
private reflector: Reflector,
) {}
) { }
/**
* Validate the tenant of the current request is seeded.
@@ -41,6 +41,12 @@ export class EnsureTenantIsSeededGuard implements CanActivate {
}
const tenant = await this.tenancyContext.getTenant();
if (!tenant) {
throw new UnauthorizedException({
message: 'Tenant not found.',
errors: [{ type: 'TENANT.NOT.FOUND' }],
});
}
if (!tenant.seededAt) {
throw new UnauthorizedException({
message: 'Tenant database is not seeded with initial data yet.',

View File

@@ -2,6 +2,7 @@ import { Inject, Injectable } from '@nestjs/common';
import { ClsService } from 'nestjs-cls';
import { SystemUser } from '../System/models/SystemUser';
import { TenantModel } from '../System/models/TenantModel';
import { ServiceError } from '../Items/ServiceError';
@Injectable()
export class TenancyContext {
@@ -13,7 +14,7 @@ export class TenancyContext {
@Inject(TenantModel.name)
private readonly systemTenantModel: typeof TenantModel,
) {}
) { }
/**
* Get the current tenant.

View File

@@ -23,7 +23,7 @@ export class TenantDBManager {
@Inject(SystemKnexConnection)
private readonly systemKnex: Knex,
) {}
) { }
/**
* Retrieves the tenant database name.
@@ -45,8 +45,8 @@ export class TenantDBManager {
const results = await this.systemKnex.raw(
'SELECT * FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = "' +
databaseName +
'"',
databaseName +
'"',
);
return results[0].length > 0;
}
@@ -73,12 +73,12 @@ export class TenantDBManager {
*/
public async dropDatabaseIfExists() {
const tenant = await this.tenancyContext.getTenant();
const isExists = await this.databaseExists(tenant);
const isExists = await this.databaseExists();
if (!isExists) {
return;
}
await this.dropDatabase(tenant);
await this.dropDatabase();
}
/**
@@ -115,7 +115,7 @@ export class TenantDBManager {
* @return {Promise<void>}
*/
async throwErrorIfTenantDBExists(tenant: TenantModel) {
const isExists = await this.databaseExists(tenant);
const isExists = await this.databaseExists();
if (isExists) {
throw new TenantDBAlreadyExists();
}

View File

@@ -8,6 +8,7 @@ import { BranchTransactionDTOTransformer } from '@/modules/Branches/integrations
import { WarehouseTransactionDTOTransform } from '@/modules/Warehouses/Integrations/WarehouseTransactionDTOTransform';
import { VendorCredit } from '../models/VendorCredit';
import { assocItemEntriesDefaultIndex } from '@/utils/associate-item-entries-index';
import { formatDateFields } from '@/utils/format-date-fields';
import { VendorCreditAutoIncrementService } from './VendorCreditAutoIncrement.service';
import { ServiceError } from '@/modules/Items/ServiceError';
import { Injectable } from '@nestjs/common';
@@ -30,7 +31,7 @@ export class VendorCreditDTOTransformService {
private branchDTOTransform: BranchTransactionDTOTransformer,
private warehouseDTOTransform: WarehouseTransactionDTOTransform,
private vendorCreditAutoIncrement: VendorCreditAutoIncrementService,
) {}
) { }
/**
* Transforms the credit/edit vendor credit DTO to model.
@@ -70,7 +71,10 @@ export class VendorCreditDTOTransformService {
autoNextNumber;
const initialDTO = {
...omit(vendorCreditDTO, ['open', 'attachments']),
...formatDateFields(
omit(vendorCreditDTO, ['open', 'attachments']),
['vendorCreditDate'],
),
amount,
currencyCode: vendorCurrencyCode,
exchangeRate: vendorCreditDTO.exchangeRate || 1,
@@ -78,8 +82,8 @@ export class VendorCreditDTOTransformService {
entries,
...(vendorCreditDTO.open &&
!oldVendorCredit?.openedAt && {
openedAt: moment().toMySqlDateTime(),
}),
openedAt: moment().toMySqlDateTime(),
}),
};
return composeAsync(
this.branchDTOTransform.transformDTO<VendorCredit>,

View File

@@ -102,13 +102,13 @@ export const StatusAccessor = (row) => {
return (
<Choose>
<Choose.When condition={!!row.is_published}>
<Tag minimal={true} round={true}>
<Tag round>
<T id={'published'} />
</Tag>
</Choose.When>
<Choose.Otherwise>
<Tag minimal={true} intent={Intent.WARNING} round={true}>
<Tag intent={Intent.WARNING} round>
<T id={'draft'} />
</Tag>
</Choose.Otherwise>

View File

@@ -42,7 +42,7 @@ export const handleDeleteErrors = (errors) => {
export const AccountCodeAccessor = (row) =>
!isBlank(row.code) ? (
<Tag minimal={true} round={true} intent={Intent.NONE}>
<Tag minimal round intent={Intent.NONE}>
{row.code}
</Tag>
) : null;

View File

@@ -32,13 +32,11 @@ export default function Login() {
email: values.crediential,
password: values.password,
}).catch(({ response }) => {
const {
data: { errors },
} = response;
const toastBuilders = transformLoginErrorsToToasts(errors);
const { data: error } = response;
const toastMessages = transformLoginErrorsToToasts(error);
toastBuilders.forEach((builder) => {
Toaster.show(builder);
toastMessages.forEach((toastMessage) => {
Toaster.show(toastMessage);
});
setSubmitting(false);
});

View File

@@ -35,7 +35,7 @@ export default function SendResetPassword() {
// Handle form submitting.
const handleSubmit = (values, { setSubmitting }) => {
sendResetPasswordMutate({ email: values.crediential })
.then((response) => {
.then(() => {
AppToaster.show({
message: intl.get('check_your_email_for_a_link_to_reset'),
intent: Intent.SUCCESS,
@@ -43,20 +43,9 @@ export default function SendResetPassword() {
history.push('/auth/login');
setSubmitting(false);
})
.catch(
({
response: {
data: { errors },
},
}) => {
const toastBuilders = transformSendResetPassErrorsToToasts(errors);
toastBuilders.forEach((builder) => {
AppToaster.show(builder);
});
setSubmitting(false);
},
);
.catch(() => {
setSubmitting(false);
});
};
return (
@@ -82,11 +71,17 @@ function SendResetPasswordFooterLinks() {
<AuthFooterLinks>
{!signupDisabled && (
<AuthFooterLink>
<T id={'dont_have_an_account'} /> <Link to={'/auth/register'}><T id={'sign_up'} /></Link>
<T id={'dont_have_an_account'} />{' '}
<Link to={'/auth/register'}>
<T id={'sign_up'} />
</Link>
</AuthFooterLink>
)}
<AuthFooterLink>
<T id={'return_to'} /> <Link to={'/auth/login'}><T id={'sign_in'} /></Link>
<T id={'return_to'} />{' '}
<Link to={'/auth/login'}>
<T id={'sign_in'} />
</Link>
</AuthFooterLink>
</AuthFooterLinks>
);

View File

@@ -13,12 +13,17 @@ export function AuthenticationLoadingOverlay() {
}
const AuthOverlayRoot = styled.div`
--x-color-background: rgba(252, 253, 255, 0.5);
.bp4-dark & {
--x-color-background: rgba(37, 42, 49, 0.60);
}
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
background: rgba(252, 253, 255, 0.5);
background: var(--x-color-background);
display: flex;
justify-content: center;
`;

View File

@@ -45,10 +45,10 @@ export const InviteAcceptSchema = Yup.object().shape({
password: Yup.string().min(4).required().label(intl.get('password')),
});
export const transformSendResetPassErrorsToToasts = (errors) => {
export const transformSendResetPassErrorsToToasts = (error) => {
const toastBuilders = [];
if (errors.find((e) => e.type === 'EMAIL.NOT.REGISTERED')) {
if (error.code === ERRORS.EMAIL_NOT_REGISTERED) {
toastBuilders.push({
message: intl.get('we_couldn_t_find_your_account_with_that_email'),
intent: Intent.DANGER,
@@ -57,38 +57,26 @@ export const transformSendResetPassErrorsToToasts = (errors) => {
return toastBuilders;
};
export const transformLoginErrorsToToasts = (errors) => {
export const transformLoginErrorsToToasts = (error) => {
const toastBuilders = [];
if (errors.find((e) => e.type === LOGIN_ERRORS.INVALID_DETAILS)) {
if (error.code === LOGIN_ERRORS.INVALID_DETAILS) {
toastBuilders.push({
message: intl.get('email_and_password_entered_did_not_match'),
intent: Intent.DANGER,
});
}
if (errors.find((e) => e.type === LOGIN_ERRORS.USER_INACTIVE)) {
} else if (error.code === LOGIN_ERRORS.USER_INACTIVE) {
toastBuilders.push({
message: intl.get('the_user_has_been_suspended_from_admin'),
intent: Intent.DANGER,
});
}
if (errors.find((e) => e.type === LOGIN_ERRORS.LOGIN_TO_MANY_ATTEMPTS)) {
toastBuilders.push({
message: intl.get('your_account_has_been_locked'),
intent: Intent.DANGER,
});
}
return toastBuilders;
};
export const transformRegisterErrorsToForm = (errors) => {
const formErrors = {};
if (errors.some((e) => e.type === REGISTER_ERRORS.PHONE_NUMBER_EXISTS)) {
formErrors.phone_number = intl.get(
'the_phone_number_already_used_in_another_account',
);
}
if (errors.some((e) => e.type === REGISTER_ERRORS.EMAIL_EXISTS)) {
formErrors.email = intl.get('the_email_already_used_in_another_account');
}

View File

@@ -15,7 +15,7 @@ const applyToTypeAccessor = (rule) => {
};
const conditionsAccessor = (rule) => (
<span style={{ fontSize: 12, color: '#5F6B7C' }}>
<span style={{ fontSize: 12 }}>
{rule.conditions_formatted}
</span>
);

View File

@@ -2,7 +2,7 @@
--x-border-color: #E1E1E1;
--x-color-placeholder-text: #738091;
.bp4-dark & {
:global(.bp4-dark) & {
--x-border-color: rgba(225, 225, 225, 0.15);
--x-color-placeholder-text: rgba(225, 225, 225, 0.65);
}

View File

@@ -53,7 +53,6 @@ export function CompanyLogoUpload({
const [initialLocalPreview, setInitialLocalPreview] = useState<string | null>(
initialPreview || null,
);
const openRef = useRef<() => void>(null);
const handleRemove = () => {

View File

@@ -101,11 +101,11 @@ export function ActionsCell(props) {
*/
export function PublishAccessor(row) {
return row.is_published ? (
<Tag round={true} minimal={true}>
<Tag round>
<T id={'published'} />
</Tag>
) : (
<Tag round={true} minimal={true} intent={Intent.WARNING}>
<Tag round minimal intent={Intent.WARNING}>
<T id={'draft'} />
</Tag>
);

View File

@@ -72,7 +72,7 @@ export const SellPriceCell = ({ cell: { value } }) => {
export const ItemTypeAccessor = (row) => {
return row.type_formatted ? (
<Tag minimal={true} round={true} intent={Intent.NONE}>
<Tag round intent={Intent.NONE}>
{row.type_formatted}
</Tag>
) : null;

View File

@@ -43,7 +43,7 @@ export function useEditProjectTimeEntry(props) {
const apiRequest = useApiRequest();
return useMutation(
([id, values]) => apiRequest.post(`projects/times/${id}`, values),
([id, values]) => apiRequest.put(`projects/times/${id}`, values),
{
onSuccess: (res, [id, values]) => {
// Invalidate specific project time entry.

View File

@@ -39,7 +39,7 @@ export function useEditProject(props) {
const apiRequest = useApiRequest();
return useMutation(
([id, values]) => apiRequest.post(`/projects/${id}`, values),
([id, values]) => apiRequest.put(`/projects/${id}`, values),
{
onSuccess: (res, [id, values]) => {
// Invalidate specific project.

View File

@@ -41,7 +41,7 @@ export function useEditProjectTask(props) {
const queryClient = useQueryClient();
const apiRequest = useApiRequest();
return useMutation(([id, values]) => apiRequest.post(`tasks/${id}`, values), {
return useMutation(([id, values]) => apiRequest.put(`tasks/${id}`, values), {
onSuccess: (res, [id, values]) => {
// Common invalidate queries.
commonInvalidateQueries(queryClient);

View File

@@ -46,12 +46,14 @@ function PaymentMadeFormProvider({ query, paymentMadeId, ...props }) {
// Handle fetch specific payment made details.
const {
data: { paymentMade: paymentMadeEditPage, entries: paymentEntriesEditPage },
data: paymentMadeEditData,
isFetching: isPaymentFetching,
isLoading: isPaymentLoading,
} = usePaymentMadeEditPage(paymentMadeId, {
enabled: !!paymentMadeId,
});
const paymentMadeEditPage = paymentMadeEditData?.bill_payment;
const paymentEntriesEditPage = paymentMadeEditData?.entries;
// Fetches the branches list.
const {

View File

@@ -22,27 +22,27 @@ import { safeCallback } from '@/utils';
export const statusAccessor = (row) => (
<Choose>
<Choose.When condition={row.is_approved}>
<Tag minimal={true} intent={Intent.SUCCESS} round={true}>
<Tag intent={Intent.SUCCESS} round>
<T id={'approved'} />
</Tag>
</Choose.When>
<Choose.When condition={row.is_rejected}>
<Tag minimal={true} intent={Intent.DANGER} round={true}>
<Tag intent={Intent.DANGER} round>
<T id={'rejected'} />
</Tag>
</Choose.When>
<Choose.When condition={row.is_expired}>
<Tag minimal={true} intent={Intent.WARNING} round={true}>
<Tag intent={Intent.WARNING} round>
<T id={'estimate.status.expired'} />
</Tag>
</Choose.When>
<Choose.When condition={row.is_delivered}>
<Tag minimal={true} intent={Intent.SUCCESS} round={true}>
<Tag intent={Intent.SUCCESS} round>
<T id={'delivered'} />
</Tag>
</Choose.When>
<Choose.Otherwise>
<Tag minimal={true} round={true}>
<Tag round>
<T id={'draft'} />
</Tag>
</Choose.Otherwise>

View File

@@ -42,15 +42,15 @@ function PaymentReceiveFormProvider({ query, paymentReceiveId, ...props }) {
// Fetches payment recevie details.
const {
data: {
paymentReceive: paymentReceiveEditPage,
entries: paymentEntriesEditPage,
},
data: paymentReceivedEditData,
isLoading: isPaymentLoading,
isFetching: isPaymentFetching,
} = usePaymentReceiveEditPage(paymentReceiveId, {
enabled: !!paymentReceiveId,
});
const paymentReceiveEditPage = paymentReceivedEditData?.data;
const paymentEntriesEditPage = paymentReceivedEditData?.entries
// Handle fetch accounts data.
const { data: accounts, isLoading: isAccountsLoading } = useAccounts();

View File

@@ -91,7 +91,7 @@ export function useEditAccount(props) {
const apiRequest = useApiRequest();
return useMutation(
([id, values]) => apiRequest.post(`accounts/${id}`, values),
([id, values]) => apiRequest.put(`accounts/${id}`, values),
{
onSuccess: () => {
// Common invalidate queries.

View File

@@ -1,7 +1,7 @@
// @ts-nocheck
import { useMutation } from 'react-query';
import { batch } from 'react-redux';
import useApiRequest from '../useRequest';
import useApiRequest, { useAuthApiRequest } from '../useRequest';
import { setCookie } from '../../utils';
import { useRequestQuery } from '../useQueryRequest';
import t from './types';
@@ -40,7 +40,7 @@ export function setAuthLoginCookies(data) {
* Authentication login.
*/
export const useAuthLogin = (props) => {
const apiRequest = useApiRequest();
const apiRequest = useAuthApiRequest();
const setAuthToken = useSetAuthToken();
const setOrganizationId = useSetOrganizationId();
@@ -49,7 +49,6 @@ export const useAuthLogin = (props) => {
const setLocale = useSetLocale();
return useMutation((values) => apiRequest.post(AuthRoute.Signin, values), {
select: (res) => res.data,
onSuccess: (res) => {
// Set authentication cookies.
setAuthLoginCookies(res.data);
@@ -75,7 +74,7 @@ export const useAuthLogin = (props) => {
* Authentication register.
*/
export const useAuthRegister = (props) => {
const apiRequest = useApiRequest();
const apiRequest = useAuthApiRequest();
return useMutation(
(values) => apiRequest.post(AuthRoute.Signup, values),
@@ -87,7 +86,7 @@ export const useAuthRegister = (props) => {
* Authentication send reset password.
*/
export const useAuthSendResetPassword = (props) => {
const apiRequest = useApiRequest();
const apiRequest = useAuthApiRequest();
return useMutation(
(values) => apiRequest.post(AuthRoute.SendResetPassword, values),
@@ -99,7 +98,7 @@ export const useAuthSendResetPassword = (props) => {
* Authentication reset password.
*/
export const useAuthResetPassword = (props) => {
const apiRequest = useApiRequest();
const apiRequest = useAuthApiRequest();
return useMutation(
([token, values]) => apiRequest.post(`auth/reset/${token}`, values),
@@ -129,7 +128,7 @@ export const useAuthMetadata = (props = {}) => {
* Resend the mail of signup verification.
*/
export const useAuthSignUpVerifyResendMail = (props) => {
const apiRequest = useApiRequest();
const apiRequest = useAuthApiRequest();
return useMutation(
() => apiRequest.post(AuthRoute.SignupVerifyResend),
@@ -146,7 +145,7 @@ interface AuthSignUpVerifyValues {
* Signup verification.
*/
export const useAuthSignUpVerify = (props) => {
const apiRequest = useApiRequest();
const apiRequest = useAuthApiRequest();
return useMutation(
(values: AuthSignUpVerifyValues) =>

View File

@@ -69,7 +69,7 @@ export function useEditBill(props) {
const apiRequest = useApiRequest();
return useMutation(
([id, values]) => apiRequest.post(`bills/${id}`, values),
([id, values]) => apiRequest.put(`bills/${id}`, values),
{
onSuccess: (res, [id, values]) => {
// Common invalidate queries.

View File

@@ -78,7 +78,7 @@ export function useEditCreditNote(props) {
const apiRequest = useApiRequest();
return useMutation(
([id, values]) => apiRequest.post(`credit-notes/${id}`, values),
([id, values]) => apiRequest.put(`credit-notes/${id}`, values),
{
onSuccess: (res, [id, values]) => {
// Common invalidate queries.

View File

@@ -47,7 +47,7 @@ export function useEditEstimate(props) {
const apiRequest = useApiRequest();
return useMutation(
([id, values]) => apiRequest.post(`sale-estimates/${id}`, values),
([id, values]) => apiRequest.put(`sale-estimates/${id}`, values),
{
onSuccess: (res, [id, values]) => {
// Common invalidate queries.

View File

@@ -85,7 +85,7 @@ export function useEditInvoice(props) {
const apiRequest = useApiRequest();
return useMutation(
([id, values]) => apiRequest.post(`sale-invoices/${id}`, values),
([id, values]) => apiRequest.put(`sale-invoices/${id}`, values),
{
onSuccess: (res, [id, values]) => {
// Invalidate specific sale invoice.

View File

@@ -1,5 +1,5 @@
// @ts-nocheck
import { useMutation, useQueryClient } from 'react-query';
import { useMutation, useQuery, useQueryClient } from 'react-query';
import { useRequestQuery } from '../useQueryRequest';
import { transformPagination } from '@/utils';
import useApiRequest from '../useRequest';
@@ -66,16 +66,13 @@ export function useCreatePaymentMade(props) {
const client = useQueryClient();
const apiRequest = useApiRequest();
return useMutation(
(values) => apiRequest.post('bill-payments', values),
{
onSuccess: (res, values) => {
// Common invalidation queries.
commonInvalidateQueries(client);
},
...props,
return useMutation((values) => apiRequest.post('bill-payments', values), {
onSuccess: (res, values) => {
// Common invalidation queries.
commonInvalidateQueries(client);
},
);
...props,
});
}
/**
@@ -86,7 +83,7 @@ export function useEditPaymentMade(props) {
const apiRequest = useApiRequest();
return useMutation(
([id, values]) => apiRequest.post(`bill-payments/${id}`, values),
([id, values]) => apiRequest.put(`bill-payments/${id}`, values),
{
onSuccess: (res, [id, values]) => {
// Common invalidation queries.
@@ -107,42 +104,28 @@ export function useDeletePaymentMade(props) {
const client = useQueryClient();
const apiRequest = useApiRequest();
return useMutation(
(id) => apiRequest.delete(`bill-payments/${id}`),
{
onSuccess: (res, id) => {
// Common invalidation queries.
commonInvalidateQueries(client);
return useMutation((id) => apiRequest.delete(`bill-payments/${id}`), {
onSuccess: (res, id) => {
// Common invalidation queries.
commonInvalidateQueries(client);
// Invalidate specific payment made.
client.invalidateQueries([t.PAYMENT_MADE, id]);
},
...props,
// Invalidate specific payment made.
client.invalidateQueries([t.PAYMENT_MADE, id]);
},
);
...props,
});
}
/**
* Retrieve specific payment made.
*/
export function usePaymentMadeEditPage(id, props) {
return useRequestQuery(
[t.PAYMENT_MADE_EDIT_PAGE, id],
{
method: 'get',
url: `bill-payments/${id}/edit-page`,
},
{
select: (res) => ({
paymentMade: res.data.bill_payment,
entries: res.data.entries,
}),
defaultData: {
paymentMade: {},
entries: [],
},
...props,
},
export function usePaymentMadeEditPage(
id: number,
props: UseQueryOptions<any, Error>,
) {
const apiRequest = useApiRequest();
return useQuery([t.PAYMENT_MADE_EDIT_PAGE, id], () =>
apiRequest.get(`bill-payments/${id}/edit-page`).then((res) => res.data),
);
}

View File

@@ -111,7 +111,7 @@ export function useEditPaymentReceive(props) {
const apiRequest = useApiRequest();
return useMutation(
([id, values]) => apiRequest.post(`payments-received/${id}`, values),
([id, values]) => apiRequest.put(`payments-received/${id}`, values),
{
onSuccess: (data, [id, values]) => {
// Invalidate specific payment receive.
@@ -171,20 +171,10 @@ export function usePaymentReceive(id, props) {
* @param {number} id - Payment receive id.
*/
export function usePaymentReceiveEditPage(id, props) {
return useRequestQuery(
const apiRequest = useApiRequest();
return useQuery(
[t.PAYMENT_RECEIVE_EDIT_PAGE, id],
{ method: 'get', url: `payments-received/${id}/edit-page` },
{
select: (res) => ({
paymentReceive: res.data,
entries: res.data.entries,
}),
defaultData: {
paymentReceive: {},
entries: [],
},
...props,
},
() => apiRequest.get(`payments-received/${id}/edit-page`).then(res => res.data),
);
}

View File

@@ -71,7 +71,7 @@ export function useEditReceipt(props) {
const apiRequest = useApiRequest();
return useMutation(
([id, values]) => apiRequest.post(`sale-receipts/${id}`, values),
([id, values]) => apiRequest.put(`sale-receipts/${id}`, values),
{
onSuccess: (res, [id, values]) => {
// Invalidate specific receipt.

View File

@@ -18,7 +18,7 @@ export function useEditRolePermissionSchema(props) {
const queryClient = useQueryClient();
const apiRequest = useApiRequest();
return useMutation(([id, values]) => apiRequest.post(`roles/${id}`, values), {
return useMutation(([id, values]) => apiRequest.put(`roles/${id}`, values), {
onSuccess: () => {
// Common invalidate queries.
commonInvalidateQueries(queryClient);

View File

@@ -54,7 +54,7 @@ export function useEditTaxRate(props) {
const apiRequest = useApiRequest();
return useMutation(
([id, values]) => apiRequest.post(`tax-rates/${id}`, values),
([id, values]) => apiRequest.put(`tax-rates/${id}`, values),
{
onSuccess: (res, id) => {
commonInvalidateQueries(queryClient);

View File

@@ -35,7 +35,7 @@ export function useEditUser(props) {
const queryClient = useQueryClient();
const apiRequest = useApiRequest();
return useMutation(([id, values]) => apiRequest.post(`users/${id}`, values), {
return useMutation(([id, values]) => apiRequest.put(`users/${id}`, values), {
onSuccess: (res, [id, values]) => {
queryClient.invalidateQueries([t.USER, id]);

View File

@@ -77,7 +77,7 @@ export function useEditVendorCredit(props) {
const apiRequest = useApiRequest();
return useMutation(
([id, values]) => apiRequest.post(`vendor-credits/${id}`, values),
([id, values]) => apiRequest.put(`vendor-credits/${id}`, values),
{
onSuccess: (res, [id, values]) => {
// Common invalidate queries.

View File

@@ -45,7 +45,7 @@ export function useEditWarehouseTransfer(props) {
const apiRequest = useApiRequest();
return useMutation(
([id, values]) => apiRequest.post(`warehouse-transfers/${id}`, values),
([id, values]) => apiRequest.put(`warehouse-transfers/${id}`, values),
{
onSuccess: (res, [id, values]) => {
// Invalidate specific sale invoice.

View File

@@ -120,3 +120,35 @@ export default function useApiRequest() {
[http],
);
}
export function useAuthApiRequest() {
const http = React.useMemo(() => {
// Axios instance.
return axios.create();
}, []);
return React.useMemo(
() => ({
http,
get(resource, params) {
return http.get(`/api/${resource}`, params);
},
post(resource, params, config) {
return http.post(`/api/${resource}`, params, config);
},
update(resource, slug, params) {
return http.put(`/api/${resource}/${slug}`, params);
},
put(resource, params) {
return http.put(`/api/${resource}`, params);
},
patch(resource, params, config) {
return http.patch(`/api/${resource}`, params, config);
},
delete(resource, params) {
return http.delete(`/api/${resource}`, params);
},
}),
[http],
);
}

254
pnpm-lock.yaml generated
View File

@@ -63,6 +63,9 @@ importers:
'@liaoliaots/nestjs-redis':
specifier: ^10.0.0
version: 10.0.0(@nestjs/common@10.4.7)(@nestjs/core@10.4.7)(ioredis@5.6.0)
'@nest-lab/throttler-storage-redis':
specifier: ^1.1.0
version: 1.1.0(@nestjs/common@10.4.7)(@nestjs/core@10.4.7)(@nestjs/throttler@6.2.1)(ioredis@5.6.0)(reflect-metadata@0.2.2)
'@nestjs/bull':
specifier: ^10.2.1
version: 10.2.2(@nestjs/common@10.4.7)(@nestjs/core@10.4.7)(bull@4.16.4)
@@ -1235,7 +1238,7 @@ packages:
'@smithy/util-middleware': 3.0.0
'@smithy/util-retry': 3.0.0
'@smithy/util-utf8': 3.0.0
tslib: 2.8.0
tslib: 2.8.1
transitivePeerDependencies:
- '@aws-sdk/client-sts'
- aws-crt
@@ -1282,7 +1285,7 @@ packages:
'@smithy/util-middleware': 3.0.0
'@smithy/util-retry': 3.0.0
'@smithy/util-utf8': 3.0.0
tslib: 2.8.0
tslib: 2.8.1
transitivePeerDependencies:
- aws-crt
dev: false
@@ -1330,7 +1333,7 @@ packages:
'@smithy/util-middleware': 3.0.0
'@smithy/util-retry': 3.0.0
'@smithy/util-utf8': 3.0.0
tslib: 2.8.0
tslib: 2.8.1
transitivePeerDependencies:
- aws-crt
dev: false
@@ -1345,7 +1348,7 @@ packages:
'@smithy/smithy-client': 3.0.1
'@smithy/types': 3.0.0
fast-xml-parser: 4.2.5
tslib: 2.8.0
tslib: 2.8.1
dev: false
/@aws-sdk/credential-provider-env@3.577.0:
@@ -1355,7 +1358,7 @@ packages:
'@aws-sdk/types': 3.577.0
'@smithy/property-provider': 3.0.0
'@smithy/types': 3.0.0
tslib: 2.8.0
tslib: 2.8.1
dev: false
/@aws-sdk/credential-provider-http@3.582.0:
@@ -1370,7 +1373,7 @@ packages:
'@smithy/smithy-client': 3.0.1
'@smithy/types': 3.0.0
'@smithy/util-stream': 3.0.1
tslib: 2.8.0
tslib: 2.8.1
dev: false
/@aws-sdk/credential-provider-ini@3.583.0(@aws-sdk/client-sso-oidc@3.583.0)(@aws-sdk/client-sts@3.583.0):
@@ -1389,7 +1392,7 @@ packages:
'@smithy/property-provider': 3.0.0
'@smithy/shared-ini-file-loader': 3.0.0
'@smithy/types': 3.0.0
tslib: 2.8.0
tslib: 2.8.1
transitivePeerDependencies:
- '@aws-sdk/client-sso-oidc'
- aws-crt
@@ -1410,7 +1413,7 @@ packages:
'@smithy/property-provider': 3.0.0
'@smithy/shared-ini-file-loader': 3.0.0
'@smithy/types': 3.0.0
tslib: 2.8.0
tslib: 2.8.1
transitivePeerDependencies:
- '@aws-sdk/client-sso-oidc'
- '@aws-sdk/client-sts'
@@ -1425,7 +1428,7 @@ packages:
'@smithy/property-provider': 3.0.0
'@smithy/shared-ini-file-loader': 3.0.0
'@smithy/types': 3.0.0
tslib: 2.8.0
tslib: 2.8.1
dev: false
/@aws-sdk/credential-provider-sso@3.583.0(@aws-sdk/client-sso-oidc@3.583.0):
@@ -1438,7 +1441,7 @@ packages:
'@smithy/property-provider': 3.0.0
'@smithy/shared-ini-file-loader': 3.0.0
'@smithy/types': 3.0.0
tslib: 2.8.0
tslib: 2.8.1
transitivePeerDependencies:
- '@aws-sdk/client-sso-oidc'
- aws-crt
@@ -1454,7 +1457,7 @@ packages:
'@aws-sdk/types': 3.577.0
'@smithy/property-provider': 3.0.0
'@smithy/types': 3.0.0
tslib: 2.8.0
tslib: 2.8.1
dev: false
/@aws-sdk/lib-storage@3.583.0(@aws-sdk/client-s3@3.583.0):
@@ -1470,7 +1473,7 @@ packages:
buffer: 5.6.0
events: 3.3.0
stream-browserify: 3.0.0
tslib: 2.8.0
tslib: 2.8.1
dev: false
/@aws-sdk/middleware-bucket-endpoint@3.577.0:
@@ -1483,7 +1486,7 @@ packages:
'@smithy/protocol-http': 4.0.0
'@smithy/types': 3.0.0
'@smithy/util-config-provider': 3.0.0
tslib: 2.8.0
tslib: 2.8.1
dev: false
/@aws-sdk/middleware-expect-continue@3.577.0:
@@ -1493,7 +1496,7 @@ packages:
'@aws-sdk/types': 3.577.0
'@smithy/protocol-http': 4.0.0
'@smithy/types': 3.0.0
tslib: 2.8.0
tslib: 2.8.1
dev: false
/@aws-sdk/middleware-flexible-checksums@3.577.0:
@@ -1507,7 +1510,7 @@ packages:
'@smithy/protocol-http': 4.0.0
'@smithy/types': 3.0.0
'@smithy/util-utf8': 3.0.0
tslib: 2.8.0
tslib: 2.8.1
dev: false
/@aws-sdk/middleware-host-header@3.577.0:
@@ -1517,7 +1520,7 @@ packages:
'@aws-sdk/types': 3.577.0
'@smithy/protocol-http': 4.0.0
'@smithy/types': 3.0.0
tslib: 2.8.0
tslib: 2.8.1
dev: false
/@aws-sdk/middleware-location-constraint@3.577.0:
@@ -1526,7 +1529,7 @@ packages:
dependencies:
'@aws-sdk/types': 3.577.0
'@smithy/types': 3.0.0
tslib: 2.8.0
tslib: 2.8.1
dev: false
/@aws-sdk/middleware-logger@3.577.0:
@@ -1535,7 +1538,7 @@ packages:
dependencies:
'@aws-sdk/types': 3.577.0
'@smithy/types': 3.0.0
tslib: 2.8.0
tslib: 2.8.1
dev: false
/@aws-sdk/middleware-recursion-detection@3.577.0:
@@ -1545,7 +1548,7 @@ packages:
'@aws-sdk/types': 3.577.0
'@smithy/protocol-http': 4.0.0
'@smithy/types': 3.0.0
tslib: 2.8.0
tslib: 2.8.1
dev: false
/@aws-sdk/middleware-sdk-s3@3.582.0:
@@ -1560,7 +1563,7 @@ packages:
'@smithy/smithy-client': 3.0.1
'@smithy/types': 3.0.0
'@smithy/util-config-provider': 3.0.0
tslib: 2.8.0
tslib: 2.8.1
dev: false
/@aws-sdk/middleware-signing@3.577.0:
@@ -1573,7 +1576,7 @@ packages:
'@smithy/signature-v4': 3.0.0
'@smithy/types': 3.0.0
'@smithy/util-middleware': 3.0.0
tslib: 2.8.0
tslib: 2.8.1
dev: false
/@aws-sdk/middleware-ssec@3.577.0:
@@ -1582,7 +1585,7 @@ packages:
dependencies:
'@aws-sdk/types': 3.577.0
'@smithy/types': 3.0.0
tslib: 2.8.0
tslib: 2.8.1
dev: false
/@aws-sdk/middleware-user-agent@3.583.0:
@@ -1593,7 +1596,7 @@ packages:
'@aws-sdk/util-endpoints': 3.583.0
'@smithy/protocol-http': 4.0.0
'@smithy/types': 3.0.0
tslib: 2.8.0
tslib: 2.8.1
dev: false
/@aws-sdk/region-config-resolver@3.577.0:
@@ -1605,7 +1608,7 @@ packages:
'@smithy/types': 3.0.0
'@smithy/util-config-provider': 3.0.0
'@smithy/util-middleware': 3.0.0
tslib: 2.8.0
tslib: 2.8.1
dev: false
/@aws-sdk/s3-request-presigner@3.583.0:
@@ -1631,7 +1634,7 @@ packages:
'@smithy/protocol-http': 4.0.0
'@smithy/signature-v4': 3.0.0
'@smithy/types': 3.0.0
tslib: 2.8.0
tslib: 2.8.1
dev: false
/@aws-sdk/token-providers@3.577.0(@aws-sdk/client-sso-oidc@3.583.0):
@@ -1645,7 +1648,7 @@ packages:
'@smithy/property-provider': 3.0.0
'@smithy/shared-ini-file-loader': 3.0.0
'@smithy/types': 3.0.0
tslib: 2.8.0
tslib: 2.8.1
dev: false
/@aws-sdk/types@3.577.0:
@@ -1653,14 +1656,14 @@ packages:
engines: {node: '>=16.0.0'}
dependencies:
'@smithy/types': 3.0.0
tslib: 2.8.0
tslib: 2.8.1
dev: false
/@aws-sdk/util-arn-parser@3.568.0:
resolution: {integrity: sha512-XUKJWWo+KOB7fbnPP0+g/o5Ulku/X53t7i/h+sPHr5xxYTJJ9CYnbToo95mzxe7xWvkLrsNtJ8L+MnNn9INs2w==}
engines: {node: '>=16.0.0'}
dependencies:
tslib: 2.8.0
tslib: 2.8.1
dev: false
/@aws-sdk/util-endpoints@3.583.0:
@@ -1670,7 +1673,7 @@ packages:
'@aws-sdk/types': 3.577.0
'@smithy/types': 3.0.0
'@smithy/util-endpoints': 2.0.0
tslib: 2.8.0
tslib: 2.8.1
dev: false
/@aws-sdk/util-format-url@3.577.0:
@@ -1680,14 +1683,14 @@ packages:
'@aws-sdk/types': 3.577.0
'@smithy/querystring-builder': 3.0.0
'@smithy/types': 3.0.0
tslib: 2.8.0
tslib: 2.8.1
dev: false
/@aws-sdk/util-locate-window@3.568.0:
resolution: {integrity: sha512-3nh4TINkXYr+H41QaPelCceEB2FXP3fxp93YZXB/kqJvX0U9j0N0Uk45gvsjmEPzG8XxkPEeLIfT2I1M7A6Lig==}
engines: {node: '>=16.0.0'}
dependencies:
tslib: 2.8.0
tslib: 2.8.1
dev: false
/@aws-sdk/util-user-agent-browser@3.577.0:
@@ -1696,7 +1699,7 @@ packages:
'@aws-sdk/types': 3.577.0
'@smithy/types': 3.0.0
bowser: 2.11.0
tslib: 2.8.0
tslib: 2.8.1
dev: false
/@aws-sdk/util-user-agent-node@3.577.0:
@@ -1711,13 +1714,13 @@ packages:
'@aws-sdk/types': 3.577.0
'@smithy/node-config-provider': 3.0.0
'@smithy/types': 3.0.0
tslib: 2.8.0
tslib: 2.8.1
dev: false
/@aws-sdk/util-utf8-browser@3.259.0:
resolution: {integrity: sha512-UvFa/vR+e19XookZF8RzFZBrw2EUkQWxiBW0yYQAhvk3C+QVGl0H3ouca8LDBlBfQKXwmW3huo/59H8rwb1wJw==}
dependencies:
tslib: 2.8.0
tslib: 2.8.1
dev: false
/@aws-sdk/xml-builder@3.575.0:
@@ -1725,7 +1728,7 @@ packages:
engines: {node: '>=16.0.0'}
dependencies:
'@smithy/types': 3.0.0
tslib: 2.8.0
tslib: 2.8.1
dev: false
/@babel/code-frame@7.10.4:
@@ -6835,6 +6838,23 @@ packages:
tar-fs: 2.1.1
dev: true
/@nest-lab/throttler-storage-redis@1.1.0(@nestjs/common@10.4.7)(@nestjs/core@10.4.7)(@nestjs/throttler@6.2.1)(ioredis@5.6.0)(reflect-metadata@0.2.2):
resolution: {integrity: sha512-7DW8MuqoB+ubu8cWby9Vw56eAFqsHFfowEflHbmmAF2sNByRdzcR4ddcyoYLwL3zG53nLmvzUa4EXoHKB4RoaQ==}
peerDependencies:
'@nestjs/common': ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0
'@nestjs/core': ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0
'@nestjs/throttler': '>=6.0.0'
ioredis: '>=5.0.0'
reflect-metadata: ^0.2.1
dependencies:
'@nestjs/common': 10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1)
'@nestjs/core': 10.4.7(@nestjs/common@10.4.7)(@nestjs/platform-express@10.4.7)(@nestjs/websockets@10.4.20)(reflect-metadata@0.2.2)(rxjs@7.8.1)
'@nestjs/throttler': 6.2.1(@nestjs/common@10.4.7)(@nestjs/core@10.4.7)(reflect-metadata@0.2.2)
ioredis: 5.6.0
reflect-metadata: 0.2.2
tslib: 2.8.1
dev: false
/@nestjs/bull-shared@10.2.2(@nestjs/common@10.4.7)(@nestjs/core@10.4.7):
resolution: {integrity: sha512-bMIEILYYovQWfdz6fCSTgqb/zuKyGmNSc7guB56MiZVW84JloUHb8330nNh3VWaamJKGtUzawbEoG2VR3uVeOg==}
peerDependencies:
@@ -7409,7 +7429,7 @@ packages:
hasBin: true
dependencies:
nx: 19.0.7
tslib: 2.8.0
tslib: 2.8.1
transitivePeerDependencies:
- '@swc-node/register'
- '@swc/core'
@@ -7440,7 +7460,7 @@ packages:
nx: 19.0.7
semver: 7.6.2
tmp: 0.2.3
tslib: 2.8.0
tslib: 2.8.1
yargs-parser: 21.1.1
dev: true
@@ -9100,20 +9120,20 @@ packages:
engines: {node: '>=16.0.0'}
dependencies:
'@smithy/types': 3.0.0
tslib: 2.8.0
tslib: 2.8.1
dev: false
/@smithy/chunked-blob-reader-native@3.0.0:
resolution: {integrity: sha512-VDkpCYW+peSuM4zJip5WDfqvg2Mo/e8yxOv3VF1m11y7B8KKMKVFtmZWDe36Fvk8rGuWrPZHHXZ7rR7uM5yWyg==}
dependencies:
'@smithy/util-base64': 3.0.0
tslib: 2.8.0
tslib: 2.8.1
dev: false
/@smithy/chunked-blob-reader@3.0.0:
resolution: {integrity: sha512-sbnURCwjF0gSToGlsBiAmd1lRCmSn72nu9axfJu5lIx6RUEgHu6GwTMbqCdhQSi0Pumcm5vFxsi9XWXb2mTaoA==}
dependencies:
tslib: 2.8.0
tslib: 2.8.1
dev: false
/@smithy/config-resolver@3.0.0:
@@ -9124,7 +9144,7 @@ packages:
'@smithy/types': 3.0.0
'@smithy/util-config-provider': 3.0.0
'@smithy/util-middleware': 3.0.0
tslib: 2.8.0
tslib: 2.8.1
dev: false
/@smithy/core@2.0.1:
@@ -9138,7 +9158,7 @@ packages:
'@smithy/smithy-client': 3.0.1
'@smithy/types': 3.0.0
'@smithy/util-middleware': 3.0.0
tslib: 2.8.0
tslib: 2.8.1
dev: false
/@smithy/credential-provider-imds@3.0.0:
@@ -9149,7 +9169,7 @@ packages:
'@smithy/property-provider': 3.0.0
'@smithy/types': 3.0.0
'@smithy/url-parser': 3.0.0
tslib: 2.8.0
tslib: 2.8.1
dev: false
/@smithy/eventstream-codec@3.0.0:
@@ -9158,7 +9178,7 @@ packages:
'@aws-crypto/crc32': 3.0.0
'@smithy/types': 3.0.0
'@smithy/util-hex-encoding': 3.0.0
tslib: 2.8.0
tslib: 2.8.1
dev: false
/@smithy/eventstream-serde-browser@3.0.0:
@@ -9167,7 +9187,7 @@ packages:
dependencies:
'@smithy/eventstream-serde-universal': 3.0.0
'@smithy/types': 3.0.0
tslib: 2.8.0
tslib: 2.8.1
dev: false
/@smithy/eventstream-serde-config-resolver@3.0.0:
@@ -9175,7 +9195,7 @@ packages:
engines: {node: '>=16.0.0'}
dependencies:
'@smithy/types': 3.0.0
tslib: 2.8.0
tslib: 2.8.1
dev: false
/@smithy/eventstream-serde-node@3.0.0:
@@ -9184,7 +9204,7 @@ packages:
dependencies:
'@smithy/eventstream-serde-universal': 3.0.0
'@smithy/types': 3.0.0
tslib: 2.8.0
tslib: 2.8.1
dev: false
/@smithy/eventstream-serde-universal@3.0.0:
@@ -9193,7 +9213,7 @@ packages:
dependencies:
'@smithy/eventstream-codec': 3.0.0
'@smithy/types': 3.0.0
tslib: 2.8.0
tslib: 2.8.1
dev: false
/@smithy/fetch-http-handler@3.0.1:
@@ -9203,7 +9223,7 @@ packages:
'@smithy/querystring-builder': 3.0.0
'@smithy/types': 3.0.0
'@smithy/util-base64': 3.0.0
tslib: 2.8.0
tslib: 2.8.1
dev: false
/@smithy/hash-blob-browser@3.0.0:
@@ -9212,7 +9232,7 @@ packages:
'@smithy/chunked-blob-reader': 3.0.0
'@smithy/chunked-blob-reader-native': 3.0.0
'@smithy/types': 3.0.0
tslib: 2.8.0
tslib: 2.8.1
dev: false
/@smithy/hash-node@3.0.0:
@@ -9222,7 +9242,7 @@ packages:
'@smithy/types': 3.0.0
'@smithy/util-buffer-from': 3.0.0
'@smithy/util-utf8': 3.0.0
tslib: 2.8.0
tslib: 2.8.1
dev: false
/@smithy/hash-stream-node@3.0.0:
@@ -9231,21 +9251,21 @@ packages:
dependencies:
'@smithy/types': 3.0.0
'@smithy/util-utf8': 3.0.0
tslib: 2.8.0
tslib: 2.8.1
dev: false
/@smithy/invalid-dependency@3.0.0:
resolution: {integrity: sha512-F6wBBaEFgJzj0s4KUlliIGPmqXemwP6EavgvDqYwCH40O5Xr2iMHvS8todmGVZtuJCorBkXsYLyTu4PuizVq5g==}
dependencies:
'@smithy/types': 3.0.0
tslib: 2.8.0
tslib: 2.8.1
dev: false
/@smithy/is-array-buffer@3.0.0:
resolution: {integrity: sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==}
engines: {node: '>=16.0.0'}
dependencies:
tslib: 2.8.0
tslib: 2.8.1
dev: false
/@smithy/md5-js@3.0.0:
@@ -9253,7 +9273,7 @@ packages:
dependencies:
'@smithy/types': 3.0.0
'@smithy/util-utf8': 3.0.0
tslib: 2.8.0
tslib: 2.8.1
dev: false
/@smithy/middleware-content-length@3.0.0:
@@ -9262,7 +9282,7 @@ packages:
dependencies:
'@smithy/protocol-http': 4.0.0
'@smithy/types': 3.0.0
tslib: 2.8.0
tslib: 2.8.1
dev: false
/@smithy/middleware-endpoint@3.0.0:
@@ -9275,7 +9295,7 @@ packages:
'@smithy/types': 3.0.0
'@smithy/url-parser': 3.0.0
'@smithy/util-middleware': 3.0.0
tslib: 2.8.0
tslib: 2.8.1
dev: false
/@smithy/middleware-retry@3.0.1:
@@ -9289,7 +9309,7 @@ packages:
'@smithy/types': 3.0.0
'@smithy/util-middleware': 3.0.0
'@smithy/util-retry': 3.0.0
tslib: 2.8.0
tslib: 2.8.1
uuid: 9.0.1
dev: false
@@ -9298,7 +9318,7 @@ packages:
engines: {node: '>=16.0.0'}
dependencies:
'@smithy/types': 3.0.0
tslib: 2.8.0
tslib: 2.8.1
dev: false
/@smithy/middleware-stack@3.0.0:
@@ -9306,7 +9326,7 @@ packages:
engines: {node: '>=16.0.0'}
dependencies:
'@smithy/types': 3.0.0
tslib: 2.8.0
tslib: 2.8.1
dev: false
/@smithy/node-config-provider@3.0.0:
@@ -9316,7 +9336,7 @@ packages:
'@smithy/property-provider': 3.0.0
'@smithy/shared-ini-file-loader': 3.0.0
'@smithy/types': 3.0.0
tslib: 2.8.0
tslib: 2.8.1
dev: false
/@smithy/node-http-handler@3.0.0:
@@ -9327,7 +9347,7 @@ packages:
'@smithy/protocol-http': 4.0.0
'@smithy/querystring-builder': 3.0.0
'@smithy/types': 3.0.0
tslib: 2.8.0
tslib: 2.8.1
dev: false
/@smithy/property-provider@3.0.0:
@@ -9335,7 +9355,7 @@ packages:
engines: {node: '>=16.0.0'}
dependencies:
'@smithy/types': 3.0.0
tslib: 2.8.0
tslib: 2.8.1
dev: false
/@smithy/protocol-http@4.0.0:
@@ -9343,7 +9363,7 @@ packages:
engines: {node: '>=16.0.0'}
dependencies:
'@smithy/types': 3.0.0
tslib: 2.8.0
tslib: 2.8.1
dev: false
/@smithy/querystring-builder@3.0.0:
@@ -9352,7 +9372,7 @@ packages:
dependencies:
'@smithy/types': 3.0.0
'@smithy/util-uri-escape': 3.0.0
tslib: 2.8.0
tslib: 2.8.1
dev: false
/@smithy/querystring-parser@3.0.0:
@@ -9360,7 +9380,7 @@ packages:
engines: {node: '>=16.0.0'}
dependencies:
'@smithy/types': 3.0.0
tslib: 2.8.0
tslib: 2.8.1
dev: false
/@smithy/service-error-classification@3.0.0:
@@ -9375,7 +9395,7 @@ packages:
engines: {node: '>=16.0.0'}
dependencies:
'@smithy/types': 3.0.0
tslib: 2.8.0
tslib: 2.8.1
dev: false
/@smithy/signature-v4@3.0.0:
@@ -9388,7 +9408,7 @@ packages:
'@smithy/util-middleware': 3.0.0
'@smithy/util-uri-escape': 3.0.0
'@smithy/util-utf8': 3.0.0
tslib: 2.8.0
tslib: 2.8.1
dev: false
/@smithy/smithy-client@3.0.1:
@@ -9400,14 +9420,14 @@ packages:
'@smithy/protocol-http': 4.0.0
'@smithy/types': 3.0.0
'@smithy/util-stream': 3.0.1
tslib: 2.8.0
tslib: 2.8.1
dev: false
/@smithy/types@3.0.0:
resolution: {integrity: sha512-VvWuQk2RKFuOr98gFhjca7fkBS+xLLURT8bUjk5XQoV0ZLm7WPwWPPY3/AwzTLuUBDeoKDCthfe1AsTUWaSEhw==}
engines: {node: '>=16.0.0'}
dependencies:
tslib: 2.8.0
tslib: 2.8.1
dev: false
/@smithy/url-parser@3.0.0:
@@ -9415,7 +9435,7 @@ packages:
dependencies:
'@smithy/querystring-parser': 3.0.0
'@smithy/types': 3.0.0
tslib: 2.8.0
tslib: 2.8.1
dev: false
/@smithy/util-base64@3.0.0:
@@ -9424,20 +9444,20 @@ packages:
dependencies:
'@smithy/util-buffer-from': 3.0.0
'@smithy/util-utf8': 3.0.0
tslib: 2.8.0
tslib: 2.8.1
dev: false
/@smithy/util-body-length-browser@3.0.0:
resolution: {integrity: sha512-cbjJs2A1mLYmqmyVl80uoLTJhAcfzMOyPgjwAYusWKMdLeNtzmMz9YxNl3/jRLoxSS3wkqkf0jwNdtXWtyEBaQ==}
dependencies:
tslib: 2.8.0
tslib: 2.8.1
dev: false
/@smithy/util-body-length-node@3.0.0:
resolution: {integrity: sha512-Tj7pZ4bUloNUP6PzwhN7K386tmSmEET9QtQg0TgdNOnxhZvCssHji+oZTUIuzxECRfG8rdm2PMw2WCFs6eIYkA==}
engines: {node: '>=16.0.0'}
dependencies:
tslib: 2.8.0
tslib: 2.8.1
dev: false
/@smithy/util-buffer-from@3.0.0:
@@ -9445,14 +9465,14 @@ packages:
engines: {node: '>=16.0.0'}
dependencies:
'@smithy/is-array-buffer': 3.0.0
tslib: 2.8.0
tslib: 2.8.1
dev: false
/@smithy/util-config-provider@3.0.0:
resolution: {integrity: sha512-pbjk4s0fwq3Di/ANL+rCvJMKM5bzAQdE5S/6RL5NXgMExFAi6UgQMPOm5yPaIWPpr+EOXKXRonJ3FoxKf4mCJQ==}
engines: {node: '>=16.0.0'}
dependencies:
tslib: 2.8.0
tslib: 2.8.1
dev: false
/@smithy/util-defaults-mode-browser@3.0.1:
@@ -9463,7 +9483,7 @@ packages:
'@smithy/smithy-client': 3.0.1
'@smithy/types': 3.0.0
bowser: 2.11.0
tslib: 2.8.0
tslib: 2.8.1
dev: false
/@smithy/util-defaults-mode-node@3.0.1:
@@ -9476,7 +9496,7 @@ packages:
'@smithy/property-provider': 3.0.0
'@smithy/smithy-client': 3.0.1
'@smithy/types': 3.0.0
tslib: 2.8.0
tslib: 2.8.1
dev: false
/@smithy/util-endpoints@2.0.0:
@@ -9485,14 +9505,14 @@ packages:
dependencies:
'@smithy/node-config-provider': 3.0.0
'@smithy/types': 3.0.0
tslib: 2.8.0
tslib: 2.8.1
dev: false
/@smithy/util-hex-encoding@3.0.0:
resolution: {integrity: sha512-eFndh1WEK5YMUYvy3lPlVmYY/fZcQE1D8oSf41Id2vCeIkKJXPcYDCZD+4+xViI6b1XSd7tE+s5AmXzz5ilabQ==}
engines: {node: '>=16.0.0'}
dependencies:
tslib: 2.8.0
tslib: 2.8.1
dev: false
/@smithy/util-middleware@3.0.0:
@@ -9500,7 +9520,7 @@ packages:
engines: {node: '>=16.0.0'}
dependencies:
'@smithy/types': 3.0.0
tslib: 2.8.0
tslib: 2.8.1
dev: false
/@smithy/util-retry@3.0.0:
@@ -9509,7 +9529,7 @@ packages:
dependencies:
'@smithy/service-error-classification': 3.0.0
'@smithy/types': 3.0.0
tslib: 2.8.0
tslib: 2.8.1
dev: false
/@smithy/util-stream@3.0.1:
@@ -9523,14 +9543,14 @@ packages:
'@smithy/util-buffer-from': 3.0.0
'@smithy/util-hex-encoding': 3.0.0
'@smithy/util-utf8': 3.0.0
tslib: 2.8.0
tslib: 2.8.1
dev: false
/@smithy/util-uri-escape@3.0.0:
resolution: {integrity: sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg==}
engines: {node: '>=16.0.0'}
dependencies:
tslib: 2.8.0
tslib: 2.8.1
dev: false
/@smithy/util-utf8@3.0.0:
@@ -9538,7 +9558,7 @@ packages:
engines: {node: '>=16.0.0'}
dependencies:
'@smithy/util-buffer-from': 3.0.0
tslib: 2.8.0
tslib: 2.8.1
dev: false
/@smithy/util-waiter@3.0.0:
@@ -9547,7 +9567,7 @@ packages:
dependencies:
'@smithy/abort-controller': 3.0.0
'@smithy/types': 3.0.0
tslib: 2.8.0
tslib: 2.8.1
dev: false
/@socket.io/component-emitter@3.1.2:
@@ -12927,7 +12947,7 @@ packages:
esbuild: '>=0.10.0'
dependencies:
esbuild: 0.18.20
tslib: 2.8.0
tslib: 2.8.1
dev: true
/@yarnpkg/fslib@2.10.3:
@@ -12955,7 +12975,7 @@ packages:
engines: {node: '>=14.15.0'}
dependencies:
js-yaml: 3.14.1
tslib: 2.8.0
tslib: 2.8.1
dev: true
/@zkochan/js-yaml@0.0.7:
@@ -13340,7 +13360,7 @@ packages:
resolution: {integrity: sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==}
engines: {node: '>=10'}
dependencies:
tslib: 2.8.0
tslib: 2.8.1
dev: true
/aria-query@4.2.2:
@@ -13547,21 +13567,21 @@ packages:
resolution: {integrity: sha512-O0yuUDnZeQDL+ncNGlJ78BiO4jnYI3bvMsD5prT0/nsgijG/LpNBIr63gTjVTNsiGkgQhiyCShTgxt8oXOrklA==}
engines: {node: '>=4'}
dependencies:
tslib: 2.8.0
tslib: 2.8.1
dev: true
/ast-types@0.15.2:
resolution: {integrity: sha512-c27loCv9QkZinsa5ProX751khO9DJl/AcB5c2KNtA6NRvHKS0PgLfcftz72KVq504vB0Gku5s2kUZzDBvQWvHg==}
engines: {node: '>=4'}
dependencies:
tslib: 2.8.0
tslib: 2.8.1
dev: true
/ast-types@0.16.1:
resolution: {integrity: sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==}
engines: {node: '>=4'}
dependencies:
tslib: 2.8.0
tslib: 2.8.1
dev: true
/async-limiter@1.0.1:
@@ -14574,7 +14594,7 @@ packages:
resolution: {integrity: sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==}
dependencies:
pascal-case: 3.1.2
tslib: 2.8.0
tslib: 2.8.1
dev: false
/camelcase-css@2.0.1:
@@ -14626,7 +14646,7 @@ packages:
resolution: {integrity: sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==}
dependencies:
no-case: 3.0.4
tslib: 2.8.0
tslib: 2.8.1
upper-case-first: 2.0.2
dev: false
@@ -14705,7 +14725,7 @@ packages:
path-case: 3.0.4
sentence-case: 3.0.4
snake-case: 3.0.4
tslib: 2.8.0
tslib: 2.8.1
dev: false
/char-regex@1.0.2:
@@ -15129,7 +15149,7 @@ packages:
resolution: {integrity: sha512-I2hSBi7Vvs7BEuJDr5dDHfzb/Ruj3FyvFyh7KLilAjNQw3Be+xgqUBA2W6scVEcL0hL1dwPRtIqEPVUCKkSsyQ==}
dependencies:
no-case: 3.0.4
tslib: 2.8.0
tslib: 2.8.1
upper-case: 2.0.2
dev: false
@@ -16295,7 +16315,7 @@ packages:
resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==}
dependencies:
no-case: 3.0.4
tslib: 2.8.0
tslib: 2.8.1
dev: false
/dot-prop@5.3.0:
@@ -17817,7 +17837,7 @@ packages:
resolution: {integrity: sha512-iACCiXeMYOvZqlF1kTiYINzgepRBymz1wwjiuup9u9nayhb6g4fSwiyJ/6adli+EPwrWtpgQAh2PoS7HukEGEg==}
engines: {node: '>= 10'}
dependencies:
tslib: 2.8.0
tslib: 2.8.1
dev: false
/file-system-cache@2.3.0:
@@ -18794,7 +18814,7 @@ packages:
resolution: {integrity: sha512-H/vuk5TEEVZwrR0lp2zed9OCo1uAILMlx0JEMgC26rzyJJ3N1v6XkwHHXJQdR2doSjcGPM6OKPYoJgf0plJ11Q==}
dependencies:
capital-case: 1.0.4
tslib: 2.8.0
tslib: 2.8.1
dev: false
/helmet-crossdomain@0.4.0:
@@ -21792,7 +21812,7 @@ packages:
/lower-case@2.0.2:
resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==}
dependencies:
tslib: 2.8.0
tslib: 2.8.1
dev: false
/lru-cache@10.2.2:
@@ -22691,7 +22711,7 @@ packages:
resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==}
dependencies:
lower-case: 2.0.2
tslib: 2.8.0
tslib: 2.8.1
dev: false
/nocache@2.1.0:
@@ -23071,7 +23091,7 @@ packages:
tar-stream: 2.2.0
tmp: 0.2.3
tsconfig-paths: 4.2.0
tslib: 2.8.0
tslib: 2.8.1
yargs: 17.7.2
yargs-parser: 21.1.1
optionalDependencies:
@@ -23515,7 +23535,7 @@ packages:
resolution: {integrity: sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==}
dependencies:
dot-case: 3.0.4
tslib: 2.8.0
tslib: 2.8.1
dev: false
/parent-module@1.0.1:
@@ -23576,7 +23596,7 @@ packages:
resolution: {integrity: sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==}
dependencies:
no-case: 3.0.4
tslib: 2.8.0
tslib: 2.8.1
dev: false
/pascalcase@0.1.1:
@@ -23645,7 +23665,7 @@ packages:
resolution: {integrity: sha512-qO4qCFjXqVTrcbPt/hQfhTQ+VhFsqNKOPtytgNKkKxSoEp3XPUQ8ObFuePylOIok5gjn69ry8XiULxCwot3Wfg==}
dependencies:
dot-case: 3.0.4
tslib: 2.8.0
tslib: 2.8.1
dev: false
/path-exists@3.0.0:
@@ -25896,7 +25916,7 @@ packages:
'@types/react': 18.3.4
react: 18.3.1
react-style-singleton: 2.2.1(@types/react@18.3.4)(react@18.3.1)
tslib: 2.8.0
tslib: 2.8.1
dev: true
/react-remove-scroll@2.5.5(@types/react@18.3.4)(react@18.3.1):
@@ -25913,7 +25933,7 @@ packages:
react: 18.3.1
react-remove-scroll-bar: 2.3.6(@types/react@18.3.4)(react@18.3.1)
react-style-singleton: 2.2.1(@types/react@18.3.4)(react@18.3.1)
tslib: 2.8.0
tslib: 2.8.1
use-callback-ref: 1.3.2(@types/react@18.3.4)(react@18.3.1)
use-sidecar: 1.1.2(@types/react@18.3.4)(react@18.3.1)
dev: true
@@ -26170,7 +26190,7 @@ packages:
get-nonce: 1.0.1
invariant: 2.2.4
react: 18.3.1
tslib: 2.8.0
tslib: 2.8.1
dev: true
/react-table-sticky@1.1.3:
@@ -26407,7 +26427,7 @@ packages:
ast-types: 0.15.2
esprima: 4.0.1
source-map: 0.6.1
tslib: 2.8.0
tslib: 2.8.1
dev: true
/recast@0.23.9:
@@ -26418,7 +26438,7 @@ packages:
esprima: 4.0.1
source-map: 0.6.1
tiny-invariant: 1.3.3
tslib: 2.8.0
tslib: 2.8.1
dev: true
/rechoir@0.8.0:
@@ -27232,7 +27252,7 @@ packages:
resolution: {integrity: sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg==}
dependencies:
no-case: 3.0.4
tslib: 2.8.0
tslib: 2.8.1
upper-case-first: 2.0.2
dev: false
@@ -27462,7 +27482,7 @@ packages:
resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==}
dependencies:
dot-case: 3.0.4
tslib: 2.8.0
tslib: 2.8.1
dev: false
/snapdragon-node@2.1.1:
@@ -28338,7 +28358,7 @@ packages:
engines: {node: ^14.18.0 || >=16.0.0}
dependencies:
'@pkgr/core': 0.1.1
tslib: 2.8.0
tslib: 2.8.1
dev: true
/tailwindcss@3.4.14(ts-node@10.9.2):
@@ -29469,13 +29489,13 @@ packages:
/upper-case-first@2.0.2:
resolution: {integrity: sha512-514ppYHBaKwfJRK/pNC6c/OxfGa0obSnAl106u97Ed0I625Nin96KAjttZF6ZL3e1XLtphxnqrOi9iWgm+u+bg==}
dependencies:
tslib: 2.8.0
tslib: 2.8.1
dev: false
/upper-case@2.0.2:
resolution: {integrity: sha512-KgdgDGJt2TpuwBUIjgG6lzw2GWFRCW9Qkfkiv0DxqHHLYJHmtmdUIKcZd8rHgFSjopVTlw6ggzCm1b8MFQwikg==}
dependencies:
tslib: 2.8.0
tslib: 2.8.1
dev: false
/uri-js@4.4.1:
@@ -29507,7 +29527,7 @@ packages:
dependencies:
'@types/react': 18.3.4
react: 18.3.1
tslib: 2.8.0
tslib: 2.8.1
dev: true
/use-resize-observer@9.1.0(react-dom@18.3.1)(react@18.3.1):
@@ -29534,7 +29554,7 @@ packages:
'@types/react': 18.3.4
detect-node-es: 1.1.0
react: 18.3.1
tslib: 2.8.0
tslib: 2.8.1
dev: true
/use@3.1.1:

286
setup.sh Normal file
View File

@@ -0,0 +1,286 @@
# Initialize the essential variables.
BRANCH=main
CURRENT=$PWD
BIGCAPITAL_INSTALL_DIR=$PWD/bigcapital-$(echo $BRANCH | sed -r 's@(\/|" "|\.)@-@g')
BIGCAPITAL_CLONE_TEMP_DIR=$(mktemp -d)
CPU_ARCH=$(uname -m)
DOCKER_FILE_PATH=./docker-compose.prod.yml
DOCKER_COMPOSE_DIR=docker
DOCKER_ENV_EXAMPLE_PATH=$CURRENT/.env.example
DOCKER_ENV_PATH=$CURRENT/.env
# if docker-compose is installed
if command -v docker-compose &> /dev/null
then
COMPOSE_CMD="docker-compose"
else
COMPOSE_CMD="docker compose"
fi
REPO=https://github.com/bigcapitalhq/bigcapital
# Prints the Bigcapital logo once running the script.
function print_logo() {
clear
cat <<"EOF"
--------------------------------------------
× ≠≠≠≠ ____ _ _ _ _
×××× ≠≠≠≠≠ | __ )(_) __ _ ___ __ _ _ __ (_) |_ __ _| |
××××× ≠≠≠≠≠ | _ \| |/ _` |/ __/ _` | '_ \| | __/ _` | |
××××× ≠≠≠≠≠= | |_) | | (_| | (_| (_| | |_) | | || (_| | |
××××× ≠≠≠≠≠≠ |____/|_|\__, |\___\__,_| .__/|_|\__\__,_|_|
×××× ≠≠≠ |___/ |_|
--------------------------------------------
Self-hosted modern core accounting software
--------------------------------------------
EOF
}
# Downloads /docker folder from Bigcapital repository
clone_github_folder() {
# Create a temporary directory to clone into
temp_dir=$BIGCAPITAL_CLONE_TEMP_DIR
# Clone the repository
git clone --branch=main --depth=1 "$1" "$temp_dir"
echo "The repository has been cloned."
DATE=$(date +%s)
if [ -f "$CURRENT/docker-compose.prod.yml" ]
then
mkdir -p $CURRENT/archive/$DATE
mv $CURRENT/docker-compose.prod.yml $CURRENT/archive/$DATE/docker-compose.prod.yml
fi
if [ -d "$CURRENT/docker" ]
then
mkdir -p $CURRENT/archive/$DATE
mv $CURRENT/docker $CURRENT/archive/$DATE/docker
fi
mv -f "$temp_dir/docker" "$CURRENT"
mv -f "$temp_dir/docker-compose.prod.yml" "$CURRENT"
mv -f "$temp_dir/.env.example" "$CURRENT/"
# Cleanup temporary directory
rm -rf "$temp_dir"
}
setup_env() {
if [ -f $DOCKER_ENV_EXAMPLE_PATH ];
then
cp "$CURRENT/.env.example" "$DOCKER_ENV_PATH"
fi
}
# Prints the main actions men.
function askForAction() {
local DEFAULT_ACTION=$1
if [ -z "$DEFAULT_ACTION" ];
then
echo
echo "Select a Action you want to perform:"
echo " 1) Install (${CPU_ARCH})"
echo " 2) Start"
echo " 3) Stop"
echo " 4) Restart"
echo " 5) Upgrade"
echo " 6) Logs"
echo " 7) Exit"
echo
read -p "Action [2]: " ACTION
until [[ -z "$ACTION" || "$ACTION" =~ ^[1-7]$ ]]; do
echo "$ACTION: invalid selection."
read -p "Action [2]: " ACTION
done
if [ -z "$ACTION" ];
then
ACTION=2
fi
echo
fi
if [ "$ACTION" == "1" ] || [ "$DEFAULT_ACTION" == "install" ]
then
install
askForAction
elif [ "$ACTION" == "2" ] || [ "$DEFAULT_ACTION" == "start" ]
then
startServices
askForAction
elif [ "$ACTION" == "3" ] || [ "$DEFAULT_ACTION" == "stop" ]
then
stopServices
askForAction
elif [ "$ACTION" == "4" ] || [ "$DEFAULT_ACTION" == "restart" ]
then
restartServices
askForAction
elif [ "$ACTION" == "5" ] || [ "$DEFAULT_ACTION" == "upgrade" ]
then
upgrade
askForAction
elif [ "$ACTION" == "6" ] || [ "$DEFAULT_ACTION" == "logs" ]
then
viewLogs $@
askForAction "logs"
elif [ "$ACTION" == "7" ]
then
exit 0
else
echo "Error: Invalid given action"
fi
}
function install() {
echo "Installing Bigcaoital.........."
echo "installing is going to take few mintues..."
download
setup_env
}
function download() {
# Download the docker/, docker-compose file and .env.example
clone_github_folder "https://github.com/bigcapitalhq/bigcapital.git"
/bin/bash -c "$COMPOSE_CMD -f $DOCKER_FILE_PATH --env-file=$DOCKER_ENV_PATH pull"
echo ""
echo "The stable version is now available for you to use"
echo ""
}
function startServices() {
/bin/bash -c "$COMPOSE_CMD -f $DOCKER_FILE_PATH --env-file=$DOCKER_ENV_PATH build"
/bin/bash -c "$COMPOSE_CMD -f $DOCKER_FILE_PATH --env-file=$DOCKER_ENV_PATH up -d"
local migrator_container_id=$(docker container ls -aq -f "name=bigcapital-database-migration")
if [ -n "$migrator_container_id" ]; then
local idx=0
while docker inspect --format='{{.State.Status}}' $migrator_container_id | grep -q "running"; do
local message=">> Waiting for database migration to finish"
local dots=$(printf '%*s' $idx | tr ' ' '.')
echo -ne "\r$message$dots"
((idx++))
sleep 1
done
fi
printf "\r\033[K"
echo ""
echo " Database migration completed successfully ✅"
# if migrator exit status is not 0, show error message and exit
if [ -n "$migrator_container_id" ]; then
local migrator_exit_code=$(docker inspect --format='{{.State.ExitCode}}' $migrator_container_id)
if [ $migrator_exit_code -ne 0 ]; then
echo "Bigcapital Server failed to start ❌"
stopServices
echo
echo "Please check the logs for the 'migrator' service and resolve the issue(s)."
echo "Stop the services by running the command: ./setup.sh stop"
exit 1
fi
fi
local api_container_id=$(docker container ls -q -f "name=bigcapital-server")
local idx2=0
while ! docker logs $api_container_id 2>&1 | grep -m 1 -i "Server listening on port" | grep -q ".";
do
local message=">> Waiting for Bigcapital Server to Start"
local dots=$(printf '%*s' $idx2 | tr ' ' '.')
echo -ne "\r$message$dots"
((idx2++))
sleep 1
done
printf "\r\033[K"
echo " API server started successfully ✅"
source "${DOCKER_ENV_PATH}"
echo " Bigcapital server started successfully ✅"
echo ""
echo " You can access the application at $WEB_URL"
echo ""
}
# Stoppes all the Docker containers.
function stopServices() {
/bin/bash -c "$COMPOSE_CMD -f $DOCKER_FILE_PATH --env-file=$DOCKER_ENV_PATH down"
}
# Restarts all the Docker containers.
function restartServices() {
stopServices
startServices
}
function viewLogs(){
ARG_SERVICE_NAME=$2
echo
echo "Select a Service you want to view the logs for:"
echo " 1) Webapp"
echo " 2) API"
echo " 3) Migration"
echo " 4) Nginx Proxy"
echo " 5) MariaDB"
echo " 0) Back to Main Menu"
echo
read -p "Service: " DOCKER_SERVICE_NAME
until (( DOCKER_SERVICE_NAME >= 0 && DOCKER_SERVICE_NAME <= 5 )); do
echo "Invalid selection. Please enter a number between 1 and 11."
read -p "Service: " DOCKER_SERVICE_NAME
done
if [ -z "$DOCKER_SERVICE_NAME" ];
then
echo "INVALID SERVICE NAME SUPPLIED"
else
case $DOCKER_SERVICE_NAME in
1) viewSpecificLogs "webapp";;
2) viewSpecificLogs "server";;
3) viewSpecificLogs "database_migration";;
4) viewSpecificLogs "nginx";;
5) viewSpecificLogs "mysql";;
0) askForAction;;
*) echo "INVALID SERVICE NAME SUPPLIED";;
esac
fi
}
function viewSpecificLogs(){
local SERVICE_NAME=$1
if /bin/bash -c "$COMPOSE_CMD -f $DOCKER_FILE_PATH ps | grep -q '$SERVICE_NAME'"; then
echo "Service '$SERVICE_NAME' is running."
else
echo "Service '$SERVICE_NAME' is not running."
fi
/bin/bash -c "$COMPOSE_CMD -f $DOCKER_FILE_PATH logs -f $SERVICE_NAME"
}
function upgrade() {
echo "***** STOPPING SERVICES ****"
stopServices
echo
echo "***** DOWNLOADING STABLE VERSION ****"
download
echo "***** PLEASE VALIDATE AND START SERVICES ****"
}
mkdir -p $CURRENT/archive
# Display the header and run the actions menu.
print_logo
askForAction $@