diff --git a/.github/workflows/test_migration.yml b/.github/workflows/test_migration.yml index 918a1b14..1eb9d522 100644 --- a/.github/workflows/test_migration.yml +++ b/.github/workflows/test_migration.yml @@ -36,6 +36,7 @@ jobs: env: CI_EXIT_AFTER_MIGRATION: "true" CI_AUTO_MIGRATION: "false" + DB_NAME: campus_db DB_DSN: root:super_secret_passw0rd@tcp(localhost:3306)/campus_db?charset=utf8mb4&parseTime=True&loc=Local ENVIRONMENT: dev - name: run auto migrations @@ -44,6 +45,7 @@ jobs: env: CI_EXIT_AFTER_MIGRATION: "true" CI_AUTO_MIGRATION: "true" + DB_NAME: campus_db DB_DSN: root:super_secret_passw0rd@tcp(localhost:3300)/campus_db?charset=utf8mb4&parseTime=True&loc=Local ENVIRONMENT: dev - uses: ariga/setup-atlas@master diff --git a/.vscode/launch.json b/.vscode/launch.json index ec27b2c2..36fcb21e 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -11,7 +11,8 @@ "mode": "auto", "program": "${workspaceFolder}/server/main.go", "env": { - "DB_DSN": "gorm:GORM_USER_PASSWORD@tcp(localhost:3306)/campus_backend" + "DB_DSN": "gorm:GORM_USER_PASSWORD@tcp(localhost:3306)/campus_backend", + "DB_NAME": "campus_backend" } }, { diff --git a/README.md b/README.md index 0128c1e7..417e8062 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,7 @@ To start the server there are environment variables, as well as command line opt ```bash cd server export DB_DSN="Your gorm DB connection string for example: gorm:GORM_USER_PASSWORD@tcp(localhost:3306)/campus_backend" +export DB_DSN="The DB-name from above string for example: campus_backend" go run ./main.go ``` @@ -70,6 +71,7 @@ go run ./main.go There are a few environment variables available: * [REQUIRED] `DB_DSN`: The [GORM](https://gorm.io/) [DB connection string](https://gorm.io/docs/connecting_to_the_database.html#MySQL) for connecting to the MySQL DB. Example: `gorm@tcp(localhost:3306)/campus_backend` +* [REQUIRED] `DB_DSN`: The name of the database from above connection string. Example: `campus_backend` * [OPTIONAL] `SENTRY_DSN`: The Sentry [Data Source Name](https://sentry-docs-git-patch-1.sentry.dev/product/sentry-basics/dsn-explainer/) for reporting issues and crashes. ## Running the Server (Docker) diff --git a/deployment/charts/backend/templates/deployments/backend-v2.yaml b/deployment/charts/backend/templates/deployments/backend-v2.yaml index b6a97c28..cce6c705 100644 --- a/deployment/charts/backend/templates/deployments/backend-v2.yaml +++ b/deployment/charts/backend/templates/deployments/backend-v2.yaml @@ -85,6 +85,8 @@ spec: key: SMTP_PORT - name: DB_DSN value: "{{ $db.username }}:{{ $db.password }}@tcp(tca-backend-mariadb.{{ $.Values.namespace }}.svc.cluster.local:3306)/{{ $db.database }}?charset=utf8mb4&parseTime=True&loc=Local" + - name: DB_NAME + value: {{ $db.database }} volumeMounts: - mountPath: /Storage/ name: storage-vol diff --git a/docker-compose.yaml b/docker-compose.yaml index 4f5cc80b..a8580b43 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -10,6 +10,7 @@ services: - 50051:50051 environment: - DB_DSN=root:${DB_ROOT_PASSWORD}@tcp(db:${DB_PORT:-3306})/${DB_NAME}?charset=utf8mb4&parseTime=True&loc=Local + - DB_NAME=${DB_NAME} - ENVIRONMENT=dev - SENTRY_DSN=${SENTRY_DSN} - OMDB_API_KEY=${OMDB_API_KEY} diff --git a/server/backend/migration/20240316000000.go b/server/backend/migration/20240316000000.go new file mode 100644 index 00000000..1157298f --- /dev/null +++ b/server/backend/migration/20240316000000.go @@ -0,0 +1,197 @@ +package migration + +import ( + "fmt" + + "github.com/go-gormigrate/gormigrate/v2" + "gorm.io/gorm" +) + +type tableWithWrongfield struct { + table string + field string +} +type fkNeedingMigration struct { + fromTable string + fromColumn string + toTable string + toColumn string + constraintName string +} + +func tablesWithWrongId() []tableWithWrongfield { + return []tableWithWrongfield{ + // PKs which are int32 + {"alarm_ban", "ban"}, + {"alarm_log", "alarm"}, + {"barrierFree_moreInfo", "id"}, + {"barrierFree_persons", "id"}, + {"chat_message", "message"}, + {"chat_room2members", "room2members"}, + {"crontab", "cron"}, + {"curricula", "curriculum"}, + {"dish2dishflags", "dish2dishflags"}, + {"dish2mensa", "dish2mensa"}, + {"feedback", "id"}, + {"log", "log"}, + {"mensaplan_mensa", "id"}, + {"mensaprices", "price"}, + {"modules", "module"}, + {"news_alert", "news_alert"}, + {"openinghours", "id"}, + {"recover", "recover"}, + {"reports", "report"}, + {"ticket_admin2group", "ticket_admin2group"}, + {"ticket_history", "ticket_history"}, + {"wifi_measurement", "id"}, + {"update_note", "version_code"}, + // should have been a fk, but is an index + {"question", "member"}, + // multi-pk-indexes + {"roomfinder_buildings2maps", "map_id"}, + {"roomfinder_maps", "map_id"}, + {"roomfinder_rooms", "room_id"}, + {"roomfinder_rooms2maps", "room_id"}, + {"roomfinder_rooms2maps", "map_id"}, + {"roomfinder_schedules", "room_id"}, + } +} + +// tablesWithWrongFk tells a user which FKs exist that have a int-ish type +// can be generated via: +// ```sql +// with fks as (select fks.table_name as from_table, +// +// group_concat(kcu.COLUMN_NAME +// order by position_in_unique_constraint separator ', ') +// as from_columns, +// fks.referenced_table_name as to_table, +// group_concat(kcu.REFERENCED_COLUMN_NAME +// order by position_in_unique_constraint separator ', ') +// as to_columns, +// fks.constraint_name +// from information_schema.referential_constraints fks +// join information_schema.key_column_usage kcu +// on fks.constraint_schema = kcu.table_schema +// and fks.table_name = kcu.table_name +// and fks.constraint_name = kcu.constraint_name +// where fks.constraint_schema = 'campus_db' +// group by fks.constraint_schema, +// fks.table_name, +// fks.unique_constraint_schema, +// fks.referenced_table_name, +// fks.constraint_name +// order by fks.constraint_schema, +// fks.table_name), +// tables_with_matching_type as (SELECT TABLE_NAME, COLUMN_NAME +// from information_schema.columns +// WHERE DATA_TYPE like '%int%' +// and TABLE_SCHEMA = 'campus_db') +// +// SELECT f.* +// from fks f +// WHERE EXISTS(SELECT * +// +// FROM tables_with_matching_type t +// where t.TABLE_NAME = f.from_table +// and t.COLUMN_NAME = f.from_columns); +func tablesWithWrongFk() []fkNeedingMigration { + return []fkNeedingMigration{ + {"chat_message", "member", "member", "member", "chat_message_ibfk_1"}, + {"chat_message", "room", "chat_room", "room", "FK_chat_message_chat_room"}, + {"chat_room2members", "room", "chat_room", "room", "FK_chat_room2members_chat_room"}, + {"chat_room2members", "member", "member", "member", "chat_room2members_ibfk_2"}, + {"device2stats", "device", "devices", "device", "device2stats_ibfk_2"}, + {"devices", "member", "member", "member", "devices_ibfk_1"}, + {"dish2dishflags", "dish", "dish", "dish", "dish2dishflags_ibfk_1"}, + {"dish2dishflags", "flag", "dishflags", "flag", "dish2dishflags_ibfk_2"}, + {"dish2mensa", "mensa", "mensa", "mensa", "dish2mensa_ibfk_1"}, + {"dish2mensa", "dish", "dish", "dish", "dish2mensa_ibfk_2"}, + {"dish_rating", "dishID", "dish", "dish", "dish_rating_dish_dish_fk"}, + {"event", "news", "news", "news", "fkNews"}, + {"event", "kino", "kino", "kino", "fkKino"}, + {"event", "file", "files", "file", "fkEventFile"}, + {"event", "ticket_group", "ticket_group", "ticket_group", "fkEventGroup"}, + {"kino", "cover", "files", "file", "kino_ibfk_1"}, + {"log", "user_executed", "users", "user", "fkLog2UsersEx"}, + {"log", "user_affected", "users", "user", "fkLog2UsersAf"}, + {"log", "action", "actions", "action", "fkLog2Actions"}, + {"menu", "right", "rights", "right", "menu_ibfk_1"}, + {"menu", "parent", "menu", "menu", "menu_ibfk_2"}, + {"modules", "right", "rights", "right", "fkMod2Rights"}, + {"news", "src", "newsSource", "source", "news_ibfk_1"}, + {"news", "file", "files", "file", "news_ibfk_2"}, + {"newsSource", "icon", "files", "file", "newsSource_ibfk_1"}, + {"notification", "type", "notification_type", "type", "notification_ibfk_1"}, + {"notification", "location", "location", "location", "notification_ibfk_2"}, + {"notification_confirmation", "notification", "notification", "notification", "notification_confirmation_ibfk_1"}, + {"notification_confirmation", "device", "devices", "device", "notification_confirmation_ibfk_2"}, + {"question2answer", "question", "question", "question", "question2answer_question_question_fk"}, + {"question2answer", "answer", "questionAnswers", "answer", "question2answer_questionAnswers_answer_fk"}, + {"question2answer", "member", "member", "member", "question2answer_member_member_fk"}, + {"question2faculty", "question", "question", "question", "question2faculty_ibfk_1"}, + {"question2faculty", "faculty", "faculty", "faculty", "question2faculty_ibfk_2"}, + {"recover", "user", "users", "user", "fkRecover2User"}, + {"reports", "device", "devices", "device", "reports_ibfk_3"}, + {"roles2rights", "role", "roles", "role", "fkRole"}, + {"roles2rights", "right", "rights", "right", "fkRight"}, + {"ticket_admin2group", "ticket_admin", "ticket_admin", "ticket_admin", "fkTicketAdmin"}, + {"ticket_admin2group", "ticket_group", "ticket_group", "ticket_group", "fkTicketGroup"}, + {"ticket_history", "member", "member", "member", "fkMember"}, + {"ticket_history", "ticket_payment", "ticket_payment", "ticket_payment", "fkTicketPayment"}, + {"ticket_history", "ticket_type", "ticket_type", "ticket_type", "fkTicketType"}, + {"ticket_type", "event", "event", "event", "fkEvent"}, + {"ticket_type", "ticket_payment", "ticket_payment", "ticket_payment", "fkPayment"}, + {"users2info", "user", "users", "user", "fkUsers"}, + {"users2roles", "user", "users", "user", "fkUser2RolesUser"}, + {"users2roles", "role", "roles", "role", "fkUser2RolesRole"}, + } +} + +func migrateField(tx *gorm.DB, table string, field string, typeDefiniton string) error { + // change both the origin of the fk and the destination to be a bigint + if err := tx.Exec(fmt.Sprintf("ALTER TABLE `%s` CHANGE `%s` `%s` %s", table, field, field, typeDefiniton)).Error; err != nil { + return err + } + // data is still stored as int32, but we can change this + if err := tx.Exec(fmt.Sprintf("UPDATE `%s` SET `%s` = CAST(`%s` AS UNSIGNED INTEGER)", table, field, field)).Error; err != nil { + return err + } + return nil +} + +// migrate20240316000000 +// made sure that all ids are int64 +func migrate20240316000000() *gormigrate.Migration { + return &gormigrate.Migration{ + ID: "20240316000000", + Migrate: func(tx *gorm.DB) error { + for _, f := range tablesWithWrongFk() { + if err := tx.Exec(fmt.Sprintf("alter table `%s` DROP FOREIGN KEY `%s`", f.fromTable, f.constraintName)).Error; err != nil { + return err + } + } + for _, f := range tablesWithWrongFk() { + if err := migrateField(tx, f.fromTable, f.fromColumn, "BIGINT NOT NULL"); err != nil { + return err + } + if err := migrateField(tx, f.toTable, f.toColumn, "BIGINT NOT NULL AUTO_INCREMENT"); err != nil { + return err + } + } + for _, f := range tablesWithWrongFk() { + if err := tx.Exec(fmt.Sprintf("ALTER TABLE `%s` ADD CONSTRAINT `%s` FOREIGN KEY (`%s`) REFERENCES `%s` (`%s`)", f.fromTable, f.constraintName, f.fromColumn, f.toTable, f.toColumn)).Error; err != nil { + return err + } + } + // because we have migrated all fk relationships, this does not mean that we have migrated all primary keys => this is done this way + for _, t := range tablesWithWrongId() { + if err := migrateField(tx, t.table, t.field, "BIGINT NOT NULL"); err != nil { + return err + } + } + return nil + }, + // intentionally no rollback function as this would be lossy! + } +} diff --git a/server/backend/migration/20240317000000.go b/server/backend/migration/20240317000000.go new file mode 100644 index 00000000..551e699a --- /dev/null +++ b/server/backend/migration/20240317000000.go @@ -0,0 +1,154 @@ +package migration + +import ( + "fmt" + "os" + + "github.com/go-gormigrate/gormigrate/v2" + "gorm.io/gorm" +) + +func tablesWithWrongCOLLATE() []string { + return []string{"crontab", "devices", "dish", "files", "kino", "news", "newsSource", "notification", "notification_type", "notification_confirmation", "feedback", "update_note", "news_alert"} +} + +type columnsWithWrongCollationOrCharset struct { + tableName string + columnName string + columnType string + characterSetName string + collationName string +} + +// feedbackColumnsWithWrongCOLLATE lists all columns that need changing +// can be gotten with +// SELECT TABLE_NAME, COLUMN_NAME, COLUMN_TYPE, CHARACTER_SET_NAME, COLLATION_NAME +// from information_schema.columns +// WHERE TABLE_SCHEMA = 'campus_db' +// +// and (CHARACTER_SET_NAME != 'utf8mb4' or COLLATION_NAME != 'utf8mb4_unicode_ci') +func feedbackColumnsWithWrongCOLLATE() []columnsWithWrongCollationOrCharset { + return []columnsWithWrongCollationOrCharset{ + {"barrierFree_moreInfo", "title", "varchar(32)", "utf8mb3", "utf8mb3_general_ci"}, + {"barrierFree_moreInfo", "category", "varchar(11)", "utf8mb3", "utf8mb3_general_ci"}, + {"barrierFree_moreInfo", "url", "varchar(128)", "utf8mb3", "utf8mb3_general_ci"}, + {"barrierFree_persons", "name", "varchar(40)", "utf8mb3", "utf8mb3_general_ci"}, + {"barrierFree_persons", "telephone", "varchar(32)", "utf8mb3", "utf8mb3_general_ci"}, + {"barrierFree_persons", "email", "varchar(32)", "utf8mb3", "utf8mb3_general_ci"}, + {"barrierFree_persons", "faculty", "varchar(32)", "utf8mb3", "utf8mb3_general_ci"}, + {"barrierFree_persons", "office", "varchar(16)", "utf8mb3", "utf8mb3_general_ci"}, + {"barrierFree_persons", "officeHour", "varchar(16)", "utf8mb3", "utf8mb3_general_ci"}, + {"barrierFree_persons", "tumID", "varchar(24)", "utf8mb3", "utf8mb3_general_ci"}, + {"event", "title", "varchar(100)", "utf8mb3", "utf8mb3_general_ci"}, + {"event", "description", "text", "utf8mb3", "utf8mb3_general_ci"}, + {"event", "locality", "varchar(200)", "utf8mb3", "utf8mb3_general_ci"}, + {"event", "link", "varchar(200)", "utf8mb3", "utf8mb3_general_ci"}, + {"faculty", "name", "varchar(150)", "utf8mb4", "utf8mb4_general_ci"}, + {"feedback", "email_id", "text", "utf8mb3", "utf8mb3_general_ci"}, + {"feedback", "receiver", "text", "utf8mb3", "utf8mb3_general_ci"}, + {"feedback", "reply_to", "text", "utf8mb3", "utf8mb3_general_ci"}, + {"feedback", "feedback", "text", "utf8mb3", "utf8mb3_general_ci"}, + {"feedback", "os_version", "text", "utf8mb4", "utf8mb4_general_ci"}, + {"feedback", "app_version", "text", "utf8mb4", "utf8mb4_general_ci"}, + {"location", "name", "text", "utf8mb3", "utf8mb3_general_ci"}, + {"news_alert", "name", "varchar(100)", "utf8mb4", "utf8mb4_general_ci"}, + {"news_alert", "link", "text", "utf8mb4", "utf8mb4_general_ci"}, + {"notification", "title", "text", "utf8mb3", "utf8mb3_general_ci"}, + {"notification", "description", "text", "utf8mb3", "utf8mb3_general_ci"}, + {"notification", "signature", "text", "utf8mb3", "utf8mb3_general_ci"}, + {"notification_type", "name", "text", "utf8mb3", "utf8mb3_general_ci"}, + {"notification_type", "confirmation", "enum('true','false')", "utf8mb3", "utf8mb3_general_ci"}, + {"question", "text", "text", "utf8mb4", "utf8mb4_general_ci"}, + {"questionAnswers", "text", "text", "utf8mb4", "utf8mb4_general_ci"}, + {"roomfinder_building2area", "building_nr", "varchar(8)", "utf8mb4", "utf8mb4_general_ci"}, + {"roomfinder_building2area", "campus", "char(1)", "utf8mb4", "utf8mb4_general_ci"}, + {"roomfinder_building2area", "name", "varchar(32)", "utf8mb4", "utf8mb4_general_ci"}, + {"roomfinder_buildings", "building_nr", "varchar(8)", "utf8mb4", "utf8mb4_general_ci"}, + {"roomfinder_buildings", "utm_zone", "varchar(4)", "utf8mb4", "utf8mb4_general_ci"}, + {"roomfinder_buildings", "utm_easting", "varchar(32)", "utf8mb4", "utf8mb4_general_ci"}, + {"roomfinder_buildings", "utm_northing", "varchar(32)", "utf8mb4", "utf8mb4_general_ci"}, + {"roomfinder_buildings2gps", "id", "varchar(8)", "utf8mb4", "utf8mb4_general_ci"}, + {"roomfinder_buildings2gps", "latitude", "varchar(30)", "utf8mb4", "utf8mb4_general_ci"}, + {"roomfinder_buildings2gps", "longitude", "varchar(30)", "utf8mb4", "utf8mb4_general_ci"}, + {"roomfinder_buildings2maps", "building_nr", "varchar(8)", "utf8mb4", "utf8mb4_general_ci"}, + {"roomfinder_maps", "description", "varchar(64)", "utf8mb4", "utf8mb4_general_ci"}, + {"roomfinder_rooms", "room_code", "varchar(32)", "utf8mb4", "utf8mb4_general_ci"}, + {"roomfinder_rooms", "building_nr", "varchar(8)", "utf8mb4", "utf8mb4_general_ci"}, + {"roomfinder_rooms", "arch_id", "varchar(16)", "utf8mb4", "utf8mb4_general_ci"}, + {"roomfinder_rooms", "info", "varchar(64)", "utf8mb4", "utf8mb4_general_ci"}, + {"roomfinder_rooms", "address", "varchar(128)", "utf8mb4", "utf8mb4_general_ci"}, + {"roomfinder_rooms", "purpose", "varchar(64)", "utf8mb4", "utf8mb4_general_ci"}, + {"roomfinder_rooms", "utm_zone", "varchar(4)", "utf8mb4", "utf8mb4_general_ci"}, + {"roomfinder_rooms", "utm_easting", "varchar(32)", "utf8mb4", "utf8mb4_general_ci"}, + {"roomfinder_rooms", "utm_northing", "varchar(32)", "utf8mb4", "utf8mb4_general_ci"}, + {"roomfinder_schedules", "title", "varchar(64)", "utf8mb4", "utf8mb4_general_ci"}, + {"roomfinder_schedules", "course_code", "varchar(32)", "utf8mb4", "utf8mb4_general_ci"}, + {"sessions", "session", "varchar(255)", "utf8mb3", "utf8mb3_general_ci"}, + {"ticket_admin", "key", "text", "utf8mb3", "utf8mb3_general_ci"}, + {"ticket_admin", "comment", "text", "utf8mb3", "utf8mb3_general_ci"}, + {"ticket_group", "description", "text", "utf8mb3", "utf8mb3_general_ci"}, + {"ticket_history", "code", "char(128)", "utf8mb3", "utf8mb3_general_ci"}, + {"ticket_payment", "name", "varchar(50)", "utf8mb3", "utf8mb3_general_ci"}, + {"ticket_payment", "config", "text", "utf8mb3", "utf8mb3_general_ci"}, + {"ticket_type", "description", "varchar(100)", "utf8mb3", "utf8mb3_general_ci"}, + {"update_note", "version_name", "text", "utf8mb4", "utf8mb4_general_ci"}, + {"update_note", "message", "text", "utf8mb4", "utf8mb4_general_ci"}, + } +} + +// migrate20240317000000 +// unified all of our tables, the database and the fields to use `utf8mb4_unicode_ci` instead of the legacy `utf8mb3_general_ci` or `latin1` +func migrate20240317000000() *gormigrate.Migration { + return &gormigrate.Migration{ + ID: "20240317000000", + Migrate: func(tx *gorm.DB) error { + // delete legacy indexes + if err := tx.Exec("drop index searchTitle on event").Error; err != nil { + return err + } + if err := tx.Exec("drop index search_index on roomfinder_rooms").Error; err != nil { + return err + } + // first migrate the db + if err := tx.Exec(fmt.Sprintf("ALTER DATABASE `%s` CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci", os.Getenv("DB_NAME"))).Error; err != nil { + return err + } + // then set the tables + for _, t := range tablesWithWrongCOLLATE() { + if err := tx.Exec(fmt.Sprintf("ALTER TABLE `%s` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci", t)).Error; err != nil { + return err + } + } + // then convert single columns in each table + for _, f := range feedbackColumnsWithWrongCOLLATE() { + if err := tx.Exec(fmt.Sprintf("alter table `%s` modify `%s` %s CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci", f.tableName, f.columnName, f.columnType)).Error; err != nil { + return err + } + } + return nil + }, + Rollback: func(tx *gorm.DB) error { + // first migrate the db + if err := tx.Exec(fmt.Sprintf("ALTER DATABASE `%s` CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci", os.Getenv("DB_NAME"))).Error; err != nil { + return err + } + // revert changes to tables + for _, t := range tablesWithWrongCOLLATE() { + if err := tx.Exec(fmt.Sprintf("ALTER TABLE `%s` COLLATE utf8mb4_general_ci", t)).Error; err != nil { + return err + } + } + // revert changes to fields + for _, f := range feedbackColumnsWithWrongCOLLATE() { + if err := tx.Exec(fmt.Sprintf("alter table `%s` modify `%s` %s CHARACTER SET %s COLLATE %s", f.tableName, f.columnName, f.columnType, f.characterSetName, f.collationName)).Error; err != nil { + return err + } + } + // re-add legacy indexes + if err := tx.Exec("create fulltext index searchTitle on event (title)").Error; err != nil { + return err + } + return tx.Exec("create fulltext index search_index on roomfinder_rooms (info, address, room_code)").Error + }, + } +} diff --git a/server/backend/migration/migration.go b/server/backend/migration/migration.go index 5fbf5291..1c70b870 100644 --- a/server/backend/migration/migration.go +++ b/server/backend/migration/migration.go @@ -73,6 +73,8 @@ func manualMigrate(db *gorm.DB) error { migrate20240207000000(), migrate20240311000000(), migrate20240312000000(), + migrate20240316000000(), + migrate20240317000000(), } return gormigrate.New(db, gormigrateOptions, migrations).Migrate() } diff --git a/server/model/notification.go b/server/model/notification.go index 8c93943d..31256045 100644 --- a/server/model/notification.go +++ b/server/model/notification.go @@ -26,3 +26,8 @@ type Notification struct { Signature null.String `gorm:"column:signature;type:text;size:65535;" json:"signature"` Silent int32 `gorm:"column:silent;type:tinyint;default:0;" json:"silent"` } + +// TableName sets the insert table name for this struct type +func (n *Notification) TableName() string { + return "notification" +} diff --git a/server/model/notification_confirmation.go b/server/model/notification_confirmation.go index f5b6a39c..7e20fa88 100644 --- a/server/model/notification_confirmation.go +++ b/server/model/notification_confirmation.go @@ -23,3 +23,8 @@ type NotificationConfirmation struct { Created time.Time `gorm:"column:created;type:timestamp;default:CURRENT_TIMESTAMP;" json:"created"` Received null.Time `gorm:"column:received;type:timestamp;" json:"received"` } + +// TableName sets the insert table name for this struct type +func (n *NotificationConfirmation) TableName() string { + return "notification_confirmation" +} diff --git a/server/model/notification_type.go b/server/model/notification_type.go index 7a53f240..f3f29592 100644 --- a/server/model/notification_type.go +++ b/server/model/notification_type.go @@ -21,3 +21,8 @@ type NotificationType struct { Name string `gorm:"column:name;type:text;size:65535;" json:"name"` Confirmation string `gorm:"column:confirmation;type:enum('true', 'false');default:'false';" json:"confirmation"` } + +// TableName sets the insert table name for this struct type +func (n *NotificationType) TableName() string { + return "notification_type" +} diff --git a/server/utils/db.go b/server/utils/db.go index 802852ac..3904cd96 100644 --- a/server/utils/db.go +++ b/server/utils/db.go @@ -22,6 +22,8 @@ func SetupDB() *gorm.DB { if err != nil { log.WithError(err).Fatal("failed to connect database") } + // without this comment, the `COLLATE` will not be set correctly + db = db.Set("gorm:table_options", "ENGINE=InnoDB CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci") // Migrate the schema // currently not activated as