mirror of
https://github.com/UniversityOfHelsinkiCS/ohtuilmo.git
synced 2026-10-12 19:03:41 +03:00
297 lines
9.2 KiB
JavaScript
297 lines
9.2 KiB
JavaScript
'use strict'
|
|
|
|
/*
|
|
* This base migration attempts to create an 1:1 schema with the schema on the
|
|
* current production server. The schema **DOES NOT MATCH** the schema created
|
|
* by Sequelize from the models, because it does not match production's schema!
|
|
* The next migration fixes this.
|
|
*
|
|
* An oddity here is that **some** models use snake_case and some camelCase, and
|
|
* some use the "underscore: true" option, whereas some don't.
|
|
* As a result, **some** tables have createdAt and some created_at, etc.
|
|
* Some foreign key associations also use underscores, whereas some use camelCase.
|
|
*
|
|
* Another change should address this change as a migration and model update.
|
|
*
|
|
* Edit (2024-01-22): renamed camelCase column names to snake_case (which is used
|
|
* in the production db).
|
|
*/
|
|
|
|
module.exports = {
|
|
up: async (query, Sequelize) => {
|
|
// first create tables, then add relations
|
|
|
|
const createTimestampColumns = ({ camelCase }) => {
|
|
const timestampType = {
|
|
type: Sequelize.DATE,
|
|
allowNull: false,
|
|
}
|
|
|
|
const fieldNames = camelCase ? ['created_at', 'updated_at'] : ['created_at', 'updated_at']
|
|
|
|
/*
|
|
* Map the fieldNames array into an array of objects
|
|
* where the field name is the key and timetampType is the value
|
|
* ['foo', 'bar'] --> [ {foo: timestampType, bar: timestampType} ]
|
|
*/
|
|
return fieldNames.reduce(
|
|
(opts, name) => ({
|
|
...opts,
|
|
[name]: timestampType,
|
|
}),
|
|
{},
|
|
)
|
|
}
|
|
// since associations are created afterwards, we can do all of these
|
|
// concurrently. query.createTable returns promises so we gather them all
|
|
// into an array to start them all at the same time, then wait for finish
|
|
// with Promise.all
|
|
const tableCreationPromises = [
|
|
query.createTable('configurations', {
|
|
id: {
|
|
type: Sequelize.INTEGER,
|
|
primaryKey: true,
|
|
autoIncrement: true,
|
|
},
|
|
name: Sequelize.STRING,
|
|
content: Sequelize.JSONB,
|
|
active: {
|
|
type: Sequelize.BOOLEAN,
|
|
defaultValue: false,
|
|
},
|
|
// sequelize timestamps
|
|
...createTimestampColumns({ camelCase: false }),
|
|
// foreign keys for associations
|
|
review_question_set1_id: Sequelize.INTEGER,
|
|
review_question_set2_id: Sequelize.INTEGER,
|
|
registration_question_set_id: Sequelize.INTEGER,
|
|
}),
|
|
query.createTable('groups', {
|
|
id: {
|
|
type: Sequelize.INTEGER,
|
|
autoIncrement: true,
|
|
primaryKey: true,
|
|
},
|
|
group_name: Sequelize.STRING,
|
|
// sequelize timestamps
|
|
...createTimestampColumns({ camelCase: false }),
|
|
}),
|
|
query.createTable('memberships', {
|
|
id: {
|
|
type: Sequelize.INTEGER,
|
|
autoIncrement: true,
|
|
primaryKey: true,
|
|
},
|
|
role: Sequelize.STRING,
|
|
// sequelize timestamps
|
|
...createTimestampColumns({ camelCase: false }),
|
|
// foreign keys for associations
|
|
student_number: Sequelize.STRING,
|
|
}),
|
|
query.createTable('registration_question_sets', {
|
|
id: {
|
|
type: Sequelize.INTEGER,
|
|
primaryKey: true,
|
|
autoIncrement: true,
|
|
},
|
|
name: Sequelize.STRING,
|
|
questions: Sequelize.JSONB,
|
|
// sequelize timestamps
|
|
...createTimestampColumns({ camelCase: false }),
|
|
}),
|
|
query.createTable('registrations', {
|
|
id: {
|
|
type: Sequelize.INTEGER,
|
|
primaryKey: true,
|
|
autoIncrement: true,
|
|
},
|
|
preferred_topics: Sequelize.JSONB,
|
|
questions: Sequelize.JSONB,
|
|
// sequelize timestamps
|
|
...createTimestampColumns({ camelCase: false }),
|
|
// foreign keys for associations
|
|
configuration_id: Sequelize.INTEGER,
|
|
student_student_number: Sequelize.STRING(255),
|
|
}),
|
|
query.createTable('review_question_sets', {
|
|
id: {
|
|
type: Sequelize.INTEGER,
|
|
primaryKey: true,
|
|
autoIncrement: true,
|
|
},
|
|
name: Sequelize.STRING,
|
|
questions: Sequelize.JSONB,
|
|
// sequelize timestamps
|
|
...createTimestampColumns({ camelCase: false }),
|
|
}),
|
|
query.createTable('topic_dates', {
|
|
id: {
|
|
type: Sequelize.INTEGER,
|
|
autoIncrement: true,
|
|
primaryKey: true,
|
|
},
|
|
dates: Sequelize.JSONB,
|
|
// sequelize timestamps
|
|
...createTimestampColumns({ camelCase: false }),
|
|
}),
|
|
query.createTable('topics', {
|
|
id: {
|
|
type: Sequelize.INTEGER,
|
|
primaryKey: true,
|
|
autoIncrement: true,
|
|
},
|
|
active: {
|
|
type: Sequelize.BOOLEAN,
|
|
defaultValue: false,
|
|
},
|
|
content: Sequelize.JSONB,
|
|
acronym: Sequelize.STRING,
|
|
secret_id: Sequelize.STRING,
|
|
// sequelize timestamps
|
|
...createTimestampColumns({ camelCase: false }),
|
|
}),
|
|
query.createTable('users', {
|
|
student_number: {
|
|
type: Sequelize.STRING,
|
|
primaryKey: true,
|
|
},
|
|
username: Sequelize.STRING,
|
|
first_names: Sequelize.STRING,
|
|
last_name: Sequelize.STRING,
|
|
email: Sequelize.STRING,
|
|
admin: {
|
|
type: Sequelize.BOOLEAN,
|
|
defaultValue: false,
|
|
},
|
|
// sequelize timestamps
|
|
...createTimestampColumns({ camelCase: false }),
|
|
}),
|
|
]
|
|
|
|
// wait for table creation
|
|
await Promise.all(tableCreationPromises)
|
|
|
|
// tables created, add association
|
|
|
|
/*
|
|
* Sequelize's model constrains, by default, add
|
|
* - ON UPDATE CASCADE
|
|
* - ON DELETE SET NULL
|
|
* However, the production schema has one relation with ON DELETE CASCADE...
|
|
*/
|
|
const defaultOnDeleteAndUpdate = {
|
|
onUpdate: 'CASCADE',
|
|
onDelete: 'SET NULL',
|
|
}
|
|
|
|
const addForeignKey = (
|
|
constraintName,
|
|
targetTable,
|
|
referencedTable,
|
|
onDeleteAndUpdate = defaultOnDeleteAndUpdate,
|
|
) => {
|
|
const [table, field] = targetTable
|
|
const [foreignTable, foreignField] = referencedTable
|
|
|
|
return query.addConstraint(table, {
|
|
fields: [field],
|
|
name: constraintName,
|
|
type: 'FOREIGN KEY',
|
|
references: {
|
|
table: foreignTable,
|
|
field: foreignField,
|
|
},
|
|
onUpdate: onDeleteAndUpdate.onUpdate,
|
|
onDelete: onDeleteAndUpdate.onDelete,
|
|
})
|
|
}
|
|
|
|
// Foreign key names have been plucked directly from the database schema
|
|
// dump
|
|
await addForeignKey(
|
|
'configurations_registration_question_set_id_fkey',
|
|
['configurations', 'registration_question_set_id'],
|
|
['registration_question_sets', 'id'],
|
|
)
|
|
await addForeignKey(
|
|
'configurations_review_question_set1_id_fkey',
|
|
['configurations', 'review_question_set1_id'],
|
|
['review_question_sets', 'id'],
|
|
)
|
|
await addForeignKey(
|
|
'configurations_review_question_set2_id_fkey',
|
|
['configurations', 'review_question_set2_id'],
|
|
['review_question_sets', 'id'],
|
|
)
|
|
await addForeignKey('memberships_id_fkey', ['memberships', 'id'], ['groups', 'id'], {
|
|
onUpdate: 'CASCADE',
|
|
onDelete: 'CASCADE', // Production schema's single constraint oddity.
|
|
})
|
|
await addForeignKey(
|
|
'registrations_configuration_id_fkey',
|
|
['registrations', 'configuration_id'],
|
|
['configurations', 'id'],
|
|
)
|
|
|
|
// Important: migration must return promise which, when resolved, marks the
|
|
// completion of all query operations. Here we just return the last awaited
|
|
// promise since all the other awaits have been done before this one.
|
|
return await addForeignKey(
|
|
'registrations_student_student_number _fkey',
|
|
['registrations', 'student_student_number'],
|
|
['users', 'student_number'],
|
|
)
|
|
},
|
|
|
|
down: async (query) => {
|
|
// Since this is the base migration, rolling back this migration should
|
|
// delete all of the tables and their associations.
|
|
|
|
// Start with associations so that we can delete tables without constraint
|
|
// violations.
|
|
|
|
const constraints = [
|
|
{
|
|
table: 'configurations',
|
|
constraint: 'configurations_registration_question_set_id_fkey',
|
|
},
|
|
{
|
|
table: 'configurations',
|
|
constraint: 'configurations_review_question_set1_id_fkey',
|
|
},
|
|
{
|
|
table: 'configurations',
|
|
constraint: 'configurations_review_question_set2_id_fkey',
|
|
},
|
|
{ table: 'memberships', constraint: 'memberships_id_fkey' },
|
|
{
|
|
table: 'registrations',
|
|
constraint: 'registrations_configuration_id_fkey',
|
|
},
|
|
{
|
|
table: 'registrations',
|
|
constraint: 'registrations_student_student_number _fkey',
|
|
},
|
|
]
|
|
const removeConstraintPromises = constraints.map(({ table, constraint }) =>
|
|
query.removeConstraint(table, constraint),
|
|
)
|
|
await Promise.all(removeConstraintPromises)
|
|
|
|
// Constraints removed, remove tables
|
|
const tableNames = [
|
|
'configurations',
|
|
'groups',
|
|
'memberships',
|
|
'registration_question_sets',
|
|
'registrations',
|
|
'review_question_sets',
|
|
'topic_dates',
|
|
'topics',
|
|
'users',
|
|
]
|
|
|
|
return await Promise.all(tableNames.map((tableName) => query.dropTable(tableName)))
|
|
},
|
|
}
|