1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-12-30 10:36:35 +02:00

Server: Add support for user flags

This commit is contained in:
Laurent Cozic 2021-08-22 11:28:15 +01:00
parent 2ae51acd29
commit 82b157b491
18 changed files with 414 additions and 82 deletions

Binary file not shown.

View File

@ -0,0 +1,74 @@
it('should pass', async function() {
expect(true).toBe(true);
});
// import { afterAllTests, beforeAllDb, beforeEachDb, db } from "./utils/testing/testUtils";
// import sqlts from '@rmp135/sql-ts';
// import config from "./config";
// import { connectDb, DbConnection, disconnectDb, migrateDown, migrateList, migrateUp, nextMigration } from "./db";
// async function dbSchemaSnapshot(db:DbConnection):Promise<any> {
// return sqlts.toObject({
// client: 'sqlite',
// knex: db,
// // 'connection': {
// // 'filename': config().database.name,
// // },
// useNullAsDefault: true,
// } as any)
// // return JSON.stringify(definitions);
// }
// describe('db', function() {
// beforeAll(async () => {
// await beforeAllDb('db', { autoMigrate: false });
// });
// afterAll(async () => {
// await afterAllTests();
// });
// beforeEach(async () => {
// await beforeEachDb();
// });
// it('should allow downgrading schema', async function() {
// const ignoreAllBefore = '20210819165350_user_flags';
// let startProcessing = false;
// //console.info(await dbSchemaSnapshot());
// while (true) {
// await migrateUp(db());
// if (!startProcessing) {
// const next = await nextMigration(db());
// if (next === ignoreAllBefore) {
// startProcessing = true;
// } else {
// continue;
// }
// }
// if (!(await nextMigration(db()))) break;
// // await disconnectDb(db());
// // const beforeSchema = await dbSchemaSnapshot(db());
// // console.info(beforeSchema);
// // await connectDb(db());
// // await migrateUp(db());
// // await migrateDown(db());
// // const afterSchema = await dbSchemaSnapshot(db());
// // // console.info(beforeSchema);
// // // console.info(afterSchema);
// // expect(beforeSchema).toEqual(afterSchema);
// }
// });
// });

View File

@ -52,6 +52,11 @@ export interface ConnectionCheckResult {
connection: DbConnection;
}
export interface Migration {
name: string;
done: boolean;
}
export function makeKnexConfig(dbConfig: DatabaseConfig): KnexDatabaseConfig {
const connection: DbConfigConnection = {};
@ -167,8 +172,6 @@ export async function migrateList(db: DbConnection, asString: boolean = true) {
// ]
// ]
if (!asString) return migrations;
const formatName = (migrationInfo: any) => {
const name = migrationInfo.file ? migrationInfo.file : migrationInfo;
@ -177,32 +180,43 @@ export async function migrateList(db: DbConnection, asString: boolean = true) {
return s.join('.');
};
interface Line {
text: string;
done: boolean;
}
const output: Line[] = [];
const output: Migration[] = [];
for (const s of migrations[0]) {
output.push({
text: formatName(s),
name: formatName(s),
done: true,
});
}
for (const s of migrations[1]) {
output.push({
text: formatName(s),
name: formatName(s),
done: false,
});
}
output.sort((a, b) => {
return a.text < b.text ? -1 : +1;
return a.name < b.name ? -1 : +1;
});
return output.map(l => `${l.done ? '✓' : '✗'} ${l.text}`).join('\n');
if (!asString) return output;
return output.map(l => `${l.done ? '✓' : '✗'} ${l.name}`).join('\n');
}
export async function nextMigration(db: DbConnection): Promise<string> {
const list = await migrateList(db, false) as Migration[];
let nextMigration: Migration = null;
while (list.length) {
const migration = list.pop();
if (migration.done) return nextMigration ? nextMigration.name : '';
nextMigration = migration;
}
return '';
}
function allTableNames(): string[] {
@ -321,6 +335,15 @@ export enum ChangeType {
Delete = 3,
}
export enum UserFlagType {
FailedPaymentWarning = 1,
FailedPaymentFinal = 2,
AccountOverLimit = 3,
AccountWithoutSubscription = 4,
SubscriptionCancelled = 5,
ManuallyDisabled = 6,
}
export enum FileContentType {
Any = 1,
JoplinItem = 2,
@ -508,6 +531,12 @@ export interface User extends WithDates, WithUuid {
enabled?: number;
}
export interface UserFlag extends WithDates {
id?: number;
user_id?: Uuid;
type?: UserFlagType;
}
export const databaseSchema: DatabaseTables = {
sessions: {
id: { type: 'string' },
@ -665,5 +694,12 @@ export const databaseSchema: DatabaseTables = {
total_item_size: { type: 'string' },
enabled: { type: 'number' },
},
user_flags: {
id: { type: 'number' },
user_id: { type: 'string' },
type: { type: 'number' },
updated_time: { type: 'string' },
created_time: { type: 'string' },
},
};
// AUTO-GENERATED-TYPES

View File

@ -0,0 +1,20 @@
import { Knex } from 'knex';
import { DbConnection } from '../db';
export async function up(db: DbConnection): Promise<any> {
await db.schema.createTable('user_flags', (table: Knex.CreateTableBuilder) => {
table.increments('id').unique().primary().notNullable();
table.string('user_id', 32).notNullable();
table.integer('type').defaultTo(0).notNullable();
table.bigInteger('updated_time').notNullable();
table.bigInteger('created_time').notNullable();
});
await db.schema.alterTable('user_flags', (table: Knex.CreateTableBuilder) => {
table.unique(['user_id', 'type']);
});
}
export async function down(db: DbConnection): Promise<any> {
await db.schema.dropTable('user_flags');
}

View File

@ -1,4 +1,4 @@
import { EmailSender, Subscription, User, Uuid } from '../db';
import { EmailSender, Subscription, User, UserFlagType, Uuid } from '../db';
import { ErrorNotFound } from '../utils/errors';
import { Day } from '../utils/time';
import uuidgen from '../utils/uuidgen';
@ -81,13 +81,10 @@ export default class SubscriptionModel extends BaseModel<Subscription> {
const user = await this.models().user().load(sub.user_id);
await this.withTransaction(async () => {
if (!user.enabled || !user.can_upload) {
await this.models().user().save({
id: sub.user_id,
enabled: 1,
can_upload: 1,
});
}
await this.models().userFlag().removeMulti(user.id, [
UserFlagType.FailedPaymentWarning,
UserFlagType.FailedPaymentFinal,
]);
await this.save({
id: sub.id,

View File

@ -0,0 +1,42 @@
import { UserFlagType } from '../db';
import { beforeAllDb, afterAllTests, beforeEachDb, models, createUserAndSession } from '../utils/testing/testUtils';
describe('UserFlagModel', function() {
beforeAll(async () => {
await beforeAllDb('UserFlagModel');
});
afterAll(async () => {
await afterAllTests();
});
beforeEach(async () => {
await beforeEachDb();
});
test('should create no more than one flag per type', async function() {
const { user } = await createUserAndSession(1);
const beforeTime = Date.now();
await models().userFlag().add(user.id, UserFlagType.AccountOverLimit);
const flag = await models().userFlag().byUserId(user.id, UserFlagType.AccountOverLimit);
expect(flag.user_id).toBe(user.id);
expect(flag.type).toBe(UserFlagType.AccountOverLimit);
expect(flag.created_time).toBeGreaterThanOrEqual(beforeTime);
expect(flag.updated_time).toBeGreaterThanOrEqual(beforeTime);
const flagCountBefore = (await models().userFlag().all()).length;
await models().userFlag().add(user.id, UserFlagType.AccountOverLimit);
const flagCountAfter = (await models().userFlag().all()).length;
expect(flagCountBefore).toBe(flagCountAfter);
await models().userFlag().add(user.id, UserFlagType.FailedPaymentFinal);
const flagCountAfter2 = (await models().userFlag().all()).length;
expect(flagCountAfter2).toBe(flagCountBefore + 1);
const differentFlag = await models().userFlag().byUserId(user.id, UserFlagType.FailedPaymentFinal);
expect(flag.id).not.toBe(differentFlag.id);
});
});

View File

@ -0,0 +1,130 @@
import { isUniqueConstraintError, User, UserFlag, UserFlagType, Uuid } from '../db';
import BaseModel from './BaseModel';
interface AddRemoveOptions {
updateUser?: boolean;
}
function defaultAddRemoveOptions(): AddRemoveOptions {
return {
updateUser: true,
};
}
export default class UserFlagModels extends BaseModel<UserFlag> {
public get tableName(): string {
return 'user_flags';
}
protected hasUuid(): boolean {
return false;
}
public async add(userId: Uuid, type: UserFlagType, options: AddRemoveOptions = {}): Promise<void> {
options = {
...defaultAddRemoveOptions(),
...options,
};
try {
await this.save({
user_id: userId,
type,
});
} catch (error) {
if (!isUniqueConstraintError(error)) {
throw error;
}
}
if (options.updateUser) await this.updateUserFromFlags(userId);
}
public async remove(userId: Uuid, type: UserFlagType, options: AddRemoveOptions = null) {
options = {
...defaultAddRemoveOptions(),
...options,
};
await this.db(this.tableName)
.where('user_id', '=', userId)
.where('type', '=', type)
.delete();
if (options.updateUser) await this.updateUserFromFlags(userId);
}
public async toggle(userId: Uuid, type: UserFlagType, apply: boolean, options: AddRemoveOptions = null) {
if (apply) {
await this.add(userId, type, options);
} else {
await this.remove(userId, type, options);
}
}
public async addMulti(userId: Uuid, flagTypes: UserFlagType[]) {
await this.withTransaction(async () => {
for (const flagType of flagTypes) {
await this.add(userId, flagType, { updateUser: false });
}
await this.updateUserFromFlags(userId);
});
}
public async removeMulti(userId: Uuid, flagTypes: UserFlagType[]) {
await this.withTransaction(async () => {
for (const flagType of flagTypes) {
await this.remove(userId, flagType, { updateUser: false });
}
await this.updateUserFromFlags(userId);
});
}
// As a general rule the `enabled` and `can_upload` properties should not
// be set directly (except maybe in tests) - instead the appropriate user
// flags should be set, and this function will derive the enabled/can_upload
// properties from them.
private async updateUserFromFlags(userId: Uuid) {
const flags = await this.allByUserId(userId);
const user = await this.models().user().load(userId, { fields: ['id', 'can_upload', 'enabled'] });
const newProps: User = {
can_upload: 1,
enabled: 1,
};
if (flags.find(f => f.type === UserFlagType.AccountWithoutSubscription)) {
newProps.can_upload = 0;
} else if (flags.find(f => f.type === UserFlagType.AccountOverLimit)) {
newProps.can_upload = 0;
} else if (flags.find(f => f.type === UserFlagType.FailedPaymentWarning)) {
newProps.can_upload = 0;
} else if (flags.find(f => f.type === UserFlagType.FailedPaymentFinal)) {
newProps.enabled = 0;
} else if (flags.find(f => f.type === UserFlagType.SubscriptionCancelled)) {
newProps.enabled = 0;
} else if (flags.find(f => f.type === UserFlagType.ManuallyDisabled)) {
newProps.enabled = 0;
}
if (user.can_upload !== newProps.can_upload || user.enabled !== newProps.enabled) {
await this.models().user().save({
id: userId,
...newProps,
});
}
}
public async byUserId(userId: Uuid, type: UserFlagType): Promise<UserFlag> {
return this.db(this.tableName)
.where('user_id', '=', userId)
.where('type', '=', type)
.first();
}
public async allByUserId(userId: Uuid): Promise<UserFlag[]> {
return this.db(this.tableName).where('user_id', '=', userId);
}
}

View File

@ -1,5 +1,5 @@
import { createUserAndSession, beforeAllDb, afterAllTests, beforeEachDb, models, checkThrowAsync, createItem } from '../utils/testing/testUtils';
import { EmailSender, User } from '../db';
import { EmailSender, User, UserFlagType } from '../db';
import { ErrorUnprocessableEntity } from '../utils/errors';
import { betaUserDateRange, stripeConfig } from '../utils/stripe';
import { AccountType } from './UserModel';
@ -160,6 +160,9 @@ describe('UserModel', function() {
const reloadedUser = await models().user().load(user1.id);
expect(reloadedUser.can_upload).toBe(0);
const userFlag = await models().userFlag().byUserId(user1.id, UserFlagType.AccountWithoutSubscription);
expect(userFlag).toBeTruthy();
});
test('should disable upload and send an email if payment failed', async function() {

View File

@ -1,5 +1,5 @@
import BaseModel, { AclAction, SaveOptions, ValidateOptions } from './BaseModel';
import { EmailSender, Item, User, Uuid } from '../db';
import { EmailSender, Item, User, UserFlagType, Uuid } from '../db';
import * as auth from '../utils/auth';
import { ErrorUnprocessableEntity, ErrorForbidden, ErrorPayloadTooLarge, ErrorNotFound } from '../utils/errors';
import { ModelType } from '@joplin/lib/BaseModel';
@ -245,16 +245,6 @@ export default class UserModel extends BaseModel<User> {
return !!s[0].length && !!s[1].length;
}
public async enable(id: Uuid, enabled: boolean) {
const user = await this.load(id);
if (!user) throw new ErrorNotFound(`No such user: ${id}`);
await this.save({ id, enabled: enabled ? 1 : 0 });
}
public async disable(id: Uuid) {
await this.enable(id, false);
}
public async delete(id: string): Promise<void> {
const shares = await this.models().share().sharesByUser(id);
@ -314,10 +304,6 @@ export default class UserModel extends BaseModel<User> {
await this.models().token().deleteByValue(user.id, token);
}
// public async disableUnpaidAccounts() {
// }
public async handleBetaUserEmails() {
if (!stripeConfig().enabled) return;
@ -355,7 +341,7 @@ export default class UserModel extends BaseModel<User> {
}
if (remainingDays <= 0) {
await this.save({ id: user.id, can_upload: 0 });
await this.models().userFlag().add(user.id, UserFlagType.AccountWithoutSubscription);
}
}
}
@ -372,7 +358,7 @@ export default class UserModel extends BaseModel<User> {
continue;
}
await this.save({ id: user.id, can_upload: 0 });
await this.models().userFlag().add(user.id, UserFlagType.FailedPaymentWarning);
await this.models().email().push({
...paymentFailedUploadDisabledTemplate(),

View File

@ -69,6 +69,7 @@ import ShareUserModel from './ShareUserModel';
import KeyValueModel from './KeyValueModel';
import TokenModel from './TokenModel';
import SubscriptionModel from './SubscriptionModel';
import UserFlagModel from './UserFlagModel';
import { Config } from '../utils/types';
export class Models {
@ -137,6 +138,10 @@ export class Models {
return new SubscriptionModel(this.db_, newModelFactory, this.config_);
}
public userFlag() {
return new UserFlagModel(this.db_, newModelFactory, this.config_);
}
}
export default function newModelFactory(db: DbConnection, config: Config): Models {

View File

@ -844,7 +844,10 @@ describe('shares.folder', function() {
test('should check permissions - cannot share with a disabled account', async function() {
const { session: session1 } = await createUserAndSession(1);
const { user: user2, session: session2 } = await createUserAndSession(2);
await models().user().disable(user2.id);
await models().user().save({
id: user2.id,
enabled: 0,
});
await expectHttpError(async () =>
shareFolderWithUser(session1.id, session2.id, '000000000000000000000000000000F1', [

View File

@ -174,7 +174,10 @@ describe('shares.link', function() {
note_id: noteItem.jop_id,
});
await models().user().disable(user.id);
await models().user().save({
id: user.id,
enabled: 0,
});
await expectHttpError(async () => getShareContent(share.id), ErrorForbidden.httpCode);
});

View File

@ -1,4 +1,5 @@
import { findPrice, PricePeriod } from '@joplin/lib/utils/joplinCloud';
import { UserFlagType } from '../../db';
import { AccountType } from '../../models/UserModel';
import { betaUserTrialPeriodDays, isBetaUser, stripeConfig } from '../../utils/stripe';
import { beforeAllDb, afterAllTests, beforeEachDb, models, koaAppContext, expectNotThrow } from '../../utils/testing/testUtils';
@ -191,5 +192,26 @@ describe('index/stripe', function() {
}
});
test('should re-enable account if successful payment is made', async function() {
const stripe = mockStripe();
const ctx = await koaAppContext();
await createUserViaSubscription(ctx, 'toto@example.com', { stripe, subscriptionId: 'sub_init' });
let user = (await models().user().all())[0];
await models().user().save({
id: user.id,
enabled: 0,
can_upload: 0,
});
await models().userFlag().add(user.id, UserFlagType.FailedPaymentFinal);
await simulateWebhook(ctx, 'invoice.paid', { subscription: 'sub_init' });
user = await models().user().load(user.id);
expect(user.enabled).toBe(1);
expect(user.can_upload).toBe(1);
});
});

View File

@ -10,7 +10,7 @@ import Logger from '@joplin/lib/Logger';
import getRawBody = require('raw-body');
import { AccountType } from '../../models/UserModel';
import { betaUserTrialPeriodDays, cancelSubscription, initStripe, isBetaUser, priceIdToAccountType, stripeConfig } from '../../utils/stripe';
import { Subscription } from '../../db';
import { Subscription, UserFlagType } from '../../db';
import { findPrice, PricePeriod } from '@joplin/lib/utils/joplinCloud';
const logger = Logger.create('/stripe');
@ -250,15 +250,21 @@ export const postHandlers: PostHandlers = {
logger.info(`Setting up subscription for existing user: ${existingUser.email}`);
// First set the account type correctly (in case the
// user also upgraded or downgraded their account). Also
// re-enable upload if it was disabled.
// user also upgraded or downgraded their account).
await models.user().save({
id: existingUser.id,
account_type: accountType,
can_upload: 1,
enabled: 1,
});
// Also clear any payment and subscription related flags
// since if we're here it means payment was successful
await models.userFlag().removeMulti(existingUser.id, [
UserFlagType.FailedPaymentWarning,
UserFlagType.FailedPaymentFinal,
UserFlagType.SubscriptionCancelled,
UserFlagType.AccountWithoutSubscription,
]);
// Then save the subscription
await models.subscription().save({
user_id: existingUser.id,
@ -319,8 +325,8 @@ export const postHandlers: PostHandlers = {
// by the user. In that case, we disable the user.
const { sub } = await getSubscriptionInfo(event, ctx);
await models.user().enable(sub.user_id, false);
await models.subscription().toggleSoftDelete(sub.id, true);
await models.userFlag().add(sub.user_id, UserFlagType.SubscriptionCancelled);
},
'customer.subscription.updated': async () => {

View File

@ -4,7 +4,7 @@ import { RouteType } from '../../utils/types';
import { AppContext, HttpMethod } from '../../utils/types';
import { bodyFields, contextSessionId, formParse } from '../../utils/requestUtils';
import { ErrorForbidden, ErrorUnprocessableEntity } from '../../utils/errors';
import { User, Uuid } from '../../db';
import { User, UserFlagType, Uuid } from '../../db';
import config from '../../config';
import { View } from '../../services/MustacheService';
import defaultView from '../../utils/defaultView';
@ -273,40 +273,41 @@ router.post('users', async (path: SubPath, ctx: AppContext) => {
if (userIsMe(path)) fields.id = userId;
user = makeUser(isNew, fields);
const userModel = ctx.joplin.models.user();
const models = ctx.joplin.models;
if (fields.post_button) {
const userToSave: User = userModel.fromApiInput(user);
await userModel.checkIfAllowed(ctx.joplin.owner, isNew ? AclAction.Create : AclAction.Update, userToSave);
const userToSave: User = models.user().fromApiInput(user);
await models.user().checkIfAllowed(ctx.joplin.owner, isNew ? AclAction.Create : AclAction.Update, userToSave);
if (isNew) {
await userModel.save(userToSave);
await models.user().save(userToSave);
} else {
await userModel.save(userToSave, { isNew: false });
await models.user().save(userToSave, { isNew: false });
}
} else if (fields.user_cancel_subscription_button) {
await cancelSubscriptionByUserId(ctx.joplin.models, userId);
await cancelSubscriptionByUserId(models, userId);
const sessionId = contextSessionId(ctx, false);
if (sessionId) {
await ctx.joplin.models.session().logout(sessionId);
await models.session().logout(sessionId);
return redirect(ctx, config().baseUrl);
}
} else {
if (ctx.joplin.owner.is_admin) {
if (fields.disable_button || fields.restore_button) {
const user = await userModel.load(path.id);
await userModel.checkIfAllowed(ctx.joplin.owner, AclAction.Delete, user);
await userModel.enable(path.id, !!fields.restore_button);
const user = await models.user().load(path.id);
await models.user().checkIfAllowed(ctx.joplin.owner, AclAction.Delete, user);
await models.userFlag().toggle(user.id, UserFlagType.ManuallyDisabled, !!fields.restore_button);
} else if (fields.send_reset_password_email) {
const user = await userModel.load(path.id);
await userModel.save({ id: user.id, must_set_password: 1 });
await userModel.sendAccountConfirmationEmail(user);
const user = await models.user().load(path.id);
await models.user().save({ id: user.id, must_set_password: 1 });
await models.user().sendAccountConfirmationEmail(user);
} else if (fields.cancel_subscription_button) {
await cancelSubscriptionByUserId(ctx.joplin.models, userId);
await cancelSubscriptionByUserId(models, userId);
} else if (fields.update_subscription_basic_button) {
await updateSubscriptionType(ctx.joplin.models, userId, AccountType.Basic);
await updateSubscriptionType(models, userId, AccountType.Basic);
} else if (fields.update_subscription_pro_button) {
await updateSubscriptionType(ctx.joplin.models, userId, AccountType.Pro);
await updateSubscriptionType(models, userId, AccountType.Pro);
} else {
throw new Error('Invalid form button');
}

View File

@ -5,7 +5,8 @@ import { DatabaseConfig } from '../utils/types';
const { execCommand } = require('@joplin/tools/tool-utils');
export interface CreateDbOptions {
dropIfExists: boolean;
dropIfExists?: boolean;
autoMigrate?: boolean;
}
export interface DropDbOptions {
@ -15,6 +16,7 @@ export interface DropDbOptions {
export async function createDb(config: DatabaseConfig, options: CreateDbOptions = null) {
options = {
dropIfExists: false,
autoMigrate: true,
...options,
};
@ -46,7 +48,7 @@ export async function createDb(config: DatabaseConfig, options: CreateDbOptions
try {
const db = await connectDb(config);
await migrateLatest(db);
if (options.autoMigrate) await migrateLatest(db);
await disconnectDb(db);
} catch (error) {
error.message += `: ${config.name}`;

View File

@ -22,36 +22,38 @@ const config = {
'tableNameCasing': 'pascal' as any,
'filename': './db',
'extends': {
'main.sessions': 'WithDates, WithUuid',
'main.users': 'WithDates, WithUuid',
'main.items': 'WithDates, WithUuid',
'main.api_clients': 'WithDates, WithUuid',
'main.changes': 'WithDates, WithUuid',
'main.notifications': 'WithDates, WithUuid',
'main.shares': 'WithDates, WithUuid',
'main.share_users': 'WithDates, WithUuid',
'main.user_items': 'WithDates',
'main.emails': 'WithDates',
'main.items': 'WithDates, WithUuid',
'main.notifications': 'WithDates, WithUuid',
'main.sessions': 'WithDates, WithUuid',
'main.share_users': 'WithDates, WithUuid',
'main.shares': 'WithDates, WithUuid',
'main.tokens': 'WithDates',
'main.user_flags': 'WithDates',
'main.user_items': 'WithDates',
'main.users': 'WithDates, WithUuid',
},
};
const propertyTypes: Record<string, string> = {
'*.item_type': 'ItemType',
'changes.type': 'ChangeType',
'notifications.level': 'NotificationLevel',
'shares.type': 'ShareType',
'items.content': 'Buffer',
'items.jop_updated_time': 'number',
'share_users.status': 'ShareUserStatus',
'emails.sender_id': 'EmailSender',
'emails.sent_time': 'number',
'subscriptions.last_payment_time': 'number',
'items.content': 'Buffer',
'items.jop_updated_time': 'number',
'notifications.level': 'NotificationLevel',
'share_users.status': 'ShareUserStatus',
'shares.type': 'ShareType',
'subscriptions.last_payment_failed_time': 'number',
'subscriptions.last_payment_time': 'number',
'user_flags.type': 'UserFlagType',
'users.can_share_folder': 'number | null',
'users.can_share_note': 'number | null',
'users.max_total_item_size': 'number | null',
'users.max_item_size': 'number | null',
'users.max_total_item_size': 'number | null',
'users.total_item_size': 'number',
};

View File

@ -1,5 +1,5 @@
import { User, Session, DbConnection, connectDb, disconnectDb, truncateTables, Item, Uuid } from '../../db';
import { createDb } from '../../tools/dbTools';
import { createDb, CreateDbOptions } from '../../tools/dbTools';
import modelFactory from '../../models/factory';
import { AppContext, Env } from '../types';
import config, { initConfig } from '../../config';
@ -60,7 +60,7 @@ function initGlobalLogger() {
}
let createdDbPath_: string = null;
export async function beforeAllDb(unitName: string) {
export async function beforeAllDb(unitName: string, createDbOptions: CreateDbOptions = null) {
unitName = unitName.replace(/\//g, '_');
createdDbPath_ = `${packageRootDir}/db-test-${unitName}.sqlite`;
@ -89,7 +89,7 @@ export async function beforeAllDb(unitName: string) {
initGlobalLogger();
await createDb(config().database, { dropIfExists: true });
await createDb(config().database, { dropIfExists: true, ...createDbOptions });
db_ = await connectDb(config().database);
const mustache = new MustacheService(config().viewDir, config().baseUrl);