exports.up = async function(knex) { // restore avatars in table in case of rollback and rerun const avatars = await knex('actors_avatars') .select('actors_avatars.*', 'actors_profiles.actor_id') .leftJoin('actors_profiles', 'actors_profiles.id', 'actors_avatars.profile_id'); await knex('actors_avatars').delete(); await knex.schema.alterTable('actors_avatars', (table) => { table.integer('profile_id') .nullable() .alter(); table.integer('actor_id') .notNullable() .references('id') .inTable('actors'); table.datetime('created_at') .notNullable() .defaultTo(knex.fn.now()); table.dropUnique('profile_id'); table.unique(['profile_id', 'media_id']); }); await knex.schema.alterTable('media', (table) => { // actor avatars often retain the same URL when updated, handle URL-deduping in app code table.dropUnique('source'); table.string('source_version'); // usually etag }); await knex.raw('CREATE UNIQUE INDEX unique_main_avatars ON actors_avatars (actor_id) WHERE (profile_id IS NULL);'); if (avatars.length > 0) { await knex('actors_avatars').insert(avatars); } const profiles = await knex('actors_profiles') .select('id', 'actor_id', 'avatar_media_id') .whereNotNull('avatar_media_id'); await knex('actors_avatars') .insert(profiles.map((profile) => ({ actor_id: profile.actor_id, profile_id: profile.id, media_id: profile.avatar_media_id, }))) .onConflict() .ignore(); }; exports.down = async function(knex) { // no need to delete all entries, only the ones incompatible with the old scheme await knex('actors_avatars') .whereNull('profile_id') .delete(); await knex.schema.alterTable('actors_avatars', (table) => { table.integer('profile_id') .notNullable() .alter(); table.dropColumn('actor_id'); table.dropColumn('created_at'); table.unique('profile_id'); table.dropUnique(['profile_id', 'media_id']); }); await knex.schema.alterTable('media', (table) => { table.dropColumn('source_version'); table.unique('source'); }); };