From deb1d457e1a2f60815d713cfb5c7bf76cf843881 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Wed, 20 Apr 2022 10:32:33 +0200 Subject: [PATCH 01/19] migration adds is_admin column to users --- database/migrations/0034-drop_server_user_table.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 database/migrations/0034-drop_server_user_table.ts diff --git a/database/migrations/0034-drop_server_user_table.ts b/database/migrations/0034-drop_server_user_table.ts new file mode 100644 index 000000000..2e0a789ac --- /dev/null +++ b/database/migrations/0034-drop_server_user_table.ts @@ -0,0 +1,13 @@ +/* MIGRATION DROP server_users TABLE +add isAdmin COLUMN to users TABLE */ + +/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ +/* eslint-disable @typescript-eslint/no-explicit-any */ + +export async function upgrade(queryFn: (query: string, values?: any[]) => Promise>) { + await queryFn('ALTER TABLE `users` ADD COLUMN `is_admin` boolean DEFAULT false AFTER `language`;') +} + +export async function downgrade(queryFn: (query: string, values?: any[]) => Promise>) { + await queryFn('ALTER TABLE `users` DROP COLUMN `is_admin`;') +} From 58ab92ff63ba88071a02aaf807eff54044f096a1 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Wed, 20 Apr 2022 11:11:40 +0200 Subject: [PATCH 02/19] drop server users --- .../migrations/0034-drop_server_user_table.ts | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/database/migrations/0034-drop_server_user_table.ts b/database/migrations/0034-drop_server_user_table.ts index 2e0a789ac..914c75457 100644 --- a/database/migrations/0034-drop_server_user_table.ts +++ b/database/migrations/0034-drop_server_user_table.ts @@ -6,8 +6,32 @@ add isAdmin COLUMN to users TABLE */ export async function upgrade(queryFn: (query: string, values?: any[]) => Promise>) { await queryFn('ALTER TABLE `users` ADD COLUMN `is_admin` boolean DEFAULT false AFTER `language`;') + + await queryFn( + 'UPDATE `users` SET `is_admin` = true WHERE `email` IN (SELECT `email` FROM `server_users`);', + ) + + await queryFn('DROP TABLE `server_users`;') } export async function downgrade(queryFn: (query: string, values?: any[]) => Promise>) { + await queryFn(` + CREATE TABLE IF NOT EXISTS \`server_users\` ( + \`id\` int(10) unsigned NOT NULL AUTO_INCREMENT, + \`username\` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL, + \`password\` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, + \`email\` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL, + \`role\` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'admin', + \`activated\` tinyint(4) NOT NULL DEFAULT '0', + \`last_login\` datetime DEFAULT NULL, + \`created\` datetime NOT NULL, + \`modified\` datetime NOT NULL, + PRIMARY KEY (\`id\`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;`) + + await queryFn( + 'INSERT INTO `server_users` (`email`, `username`, `password`, `created`, `modified`) SELECT `email`, `first_name`, `password`, `created`, `created` FROM `users` WHERE `is_admin` = true;', + ) + await queryFn('ALTER TABLE `users` DROP COLUMN `is_admin`;') } From ca4a92d321d804dda4c8c4a79b0bea716616ff1b Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Wed, 20 Apr 2022 11:19:02 +0200 Subject: [PATCH 03/19] add isAdmin entity to user db model --- .../0034-drop_server_user_table/User.ts | 81 +++++++++++++++++++ database/entity/User.ts | 2 +- 2 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 database/entity/0034-drop_server_user_table/User.ts diff --git a/database/entity/0034-drop_server_user_table/User.ts b/database/entity/0034-drop_server_user_table/User.ts new file mode 100644 index 000000000..9192b92ff --- /dev/null +++ b/database/entity/0034-drop_server_user_table/User.ts @@ -0,0 +1,81 @@ +import { + BaseEntity, + Entity, + PrimaryGeneratedColumn, + Column, + OneToMany, + DeleteDateColumn, +} from 'typeorm' +import { UserSetting } from '../UserSetting' + +@Entity('users', { engine: 'InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci' }) +export class User extends BaseEntity { + @PrimaryGeneratedColumn('increment', { unsigned: true }) + id: number + + @Column({ name: 'public_key', type: 'binary', length: 32, default: null, nullable: true }) + pubKey: Buffer + + @Column({ name: 'privkey', type: 'binary', length: 80, default: null, nullable: true }) + privKey: Buffer + + @Column({ length: 255, unique: true, nullable: false, collation: 'utf8mb4_unicode_ci' }) + email: string + + @Column({ + name: 'first_name', + length: 255, + nullable: true, + default: null, + collation: 'utf8mb4_unicode_ci', + }) + firstName: string + + @Column({ + name: 'last_name', + length: 255, + nullable: true, + default: null, + collation: 'utf8mb4_unicode_ci', + }) + lastName: string + + @DeleteDateColumn() + deletedAt: Date | null + + @Column({ type: 'bigint', default: 0, unsigned: true }) + password: BigInt + + @Column({ name: 'email_hash', type: 'binary', length: 32, default: null, nullable: true }) + emailHash: Buffer + + @Column({ name: 'created', default: () => 'CURRENT_TIMESTAMP', nullable: false }) + createdAt: Date + + @Column({ name: 'email_checked', type: 'bool', nullable: false, default: false }) + emailChecked: boolean + + @Column({ length: 4, default: 'de', collation: 'utf8mb4_unicode_ci', nullable: false }) + language: string + + @Column({ name: 'is_admin', type: 'bool', nullable: false, default: false }) + isAdmin: boolean + + @Column({ name: 'referrer_id', type: 'int', unsigned: true, nullable: true, default: null }) + referrerId?: number | null + + @Column({ name: 'publisher_id', default: 0 }) + publisherId: number + + @Column({ + type: 'text', + name: 'passphrase', + collation: 'utf8mb4_unicode_ci', + nullable: true, + default: null, + }) + passphrase: string + + @OneToMany(() => UserSetting, (userSetting) => userSetting.user) + settings: UserSetting[] +} diff --git a/database/entity/User.ts b/database/entity/User.ts index 35dfb7bbe..4cd68174c 100644 --- a/database/entity/User.ts +++ b/database/entity/User.ts @@ -1 +1 @@ -export { User } from './0033-add_referrer_id/User' +export { User } from './0034-drop_server_user_table/User' From 5c6aa5d4ce715911dd60128ac6448b4c807e9c6e Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Wed, 20 Apr 2022 11:20:28 +0200 Subject: [PATCH 04/19] new db version for isAdmin in user db model --- backend/src/config/index.ts | 2 +- backend/src/util/communityUser.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/src/config/index.ts b/backend/src/config/index.ts index 91f450369..4bf550038 100644 --- a/backend/src/config/index.ts +++ b/backend/src/config/index.ts @@ -10,7 +10,7 @@ Decimal.set({ }) const constants = { - DB_VERSION: '0033-add_referrer_id', + DB_VERSION: '0034-drop_server_user_table', DECAY_START_TIME: new Date('2021-05-13 17:46:31'), // GMT+0 CONFIG_VERSION: { DEFAULT: 'DEFAULT', diff --git a/backend/src/util/communityUser.ts b/backend/src/util/communityUser.ts index 33ac2fad2..8b75b37a1 100644 --- a/backend/src/util/communityUser.ts +++ b/backend/src/util/communityUser.ts @@ -17,6 +17,7 @@ const communityDbUser: dbUser = { createdAt: new Date(), emailChecked: false, language: '', + isAdmin: false, publisherId: 0, passphrase: '', settings: [], From 64859a71f44457afc97b31f2fbe225451dedb90b Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Wed, 20 Apr 2022 11:27:09 +0200 Subject: [PATCH 05/19] use isAdmin on user to determine if user is admin --- backend/src/graphql/model/User.ts | 8 ++++---- backend/src/graphql/resolver/UserResolver.ts | 10 ---------- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/backend/src/graphql/model/User.ts b/backend/src/graphql/model/User.ts index 1a187a38f..1949592c0 100644 --- a/backend/src/graphql/model/User.ts +++ b/backend/src/graphql/model/User.ts @@ -14,8 +14,8 @@ export class User { this.emailChecked = user.emailChecked this.language = user.language this.publisherId = user.publisherId + this.isAdmin = user.isAdmin // TODO - this.isAdmin = null this.coinanimation = null this.klickTipp = null this.hasElopage = null @@ -58,11 +58,11 @@ export class User { // `passphrase` text COLLATE utf8mb4_unicode_ci DEFAULT NULL, + @Field(() => Boolean) + isAdmin: boolean + // TODO this is a bit inconsistent with what we query from the database // therefore all those fields are now nullable with default value null - @Field(() => Boolean, { nullable: true }) - isAdmin: boolean | null - @Field(() => Boolean, { nullable: true }) coinanimation: boolean | null diff --git a/backend/src/graphql/resolver/UserResolver.ts b/backend/src/graphql/resolver/UserResolver.ts index 137c09622..7685268b4 100644 --- a/backend/src/graphql/resolver/UserResolver.ts +++ b/backend/src/graphql/resolver/UserResolver.ts @@ -19,9 +19,7 @@ import { sendResetPasswordEmail as sendResetPasswordEmailMailer } from '@/mailer import { sendAccountActivationEmail } from '@/mailer/sendAccountActivationEmail' import { klicktippSignIn } from '@/apis/KlicktippController' import { RIGHTS } from '@/auth/RIGHTS' -import { ROLE_ADMIN } from '@/auth/ROLES' import { hasElopageBuys } from '@/util/hasElopageBuys' -import { ServerUser } from '@entity/ServerUser' // eslint-disable-next-line @typescript-eslint/no-var-requires const sodium = require('sodium-native') @@ -207,7 +205,6 @@ export class UserResolver { }) user.coinanimation = coinanimation - user.isAdmin = context.role === ROLE_ADMIN return user } @@ -243,9 +240,6 @@ export class UserResolver { } const user = new User(dbUser) - // user.email = email - // user.pubkey = dbUser.pubKey.toString('hex') - user.language = dbUser.language // Elopage Status & Stored PublisherId user.hasElopage = await this.hasElopage({ ...context, user: dbUser }) @@ -266,10 +260,6 @@ export class UserResolver { }) user.coinanimation = coinanimation - // context.role is not set to the actual role yet on login - const countServerUsers = await ServerUser.count({ email: user.email }) - user.isAdmin = countServerUsers > 0 - context.setHeaders.push({ key: 'token', value: encode(dbUser.pubKey), From 8ca72beac8cdbd5ebf112f90d09652e398ad41c5 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Wed, 20 Apr 2022 11:30:29 +0200 Subject: [PATCH 06/19] use isAdmin of user to determine user role --- backend/src/graphql/directive/isAuthorized.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/backend/src/graphql/directive/isAuthorized.ts b/backend/src/graphql/directive/isAuthorized.ts index 84756c45a..065c01957 100644 --- a/backend/src/graphql/directive/isAuthorized.ts +++ b/backend/src/graphql/directive/isAuthorized.ts @@ -8,7 +8,6 @@ import { RIGHTS } from '@/auth/RIGHTS' import { getCustomRepository } from '@dbTools/typeorm' import { UserRepository } from '@repository/User' import { INALIENABLE_RIGHTS } from '@/auth/INALIENABLE_RIGHTS' -import { ServerUser } from '@entity/ServerUser' const isAuthorized: AuthChecker = async ({ context }, rights) => { context.role = ROLE_UNAUTHORIZED // unauthorized user @@ -36,8 +35,7 @@ const isAuthorized: AuthChecker = async ({ context }, rights) => { try { const user = await userRepository.findByPubkeyHex(context.pubKey) context.user = user - const countServerUsers = await ServerUser.count({ email: user.email }) - context.role = countServerUsers > 0 ? ROLE_ADMIN : ROLE_USER + context.role = user.isAdmin ? ROLE_ADMIN : ROLE_USER } catch { // in case the database query fails (user deleted) throw new Error('401 Unauthorized') From 9136c4596dad00460523b789718ede5871fa80ab Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Wed, 20 Apr 2022 11:32:30 +0200 Subject: [PATCH 07/19] remove server user entity --- database/entity/ServerUser.ts | 1 - database/entity/index.ts | 2 -- 2 files changed, 3 deletions(-) delete mode 100644 database/entity/ServerUser.ts diff --git a/database/entity/ServerUser.ts b/database/entity/ServerUser.ts deleted file mode 100644 index 495513823..000000000 --- a/database/entity/ServerUser.ts +++ /dev/null @@ -1 +0,0 @@ -export { ServerUser } from './0001-init_db/ServerUser' diff --git a/database/entity/index.ts b/database/entity/index.ts index cb6f56ab0..542333755 100644 --- a/database/entity/index.ts +++ b/database/entity/index.ts @@ -1,7 +1,6 @@ import { LoginElopageBuys } from './LoginElopageBuys' import { LoginEmailOptIn } from './LoginEmailOptIn' import { Migration } from './Migration' -import { ServerUser } from './ServerUser' import { Transaction } from './Transaction' import { TransactionLink } from './TransactionLink' import { User } from './User' @@ -13,7 +12,6 @@ export const entities = [ LoginElopageBuys, LoginEmailOptIn, Migration, - ServerUser, Transaction, TransactionLink, User, From c9701574f38cf28d3ad26a3a37b02dd0bafa82df Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Wed, 20 Apr 2022 11:44:34 +0200 Subject: [PATCH 08/19] seed with isAdmin, test user resolver with isAdmin --- .../src/graphql/resolver/UserResolver.test.ts | 1 + backend/src/seeds/factory/user.ts | 23 ++++--------------- 2 files changed, 5 insertions(+), 19 deletions(-) diff --git a/backend/src/graphql/resolver/UserResolver.test.ts b/backend/src/graphql/resolver/UserResolver.test.ts index 07b8e59e2..b9e230afe 100644 --- a/backend/src/graphql/resolver/UserResolver.test.ts +++ b/backend/src/graphql/resolver/UserResolver.test.ts @@ -100,6 +100,7 @@ describe('UserResolver', () => { emailChecked: false, passphrase: expect.any(String), language: 'de', + isAdmin: false, deletedAt: null, publisherId: 1234, referrerId: null, diff --git a/backend/src/seeds/factory/user.ts b/backend/src/seeds/factory/user.ts index ff4c1d6c9..373a3da4f 100644 --- a/backend/src/seeds/factory/user.ts +++ b/backend/src/seeds/factory/user.ts @@ -1,7 +1,6 @@ import { createUser, setPassword } from '@/seeds/graphql/mutations' import { User } from '@entity/User' import { LoginEmailOptIn } from '@entity/LoginEmailOptIn' -import { ServerUser } from '@entity/ServerUser' import { UserInterface } from '@/seeds/users/UserInterface' import { ApolloServerTestClient } from 'apollo-server-testing' @@ -29,23 +28,9 @@ export const userFactory = async ( // get user from database const dbUser = await User.findOneOrFail({ id }) - if (user.createdAt || user.deletedAt) { - if (user.createdAt) dbUser.createdAt = user.createdAt - if (user.deletedAt) dbUser.deletedAt = user.deletedAt - await dbUser.save() - } - - if (user.isAdmin) { - const admin = new ServerUser() - admin.username = dbUser.firstName - admin.password = 'please_refactor' - admin.email = dbUser.email - admin.role = 'admin' - admin.activated = 1 - admin.lastLogin = new Date() - admin.created = dbUser.createdAt - admin.modified = dbUser.createdAt - await admin.save() - } + if (user.createdAt) dbUser.createdAt = user.createdAt + if (user.deletedAt) dbUser.deletedAt = user.deletedAt + if (user.isAdmin) dbUser.isAdmin = user.isAdmin + await dbUser.save() } } From 863ecfa4746c4f4f3a0bf5127119e0cce7251520 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Thu, 21 Apr 2022 10:58:22 +0200 Subject: [PATCH 09/19] migrate is_admin column in users as datetime with default null --- database/migrations/0034-drop_server_user_table.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/database/migrations/0034-drop_server_user_table.ts b/database/migrations/0034-drop_server_user_table.ts index 914c75457..fd5a9a682 100644 --- a/database/migrations/0034-drop_server_user_table.ts +++ b/database/migrations/0034-drop_server_user_table.ts @@ -5,10 +5,10 @@ add isAdmin COLUMN to users TABLE */ /* eslint-disable @typescript-eslint/no-explicit-any */ export async function upgrade(queryFn: (query: string, values?: any[]) => Promise>) { - await queryFn('ALTER TABLE `users` ADD COLUMN `is_admin` boolean DEFAULT false AFTER `language`;') + await queryFn('ALTER TABLE `users` ADD COLUMN `is_admin` datetime DEFAULT NULL AFTER `language`;') await queryFn( - 'UPDATE `users` SET `is_admin` = true WHERE `email` IN (SELECT `email` FROM `server_users`);', + 'UPDATE `users` AS `users`, (SELECT * FROM `server_users`) AS `server_users` SET users.`is_admin` = server_users.`modified` WHERE users.`email` IN (SELECT email from `server_users`);', ) await queryFn('DROP TABLE `server_users`;') @@ -30,7 +30,7 @@ export async function downgrade(queryFn: (query: string, values?: any[]) => Prom ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;`) await queryFn( - 'INSERT INTO `server_users` (`email`, `username`, `password`, `created`, `modified`) SELECT `email`, `first_name`, `password`, `created`, `created` FROM `users` WHERE `is_admin` = true;', + 'INSERT INTO `server_users` (`email`, `username`, `password`, `created`, `modified`) SELECT `email`, `first_name`, `password`, `is_admin`, `is_admin` FROM `users` WHERE `is_admin` IS NOT NULL;', ) await queryFn('ALTER TABLE `users` DROP COLUMN `is_admin`;') From ba648c67a98dd31c1bf07f494ae47529399a7364 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Thu, 21 Apr 2022 11:01:23 +0200 Subject: [PATCH 10/19] entity model users.isAdmin as nullable date --- database/entity/0034-drop_server_user_table/User.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/database/entity/0034-drop_server_user_table/User.ts b/database/entity/0034-drop_server_user_table/User.ts index 9192b92ff..1f56d13d2 100644 --- a/database/entity/0034-drop_server_user_table/User.ts +++ b/database/entity/0034-drop_server_user_table/User.ts @@ -58,8 +58,8 @@ export class User extends BaseEntity { @Column({ length: 4, default: 'de', collation: 'utf8mb4_unicode_ci', nullable: false }) language: string - @Column({ name: 'is_admin', type: 'bool', nullable: false, default: false }) - isAdmin: boolean + @Column({ name: 'is_admin', type: 'datetime', nullable: true, default: null }) + isAdmin: Date | null @Column({ name: 'referrer_id', type: 'int', unsigned: true, nullable: true, default: null }) referrerId?: number | null From fa6fbe38c8e0ba1b44592146602cfb3938d9242d Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Thu, 21 Apr 2022 11:05:51 +0200 Subject: [PATCH 11/19] User.isAdmin as nullable Date --- backend/src/graphql/model/User.ts | 4 ++-- backend/src/seeds/factory/user.ts | 2 +- backend/src/util/communityUser.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/src/graphql/model/User.ts b/backend/src/graphql/model/User.ts index 1949592c0..4f577f60a 100644 --- a/backend/src/graphql/model/User.ts +++ b/backend/src/graphql/model/User.ts @@ -58,8 +58,8 @@ export class User { // `passphrase` text COLLATE utf8mb4_unicode_ci DEFAULT NULL, - @Field(() => Boolean) - isAdmin: boolean + @Field(() => Date, { nullable: true }) + isAdmin: Date | null // TODO this is a bit inconsistent with what we query from the database // therefore all those fields are now nullable with default value null diff --git a/backend/src/seeds/factory/user.ts b/backend/src/seeds/factory/user.ts index 373a3da4f..4b5913d48 100644 --- a/backend/src/seeds/factory/user.ts +++ b/backend/src/seeds/factory/user.ts @@ -30,7 +30,7 @@ export const userFactory = async ( if (user.createdAt) dbUser.createdAt = user.createdAt if (user.deletedAt) dbUser.deletedAt = user.deletedAt - if (user.isAdmin) dbUser.isAdmin = user.isAdmin + if (user.isAdmin) dbUser.isAdmin = new Date() await dbUser.save() } } diff --git a/backend/src/util/communityUser.ts b/backend/src/util/communityUser.ts index 8b75b37a1..0d0d12f6c 100644 --- a/backend/src/util/communityUser.ts +++ b/backend/src/util/communityUser.ts @@ -17,7 +17,7 @@ const communityDbUser: dbUser = { createdAt: new Date(), emailChecked: false, language: '', - isAdmin: false, + isAdmin: null, publisherId: 0, passphrase: '', settings: [], From 50bddf49e4a1de785e5e413999c6c69186de2d84 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Thu, 21 Apr 2022 11:16:14 +0200 Subject: [PATCH 12/19] fix test for User.isAdmin as nullable date --- backend/src/graphql/resolver/UserResolver.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/graphql/resolver/UserResolver.test.ts b/backend/src/graphql/resolver/UserResolver.test.ts index b9e230afe..8e5cb299d 100644 --- a/backend/src/graphql/resolver/UserResolver.test.ts +++ b/backend/src/graphql/resolver/UserResolver.test.ts @@ -100,7 +100,7 @@ describe('UserResolver', () => { emailChecked: false, passphrase: expect.any(String), language: 'de', - isAdmin: false, + isAdmin: null, deletedAt: null, publisherId: 1234, referrerId: null, @@ -337,7 +337,7 @@ describe('UserResolver', () => { firstName: 'Bibi', hasElopage: false, id: expect.any(Number), - isAdmin: false, + isAdmin: null, klickTipp: { newsletterState: false, }, From c3c3e556a0dd3b90a4c35bde3606f0aab37080fb Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Wed, 6 Apr 2022 14:46:45 +0200 Subject: [PATCH 13/19] convert admin_pending_creations to decimal --- .../AdminPendingCreation.ts | 33 +++++++++++++++++++ database/entity/AdminPendingCreation.ts | 2 +- .../0034-admin_pending_creations_decimal.ts | 33 +++++++++++++++++++ 3 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 database/entity/0034-admin_pending_creations_decimal/AdminPendingCreation.ts create mode 100644 database/migrations/0034-admin_pending_creations_decimal.ts diff --git a/database/entity/0034-admin_pending_creations_decimal/AdminPendingCreation.ts b/database/entity/0034-admin_pending_creations_decimal/AdminPendingCreation.ts new file mode 100644 index 000000000..d204942b3 --- /dev/null +++ b/database/entity/0034-admin_pending_creations_decimal/AdminPendingCreation.ts @@ -0,0 +1,33 @@ +import Decimal from 'decimal.js-light' +import { BaseEntity, Column, Entity, PrimaryGeneratedColumn } from 'typeorm' +import { DecimalTransformer } from '../../src/typeorm/DecimalTransformer' + +@Entity('admin_pending_creations') +export class AdminPendingCreation extends BaseEntity { + @PrimaryGeneratedColumn('increment', { unsigned: true }) + id: number + + @Column({ unsigned: true, nullable: false }) + userId: number + + @Column({ type: 'datetime', default: () => 'CURRENT_TIMESTAMP' }) + created: Date + + @Column({ type: 'datetime', nullable: false }) + date: Date + + @Column({ length: 256, nullable: true, default: null }) + memo: string + + @Column({ + type: 'decimal', + precision: 40, + scale: 20, + nullable: false, + transformer: DecimalTransformer, + }) + amount: Decimal + + @Column() + moderator: number +} diff --git a/database/entity/AdminPendingCreation.ts b/database/entity/AdminPendingCreation.ts index 03eeab883..a32590a22 100644 --- a/database/entity/AdminPendingCreation.ts +++ b/database/entity/AdminPendingCreation.ts @@ -1 +1 @@ -export { AdminPendingCreation } from './0015-admin_pending_creations/AdminPendingCreation' +export { AdminPendingCreation } from './0034-admin_pending_creations_decimal/AdminPendingCreation' diff --git a/database/migrations/0034-admin_pending_creations_decimal.ts b/database/migrations/0034-admin_pending_creations_decimal.ts new file mode 100644 index 000000000..6df1e563b --- /dev/null +++ b/database/migrations/0034-admin_pending_creations_decimal.ts @@ -0,0 +1,33 @@ +/* MIGRATION TO CHANGE `amount` FIELD TYPE TO `Decimal` ON `admin_pending_creations` */ + +/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ +/* eslint-disable @typescript-eslint/no-explicit-any */ + +export async function upgrade(queryFn: (query: string, values?: any[]) => Promise>) { + // rename `amount` to `amount_bigint` + await queryFn('ALTER TABLE `admin_pending_creations` RENAME COLUMN `amount` TO `amount_bigint`;') + // add `amount` (decimal) + await queryFn( + 'ALTER TABLE `admin_pending_creations` ADD COLUMN `amount` DECIMAL(40,20) DEFAULT NULL AFTER `amount_bigint`;', + ) + // fill new `amount` column + await queryFn('UPDATE `admin_pending_creations` SET `amount` = `amount_bigint` DIV 10000;') + // make `amount` not nullable + await queryFn( + 'ALTER TABLE `admin_pending_creations` MODIFY COLUMN `amount` DECIMAL(40,20) NOT NULL;', + ) + // drop `amount_bitint` column + await queryFn('ALTER TABLE `admin_pending_creations` DROP COLUMN `amount_bigint`;') +} + +export async function downgrade(queryFn: (query: string, values?: any[]) => Promise>) { + await queryFn( + 'ALTER TABLE `admin_pending_creations` ADD COLUMN `amount_bigint` bigint(20) DEFAULT NULL AFTER `amount`;', + ) + await queryFn('UPDATE `admin_pending_creations` SET `amount_bigint` = `amount` * 10000;') + await queryFn( + 'ALTER TABLE `admin_pending_creations` MODIFY COLUMN `amount_bigint` bigint(20) NOT NULL;', + ) + await queryFn('ALTER TABLE `admin_pending_creations` DROP COLUMN `amount`;') + await queryFn('ALTER TABLE `admin_pending_creations` RENAME COLUMN `amount_bigint` TO `amount`;') +} From 56ed35d6928a3a38c97b33c27c888fa346aafad7 Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Wed, 6 Apr 2022 14:48:28 +0200 Subject: [PATCH 14/19] backend fix types for admin_pending_creations is now decimal --- .../graphql/arg/CreatePendingCreationArgs.ts | 7 +++-- .../graphql/arg/UpdatePendingCreationArgs.ts | 7 +++-- backend/src/graphql/model/PendingCreation.ts | 9 +++--- .../graphql/model/UpdatePendingCreation.ts | 9 +++--- backend/src/graphql/model/UserAdmin.ts | 9 +++--- backend/src/graphql/resolver/AdminResolver.ts | 28 +++++++++---------- 6 files changed, 37 insertions(+), 32 deletions(-) diff --git a/backend/src/graphql/arg/CreatePendingCreationArgs.ts b/backend/src/graphql/arg/CreatePendingCreationArgs.ts index b90ad3231..0cadf5e62 100644 --- a/backend/src/graphql/arg/CreatePendingCreationArgs.ts +++ b/backend/src/graphql/arg/CreatePendingCreationArgs.ts @@ -1,4 +1,5 @@ -import { ArgsType, Field, Float, InputType, Int } from 'type-graphql' +import { ArgsType, Field, InputType, Int } from 'type-graphql' +import Decimal from 'decimal.js-light' @InputType() @ArgsType() @@ -6,8 +7,8 @@ export default class CreatePendingCreationArgs { @Field(() => String) email: string - @Field(() => Float) - amount: number + @Field(() => Decimal) + amount: Decimal @Field(() => String) memo: string diff --git a/backend/src/graphql/arg/UpdatePendingCreationArgs.ts b/backend/src/graphql/arg/UpdatePendingCreationArgs.ts index 73f70c058..3cd85e84b 100644 --- a/backend/src/graphql/arg/UpdatePendingCreationArgs.ts +++ b/backend/src/graphql/arg/UpdatePendingCreationArgs.ts @@ -1,4 +1,5 @@ -import { ArgsType, Field, Float, Int } from 'type-graphql' +import { ArgsType, Field, Int } from 'type-graphql' +import Decimal from 'decimal.js-light' @ArgsType() export default class UpdatePendingCreationArgs { @@ -8,8 +9,8 @@ export default class UpdatePendingCreationArgs { @Field(() => String) email: string - @Field(() => Float) - amount: number + @Field(() => Decimal) + amount: Decimal @Field(() => String) memo: string diff --git a/backend/src/graphql/model/PendingCreation.ts b/backend/src/graphql/model/PendingCreation.ts index 594657a59..500ba6f6b 100644 --- a/backend/src/graphql/model/PendingCreation.ts +++ b/backend/src/graphql/model/PendingCreation.ts @@ -1,4 +1,5 @@ import { ObjectType, Field, Int } from 'type-graphql' +import Decimal from 'decimal.js-light' @ObjectType() export class PendingCreation { @@ -23,12 +24,12 @@ export class PendingCreation { @Field(() => String) memo: string - @Field(() => Number) - amount: number + @Field(() => Decimal) + amount: Decimal @Field(() => Number) moderator: number - @Field(() => [Number]) - creation: number[] + @Field(() => [Decimal]) + creation: Decimal[] } diff --git a/backend/src/graphql/model/UpdatePendingCreation.ts b/backend/src/graphql/model/UpdatePendingCreation.ts index c8033f86e..85d3af2cc 100644 --- a/backend/src/graphql/model/UpdatePendingCreation.ts +++ b/backend/src/graphql/model/UpdatePendingCreation.ts @@ -1,4 +1,5 @@ import { ObjectType, Field } from 'type-graphql' +import Decimal from 'decimal.js-light' @ObjectType() export class UpdatePendingCreation { @@ -8,12 +9,12 @@ export class UpdatePendingCreation { @Field(() => String) memo: string - @Field(() => Number) - amount: number + @Field(() => Decimal) + amount: Decimal @Field(() => Number) moderator: number - @Field(() => [Number]) - creation: number[] + @Field(() => [Decimal]) + creation: Decimal[] } diff --git a/backend/src/graphql/model/UserAdmin.ts b/backend/src/graphql/model/UserAdmin.ts index 1d418c66c..8a1459c0f 100644 --- a/backend/src/graphql/model/UserAdmin.ts +++ b/backend/src/graphql/model/UserAdmin.ts @@ -1,9 +1,10 @@ -import { User } from '@entity/User' import { ObjectType, Field, Int } from 'type-graphql' +import Decimal from 'decimal.js-light' +import { User } from '@entity/User' @ObjectType() export class UserAdmin { - constructor(user: User, creation: number[], hasElopage: boolean, emailConfirmationSend: string) { + constructor(user: User, creation: Decimal[], hasElopage: boolean, emailConfirmationSend: string) { this.userId = user.id this.email = user.email this.firstName = user.firstName @@ -27,8 +28,8 @@ export class UserAdmin { @Field(() => String) lastName: string - @Field(() => [Number]) - creation: number[] + @Field(() => [Decimal]) + creation: Decimal[] @Field(() => Boolean) emailChecked: boolean diff --git a/backend/src/graphql/resolver/AdminResolver.ts b/backend/src/graphql/resolver/AdminResolver.ts index 7ca3460ee..379412cdc 100644 --- a/backend/src/graphql/resolver/AdminResolver.ts +++ b/backend/src/graphql/resolver/AdminResolver.ts @@ -43,7 +43,7 @@ import CONFIG from '@/config' // const EMAIL_OPT_IN_REGISTER = 1 // const EMAIL_OPT_UNKNOWN = 3 // elopage? -const MAX_CREATION_AMOUNT = 1000 +const MAX_CREATION_AMOUNT = new Decimal(1000) const FULL_CREATION_AVAILABLE = [MAX_CREATION_AMOUNT, MAX_CREATION_AMOUNT, MAX_CREATION_AMOUNT] @Resolver() @@ -170,7 +170,7 @@ export class AdminResolver { @Mutation(() => [Number]) async createPendingCreation( @Args() { email, amount, memo, creationDate, moderator }: CreatePendingCreationArgs, - ): Promise { + ): Promise { const user = await dbUser.findOne({ email }, { withDeleted: true }) if (!user) { throw new Error(`Could not find user with email: ${email}`) @@ -186,7 +186,7 @@ export class AdminResolver { if (isCreationValid(creations, amount, creationDateObj)) { const adminPendingCreation = AdminPendingCreation.create() adminPendingCreation.userId = user.id - adminPendingCreation.amount = BigInt(amount) + adminPendingCreation.amount = amount adminPendingCreation.created = new Date() adminPendingCreation.date = creationDateObj adminPendingCreation.memo = memo @@ -251,14 +251,14 @@ export class AdminResolver { if (!isCreationValid(creations, amount, creationDateObj)) { throw new Error('Creation is not valid') } - pendingCreationToUpdate.amount = BigInt(amount) + pendingCreationToUpdate.amount = amount pendingCreationToUpdate.memo = memo pendingCreationToUpdate.date = new Date(creationDate) pendingCreationToUpdate.moderator = moderator await AdminPendingCreation.save(pendingCreationToUpdate) const result = new UpdatePendingCreation() - result.amount = parseInt(amount.toString()) + result.amount = amount result.memo = pendingCreationToUpdate.memo result.date = pendingCreationToUpdate.date result.moderator = pendingCreationToUpdate.moderator @@ -286,7 +286,7 @@ export class AdminResolver { return { ...pendingCreation, - amount: Number(pendingCreation.amount.toString()), + amount: pendingCreation.amount, firstName: user ? user.firstName : '', lastName: user ? user.lastName : '', email: user ? user.email : '', @@ -318,7 +318,7 @@ export class AdminResolver { if (user.deletedAt) throw new Error('This user was deleted. Cannot confirm a creation.') const creations = await getUserCreation(pendingCreation.userId, false) - if (!isCreationValid(creations, Number(pendingCreation.amount), pendingCreation.date)) { + if (!isCreationValid(creations, pendingCreation.amount, pendingCreation.date)) { throw new Error('Creation is not valid!!') } @@ -448,10 +448,10 @@ export class AdminResolver { interface CreationMap { id: number - creations: number[] + creations: Decimal[] } -async function getUserCreation(id: number, includePending = true): Promise { +async function getUserCreation(id: number, includePending = true): Promise { const creations = await getUserCreations([id], includePending) return creations[0] ? creations[0].creations : FULL_CREATION_AVAILABLE } @@ -493,30 +493,30 @@ async function getUserCreations(ids: number[], includePending = true): Promise parseInt(raw.month) === month && parseInt(raw.id) === id, ) - return MAX_CREATION_AMOUNT - (creation ? Number(creation.sum) : 0) + return MAX_CREATION_AMOUNT.minus(creation ? creation.sum : 0) }), } }) } -function updateCreations(creations: number[], pendingCreation: AdminPendingCreation): number[] { +function updateCreations(creations: Decimal[], pendingCreation: AdminPendingCreation): Decimal[] { const index = getCreationIndex(pendingCreation.date.getMonth()) if (index < 0) { throw new Error('You cannot create GDD for a month older than the last three months.') } - creations[index] += parseInt(pendingCreation.amount.toString()) + creations[index] = creations[index].plus(pendingCreation.amount) return creations } -function isCreationValid(creations: number[], amount: number, creationDate: Date) { +function isCreationValid(creations: Decimal[], amount: Decimal, creationDate: Date) { const index = getCreationIndex(creationDate.getMonth()) if (index < 0) { throw new Error(`No Creation found!`) } - if (amount > creations[index]) { + if (amount.greaterThan(creations[index])) { throw new Error( `The amount (${amount} GDD) to be created exceeds the available amount (${creations[index]} GDD) for this month.`, ) From 7b86c4bc2470fa589977a25f2de2949e6cd9ed8e Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Wed, 6 Apr 2022 14:53:03 +0200 Subject: [PATCH 15/19] admin use decimal instead of float for pending creations in graphql queries --- admin/src/graphql/createPendingCreation.js | 2 +- admin/src/graphql/updatePendingCreation.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/admin/src/graphql/createPendingCreation.js b/admin/src/graphql/createPendingCreation.js index 183fa5b15..05402ed9f 100644 --- a/admin/src/graphql/createPendingCreation.js +++ b/admin/src/graphql/createPendingCreation.js @@ -3,7 +3,7 @@ import gql from 'graphql-tag' export const createPendingCreation = gql` mutation ( $email: String! - $amount: Float! + $amount: Decimal! $memo: String! $creationDate: String! $moderator: Int! diff --git a/admin/src/graphql/updatePendingCreation.js b/admin/src/graphql/updatePendingCreation.js index 77668f15b..cd0ae6c8e 100644 --- a/admin/src/graphql/updatePendingCreation.js +++ b/admin/src/graphql/updatePendingCreation.js @@ -4,7 +4,7 @@ export const updatePendingCreation = gql` mutation ( $id: Int! $email: String! - $amount: Float! + $amount: Decimal! $memo: String! $creationDate: String! $moderator: Int! From efcd75717a098abb670ee469073992192ac18dd6 Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Wed, 20 Apr 2022 14:24:41 +0200 Subject: [PATCH 16/19] updated backend required database version --- backend/src/config/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/config/index.ts b/backend/src/config/index.ts index 91f450369..1f134f23d 100644 --- a/backend/src/config/index.ts +++ b/backend/src/config/index.ts @@ -10,7 +10,7 @@ Decimal.set({ }) const constants = { - DB_VERSION: '0033-add_referrer_id', + DB_VERSION: '0034-admin_pending_creations_decimal.ts', DECAY_START_TIME: new Date('2021-05-13 17:46:31'), // GMT+0 CONFIG_VERSION: { DEFAULT: 'DEFAULT', From fdb64071d6cff9a65198594e20b554ed01dc8616 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Mon, 25 Apr 2022 11:32:26 +0200 Subject: [PATCH 17/19] join tables to update is_admin --- database/migrations/0034-drop_server_user_table.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/database/migrations/0034-drop_server_user_table.ts b/database/migrations/0034-drop_server_user_table.ts index fd5a9a682..be6b44489 100644 --- a/database/migrations/0034-drop_server_user_table.ts +++ b/database/migrations/0034-drop_server_user_table.ts @@ -8,7 +8,7 @@ export async function upgrade(queryFn: (query: string, values?: any[]) => Promis await queryFn('ALTER TABLE `users` ADD COLUMN `is_admin` datetime DEFAULT NULL AFTER `language`;') await queryFn( - 'UPDATE `users` AS `users`, (SELECT * FROM `server_users`) AS `server_users` SET users.`is_admin` = server_users.`modified` WHERE users.`email` IN (SELECT email from `server_users`);', + 'UPDATE users AS users INNER JOIN server_users AS server_users ON users.email = server_users.email SET users.is_admin = server_users.modified WHERE users.email IN (SELECT email from server_users);', ) await queryFn('DROP TABLE `server_users`;') From 73fe46c39d80b5f867eb015d2072b9bc44357c25 Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Mon, 25 Apr 2022 14:03:38 +0200 Subject: [PATCH 18/19] also change memo column --- .../AdminPendingCreation.ts | 2 +- .../0034-admin_pending_creations_decimal.ts | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/database/entity/0034-admin_pending_creations_decimal/AdminPendingCreation.ts b/database/entity/0034-admin_pending_creations_decimal/AdminPendingCreation.ts index d204942b3..3cd83a3a5 100644 --- a/database/entity/0034-admin_pending_creations_decimal/AdminPendingCreation.ts +++ b/database/entity/0034-admin_pending_creations_decimal/AdminPendingCreation.ts @@ -16,7 +16,7 @@ export class AdminPendingCreation extends BaseEntity { @Column({ type: 'datetime', nullable: false }) date: Date - @Column({ length: 256, nullable: true, default: null }) + @Column({ length: 255, nullable: false, collation: 'utf8mb4_unicode_ci' }) memo: string @Column({ diff --git a/database/migrations/0034-admin_pending_creations_decimal.ts b/database/migrations/0034-admin_pending_creations_decimal.ts index 6df1e563b..d3648f376 100644 --- a/database/migrations/0034-admin_pending_creations_decimal.ts +++ b/database/migrations/0034-admin_pending_creations_decimal.ts @@ -1,4 +1,7 @@ -/* MIGRATION TO CHANGE `amount` FIELD TYPE TO `Decimal` ON `admin_pending_creations` */ +/* MIGRATION TO CHANGE SEVERAL FIELDS ON `admin_pending_creations` + * - `amount` FIELD TYPE TO `Decimal` + * - `memo` FIELD TYPE TO `varchar(255)`, collate `utf8mb4_unicode_ci` + */ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */ /* eslint-disable @typescript-eslint/no-explicit-any */ @@ -18,9 +21,15 @@ export async function upgrade(queryFn: (query: string, values?: any[]) => Promis ) // drop `amount_bitint` column await queryFn('ALTER TABLE `admin_pending_creations` DROP COLUMN `amount_bigint`;') + + // change `memo` to varchar(255), collate utf8mb4_unicode_ci + await queryFn( + 'ALTER TABLE `admin_pending_creations` MODIFY COLUMN `memo` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL;', + ) } export async function downgrade(queryFn: (query: string, values?: any[]) => Promise>) { + await queryFn('ALTER TABLE `admin_pending_creations` MODIFY COLUMN `memo` text DEFAULT NULL;') await queryFn( 'ALTER TABLE `admin_pending_creations` ADD COLUMN `amount_bigint` bigint(20) DEFAULT NULL AFTER `amount`;', ) From a4a92812bf3e21f9b0b6feb05cca3e84c1d0662d Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Mon, 25 Apr 2022 15:14:55 +0200 Subject: [PATCH 19/19] adjusted migrtion number to 35 --- .../AdminPendingCreation.ts | 0 database/entity/AdminPendingCreation.ts | 2 +- ...tions_decimal.ts => 0035-admin_pending_creations_decimal.ts} | 0 3 files changed, 1 insertion(+), 1 deletion(-) rename database/entity/{0034-admin_pending_creations_decimal => 0035-admin_pending_creations_decimal}/AdminPendingCreation.ts (100%) rename database/migrations/{0034-admin_pending_creations_decimal.ts => 0035-admin_pending_creations_decimal.ts} (100%) diff --git a/database/entity/0034-admin_pending_creations_decimal/AdminPendingCreation.ts b/database/entity/0035-admin_pending_creations_decimal/AdminPendingCreation.ts similarity index 100% rename from database/entity/0034-admin_pending_creations_decimal/AdminPendingCreation.ts rename to database/entity/0035-admin_pending_creations_decimal/AdminPendingCreation.ts diff --git a/database/entity/AdminPendingCreation.ts b/database/entity/AdminPendingCreation.ts index a32590a22..b2b37d7c4 100644 --- a/database/entity/AdminPendingCreation.ts +++ b/database/entity/AdminPendingCreation.ts @@ -1 +1 @@ -export { AdminPendingCreation } from './0034-admin_pending_creations_decimal/AdminPendingCreation' +export { AdminPendingCreation } from './0035-admin_pending_creations_decimal/AdminPendingCreation' diff --git a/database/migrations/0034-admin_pending_creations_decimal.ts b/database/migrations/0035-admin_pending_creations_decimal.ts similarity index 100% rename from database/migrations/0034-admin_pending_creations_decimal.ts rename to database/migrations/0035-admin_pending_creations_decimal.ts