forked from PluralFlux/PluralFlux
feat: add db migrations with typeORM (#28)
* adding typescript packages for typeORM * add typeORM initial files * updating package scripts * updating compose.yaml to have an exposed port for the postgres * modifying setup for typeORM * update database stuff and and package.json to help generate migrations * made models and migrations in typeORM * delete unneeded database.js * made database pattern ignored by jest * remove sequelize * separate member repo from member helper * not sure why i made everything numbers in the model but it's fixed now * edited package.json script * remove unused index.ts * adjusted files to reference repository correctly and appdatasource * made appdatasource export as named * removed start-db script * added init to appdatasource in bot.js * migrations finally! * new migration matching model names I want * updating tests * removing testpathignore patterns since it seems to be unecessary? * adjusting migrations to match current schema * removed reference to secrets file * delete old migration * Revert "delete old migration" This reverts commit db1efa39a7a80d8976878856250ccaac6a753ab2. * Revert "adjusting migrations to match current schema" This reverts commit ef89a83f6a2ef0643d6ace0a3fcf9c40f4bc6dd6. * just deleted system creation since it's got nothing in it anyway * renamed memberRepository to memberRepo for consistency * added await back to parseMemberCommand call to memberArgumentHandler * changed call to memberHelper.getMembersByAuthor to memberRepo * renamed repo updateMemberValue to updateMemberField * removed throw references in repo docstrings * remove unneeded subscriber directory ref * changed createdAt and updatedAt columns to be auto-generated made member table have timezone * changed casing of isInitialized in mock for bot.js * removed % from ILike query so that it doesn't match substrings/wildcard * renamed some stray updateMemberValue in mocks -> updateMemberField --------- Co-authored-by: Aster Fialla <asterfialla@gmail.com>
This commit is contained in:
7
.gitignore
vendored
7
.gitignore
vendored
@@ -1,8 +1,11 @@
|
||||
node_modules
|
||||
node_modules/
|
||||
build/
|
||||
tmp/
|
||||
temp/
|
||||
.idea
|
||||
secrets/
|
||||
config.json
|
||||
coverage
|
||||
config.json
|
||||
log.txt
|
||||
.env
|
||||
oya.png
|
||||
|
||||
@@ -10,6 +10,8 @@ services:
|
||||
volumes:
|
||||
- pgdata:/var/lib/postgresql
|
||||
- ./pgBackup:/mnt/pgBackup
|
||||
ports:
|
||||
- "5432:5432"
|
||||
pgadmin:
|
||||
image: dpage/pgadmin4:latest
|
||||
ports:
|
||||
@@ -19,9 +21,5 @@ services:
|
||||
- postgres
|
||||
volumes:
|
||||
- pgadmindata:/var/lib/pgadmin
|
||||
#- ./pgBackup:/mnt/host
|
||||
# uncomment the above line if you plan to restore / backup dump files from PGAdmin UI
|
||||
|
||||
volumes:
|
||||
pgdata:
|
||||
pgadmindata:
|
||||
pgdata:
|
||||
26
database/data-source.ts
Normal file
26
database/data-source.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import "reflect-metadata"
|
||||
import { DataSource } from "typeorm"
|
||||
import * as env from 'dotenv';
|
||||
import * as path from "path";
|
||||
|
||||
env.config();
|
||||
|
||||
export const AppDataSource = new DataSource({
|
||||
type: "postgres",
|
||||
host: "localhost",
|
||||
port: 5432,
|
||||
username: "postgres",
|
||||
password: process.env.POSTGRES_PASSWORD,
|
||||
database: "postgres",
|
||||
synchronize: false,
|
||||
logging: true,
|
||||
entities: [path.join(__dirname, "./entity/*.{ts,js}")],
|
||||
migrations: [path.join(__dirname, "./migrations/*.{ts,js}")],
|
||||
migrationsRun: true,
|
||||
migrationsTableName: 'migrations',
|
||||
migrationsTransactionMode: 'all',
|
||||
invalidWhereValuesBehavior: {
|
||||
null: "sql-null",
|
||||
undefined: "throw",
|
||||
},
|
||||
});
|
||||
39
database/entity/Member.ts
Normal file
39
database/entity/Member.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import {Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn} from "typeorm"
|
||||
|
||||
@Entity({name: "Member", synchronize: true})
|
||||
export class Member {
|
||||
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number
|
||||
|
||||
@Column()
|
||||
userid: string
|
||||
|
||||
@Column({
|
||||
length: 100
|
||||
})
|
||||
name: string
|
||||
|
||||
@Column({
|
||||
type: "varchar",
|
||||
nullable: true,
|
||||
length: 100
|
||||
})
|
||||
displayname: string
|
||||
|
||||
@Column({
|
||||
nullable: true,
|
||||
})
|
||||
proxy: string
|
||||
|
||||
@Column({
|
||||
nullable: true,
|
||||
})
|
||||
propic: string
|
||||
|
||||
@CreateDateColumn({ type: 'timestamptz' })
|
||||
createdAt: Date
|
||||
|
||||
@UpdateDateColumn({ type: 'timestamptz' })
|
||||
updatedAt: Date
|
||||
}
|
||||
14
database/migrations/1772417745487-update.ts
Normal file
14
database/migrations/1772417745487-update.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class Update1772417745487 implements MigrationInterface {
|
||||
name = 'Update1772417745487'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`CREATE TABLE "Member" ("id" SERIAL NOT NULL, "userid" character varying NOT NULL, "name" character varying(100) NOT NULL, "displayname" character varying(100), "proxy" character varying, "propic" character varying, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), CONSTRAINT "PK_235428a1d87c5f639ef7b7cf170" PRIMARY KEY ("id"))`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`DROP TABLE "Member"`);
|
||||
}
|
||||
|
||||
}
|
||||
1961
package-lock.json
generated
1961
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
19
package.json
19
package.json
@@ -12,22 +12,31 @@
|
||||
"dependencies": {
|
||||
"@fluxerjs/core": "^1.2.2",
|
||||
"dotenv": "^17.3.1",
|
||||
"pg": "^8.18.0",
|
||||
"pg": "^8.19.0",
|
||||
"pg-hstore": "^2.3.4",
|
||||
"pm2": "^6.0.14",
|
||||
"sequelize": "^6.37.7",
|
||||
"tmp": "^0.2.5"
|
||||
"psql": "^0.0.1",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"tmp": "^0.2.5",
|
||||
"typeorm": "^0.3.28"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.29.0",
|
||||
"@babel/plugin-transform-modules-commonjs": "^7.28.6",
|
||||
"@babel/preset-env": "^7.29.0",
|
||||
"@fetch-mock/jest": "^0.2.20",
|
||||
"@types/node": "^25.3.3",
|
||||
"babel-jest": "^30.2.0",
|
||||
"fetch-mock": "^12.6.0",
|
||||
"jest": "^30.2.0"
|
||||
"jest": "^30.2.0",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.9.3"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "jest"
|
||||
"test": "jest",
|
||||
"start": "ts-node src/bot.js",
|
||||
"build-db": "tsc",
|
||||
"generate-db": "typeorm-ts-node-commonjs migration:generate -d database/data-source.ts database/migrations/update",
|
||||
"run-migration": "typeorm-ts-node-commonjs migration:run -d database/data-source.ts"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ const {commands} = require("./commands.js");
|
||||
const {webhookHelper} = require("./helpers/webhookHelper.js");
|
||||
const env = require('dotenv');
|
||||
const {utils} = require("./helpers/utils.js");
|
||||
const { AppDataSource } = require("../database/data-source");
|
||||
|
||||
env.config();
|
||||
|
||||
@@ -20,7 +21,7 @@ client = new Client({ intents: 0 });
|
||||
module.exports.client = client;
|
||||
|
||||
client.on(Events.MessageCreate, async (message) => {
|
||||
await handleMessageCreate(message);
|
||||
await module.exports.handleMessageCreate(message);
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -85,8 +86,10 @@ const debouncePrintGuilds = utils.debounce(printGuilds, 2000);
|
||||
|
||||
module.exports.login = async function() {
|
||||
try {
|
||||
if (!AppDataSource.isInitialized) {
|
||||
await AppDataSource.initialize();
|
||||
}
|
||||
await client.login(token);
|
||||
// await db.check_connection();
|
||||
} catch (err) {
|
||||
console.error('Login failed:', err);
|
||||
process.exit(1);
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
const env = require('dotenv')
|
||||
const {Sequelize, DataTypes} = require('sequelize');
|
||||
|
||||
env.config();
|
||||
|
||||
const password = process.env.POSTGRES_PASSWORD;
|
||||
|
||||
if (!password) {
|
||||
console.error("Missing POSTGRES_PASSWORD environment variable.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const database = {};
|
||||
|
||||
const sequelize = new Sequelize('postgres', 'postgres', password, {
|
||||
host: 'localhost',
|
||||
logging: false,
|
||||
dialect: 'postgres'
|
||||
});
|
||||
|
||||
database.sequelize = sequelize;
|
||||
database.Sequelize = Sequelize;
|
||||
|
||||
database.members = sequelize.define('Member', {
|
||||
userid: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
name: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
displayname: {
|
||||
type: DataTypes.STRING,
|
||||
},
|
||||
propic: {
|
||||
type: DataTypes.STRING,
|
||||
},
|
||||
proxy: {
|
||||
type: DataTypes.STRING,
|
||||
}
|
||||
});
|
||||
|
||||
database.systems = sequelize.define('System', {
|
||||
userid: {
|
||||
type: DataTypes.STRING,
|
||||
},
|
||||
fronter: {
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
grouptag: {
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
autoproxy: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* Checks Sequelize database connection.
|
||||
*/
|
||||
database.check_connection = async function () {
|
||||
try {
|
||||
await sequelize.authenticate();
|
||||
console.log('Connection has been established successfully.');
|
||||
await syncModels();
|
||||
} catch (err) {
|
||||
console.error('Unable to connect to the database:', err);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Syncs Sequelize models.
|
||||
*/
|
||||
async function syncModels() {
|
||||
try {
|
||||
await sequelize.sync()
|
||||
console.log('Models synced successfully.');
|
||||
} catch(err) {
|
||||
console.error('Syncing models did not work', err);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.database = database;
|
||||
@@ -1,8 +1,7 @@
|
||||
const {database} = require('../database.js');
|
||||
const {enums} = require("../enums.js");
|
||||
const {Op} = require("sequelize");
|
||||
const {EmbedBuilder} = require("@fluxerjs/core");
|
||||
const {utils} = require("./utils.js");
|
||||
const {memberRepo} = require("../repositories/memberRepo.js");
|
||||
|
||||
const memberHelper = {};
|
||||
|
||||
@@ -52,7 +51,7 @@ memberHelper.parseMemberCommand = async function (authorId, authorFull, args, at
|
||||
isHelp = true;
|
||||
}
|
||||
|
||||
return await memberHelper.memberArgumentHandler(authorId, authorFull, isHelp, command, memberName, args, attachmentUrl, attachmentExpiration)
|
||||
return await memberHelper.memberArgumentHandler(authorId, authorFull, isHelp, command, memberName, args, attachmentUrl, attachmentExpiration);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -71,6 +70,7 @@ memberHelper.parseMemberCommand = async function (authorId, authorFull, args, at
|
||||
* @returns {Promise <EmbedBuilder>} A list of 25 members as an embed.
|
||||
* @returns {Promise <EmbedBuilder>} A list of member commands and descriptions.
|
||||
* @returns {Promise<{EmbedBuilder, [string], string}>} A member info embed + info/errors.
|
||||
* @returns {Promise<string>} - A help message
|
||||
* @throws {Error} When there's no member or a command is not recognized.
|
||||
*/
|
||||
memberHelper.memberArgumentHandler = async function(authorId, authorFull, isHelp, command = null, memberName = null, args = [], attachmentUrl = null, attachmentExpiration = null) {
|
||||
@@ -113,7 +113,7 @@ memberHelper.memberArgumentHandler = async function(authorId, authorFull, isHelp
|
||||
* @throws {Error} When there's no member
|
||||
*/
|
||||
memberHelper.sendCurrentValue = async function(authorId, memberName, command= null) {
|
||||
const member = await memberHelper.getMemberByName(authorId, memberName);
|
||||
const member = await memberRepo.getMemberByName(authorId, memberName);
|
||||
if (!member) throw new Error(enums.err.NO_MEMBER);
|
||||
|
||||
if (!command) {
|
||||
@@ -167,10 +167,7 @@ memberHelper.sendHelpEnum = function(command) {
|
||||
* @param {string[]} values - The values to be passed in. Only includes the values after member name and command name.
|
||||
* @param {string | null} attachmentUrl - The attachment URL, if any
|
||||
* @param {string | null} attachmentExpiration - The attachment expiry date, if any
|
||||
* @returns {Promise<string>} A success message.
|
||||
* @returns {Promise <EmbedBuilder>} A list of 25 members as an embed.
|
||||
* @returns {Promise <EmbedBuilder>} A list of member commands and descriptions.
|
||||
* @returns {Promise<{EmbedBuilder, [string], string}>} A member info embed + info/errors.
|
||||
* @returns {Promise<string> | Promise <EmbedBuilder> | Promise<{EmbedBuilder, [string], string}>}
|
||||
*/
|
||||
memberHelper.memberCommandHandler = async function(authorId, command, memberName, values, attachmentUrl = null, attachmentExpiration = null) {
|
||||
switch (command) {
|
||||
@@ -297,12 +294,7 @@ memberHelper.updatePropic = async function (authorId, memberName, values, attach
|
||||
* @throws {Error} When there is no member to remove.
|
||||
*/
|
||||
memberHelper.removeMember = async function (authorId, memberName) {
|
||||
const destroyed = await database.members.destroy({
|
||||
where: {
|
||||
name: {[Op.iLike]: memberName},
|
||||
userid: authorId
|
||||
}
|
||||
})
|
||||
const destroyed = await memberRepo.removeMember(authorId, memberName);
|
||||
if (destroyed > 0) {
|
||||
return `Member "${memberName}" has been deleted.`;
|
||||
} else {
|
||||
@@ -322,11 +314,11 @@ memberHelper.removeMember = async function (authorId, memberName) {
|
||||
* @param {string | null} [proxy] - The proxy tag of the member.
|
||||
* @param {string | null} [propic] - The profile picture URL of the member.
|
||||
* @param {string | null} [attachmentExpiration] - The expiration date of an uploaded profile picture.
|
||||
* @returns {Promise<{model, string[]}>} A successful addition object, including errors if there are any.
|
||||
* @returns {Promise<{Members, string[]}>} A successful addition object, including errors if there are any.
|
||||
* @throws {Error} When the member already exists, there are validation errors, or adding a member doesn't work.
|
||||
*/
|
||||
memberHelper.addFullMember = async function (authorId, memberName, displayName = null, proxy = null, propic = null, attachmentExpiration = null) {
|
||||
const existingMember = await memberHelper.getMemberByName(authorId, memberName);
|
||||
const existingMember = await memberRepo.getMemberByName(authorId, memberName);
|
||||
if (existingMember) {
|
||||
throw new Error(`Can't add ${memberName}. ${enums.err.MEMBER_EXISTS}`);
|
||||
}
|
||||
@@ -380,7 +372,7 @@ memberHelper.addFullMember = async function (authorId, memberName, displayName =
|
||||
}
|
||||
}
|
||||
|
||||
const member = await database.members.create({
|
||||
const member = await memberRepo.createMember({
|
||||
name: memberName, userid: authorId, displayname: isValidDisplayName ? displayName : null, proxy: isValidProxy ? proxy : null, propic: isValidPropic ? propic : null
|
||||
});
|
||||
|
||||
@@ -400,13 +392,8 @@ memberHelper.addFullMember = async function (authorId, memberName, displayName =
|
||||
* @throws {Error} When no member row was updated.
|
||||
*/
|
||||
memberHelper.updateMemberField = async function (authorId, memberName, columnName, value, expirationWarning = null) {
|
||||
const res = await database.members.update({[columnName]: value}, {
|
||||
where: {
|
||||
name: {[Op.iLike]: memberName},
|
||||
userid: authorId
|
||||
}
|
||||
})
|
||||
if (res[0] === 0) {
|
||||
const res = await memberRepo.updateMemberField(authorId, memberName, columnName, value);
|
||||
if (res === 0) {
|
||||
throw new Error(`Can't update ${memberName}. ${enums.err.NO_MEMBER}.`);
|
||||
} else {
|
||||
return `Updated ${columnName} for ${memberName} to ${value}${expirationWarning ? `. ${expirationWarning}.` : '.'}`;
|
||||
@@ -416,7 +403,7 @@ memberHelper.updateMemberField = async function (authorId, memberName, columnNam
|
||||
/**
|
||||
* Gets the details for a member.
|
||||
*
|
||||
* @param {model} member - The member object
|
||||
* @param {{Members, string[]}} member - The member object
|
||||
* @returns {EmbedBuilder} The member's info.
|
||||
*/
|
||||
memberHelper.getMemberInfo = function (member) {
|
||||
@@ -441,7 +428,7 @@ memberHelper.getMemberInfo = function (member) {
|
||||
* @throws {Error} When there are no members for an author.
|
||||
*/
|
||||
memberHelper.getAllMembersInfo = async function (authorId, authorName) {
|
||||
const members = await memberHelper.getMembersByAuthor(authorId);
|
||||
const members = await memberRepo.getMembersByAuthor(authorId);
|
||||
if (members.length === 0) throw Error(enums.err.USER_NO_MEMBERS);
|
||||
const fields = [...members.entries()].map(([index, member]) => ({
|
||||
name: member.name, value: `(Proxy: \`${member.proxy ?? "unset"}\`)`, inline: true,
|
||||
@@ -451,29 +438,6 @@ memberHelper.getAllMembersInfo = async function (authorId, authorName) {
|
||||
.addFields(...fields);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a member based on the author and proxy tag.
|
||||
*
|
||||
* @async
|
||||
* @param {string} authorId - The author of the message.
|
||||
* @param {string} memberName - The member's name.
|
||||
* @returns {Promise<model>} The member object.
|
||||
*/
|
||||
memberHelper.getMemberByName = async function (authorId, memberName) {
|
||||
return await database.members.findOne({where: {userid: authorId, name: {[Op.iLike]: memberName}}});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all members belonging to the author.
|
||||
*
|
||||
* @async
|
||||
* @param {string} authorId - The author of the message
|
||||
* @returns {Promise<model[] | null>} The member object array.
|
||||
*/
|
||||
memberHelper.getMembersByAuthor = async function (authorId) {
|
||||
return await database.members.findAll({where: {userid: authorId}});
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if proxy exists for a member.
|
||||
*
|
||||
@@ -487,7 +451,7 @@ memberHelper.checkIfProxyExists = async function (authorId, proxy) {
|
||||
if (splitProxy.length < 2) throw new Error(enums.err.NO_TEXT_FOR_PROXY);
|
||||
if (!splitProxy[0] && !splitProxy[1]) throw new Error(enums.err.NO_PROXY_WRAPPER);
|
||||
|
||||
const memberList = await memberHelper.getMembersByAuthor(authorId);
|
||||
const memberList = await memberRepo.getMembersByAuthor(authorId);
|
||||
const proxyExists = memberList.some(member => member.proxy === proxy);
|
||||
if (proxyExists) {
|
||||
throw new Error(enums.err.PROXY_EXISTS);
|
||||
|
||||
81
src/repositories/memberRepo.js
Normal file
81
src/repositories/memberRepo.js
Normal file
@@ -0,0 +1,81 @@
|
||||
const Member = require("../../database/entity/Member");
|
||||
const { AppDataSource } = require("../../database/data-source");
|
||||
const {ILike} = require("typeorm");
|
||||
const members = AppDataSource.getRepository(Member.Member)
|
||||
|
||||
const memberRepo = {};
|
||||
/**
|
||||
* Gets a member based on the author and proxy tag.
|
||||
*
|
||||
* @async
|
||||
* @param {string} authorId - The author of the message.
|
||||
* @param {string} memberName - The member's name.
|
||||
* @returns {Promise<Member | null>} The member object or null if not found.
|
||||
*/
|
||||
memberRepo.getMemberByName = async function (authorId, memberName) {
|
||||
return await members.findOne({where: {userid: authorId, name: ILike(memberName)}});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all members belonging to the author.
|
||||
*
|
||||
* @async
|
||||
* @param {string} authorId - The author of the message
|
||||
* @returns {Promise<Member[]>} The member object array.
|
||||
*/
|
||||
memberRepo.getMembersByAuthor = async function (authorId) {
|
||||
return await members.findBy({userid: authorId});
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a member.
|
||||
*
|
||||
* @async
|
||||
* @param {string} authorId - The author of the message
|
||||
* @param {string} memberName - The name of the member to remove
|
||||
* @returns {Promise<number>} Number of results removed.
|
||||
*/
|
||||
memberRepo.removeMember = async function (authorId, memberName) {
|
||||
const deleted = await members.delete({
|
||||
where: {
|
||||
name: ILike(memberName),
|
||||
userid: authorId
|
||||
}
|
||||
})
|
||||
return deleted.affected;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a member with full details.
|
||||
*
|
||||
* @async
|
||||
* @param {{name: string, userid: string, displayname: (string|null), proxy: (string|null), propic: (string|null)}} createObj - Object with parameters in it
|
||||
* @returns {Promise<Member>} A successful inserted object.
|
||||
*/
|
||||
memberRepo.createMember = async function (createObj) {
|
||||
return members.insert({
|
||||
name: createObj.name, userid: createObj.authorId, displayname: createObj.displayName, proxy: createObj.proxy, propic: createObj.propic
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates one fields for a member in the database.
|
||||
*
|
||||
* @async
|
||||
* @param {string} authorId - The author of the message
|
||||
* @param {string} memberName - The member to update
|
||||
* @param {string} columnName - The column name to update.
|
||||
* @param {string} value - The value to update to.
|
||||
* @returns {Promise<number>} A successful update.
|
||||
*/
|
||||
memberRepo.updateMemberField = async function (authorId, memberName, columnName, value) {
|
||||
const updated = await members.update({[columnName]: value}, {
|
||||
where: {
|
||||
name: ILike(memberName),
|
||||
userid: authorId
|
||||
}
|
||||
})
|
||||
return updated.affected;
|
||||
}
|
||||
|
||||
module.exports.memberRepo = memberRepo;
|
||||
@@ -56,6 +56,15 @@ jest.mock("../src/commands.js", () => {
|
||||
}
|
||||
})
|
||||
|
||||
jest.mock('../database/data-source.ts', () => {
|
||||
return {
|
||||
AppDataSource: {
|
||||
isInitialized: false,
|
||||
initialize: jest.fn().mockResolvedValue()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
const {Client, Events} = require('@fluxerjs/core');
|
||||
const {messageHelper} = require("../src/helpers/messageHelper.js");
|
||||
|
||||
@@ -2,17 +2,15 @@ const {enums} = require('../../src/enums.js');
|
||||
const {utils} = require("../../src/helpers/utils.js");
|
||||
|
||||
jest.mock('@fluxerjs/core', () => jest.fn());
|
||||
jest.mock('../../src/database.js', () => {
|
||||
jest.mock('../../src/repositories/memberRepo.js', () => {
|
||||
return {
|
||||
database: {
|
||||
members: {
|
||||
create: jest.fn().mockResolvedValue(),
|
||||
update: jest.fn().mockResolvedValue(),
|
||||
destroy: jest.fn().mockResolvedValue(),
|
||||
findOne: jest.fn().mockResolvedValue(),
|
||||
findAll: jest.fn().mockResolvedValue(),
|
||||
memberRepo: {
|
||||
getMemberByName: jest.fn().mockResolvedValue(),
|
||||
getMembersByAuthor: jest.fn().mockResolvedValue(),
|
||||
removeMember: jest.fn().mockResolvedValue(),
|
||||
createMember: jest.fn().mockResolvedValue(),
|
||||
updateMemberField: jest.fn().mockResolvedValue(),
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -25,10 +23,8 @@ jest.mock("../../src/helpers/utils.js", () => {
|
||||
}
|
||||
});
|
||||
|
||||
const {Op} = require('sequelize');
|
||||
|
||||
const {memberHelper} = require("../../src/helpers/memberHelper.js");
|
||||
const {database} = require("../../src/database");
|
||||
const {memberRepo} = require("../../src/repositories/memberRepo.js");
|
||||
|
||||
describe('MemberHelper', () => {
|
||||
const authorId = "0001";
|
||||
@@ -270,29 +266,29 @@ describe('MemberHelper', () => {
|
||||
['propic', `The profile picture for ${mockMember.name} is \"${mockMember.propic}\".`],
|
||||
])('%s calls getMemberByName and returns value', async (command, expected) => {
|
||||
// Arrange
|
||||
jest.spyOn(memberHelper, 'getMemberByName').mockResolvedValue(mockMember);
|
||||
memberRepo.getMemberByName.mockResolvedValue(mockMember);
|
||||
// Act
|
||||
const result = await memberHelper.sendCurrentValue(authorId, mockMember.name, command);
|
||||
// Assert
|
||||
expect(result).toEqual(expected);
|
||||
expect(memberHelper.getMemberByName).toHaveBeenCalledTimes(1);
|
||||
expect(memberHelper.getMemberByName).toHaveBeenCalledWith(authorId, mockMember.name);
|
||||
expect(memberRepo.getMemberByName).toHaveBeenCalledTimes(1);
|
||||
expect(memberRepo.getMemberByName).toHaveBeenCalledWith(authorId, mockMember.name);
|
||||
|
||||
})
|
||||
|
||||
test('returns error if no member found', async () => {
|
||||
// Arrange
|
||||
jest.spyOn(memberHelper, 'getMemberByName').mockResolvedValue(null);
|
||||
memberRepo.getMemberByName.mockResolvedValue(null);
|
||||
// Act
|
||||
await expect(memberHelper.sendCurrentValue(authorId, mockMember.name, 'name')).rejects.toThrow(enums.err.NO_MEMBER);
|
||||
// Assert
|
||||
expect(memberHelper.getMemberByName).toHaveBeenCalledTimes(1);
|
||||
expect(memberHelper.getMemberByName).toHaveBeenCalledWith(authorId, mockMember.name);
|
||||
expect(memberRepo.getMemberByName).toHaveBeenCalledTimes(1);
|
||||
expect(memberRepo.getMemberByName).toHaveBeenCalledWith(authorId, mockMember.name);
|
||||
});
|
||||
|
||||
test('calls getMemberInfo with member if no command present', async () => {
|
||||
// Arrange
|
||||
jest.spyOn(memberHelper, 'getMemberByName').mockResolvedValue(mockMember);
|
||||
memberRepo.getMemberByName.mockResolvedValue(mockMember);
|
||||
jest.spyOn(memberHelper, 'getMemberInfo').mockResolvedValue('member info');
|
||||
// Act
|
||||
const result = await memberHelper.sendCurrentValue(authorId, mockMember.name, null);
|
||||
@@ -309,13 +305,13 @@ describe('MemberHelper', () => {
|
||||
])('returns null message if no value found', async (command, expected) => {
|
||||
// Arrange
|
||||
const empty = {name: mockMember.name, displayname: null, proxy: null, propic: null}
|
||||
jest.spyOn(memberHelper, 'getMemberByName').mockResolvedValue(empty);
|
||||
memberRepo.getMemberByName.mockResolvedValue(empty);
|
||||
// Act
|
||||
const result = await memberHelper.sendCurrentValue(authorId, mockMember.name, command);
|
||||
// Assert
|
||||
expect(result).toEqual(expected);
|
||||
expect(memberHelper.getMemberByName).toHaveBeenCalledTimes(1);
|
||||
expect(memberHelper.getMemberByName).toHaveBeenCalledWith(authorId, mockMember.name);
|
||||
expect(memberRepo.getMemberByName).toHaveBeenCalledTimes(1);
|
||||
expect(memberRepo.getMemberByName).toHaveBeenCalledWith(authorId, mockMember.name);
|
||||
})
|
||||
})
|
||||
|
||||
@@ -479,39 +475,38 @@ describe('MemberHelper', () => {
|
||||
})
|
||||
|
||||
describe('addFullMember', () => {
|
||||
const {database} = require('../../src/database.js');
|
||||
beforeEach(() => {
|
||||
jest.spyOn(memberHelper, 'getMemberByName').mockResolvedValue();
|
||||
})
|
||||
|
||||
test('calls getMemberByName', async () => {
|
||||
// Arrange
|
||||
memberRepo.getMemberByName.mockResolvedValue();
|
||||
// Act
|
||||
await memberHelper.addFullMember(authorId, mockMember.name)
|
||||
// Assert
|
||||
expect(memberHelper.getMemberByName).toHaveBeenCalledWith(authorId, mockMember.name);
|
||||
expect(memberHelper.getMemberByName).toHaveBeenCalledTimes(1);
|
||||
expect(memberRepo.getMemberByName).toHaveBeenCalledWith(authorId, mockMember.name);
|
||||
expect(memberRepo.getMemberByName).toHaveBeenCalledTimes(1);
|
||||
})
|
||||
|
||||
test('if getMemberByName returns member, throw error', async () => {
|
||||
// Arrange
|
||||
memberHelper.getMemberByName.mockResolvedValue({name: mockMember.name});
|
||||
memberRepo.getMemberByName.mockResolvedValue({name: mockMember.name});
|
||||
// Act & Assert
|
||||
await expect(memberHelper.addFullMember(authorId, mockMember.name)).rejects.toThrow(`Can't add ${mockMember.name}. ${enums.err.MEMBER_EXISTS}`)
|
||||
// Assert
|
||||
expect(database.members.create).not.toHaveBeenCalled();
|
||||
expect(memberRepo.createMember).not.toHaveBeenCalled();
|
||||
})
|
||||
|
||||
|
||||
test('if name is not filled out, throw error', async () => {
|
||||
// Act & Assert
|
||||
// Arrange
|
||||
memberRepo.getMemberByName.mockResolvedValue();
|
||||
// Act
|
||||
await expect(memberHelper.addFullMember(authorId, " ")).rejects.toThrow(`Name ${enums.err.NO_VALUE}. ${enums.err.NAME_REQUIRED}`);
|
||||
// Assert
|
||||
expect(database.members.create).not.toHaveBeenCalled();
|
||||
expect(memberRepo.createMember).not.toHaveBeenCalled();
|
||||
})
|
||||
|
||||
test('if displayname is over 32 characters, call database.member.create with null value', async () => {
|
||||
test('if displayname is over 32 characters, call memberRepo.createMember with null value', async () => {
|
||||
// Arrange
|
||||
memberHelper.getMemberByName.mockResolvedValue();
|
||||
memberRepo.getMemberByName.mockResolvedValue();
|
||||
const tooLongDisplayName = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
|
||||
const expectedMemberArgs = {
|
||||
name: mockMember.name,
|
||||
@@ -520,7 +515,7 @@ describe('MemberHelper', () => {
|
||||
proxy: null,
|
||||
propic: null
|
||||
}
|
||||
database.members.create = jest.fn().mockResolvedValue(expectedMemberArgs);
|
||||
memberRepo.createMember = jest.fn().mockResolvedValue(expectedMemberArgs);
|
||||
const expectedReturn = {
|
||||
member: expectedMemberArgs,
|
||||
errors: [`Tried to set displayname to \"${tooLongDisplayName}\". ${enums.err.DISPLAY_NAME_TOO_LONG}. ${enums.err.SET_TO_NULL}`]
|
||||
@@ -530,8 +525,8 @@ describe('MemberHelper', () => {
|
||||
const res = await memberHelper.addFullMember(authorId, mockMember.name, tooLongDisplayName, null, null);
|
||||
// Assert
|
||||
expect(res).toEqual(expectedReturn);
|
||||
expect(database.members.create).toHaveBeenCalledWith(expectedMemberArgs);
|
||||
expect(database.members.create).toHaveBeenCalledTimes(1);
|
||||
expect(memberRepo.createMember).toHaveBeenCalledWith(expectedMemberArgs);
|
||||
expect(memberRepo.createMember).toHaveBeenCalledTimes(1);
|
||||
})
|
||||
|
||||
test('if proxy, call checkIfProxyExists', async () => {
|
||||
@@ -544,7 +539,7 @@ describe('MemberHelper', () => {
|
||||
proxy: null,
|
||||
propic: null
|
||||
}
|
||||
database.members.create = jest.fn().mockResolvedValue(expectedMemberArgs);
|
||||
memberRepo.createMember = jest.fn().mockResolvedValue(expectedMemberArgs);
|
||||
const expectedReturn = {member: expectedMemberArgs, errors: []}
|
||||
|
||||
// Act
|
||||
@@ -553,8 +548,8 @@ describe('MemberHelper', () => {
|
||||
expect(res).toEqual(expectedReturn);
|
||||
expect(memberHelper.checkIfProxyExists).toHaveBeenCalledWith(authorId, mockMember.proxy);
|
||||
expect(memberHelper.checkIfProxyExists).toHaveBeenCalledTimes(1);
|
||||
expect(database.members.create).toHaveBeenCalledWith(expectedMemberArgs);
|
||||
expect(database.members.create).toHaveBeenCalledTimes(1);
|
||||
expect(memberRepo.createMember).toHaveBeenCalledWith(expectedMemberArgs);
|
||||
expect(memberRepo.createMember).toHaveBeenCalledTimes(1);
|
||||
})
|
||||
|
||||
test('if checkProxyExists throws error, call database.member.create with null value', async () => {
|
||||
@@ -567,7 +562,7 @@ describe('MemberHelper', () => {
|
||||
proxy: null,
|
||||
propic: null
|
||||
}
|
||||
database.members.create = jest.fn().mockResolvedValue(expectedMemberArgs);
|
||||
memberRepo.createMember = jest.fn().mockResolvedValue(expectedMemberArgs);
|
||||
const expectedReturn = {
|
||||
member: expectedMemberArgs,
|
||||
errors: [`Tried to set proxy to \"${mockMember.proxy}\". error. ${enums.err.SET_TO_NULL}`]
|
||||
@@ -577,8 +572,8 @@ describe('MemberHelper', () => {
|
||||
const res = await memberHelper.addFullMember(authorId, mockMember.name, null, mockMember.proxy, null)
|
||||
// Assert
|
||||
expect(res).toEqual(expectedReturn);
|
||||
expect(database.members.create).toHaveBeenCalledWith(expectedMemberArgs);
|
||||
expect(database.members.create).toHaveBeenCalledTimes(1);
|
||||
expect(memberRepo.createMember).toHaveBeenCalledWith(expectedMemberArgs);
|
||||
expect(memberRepo.createMember).toHaveBeenCalledTimes(1);
|
||||
})
|
||||
|
||||
test('if propic, call checkImageFormatValidity', async () => {
|
||||
@@ -591,7 +586,7 @@ describe('MemberHelper', () => {
|
||||
propic: null
|
||||
}
|
||||
utils.setExpirationWarning = jest.fn().mockReturnValue();
|
||||
database.members.create = jest.fn().mockResolvedValue(expectedMemberArgs);
|
||||
memberRepo.createMember = jest.fn().mockResolvedValue(expectedMemberArgs);
|
||||
const expectedReturn = {member: expectedMemberArgs, errors: []}
|
||||
// Act
|
||||
const res = await memberHelper.addFullMember(authorId, mockMember.name, null, null, mockMember.propic);
|
||||
@@ -599,8 +594,8 @@ describe('MemberHelper', () => {
|
||||
expect(res).toEqual(expectedReturn);
|
||||
expect(utils.checkImageFormatValidity).toHaveBeenCalledWith(mockMember.propic);
|
||||
expect(utils.checkImageFormatValidity).toHaveBeenCalledTimes(1);
|
||||
expect(database.members.create).toHaveBeenCalledWith(expectedMemberArgs);
|
||||
expect(database.members.create).toHaveBeenCalledTimes(1);
|
||||
expect(memberRepo.createMember).toHaveBeenCalledWith(expectedMemberArgs);
|
||||
expect(memberRepo.createMember).toHaveBeenCalledTimes(1);
|
||||
})
|
||||
|
||||
test('if checkImageFormatValidity throws error, call database.member.create with null value', async () => {
|
||||
@@ -613,7 +608,7 @@ describe('MemberHelper', () => {
|
||||
proxy: null,
|
||||
propic: null
|
||||
}
|
||||
database.members.create = jest.fn().mockResolvedValue(expectedMemberArgs);
|
||||
memberRepo.createMember = jest.fn().mockResolvedValue(expectedMemberArgs);
|
||||
const expectedReturn = {
|
||||
member: expectedMemberArgs,
|
||||
errors: [`Tried to set profile picture to \"${mockMember.propic}\". error. ${enums.err.SET_TO_NULL}`]
|
||||
@@ -622,8 +617,8 @@ describe('MemberHelper', () => {
|
||||
const res = await memberHelper.addFullMember(authorId, mockMember.name, null, null, mockMember.propic);
|
||||
// Assert
|
||||
expect(res).toEqual(expectedReturn);
|
||||
expect(database.members.create).toHaveBeenCalledWith(expectedMemberArgs);
|
||||
expect(database.members.create).toHaveBeenCalledTimes(1);
|
||||
expect(memberRepo.createMember).toHaveBeenCalledWith(expectedMemberArgs);
|
||||
expect(memberRepo.createMember).toHaveBeenCalledTimes(1);
|
||||
})
|
||||
|
||||
test('calls setExpirationWarning if attachmentExpiration exists', async () => {
|
||||
@@ -647,7 +642,7 @@ describe('MemberHelper', () => {
|
||||
proxy: mockMember.proxy,
|
||||
propic: mockMember.propic
|
||||
}
|
||||
database.members.create = jest.fn().mockResolvedValue(expectedMemberArgs);
|
||||
memberRepo.createMember = jest.fn().mockResolvedValue(expectedMemberArgs);
|
||||
utils.checkImageFormatValidity = jest.fn().mockResolvedValue(true);
|
||||
utils.setExpirationWarning = jest.fn().mockReturnValue();
|
||||
const expectedReturn = {member: expectedMemberArgs, errors: []}
|
||||
@@ -655,19 +650,16 @@ describe('MemberHelper', () => {
|
||||
const res = await memberHelper.addFullMember(authorId, mockMember.name, mockMember.displayname, mockMember.proxy, mockMember.propic);
|
||||
// Assert
|
||||
expect(res).toEqual(expectedReturn);
|
||||
expect(database.members.create).toHaveBeenCalledWith(expectedMemberArgs);
|
||||
expect(database.members.create).toHaveBeenCalledTimes(1);
|
||||
expect(memberRepo.createMember).toHaveBeenCalledWith(expectedMemberArgs);
|
||||
expect(memberRepo.createMember).toHaveBeenCalledTimes(1);
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
describe('updateMemberField', () => {
|
||||
const {database} = require('../../src/database.js');
|
||||
beforeEach(() => {
|
||||
utils.setExpirationWarning = jest.fn().mockReturnValue(`warning`);
|
||||
database.members = {
|
||||
update: jest.fn().mockResolvedValue([1])
|
||||
};
|
||||
memberRepo.updateMemberField = jest.fn().mockResolvedValue([1]);
|
||||
})
|
||||
|
||||
test.each([
|
||||
@@ -682,20 +674,13 @@ describe('MemberHelper', () => {
|
||||
const res = await memberHelper.updateMemberField(authorId, mockMember.name, columnName, value, attachmentExpiration)
|
||||
// Assert
|
||||
expect(res).toEqual(expected);
|
||||
expect(database.members.update).toHaveBeenCalledTimes(1);
|
||||
expect(database.members.update).toHaveBeenCalledWith({[columnName]: value}, {
|
||||
where: {
|
||||
name: {[Op.iLike]: mockMember.name},
|
||||
userid: authorId
|
||||
}
|
||||
})
|
||||
expect(memberRepo.updateMemberField).toHaveBeenCalledTimes(1);
|
||||
expect(memberRepo.updateMemberField).toHaveBeenCalledWith(authorId, mockMember.name, columnName, value)
|
||||
})
|
||||
|
||||
test('if database.members.update returns 0 rows changed, throw error', async () => {
|
||||
// Arrange
|
||||
database.members = {
|
||||
update: jest.fn().mockResolvedValue([0])
|
||||
};
|
||||
memberRepo.updateMemberField = jest.fn().mockResolvedValue(0);
|
||||
// Act
|
||||
await expect(memberHelper.updateMemberField(authorId, mockMember.name, "displayname", mockMember.displayname)).rejects.toThrow(`Can't update ${mockMember.name}. ${enums.err.NO_MEMBER}.`);
|
||||
})
|
||||
@@ -704,7 +689,7 @@ describe('MemberHelper', () => {
|
||||
describe('checkIfProxyExists', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
jest.spyOn(memberHelper, "getMembersByAuthor").mockResolvedValue([mockMember]);
|
||||
memberRepo.getMembersByAuthor.mockResolvedValue([mockMember]);
|
||||
})
|
||||
|
||||
test.each([
|
||||
@@ -722,8 +707,8 @@ describe('MemberHelper', () => {
|
||||
const res = await memberHelper.checkIfProxyExists(authorId, proxy)
|
||||
// Assert
|
||||
expect(res).toEqual(false)
|
||||
expect(memberHelper.getMembersByAuthor).toHaveBeenCalledTimes(1);
|
||||
expect(memberHelper.getMembersByAuthor).toHaveBeenCalledWith(authorId);
|
||||
expect(memberRepo.getMembersByAuthor).toHaveBeenCalledTimes(1);
|
||||
expect(memberRepo.getMembersByAuthor).toHaveBeenCalledWith(authorId);
|
||||
})
|
||||
|
||||
test.each([
|
||||
@@ -734,13 +719,13 @@ describe('MemberHelper', () => {
|
||||
// Act & Assert
|
||||
await expect(memberHelper.checkIfProxyExists(authorId, proxy)).rejects.toThrow(error);
|
||||
|
||||
expect(memberHelper.getMembersByAuthor).not.toHaveBeenCalled();
|
||||
expect(memberRepo.getMembersByAuthor).not.toHaveBeenCalled();
|
||||
})
|
||||
|
||||
test('--text returns correct error and calls getMemberByAuthor', async () => {
|
||||
await expect(memberHelper.checkIfProxyExists(authorId, "--text")).rejects.toThrow(enums.err.PROXY_EXISTS);
|
||||
expect(memberHelper.getMembersByAuthor).toHaveBeenCalledTimes(1);
|
||||
expect(memberHelper.getMembersByAuthor).toHaveBeenCalledWith(authorId);
|
||||
expect(memberRepo.getMembersByAuthor).toHaveBeenCalledTimes(1);
|
||||
expect(memberRepo.getMembersByAuthor).toHaveBeenCalledWith(authorId);
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
16
tsconfig.json
Normal file
16
tsconfig.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"lib": [
|
||||
"es2021"
|
||||
],
|
||||
"target": "es2021",
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"outDir": "./database/build",
|
||||
"rootDir": "./database",
|
||||
"esModuleInterop": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"sourceMap": true
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user