diff --git a/packages/app-cli/tools/populateDatabase.ts b/packages/app-cli/tools/populateDatabase.ts index b791b9cbb..7bbfbbd0d 100644 --- a/packages/app-cli/tools/populateDatabase.ts +++ b/packages/app-cli/tools/populateDatabase.ts @@ -90,12 +90,12 @@ const main = async () => { // run the scripts (faster) await execCommand2(['npm', 'run', 'build']); - const focusUserNum = 400; + const focusUserNum = 0; while (true) { let userNum = randomInt(minUserNum, maxUserNum); - if (Math.random() >= .7) userNum = focusUserNum; + if (focusUserNum && Math.random() >= .7) userNum = focusUserNum; void processUser(userNum); await waitForProcessing(10); diff --git a/packages/server/schema.sqlite b/packages/server/schema.sqlite index 1dc95da07..de0102caa 100644 Binary files a/packages/server/schema.sqlite and b/packages/server/schema.sqlite differ diff --git a/packages/server/src/app.ts b/packages/server/src/app.ts index eafb0c312..00aedccfe 100644 --- a/packages/server/src/app.ts +++ b/packages/server/src/app.ts @@ -282,8 +282,12 @@ async function main() { await setupAppContext(ctx, env, connectionCheck.connection, appLogger); await initializeJoplinUtils(config(), ctx.joplinBase.models, ctx.joplinBase.services.mustache); - appLogger().info('Migrating database...'); - await migrateLatest(ctx.joplinBase.db); + if (config().database.autoMigration) { + appLogger().info('Auto-migrating database...'); + await migrateLatest(ctx.joplinBase.db); + } else { + appLogger().info('Skipped database auto-migration.'); + } appLogger().info('Starting services...'); await startServices(ctx.joplinBase.services); diff --git a/packages/server/src/config.ts b/packages/server/src/config.ts index 51b80014f..00c295565 100644 --- a/packages/server/src/config.ts +++ b/packages/server/src/config.ts @@ -10,16 +10,36 @@ interface PackageJson { const packageJson: PackageJson = require(`${__dirname}/packageInfo.js`); export interface EnvVariables { + // ================================================== + // General config + // ================================================== + APP_NAME?: string; + APP_PORT?: string; + SIGNUP_ENABLED?: string; + TERMS_ENABLED?: string; + ACCOUNT_TYPES_ENABLED?: string; + ERROR_STACK_TRACES?: string; + COOKIES_SECURE?: string; + RUNNING_IN_DOCKER?: string; + + // ================================================== + // URL config + // ================================================== APP_BASE_URL?: string; USER_CONTENT_BASE_URL?: string; API_BASE_URL?: string; JOPLINAPP_BASE_URL?: string; - APP_PORT?: string; + // ================================================== + // Database config + // ================================================== + DB_CLIENT?: string; - RUNNING_IN_DOCKER?: string; + DB_SLOW_QUERY_LOG_ENABLED?: string; + DB_SLOW_QUERY_LOG_MIN_DURATION?: string; // ms + DB_AUTO_MIGRATION?: string; POSTGRES_PASSWORD?: string; POSTGRES_DATABASE?: string; @@ -27,6 +47,13 @@ export interface EnvVariables { POSTGRES_HOST?: string; POSTGRES_PORT?: string; + // This must be the full path to the database file + SQLITE_DATABASE?: string; + + // ================================================== + // Mailer config + // ================================================== + MAILER_ENABLED?: string; MAILER_HOST?: string; MAILER_PORT?: string; @@ -36,27 +63,16 @@ export interface EnvVariables { MAILER_NOREPLY_NAME?: string; MAILER_NOREPLY_EMAIL?: string; - // This must be the full path to the database file - SQLITE_DATABASE?: string; + SUPPORT_EMAIL?: string; + SUPPORT_NAME?: string; + BUSINESS_EMAIL?: string; + + // ================================================== + // Stripe config + // ================================================== STRIPE_SECRET_KEY?: string; STRIPE_WEBHOOK_SECRET?: string; - - SIGNUP_ENABLED?: string; - TERMS_ENABLED?: string; - ACCOUNT_TYPES_ENABLED?: string; - - ERROR_STACK_TRACES?: string; - - SUPPORT_EMAIL?: string; - SUPPORT_NAME?: string; - - BUSINESS_EMAIL?: string; - - COOKIES_SECURE?: string; - - SLOW_QUERY_LOG_ENABLED?: string; - SLOW_QUERY_LOG_MIN_DURATION?: string; // ms } let runningInDocker_: boolean = false; @@ -69,7 +85,8 @@ function envReadString(s: string, defaultValue: string = ''): string { return s === undefined || s === null ? defaultValue : s; } -function envReadBool(s: string): boolean { +function envReadBool(s: string, defaultValue = false): boolean { + if (s === undefined || s === null) return defaultValue; return s === '1'; } @@ -99,8 +116,9 @@ function databaseConfigFromEnv(runningInDocker: boolean, env: EnvVariables): Dat const baseConfig: DatabaseConfig = { client: DatabaseConfigClient.Null, name: '', - slowQueryLogEnabled: envReadBool(env.SLOW_QUERY_LOG_ENABLED), - slowQueryLogMinDuration: envReadInt(env.SLOW_QUERY_LOG_MIN_DURATION, 10000), + slowQueryLogEnabled: envReadBool(env.DB_SLOW_QUERY_LOG_ENABLED), + slowQueryLogMinDuration: envReadInt(env.DB_SLOW_QUERY_LOG_MIN_DURATION, 10000), + autoMigration: envReadBool(env.DB_AUTO_MIGRATION, true), }; if (env.DB_CLIENT === 'pg') { diff --git a/packages/server/src/migrations/20211027112530_item_owner.ts b/packages/server/src/migrations/20211027112530_item_owner.ts new file mode 100644 index 000000000..8d17cfedf --- /dev/null +++ b/packages/server/src/migrations/20211027112530_item_owner.ts @@ -0,0 +1,25 @@ +import { Knex } from 'knex'; +import { DbConnection } from '../db'; + +export async function up(db: DbConnection): Promise { + await db.schema.alterTable('items', (table: Knex.CreateTableBuilder) => { + table.string('owner_id', 32).defaultTo('').notNullable(); + }); + + await db.raw(` + UPDATE items + SET owner_id = user_items.user_id + FROM user_items + WHERE user_items.item_id = items.id + `); + + await db.schema.alterTable('items', (table: Knex.CreateTableBuilder) => { + table.string('owner_id', 32).notNullable().alter(); + }); +} + +export async function down(db: DbConnection): Promise { + await db.schema.alterTable('items', (table: Knex.CreateTableBuilder) => { + table.dropColumn('owner_id'); + }); +} diff --git a/packages/server/src/models/ItemModel.ts b/packages/server/src/models/ItemModel.ts index 0dfa4db38..85c2c0172 100644 --- a/packages/server/src/models/ItemModel.ts +++ b/packages/server/src/models/ItemModel.ts @@ -579,6 +579,7 @@ export default class ItemModel extends BaseModel { if (isNew) { if (!item.mime_type) item.mime_type = mimeUtils.fromFilename(item.name) || ''; + if (!item.owner_id) item.owner_id = userId; } else { const beforeSaveItem = (await this.load(item.id, { fields: ['name', 'jop_type', 'jop_parent_id', 'jop_share_id'] })); const resourceIds = beforeSaveItem.jop_type === ModelType.Note ? await this.models().itemResource().byItemId(item.id) : []; diff --git a/packages/server/src/routes/api/items.test.ts b/packages/server/src/routes/api/items.test.ts index b40261733..427c49be4 100644 --- a/packages/server/src/routes/api/items.test.ts +++ b/packages/server/src/routes/api/items.test.ts @@ -49,6 +49,7 @@ describe('api_items', function() { expect(item.jop_type).toBe(ModelType.Note); expect(!item.content).toBe(true); expect(item.content_size).toBeGreaterThan(0); + expect(item.owner_id).toBe(user.id); { const item: NoteEntity = await models().item().loadAsJoplinItem(itemId); diff --git a/packages/server/src/services/database/types.ts b/packages/server/src/services/database/types.ts index 678f24dc4..f744c0154 100644 --- a/packages/server/src/services/database/types.ts +++ b/packages/server/src/services/database/types.ts @@ -145,19 +145,6 @@ export interface ShareUser extends WithDates, WithUuid { status?: ShareUserStatus; } -export interface Item extends WithDates, WithUuid { - name?: string; - mime_type?: string; - content?: Buffer; - content_size?: number; - jop_id?: Uuid; - jop_parent_id?: Uuid; - jop_share_id?: Uuid; - jop_type?: number; - jop_encryption_applied?: number; - jop_updated_time?: number; -} - export interface UserItem extends WithDates { id?: number; user_id?: Uuid; @@ -257,6 +244,20 @@ export interface Event extends WithUuid { created_time?: number; } +export interface Item extends WithDates, WithUuid { + name?: string; + mime_type?: string; + content?: Buffer; + content_size?: number; + jop_id?: Uuid; + jop_parent_id?: Uuid; + jop_share_id?: Uuid; + jop_type?: number; + jop_encryption_applied?: number; + jop_updated_time?: number; + owner_id?: Uuid; +} + export const databaseSchema: DatabaseTables = { sessions: { id: { type: 'string' }, @@ -307,21 +308,6 @@ export const databaseSchema: DatabaseTables = { updated_time: { type: 'string' }, created_time: { type: 'string' }, }, - items: { - id: { type: 'string' }, - name: { type: 'string' }, - mime_type: { type: 'string' }, - updated_time: { type: 'string' }, - created_time: { type: 'string' }, - content: { type: 'any' }, - content_size: { type: 'number' }, - jop_id: { type: 'string' }, - jop_parent_id: { type: 'string' }, - jop_share_id: { type: 'string' }, - jop_type: { type: 'number' }, - jop_encryption_applied: { type: 'number' }, - jop_updated_time: { type: 'string' }, - }, user_items: { id: { type: 'number' }, user_id: { type: 'string' }, @@ -428,5 +414,21 @@ export const databaseSchema: DatabaseTables = { name: { type: 'string' }, created_time: { type: 'string' }, }, + items: { + id: { type: 'string' }, + name: { type: 'string' }, + mime_type: { type: 'string' }, + updated_time: { type: 'string' }, + created_time: { type: 'string' }, + content: { type: 'any' }, + content_size: { type: 'number' }, + jop_id: { type: 'string' }, + jop_parent_id: { type: 'string' }, + jop_share_id: { type: 'string' }, + jop_type: { type: 'number' }, + jop_encryption_applied: { type: 'number' }, + jop_updated_time: { type: 'string' }, + owner_id: { type: 'string' }, + }, }; // AUTO-GENERATED-TYPES diff --git a/packages/server/src/utils/types.ts b/packages/server/src/utils/types.ts index 4a6cbfb50..d66d04fcf 100644 --- a/packages/server/src/utils/types.ts +++ b/packages/server/src/utils/types.ts @@ -67,6 +67,7 @@ export interface DatabaseConfig { asyncStackTraces?: boolean; slowQueryLogEnabled?: boolean; slowQueryLogMinDuration?: number; + autoMigration?: boolean; } export interface MailerConfig {