From f836abf4d3b2838ffd354a7e11fc889ad08e72c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus-Peter=20H=C3=BCbner?= Date: Fri, 5 Aug 2022 03:33:41 +0200 Subject: [PATCH] migration of users and user_contacts including merge of email_optin content --- .../0045-adapt_users_table_for_gradidoid.ts | 162 +++++++++++++++--- 1 file changed, 136 insertions(+), 26 deletions(-) diff --git a/database/migrations/0045-adapt_users_table_for_gradidoid.ts b/database/migrations/0045-adapt_users_table_for_gradidoid.ts index 65c2d4b97..cc94ab7aa 100644 --- a/database/migrations/0045-adapt_users_table_for_gradidoid.ts +++ b/database/migrations/0045-adapt_users_table_for_gradidoid.ts @@ -7,20 +7,56 @@ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */ /* eslint-disable @typescript-eslint/no-explicit-any */ +import { v4 as uuidv4 } from 'uuid'; + export async function upgrade(queryFn: (query: string, values?: any[]) => Promise>) { + /* await queryFn(` - CREATE FUNCTION UuidToBin(_uuid BINARY(36)) + CREATE FUNCTION uuid_v4s() + RETURNS CHAR(36) + BEGIN + -- 1th and 2nd block are made of 6 random bytes + SET @h1 = HEX(RANDOM_BYTES(4)); + SET @h2 = HEX(RANDOM_BYTES(2)); + + -- 3th block will start with a 4 indicating the version, remaining is random + SET @h3 = SUBSTR(HEX(RANDOM_BYTES(2)), 2, 3); + + -- 4th block first nibble can only be 8, 9 A or B, remaining is random + SET @h4 = CONCAT(HEX(FLOOR(ASCII(RANDOM_BYTES(1)) / 64)+8), + SUBSTR(HEX(RANDOM_BYTES(2)), 2, 3)); + + -- 5th block is made of 6 random bytes + SET @h5 = HEX(RANDOM_BYTES(6)); + + -- Build the complete UUID + RETURN LOWER(CONCAT( + @h1, '-', @h2, '-4', @h3, '-', @h4, '-', @h5 + )); + END`) + + + + SELECT LOWER(CONCAT( + HEX(RANDOM_BYTES(4)), '-', + HEX(RANDOM_BYTES(2)), '-4', + SUBSTR(HEX(RANDOM_BYTES(2)), 2, 3), '-', + CONCAT(HEX(FLOOR(ASCII(RANDOM_BYTES(1)) / 64)+8),SUBSTR(HEX(RANDOM_BYTES(2)), 2, 3)), '-', + HEX(RANDOM_BYTES(6)) + + + await queryFn( + `CREATE FUNCTION UuidToBin(_uuid BINARY(36)) RETURNS BINARY(16) LANGUAGE SQL DETERMINISTIC CONTAINS SQL SQL SECURITY INVOKER - RETURN + RETURN UNHEX(CONCAT( SUBSTR(_uuid, 15, 4), SUBSTR(_uuid, 10, 4), SUBSTR(_uuid, 1, 8), SUBSTR(_uuid, 20, 4), - SUBSTR(_uuid, 25) ));`) - - await queryFn(` + SUBSTR(_uuid, 25) )); + // CREATE FUNCTION UuidFromBin(_bin BINARY(16)) RETURNS BINARY(36) LANGUAGE SQL DETERMINISTIC CONTAINS SQL SQL SECURITY INVOKER @@ -31,15 +67,49 @@ export async function upgrade(queryFn: (query: string, values?: any[]) => Promis HEX(SUBSTR(_bin, 1, 2)), HEX(SUBSTR(_bin, 9, 2)), HEX(SUBSTR(_bin, 11)) - ));`) + )); + + // + DELIMITER ; + + + + CREATE FUNCTION BIN_TO_UUID(b BINARY(16)) + RETURNS CHAR(36) + BEGIN + DECLARE hexStr CHAR(32); + SET hexStr = HEX(b); + RETURN LOWER(CONCAT( + SUBSTR(hexStr, 1, 8), '-', + SUBSTR(hexStr, 9, 4), '-', + SUBSTR(hexStr, 13, 4), '-', + SUBSTR(hexStr, 17, 4), '-', + SUBSTR(hexStr, 21) + )); + END `) + + await queryFn(` DELIMITER ;`) + + await queryFn(`DELIMITER $$ + + CREATE FUNCTION UUID_TO_BIN(uuid CHAR(36)) + RETURNS BINARY(16) + BEGIN + RETURN UNHEX(REPLACE(uuid, '-', '')); + END + + $$ + + DELIMITER ;`) +*/ await queryFn(` CREATE TABLE IF NOT EXISTS \`user_contacts\` ( \`id\` int(10) unsigned NOT NULL AUTO_INCREMENT, \`type\` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL, \`user_id\` int(10) unsigned NOT NULL, - \`email\` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, - \`email_verification_code\` bigint(20) unsigned NOT NULL, + \`email\` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL UNIQUE, + \`email_verification_code\` bigint(20) unsigned NOT NULL UNIQUE, \`email_opt_in_type_id\` int NOT NULL, \`email_resend_count\` int DEFAULT '0', \`email_checked\` tinyint(4) NOT NULL DEFAULT 0, @@ -51,33 +121,73 @@ export async function upgrade(queryFn: (query: string, values?: any[]) => Promis UNIQUE KEY \`email_verification_code\` (\`email_verification_code\`), UNIQUE KEY \`email\` (\`email\`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;`) + console.log('user_contacts created...') + + // First add gradido_id as nullable column without Default + await queryFn('ALTER TABLE `users` ADD COLUMN `gradido_id` CHAR(36) NULL UNIQUE AFTER `id`;') + console.log('users.gradido_id added...\n') + + // Second update gradido_id with ensured unique uuidv4 + console.log('search for all users with gradido_id is null...\n') + const usersToUpdate = await queryFn(`SELECT 'u.id', 'u.gradido_id' FROM 'users' as u WHERE 'u.gradido_id' is null`) + for (const id in usersToUpdate) { + const user = usersToUpdate[id] + console.log('found user: %s\n', user) + let gradidoId = null + let countIds = null + do { + gradidoId = uuidv4() + console.log('uuid: %s\n', gradidoId) + countIds = await queryFn('SELECT COUNT(*) FROM `users` as u WHERE u.gradido_id = ${gradidoId}') + console.log('found uuids: %d\n', countIds[0]) + } while (countIds[0] > 0) + await queryFn('UPDATE `users` SET `gradido_id` = ${gradidoId} WHERE `id` = ${user.id}') + console.log('update user with id=%d and gradidoId=%s\n', user.id, gradidoId) + } + + // third modify gradido_id to not nullable and unique + await queryFn('ALTER TABLE `users` MODIFY COLUMN `gradido_id` CHAR(36) NOT NULL UNIQUE;') + console.log('alter users.gradido_id to NOT NULL and UNIQUE...\n') - await queryFn( - 'ALTER TABLE `users` ADD COLUMN `gradido_id` BINARY(16) NOT NULL UNIQUE DEFAULT UuidToBin(UUID()) AFTER `id`;', - ) await queryFn( 'ALTER TABLE `users` ADD COLUMN `alias` varchar(20) NULL UNIQUE AFTER `gradido_id`;', ) + console.log('users.alias added...\n') + await queryFn('ALTER TABLE `users` ADD COLUMN `email_id` int(10) NULL AFTER `email`;') + console.log('users.email_id added...\n') + + // merge values from login_email_opt_in table with users.email in new user_contacts table await queryFn(` - INSERT INTO gradido_community.user_contacts - (type, user_id, email, email_verification_code, email_opt_in_type_id, email_resent_count, email_checked, created_at, updated_at, deleted_at) + INSERT INTO 'user_contacts' + ('type', 'user_id', 'email', 'email_verification_code', 'email_opt_in_type_id', 'email_resent_count', 'email_checked', 'created_at', 'updated_at', 'deleted_at') SELECT - 'EMAIL' as type, - u.id as user_id, - u.email, - e.verification_code, - e.email_opt_in_type_id, - e.resend_count, - u.email_checked, - e.created, - e.updated, - u.deletedAt + "EMAIL" as 'type', + 'u.id' as 'user_id', + 'u.email', + 'e.verification_code' as 'email_verification_code', + 'e.email_opt_in_type_id', + 'e.resend_count' as 'email_resent_count', + 'u.email_checked', + 'e.created as created_at', + 'e.updated as updated_at', + 'u.deletedAt as deleted_at' FROM - gradido_community.users as u, - gradido_community.login_email_opt_in as e + 'users' as u, + 'login_email_opt_in' as e WHERE - u.id = e.user_id;`) + 'u.id' = 'e.user_id';`) + console.log('user_contacts inserted...\n') + + // insert in users table the email_id of the new created email-contacts + const contacts = await queryFn(`SELECT 'c.id', 'c.user_id' FROM 'user_contacts' as c`) + for (const id in contacts) { + const contact = contacts[id] + console.log('found contact: %s\n', contact) + await queryFn(`UPDATE 'users' as u SET 'u.email_id' = ${contact.id} WHERE 'u.id' = ${contact.user_id}`) + console.log('update users with id=%d and email_id=%d\n', contact.user_id, contact.id) + } + console.log('upgrade finished...\n') } export async function downgrade(queryFn: (query: string, values?: any[]) => Promise>) {