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! diff --git a/backend/src/config/index.ts b/backend/src/config/index.ts index 86cefe9e6..63968c235 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: '0035-admin_pending_creations_decimal.ts', DECAY_START_TIME: new Date('2021-05-13 17:46:31'), // GMT+0 CONFIG_VERSION: { DEFAULT: 'DEFAULT', 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/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') 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/User.ts b/backend/src/graphql/model/User.ts index 1a187a38f..4f577f60a 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(() => 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 - @Field(() => Boolean, { nullable: true }) - isAdmin: boolean | null - @Field(() => Boolean, { nullable: true }) coinanimation: boolean | null 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.`, ) diff --git a/backend/src/graphql/resolver/UserResolver.test.ts b/backend/src/graphql/resolver/UserResolver.test.ts index 07b8e59e2..8e5cb299d 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: null, deletedAt: null, publisherId: 1234, referrerId: null, @@ -336,7 +337,7 @@ describe('UserResolver', () => { firstName: 'Bibi', hasElopage: false, id: expect.any(Number), - isAdmin: false, + isAdmin: null, klickTipp: { newsletterState: false, }, 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), diff --git a/backend/src/seeds/factory/user.ts b/backend/src/seeds/factory/user.ts index ff4c1d6c9..4b5913d48 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 = new Date() + await dbUser.save() } } diff --git a/backend/src/util/communityUser.ts b/backend/src/util/communityUser.ts index 33ac2fad2..0d0d12f6c 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: null, publisherId: 0, passphrase: '', settings: [], diff --git a/database/README.md b/database/README.md index 84db3d194..d6cf84518 100644 --- a/database/README.md +++ b/database/README.md @@ -30,4 +30,3 @@ yarn dev_down yarn dev_reset ``` Runs all down migrations and after this all up migrations. - 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..1f56d13d2 --- /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: 'datetime', nullable: true, default: null }) + isAdmin: Date | null + + @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/0035-admin_pending_creations_decimal/AdminPendingCreation.ts b/database/entity/0035-admin_pending_creations_decimal/AdminPendingCreation.ts new file mode 100644 index 000000000..3cd83a3a5 --- /dev/null +++ b/database/entity/0035-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: 255, nullable: false, collation: 'utf8mb4_unicode_ci' }) + 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..b2b37d7c4 100644 --- a/database/entity/AdminPendingCreation.ts +++ b/database/entity/AdminPendingCreation.ts @@ -1 +1 @@ -export { AdminPendingCreation } from './0015-admin_pending_creations/AdminPendingCreation' +export { AdminPendingCreation } from './0035-admin_pending_creations_decimal/AdminPendingCreation' 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/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' 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, 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..be6b44489 --- /dev/null +++ b/database/migrations/0034-drop_server_user_table.ts @@ -0,0 +1,37 @@ +/* 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` datetime DEFAULT NULL AFTER `language`;') + + await queryFn( + '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`;') +} + +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`, `is_admin`, `is_admin` FROM `users` WHERE `is_admin` IS NOT NULL;', + ) + + await queryFn('ALTER TABLE `users` DROP COLUMN `is_admin`;') +} diff --git a/database/migrations/0035-admin_pending_creations_decimal.ts b/database/migrations/0035-admin_pending_creations_decimal.ts new file mode 100644 index 000000000..d3648f376 --- /dev/null +++ b/database/migrations/0035-admin_pending_creations_decimal.ts @@ -0,0 +1,42 @@ +/* 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 */ + +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`;') + + // 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`;', + ) + 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`;') +}