150 Commits

Author SHA1 Message Date
Aster Fialla
db8f2c59de adjusted importHelper to properly test throwing of aggregate error 2026-02-24 22:29:03 -05:00
Aster Fialla
dd21ed564b changed getAllMembersInfo map to say index not name as it actually gets the index of a member and then the member object 2026-02-24 21:26:42 -05:00
Aster Fialla
d226238e14 adjusted tests to properly use mockRejectedValue for async rejections 2026-02-24 21:08:04 -05:00
Aster Fialla
5050f98e8f pushes e.message instead of full error object to errors array in importHelper 2026-02-24 20:38:39 -05:00
Aster Fialla
dad80bd385 reversed check for valid proxy (was returning valid if the proxy existed and invalid if it didn't) 2026-02-24 20:38:39 -05:00
Aster Fialla
545136bec7 added default case to memberCommandHandler to throw error if command is not recognized 2026-02-24 20:38:39 -05:00
Aster Fialla
b02937a660 getAllMembersInfo checks for fields.length 2026-02-24 20:38:39 -05:00
Aster Fialla
6d82a067a1 Got rid of unnecessary check for empty message from user (Fluxer doesn't allow this to happen)
Removed export of token
2026-02-24 20:38:37 -05:00
Aster Fialla
1d9d9600e5 replaced "pk;" with "pf;" in test 2026-02-24 20:08:52 -05:00
Aster Fialla
c7d5f8a341 made importHelper mock throw error instead of "resolve" error 2026-02-24 20:05:29 -05:00
Aster Fialla
15d2f7d79d updated console.error message in database.js 2026-02-24 20:02:32 -05:00
Aster Fialla
ba2274e5be changed commands.js to not have user interaction within the catch 2026-02-24 20:02:11 -05:00
Aster Fialla
de674281f9 make memberCommand exit when error occurs with parseMemberCommand 2026-02-24 19:54:10 -05:00
Aster Fialla
102d694240 Simplify getWebhook to use .find() instead of foreach logic 2026-02-24 19:52:29 -05:00
Aster Fialla
fb878222dd removed unnecessary try/catch from utils 2026-02-24 19:49:59 -05:00
Aster Fialla
a1e007448f changed utils to check for 10MB size not 1MB 2026-02-24 19:47:46 -05:00
Aster Fialla
efefcb69f4 added the remaining webhook tests 2026-02-24 18:58:45 -05:00
Aster Fialla
ad783ae6b7 added dash to commands.js and test to pass 2026-02-24 18:48:59 -05:00
Aster Fialla
664295e4a5 refactored database to not use then/catch 2026-02-24 18:12:43 -05:00
Aster Fialla
ded5910a7c removed jest-fetch-mock since it turns out I was just manually mocking it anyway 2026-02-24 18:06:13 -05:00
Aster Fialla
cdf5c6e537 tested utils checkImageFormatValidity 2026-02-24 18:04:15 -05:00
Aster Fialla
34cb8f3cdb converted utils.js to not use then/catch 2026-02-24 16:58:56 -05:00
Aster Fialla
8d946ce921 put console.error in commands 2026-02-24 16:58:39 -05:00
Aster Fialla
8919fce34f removed console.log from import helper 2026-02-24 16:56:03 -05:00
Aster Fialla
15a040d1d1 forgot to switch over some tests in bot.test and commands.test 2026-02-24 16:55:51 -05:00
Aster Fialla
6470b223f6 fixed typo in webhookHelper 2026-02-24 16:41:00 -05:00
Aster Fialla
5d8ff9c6c6 one more test in commands.js, and removed console.error 2026-02-24 16:22:38 -05:00
Aster Fialla
2bf218683a edited bot to have top level main function 2026-02-24 16:18:40 -05:00
Aster Fialla
8b8cdd6d12 fixed set up for commands tests 2026-02-24 15:57:33 -05:00
Aster Fialla
5a91a529b1 removing then/catch from messageHelper.test.js 2026-02-24 15:47:30 -05:00
Aster Fialla
b36a3ae236 refactoring memberHelper.js and tests to not use then/catch 2026-02-24 15:43:43 -05:00
Aster Fialla
c19614b6a1 refactoring commands.js and tests to not use then/catch 2026-02-24 14:50:46 -05:00
Aster Fialla
b4e4e5a951 refactoring bot and tests to not use then/catch 2026-02-24 14:13:48 -05:00
Aster Fialla
5ba5435d46 changed docstring 2026-02-24 13:56:46 -05:00
Aster Fialla
fc58c47b03 refactor webhookHelper and tests to not use then/catch 2026-02-24 13:56:37 -05:00
Aster Fialla
a0f62763af Merge branch 'main' of https://github.com/pieartsy/PluralFlux into refactor-async-await
# Conflicts:
#	src/helpers/importHelper.js
#	tests/helpers/importHelper.test.js
2026-02-24 12:43:52 -05:00
Aster Fialla
8318d715ad added enum 2026-02-24 12:39:42 -05:00
Aster Fialla
971638bfe3 refactored async/await for import helper to not also use then/catch 2026-02-24 12:39:37 -05:00
Aster Fialla
123aabf650 documentation: adjusted docstrings 2026-02-24 10:37:02 -05:00
Aster Fialla
3c7dfcb72b update enums 2026-02-23 15:55:01 -05:00
Aster Fialla
9059c63747 mostly finished command tests 2026-02-23 15:54:45 -05:00
Aster Fialla
6937d7b694 made import error not a PDF again 2026-02-23 15:51:12 -05:00
Aster Fialla
e31d9c2881 removed now-unneeded .toJSON() for embeds 2026-02-23 15:20:39 -05:00
Aster Fialla
654338f88e removed duplicate text from importHelper 2026-02-23 15:11:10 -05:00
Aster Fialla
2a2de70b46 starting commands tests 2026-02-23 15:10:57 -05:00
Aster Fialla
7dfc949b63 separated out commands to be more easily testable and added functionality for aliases 2026-02-23 15:10:48 -05:00
Aster Fialla
bb99ee7ef1 structure for utils test edited a bit 2026-02-23 13:38:38 -05:00
Aster Fialla
088eaaa654 package.lock 2026-02-23 13:36:58 -05:00
Aster Fialla
a2746a25f9 updated enums 2026-02-23 13:36:40 -05:00
Aster Fialla
daf23b2f5d adding import tests 2026-02-23 13:36:31 -05:00
Aster Fialla
813e893afb more accurate framework for utils tests 2026-02-23 12:30:40 -05:00
Aster Fialla
10c145a02d imported env files better 2026-02-23 12:27:25 -05:00
Aster Fialla
6638756a9a replaced reference to [text] in proxy command instructions so it's clearer in contrast to the instructions to type without brackets 2026-02-23 12:01:07 -05:00
Aster Fialla
df60ec489b updated package.json 2026-02-23 11:56:35 -05:00
Aster Fialla
79f0f19733 adjusted bot.test mock 2026-02-23 11:50:50 -05:00
Aster Fialla
91e98f2434 fixed bug with memberHelper not processing attached URL 2026-02-23 11:35:01 -05:00
Aster Fialla
c752ee2bf2 Revert "adding debounce" cus it's not working
This reverts commit 1d27db960c.

# Conflicts:
#	src/bot.js
2026-02-23 11:19:26 -05:00
Aster Fialla
bfb5bf9cbb testing bot login, added invoke for login 2026-02-23 10:47:02 -05:00
Aster Fialla
a2bd7b755c testing bot.js handleMessageCreate 2026-02-23 10:38:04 -05:00
Aster Fialla
0f65db2d42 add frameworks for other tests 2026-02-23 09:00:48 -05:00
Aster Fialla
da79db856a removed unneeded imports from messageHelper tests 2026-02-23 08:59:56 -05:00
Aster Fialla
50c41990ed removed unused argument from bot call to sendMessageAsMember 2026-02-23 08:59:32 -05:00
Aster Fialla
1d27db960c adding debounce 2026-02-23 08:47:27 -05:00
Aster Fialla
264c3e19fd removing some unused imports in messageHelper 2026-02-23 08:44:50 -05:00
Aster Fialla
0a1652f243 updated docstrings 2026-02-23 08:02:30 -05:00
Aster Fialla
580a0cb42e renaming test interpolation to be less silly 2026-02-23 07:53:03 -05:00
Aster Fialla
71c8736a38 added to docstring 2026-02-23 00:18:57 -05:00
Aster Fialla
d9530a2ccf structure and test revamp for memberHelper 2026-02-23 00:18:14 -05:00
Aster Fialla
72ec047f48 test frame for utils 2026-02-22 21:40:53 -05:00
Aster Fialla
be24034d48 updating tests and memberHelper to try and make parseMemberCommand less bloated/broken (not working ATM) 2026-02-22 21:40:42 -05:00
Aster Fialla
c3e7bf4433 updated enums 2026-02-22 18:48:52 -05:00
Aster Fialla
7dae9f52b1 fixed call to getMemberInfo to not include authorId 2026-02-22 18:32:47 -05:00
Aster Fialla
630ce7888c adjusted parsing logic to not try and call member when none exists (due to "new" command) 2026-02-22 18:06:18 -05:00
Aster Fialla
b88b9252be forgot to import enums into utils 2026-02-22 17:09:02 -05:00
Aster Fialla
79cfc1563d add license 2026-02-22 12:03:44 -05:00
Aster Fialla
fa4ca779ee accidental error with memberHelper not allowing pf;member fixed 2026-02-22 11:52:51 -05:00
Aster Fialla
eb7340faf7 finished relevant tests for memberHelper 2026-02-22 11:24:57 -05:00
Aster Fialla
d4adbc028a typo not importing utils right 2026-02-22 09:54:01 -05:00
Aster Fialla
57336b4e58 added to enums 2026-02-21 23:05:59 -05:00
Aster Fialla
71803fd10c made utils file 2026-02-21 23:05:15 -05:00
Aster Fialla
cc8cb6e925 write more tests for addNewMember 2026-02-21 23:05:01 -05:00
Aster Fialla
a797e9e06d write more tests for parseMemberCommand 2026-02-21 21:37:57 -05:00
Aster Fialla
4146591c90 figured out issue (was missing return value) 2026-02-21 20:09:39 -05:00
Aster Fialla
0bca359b7f trying to get refactored memberhelper to pass tests -_- 2026-02-20 11:28:28 -05:00
Aster Fialla
932fc9c967 update getMemberCommandInfo 2026-02-20 11:03:50 -05:00
Aster Fialla
cd01473121 update enums 2026-02-20 11:00:57 -05:00
Aster Fialla
c133ee2c42 refactored memberHelper 2026-02-20 11:00:21 -05:00
Aster Fialla
f055518b1f Merge branch 'main' into add-tests
# Conflicts:
#	README.md
2026-02-20 10:02:13 -05:00
Aster Fialla
3ed24458e0 Merge branch 'main' of https://github.com/pieartsy/PluralFlux into add-tests 2026-02-19 21:44:07 -05:00
Aster Fialla
a30d408c02 Merge branch 'main' of https://github.com/pieartsy/PluralFlux into add-tests 2026-02-19 21:41:59 -05:00
Aster Fialla
e9052624d4 Merge branch 'main' of https://github.com/pieartsy/PluralFlux into add-tests 2026-02-19 21:41:29 -05:00
Aster Fialla
4d3db8c001 updated documentation 2026-02-19 21:38:10 -05:00
Aster Fialla
eb6c60618b added nicer error listing to importHelper 2026-02-19 21:38:03 -05:00
Aster Fialla
b64ba6e787 made it easier to make a member 2026-02-19 21:36:19 -05:00
Aster Fialla
d640322392 removed unnecessary await and console.log 2026-02-19 20:32:35 -05:00
Aster Fialla
b1a29fd3ff edit enums, tweak import content command 2026-02-19 20:30:55 -05:00
Aster Fialla
e7198230a7 updated member helper and tests 2026-02-19 20:08:26 -05:00
Aster Fialla
fad6d42ee2 updated to be enum 2026-02-19 19:32:37 -05:00
Aster Fialla
c4c6ad0fdd fixed bug for import not sending help text, added help text if you type a unrecognized command 2026-02-19 19:31:56 -05:00
Aster Fialla
1e2724bbfb update bot to suppress errors from API 2026-02-19 19:13:54 -05:00
Aster Fialla
849acf7275 update message helper test to include space case 2026-02-19 18:54:35 -05:00
Aster Fialla
7a3b8c1994 edited help message trigger for updatePropic 2026-02-19 01:45:43 -05:00
Aster Fialla
2e0a8adec5 added tests for updateDisplayName 2026-02-19 01:31:38 -05:00
Aster Fialla
7aeae1837f added todo note 2026-02-19 00:59:57 -05:00
Aster Fialla
6eb9fef376 added debounce to count guilds properly 2026-02-19 00:52:06 -05:00
Aster Fialla
9dab429d0d , updated tests for webhookHelper and removed error response when proxy is sent without content 2026-02-19 00:51:33 -05:00
Aster Fialla
f9199f8477 removed error response when proxy is sent without content 2026-02-19 00:48:57 -05:00
Aster Fialla
a7cd4e96f0 updating enums 2026-02-19 00:20:53 -05:00
Aster Fialla
21efbccfd7 adding more cases to messageHelper tests 2026-02-19 00:20:46 -05:00
Aster Fialla
873959a5f4 finally mocking correctly 2026-02-18 21:24:41 -05:00
Aster Fialla
d33c3213f3 removed confusing brackets from enum docs 2026-02-18 21:20:56 -05:00
Aster Fialla
75c4c548d8 deleted extra file added during merge 2026-02-18 21:13:25 -05:00
Aster Fialla
9d5493e8ab Merge remote-tracking branch 'origin/add-tests' into add-tests
# Conflicts:
#	src/bot.js
#	src/commands.js
#	src/database.js
#	src/helpers/importHelper.js
#	src/helpers/memberHelper.js
#	src/helpers/messageHelper.js
#	src/helpers/webhookHelper.js
2026-02-18 21:03:44 -05:00
Aster Fialla
fc1c463696 more tests for webhookhelper 2026-02-18 17:14:04 -05:00
Aster Fialla
1bba8099e9 updating docstring for messageHelper parseProxyTags 2026-02-18 16:29:08 -05:00
Aster Fialla
acd9ce7c3e more cases for messageHelper just in case 2026-02-18 16:28:35 -05:00
Aster Fialla
da9a3d2c8a finished tests for messageHelper! 2026-02-18 12:37:39 -05:00
Aster Fialla
274f1ead15 added "return" so tests dont terminate on failure and deleted env.jest 2026-02-18 12:11:55 -05:00
Aster Fialla
223292c2d3 added tests for parseProxyTags and updated logic 2026-02-18 10:17:08 -05:00
Aster Fialla
400e40a405 updated sendMessageAsAttachment to returnBufferFromText and updated commands/webhookHelper accordingly 2026-02-18 09:19:44 -05:00
Aster Fialla
152bc8873d added test for memberHelper 2026-02-18 09:16:26 -05:00
Aster Fialla
e16694ac2d fixed test and logic 2026-02-18 08:47:37 -05:00
Aster Fialla
f0ac02e86d readded line i shouldn't have removed in sendMessageAsMember 2026-02-18 00:33:05 -05:00
Aster Fialla
5c01f2e284 test setup for messagehelper and webhookhelper 2026-02-18 00:28:18 -05:00
Aster Fialla
da5a250445 added return to addFullMember so that addNewMember can reference it properly in strings 2026-02-18 00:27:51 -05:00
Aster Fialla
23a57b3e99 simplified sendMessageAsAttachment 2026-02-18 00:26:51 -05:00
Aster Fialla
1bf6c8c1f2 think i fixed weird error with webhook sending error when a user has no members 2026-02-18 00:25:41 -05:00
Aster Fialla
fe00f66104 more tests for member helper 2026-02-17 23:03:45 -05:00
Aster Fialla
15703c24cd moved import to helpers folder 2026-02-17 23:03:19 -05:00
Aster Fialla
3dbbe7df50 moved import to helpers folder 2026-02-17 23:03:08 -05:00
Aster Fialla
31eb4262dd upgraded fluxer.js 2026-02-17 22:23:55 -05:00
Aster Fialla
c645bb0aea renamed db to database
more tests and fixing logic for memberhelper
2026-02-17 22:23:47 -05:00
Aster Fialla
0b7f549bdf added error handling parseMemberCommand test 2026-02-17 21:21:07 -05:00
Aster Fialla
bfc633a755 setup fixed more 2026-02-17 20:52:22 -05:00
Aster Fialla
01e620a935 finally figured out issue with tests (referencing the method directly in the test.each calls the real method not the mock in beforeEach()) 2026-02-17 20:49:19 -05:00
Aster Fialla
a4804c2ea7 added babel to convert es modules to cjs 2026-02-17 20:48:44 -05:00
Aster Fialla
164ff7d8b6 nevermind it wasn't actually working, gonna move on for now 2026-02-17 19:58:56 -05:00
Aster Fialla
5e3b3f33d3 mostly working except for the weirdest error 2026-02-17 19:38:50 -05:00
Aster Fialla
4fcb53482c separating out enum return from method return 2026-02-17 19:36:14 -05:00
Aster Fialla
ba9552b4aa updated jest to sort of work with es6 2026-02-17 17:38:06 -05:00
Aster Fialla
35b454bc80 Revert "converted import syntax to commonJS"
This reverts commit 5ab0d62b
2026-02-17 17:25:18 -05:00
Aster Fialla
321fe7f0a9 more correct dockerfile and compose.yaml 2026-02-17 17:16:48 -05:00
Aster Fialla
0a4bfa59ad more correct test setup 2026-02-17 17:16:10 -05:00
Aster Fialla
79d98c3618 adjusted beforeeach/beforeall so more pass 2026-02-17 17:16:10 -05:00
Aster Fialla
a44e2745c5 got test sort of working (jest set up is not crashing but also not mocking correctly) 2026-02-17 17:16:10 -05:00
Aster Fialla
5ab0d62bdb converted import syntax to commonJS
removed unused methods
2026-02-17 17:16:09 -05:00
Aster Fialla
876f9486ad more correct test setup 2026-02-17 07:14:47 -05:00
Aster Fialla
5e28cdfd01 adjusted beforeeach/beforeall so more pass 2026-02-17 07:11:22 -05:00
Aster Fialla
5a39610547 got test sort of working (jest set up is not crashing but also not mocking correctly) 2026-02-16 18:46:33 -05:00
Aster Fialla
a3caa2dc42 converted import syntax to ES modules
removed unused methods
2026-02-16 18:46:32 -05:00
30 changed files with 816 additions and 2211 deletions

View File

@@ -1,49 +0,0 @@
name: Build Dev instance
on:
push:
branches: ["develop", "Develop"]
pull_request:
branches: ["develop", "Develop"]
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Docker BuildX
uses: docker/setup-buildx-action@v3
- name: login to gitea registry
uses: docker/login-action@v3
with:
registry: ${{ gitea.server_url }}
username: ${{ gitea.actor }}
password: ${{ secrets.GITEA }}
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: |
engineering.sanya.gay/pluralflux/pluralflux-dev:latest
- name: Deploy bot
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
port: 22
script: |
cd ${{ secrets.BOT_DIRECTORY }}
docker compose pull
docker compose up -d pluralflux-dev

View File

@@ -1,47 +0,0 @@
name: nodeJS remote worker
on:
push:
branches: ["main"]
pull_request:
branches: ["main"]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Docker BuildX
uses: docker/setup-buildx-action@v3
- name: login to gitea registry
uses: docker/login-action@v3
with:
registry: ${{ gitea.server_url }}
username: ${{ gitea.actor }}
password: ${{ secrets.GITEA }}
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: |
engineering.sanya.gay/pluralflux/pluralflux:latest
- name: Deploy bot
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
port: 22
script: |
cd ${{ secrets.BOT_DIRECTORY }}
docker compose pull
docker compose up -d pluralflux-prod

View File

@@ -1,26 +0,0 @@
name: Auto-Sync from Mirror
on:
push:
repository: "Pluralflux/Pluralflux"
branches: [main,develop]
workflow_dispatch:
jobs:
sync:
runs-on: ubuntu-latest
steps:
- name: Checkout Fork
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GITEA_TOKEN }}
- name: Pull from Mirror
run: |
git remote add upstream https://engineering.sanya.gay/PluralFlux/PluralFlux.git
git fetch upstream --prune
git reset --hard origin/main
git push origin "refs/remotes/upstream/*:refs/heads/*" --force-with-lease
git merge upstream/main -m "Syncing from github"
git push origin main

9
.gitignore vendored
View File

@@ -1,13 +1,8 @@
node_modules/
build/
tmp/
temp/
node_modules
.idea
secrets/
coverage
config.json
coverage
log.txt
.env
oya.png
variables.env
.env.production

View File

@@ -7,4 +7,4 @@ FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/node_modules ./node_modules
COPY . .
CMD ["npm", "start"]
CMD ["node", "src/bot.js"]

View File

@@ -1,25 +1,46 @@
services:
main:
image: engineering.sanya.gay/pluralflux/pluralflux
build: .
container_name: pluralflux
restart: unless-stopped
env_file: "variables.env"
networks:
- pluralflux-net
postgres:
image: postgres:latest
env_file: "variables.env"
container_name: pluralflux-postgres
environment:
POSTGRES_PASSWORD_FILE: /run/secrets/postgres_pwd
secrets:
- postgres_pwd
volumes:
- pgdata:/var/lib/postgresql
- ./pgBackup:/mnt/pgBackup
ports:
- "5432:5432"
networks:
- pluralflux-net
pgadmin:
image: dpage/pgadmin4:latest
container_name: pluralflux-pgadmin
ports:
- "5050:80"
env_file: "variables.env"
environment:
PGADMIN_DEFAULT_EMAIL: code@asterfialla.com
PGADMIN_DEFAULT_PASSWORD_FILE: /run/secrets/postgres_pwd
PGADMIN_CONFIG_SERVER_MODE: 'False'
PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED: 'False'
secrets:
- postgres_pwd
depends_on:
- postgres
volumes:
- pgadmindata:/var/lib/pgadmin
networks:
- pluralflux-net
networks:
pluralflux-net:
volumes:
pgdata:
secrets:
postgres_pwd:
file: ./secrets/postgres-password.txt

View File

@@ -1,26 +0,0 @@
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: process.env.POSTGRES_ENDPOINT,
port: 5432,
username: "postgres",
password: process.env.POSTGRES_PASSWORD,
database: "postgres",
synchronize: false,
logging: false,
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",
},
});

View File

@@ -1,40 +0,0 @@
import {Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, Unique} from "typeorm"
@Entity({name: "Member", synchronize: true})
@Unique("UQ_Member_userid_name", ['userid', 'name'])
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
}

View File

@@ -1,14 +0,0 @@
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"`);
}
}

View File

@@ -1,12 +0,0 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class AddData1772419448503 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`INSERT INTO "Member"(id, userid, name,displayname, proxy, propic, "createdAt", "updatedAt") SELECT id,userid, name,displayname, proxy, propic, "createdAt", "updatedAt" FROM "Members";`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`TRUNCATE TABLE "Member"`);
}
}

View File

@@ -1,17 +0,0 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class DeleteDuplicates1772825438973 implements MigrationInterface {
name= "DeleteDuplicates1772825438973"
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DELETE
FROM "Member" a USING "Member" b
WHERE a.id
> b.id
AND a.name = b.name
AND a.userid = b.userid;`)
}
public async down(queryRunner: QueryRunner): Promise<void> {
}
}

View File

@@ -1,14 +0,0 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class Update1772830252670 implements MigrationInterface {
name = 'Update1772830252670'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "Member" ADD CONSTRAINT "UQ_Member_userid_name" UNIQUE ("userid", "name")`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "Member" DROP CONSTRAINT "UQ_Member_userid_name"`);
}
}

1953
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -7,36 +7,26 @@
"type": "git",
"url": "https://github.com/pieartsy/PluralFlux.git"
},
"type": "commonjs",
"private": true,
"dependencies": {
"@fluxerjs/core": "^1.2.2",
"dotenv": "^17.3.1",
"pg": "^8.19.0",
"pg": "^8.18.0",
"pg-hstore": "^2.3.4",
"pm2": "^6.0.14",
"psql": "^0.0.1",
"reflect-metadata": "^0.2.2",
"tmp": "^0.2.5",
"typeorm": "^0.3.28"
"sequelize": "^6.37.7",
"tmp": "^0.2.5"
},
"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",
"ts-node": "^10.9.2",
"typescript": "^5.9.3"
"jest": "^30.2.0"
},
"scripts": {
"test": "jest",
"start": "ts-node src/bot.js",
"new-migration": "typeorm-ts-node-commonjs migration:create database/migrations/update",
"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"
"test": "jest"
}
}

View File

@@ -1,13 +1,12 @@
const {Client, Events, Message} = require('@fluxerjs/core');
const {messageHelper} = require("./helpers/messageHelper.js");
const {enums} = require("./enums.js");
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");
import { Client, Events, Message } from '@fluxerjs/core';
import { messageHelper } from "./helpers/messageHelper.js";
import {enums} from "./enums.js";
import {commands} from "./commands.js";
import {webhookHelper} from "./helpers/webhookHelper.js";
import env from 'dotenv';
import {utils} from "./helpers/utils.js";
env.config();
env.config({path: './.env'});
const token = process.env.FLUXER_BOT_TOKEN;
@@ -16,12 +15,10 @@ if (!token) {
process.exit(1);
}
client = new Client({ intents: 0 });
module.exports.client = client;
export const client = new Client({ intents: 0 });
client.on(Events.MessageCreate, async (message) => {
await module.exports.handleMessageCreate(message);
await handleMessageCreate(message);
});
/**
@@ -31,7 +28,7 @@ client.on(Events.MessageCreate, async (message) => {
* @param {Message} message - The message object
*
**/
module.exports.handleMessageCreate = async function(message) {
export const handleMessageCreate = async function(message) {
try {
// Ignore bots
if (message.author.bot) return;
@@ -82,14 +79,12 @@ function printGuilds() {
}
const debouncePrintGuilds = utils.debounce(printGuilds, 2000);
// export const debounceLogin = utils.debounce(client.login, 60000);
export const debounceLogin = utils.debounce(client.login, 60000);
module.exports.login = async function() {
export const 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);
@@ -98,7 +93,7 @@ module.exports.login = async function() {
function main()
{
exports.login();
login();
}
main();

View File

@@ -1,20 +1,20 @@
const {messageHelper} = require("./helpers/messageHelper.js");
const {enums} = require("./enums.js");
const {memberHelper} = require("./helpers/memberHelper.js");
const {EmbedBuilder} = require("@fluxerjs/core");
const {importHelper} = require("./helpers/importHelper.js");
import {messageHelper} from "./helpers/messageHelper.js";
import {enums} from "./enums.js";
import {memberHelper} from "./helpers/memberHelper.js";
import {EmbedBuilder} from "@fluxerjs/core";
import {importHelper} from "./helpers/importHelper.js";
const commands = {
const cmds = {
commandsMap: new Map(),
aliasesMap: new Map()
};
commands.aliasesMap.set('m', {command: 'member'})
cmds.aliasesMap.set('m', {command: 'member'})
commands.commandsMap.set('member', {
cmds.commandsMap.set('member', {
description: enums.help.SHORT_DESC_MEMBER,
async execute(message, args) {
await commands.memberCommand(message, args)
await cmds.memberCommand(message, args)
}
})
@@ -26,7 +26,7 @@ commands.commandsMap.set('member', {
* @param {string[]} args - The parsed arguments
*
**/
commands.memberCommand = async function (message, args) {
cmds.memberCommand = async function (message, args) {
const authorFull = `${message.author.username}#${message.author.discriminator}`
const attachmentUrl = message.attachments.size > 0 ? message.attachments.first().url : null;
const attachmentExpires = message.attachments.size > 0 ? message.attachments.first().expires_at : null;
@@ -53,10 +53,10 @@ commands.memberCommand = async function (message, args) {
}
commands.commandsMap.set('help', {
cmds.commandsMap.set('help', {
description: enums.help.SHORT_DESC_HELP,
async execute(message) {
const fields = [...commands.commandsMap.entries()].map(([name, cmd]) => ({
const fields = [...cmds.commandsMap.entries()].map(([name, cmd]) => ({
name: `${messageHelper.prefix}${name}`,
value: cmd.description,
inline: true,
@@ -73,10 +73,10 @@ commands.commandsMap.set('help', {
},
})
commands.commandsMap.set('import', {
cmds.commandsMap.set('import', {
description: enums.help.SHORT_DESC_IMPORT,
async execute(message, args) {
await commands.importCommand(message, args);
await cmds.importCommand(message, args);
}
})
@@ -88,7 +88,7 @@ commands.commandsMap.set('import', {
* @param {string[]} args - The parsed arguments
*
**/
commands.importCommand = async function (message, args) {
cmds.importCommand = async function (message, args) {
const attachmentUrl = message.attachments.size > 0 ? message.attachments.first().url : null;
if ((message.content.includes('--help') || (args[0] === '' && args.length === 1)) && !attachmentUrl) {
return await message.reply(enums.help.IMPORT);
@@ -119,4 +119,4 @@ commands.importCommand = async function (message, args) {
}
module.exports.commands = commands;
export const commands = cmds;

86
src/database.js Normal file
View File

@@ -0,0 +1,86 @@
import {DataTypes, Sequelize} from 'sequelize';
import * as env from 'dotenv';
env.config();
const password = process.env.POSTGRES_PASSWORD;
if (!password) {
console.error("Missing POSTGRES_PASSWORD environment variable.");
process.exit(1);
}
const db = {};
const sequelize = new Sequelize('postgres', 'postgres', password, {
host: 'localhost',
logging: false,
dialect: 'postgres'
});
db.sequelize = sequelize;
db.Sequelize = Sequelize;
db.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,
}
});
db.systems = sequelize.define('System', {
userid: {
type: DataTypes.STRING,
},
fronter: {
type: DataTypes.STRING
},
grouptag: {
type: DataTypes.STRING
},
autoproxy: {
type: DataTypes.BOOLEAN,
}
})
/**
* Checks Sequelize database connection.
*/
db.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);
}
}
export const database = db;

View File

@@ -1,6 +1,6 @@
const enums = {};
const helperEnums = {};
enums.err = {
helperEnums.err = {
NO_MEMBER: "No such member was found.",
NO_NAME_PROVIDED: "No member name was provided for",
NO_VALUE: "has not been set for this member.",
@@ -26,7 +26,7 @@ enums.err = {
CANNOT_FETCH_RESOURCE: "Could not download the file at this time."
}
enums.help = {
helperEnums.help = {
SHORT_DESC_HELP: "Lists available commands.",
SHORT_DESC_MEMBER: "Accesses subcommands related to proxy members.",
SHORT_DESC_IMPORT: "Imports from PluralKit.",
@@ -43,11 +43,8 @@ enums.help = {
IMPORT: "Imports from PluralKit using the JSON file provided by their export command. Importing from other proxy bots is TBD. `pf;import` and attach your JSON file to the message. This will only save the fields that are present in the bot currently, not anything else like birthdays or system handles (yet?). **Only one proxy can be set per member currently.**\n\n**PRO TIP**: For privacy reasons, try DMing the bot with this command and your JSON file--it should still work the same."
}
enums.misc = {
ATTACHMENT_SENT_BY: "Attachment sent by:",
ATTACHMENT_EXPIRATION_WARNING: "**NOTE:** Because this profile picture is hosted on Fluxer, it will expire. To avoid this, upload the picture to another website like <https://imgbb.com/> and link to it directly.",
FLUXER_ATTACHMENT_URL: "https://fluxerusercontent.com/attachments/"
helperEnums.misc = {
ATTACHMENT_SENT_BY: "Attachment sent by:"
}
module.exports.enums = enums;
export const enums = helperEnums;

View File

@@ -1,7 +1,7 @@
const {enums} = require("../enums.js");
const {memberHelper} = require("./memberHelper.js");
import {enums} from "../enums.js";
import {memberHelper} from "./memberHelper.js";
const importHelper = {};
const ih = {};
/**
* Tries to import from Pluralkit.
@@ -12,7 +12,7 @@ const importHelper = {};
* @returns {Promise<string>} A successful addition of all members.
* @throws {Error} When the member exists, or creating a member doesn't work.
*/
importHelper.pluralKitImport = async function (authorId, attachmentUrl= null) {
ih.pluralKitImport = async function (authorId, attachmentUrl= null) {
let fetchResult, pkData;
if (!attachmentUrl) {
throw new Error(enums.err.NOT_JSON_FILE);
@@ -55,4 +55,4 @@ importHelper.pluralKitImport = async function (authorId, attachmentUrl= null) {
return aggregatedText;
}
exports.importHelper = importHelper;
export const importHelper = ih;

View File

@@ -1,9 +1,10 @@
const {enums} = require("../enums.js");
const {EmbedBuilder} = require("@fluxerjs/core");
const {utils} = require("./utils.js");
const {memberRepo} = require("../repositories/memberRepo.js");
import {database} from '../database.js';
import {enums} from "../enums.js";
import {Op} from "sequelize";
import {EmbedBuilder} from "@fluxerjs/core";
import {utils} from "./utils.js";
const memberHelper = {};
const mh = {};
const commandList = ['new', 'remove', 'name', 'list', 'displayname', 'proxy', 'propic'];
const newAndRemoveCommands = ['new', 'remove'];
@@ -22,14 +23,14 @@ const newAndRemoveCommands = ['new', 'remove'];
* @returns {Promise <EmbedBuilder>} A list of member commands and descriptions.
* @returns {Promise<{EmbedBuilder, string[], string}>} A member info embed + info/errors.
*/
memberHelper.parseMemberCommand = async function (authorId, authorFull, args, attachmentUrl = null, attachmentExpiration = null) {
mh.parseMemberCommand = async function (authorId, authorFull, args, attachmentUrl = null, attachmentExpiration = null) {
let memberName, command, isHelp = false;
// checks whether command is in list, otherwise assumes it's a name
// ex: pf;member remove, pf;member remove --help
// ex: pf;member, pf;member --help
if (args.length === 0 || args[0] === '--help' || args[0] === '') {
return memberHelper.getMemberCommandInfo();
return mh.getMemberCommandInfo();
}
// ex: pf;member remove somePerson
if (commandList.includes(args[0])) {
@@ -51,7 +52,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 mh.memberArgumentHandler(authorId, authorFull, isHelp, command, memberName, args, attachmentUrl, attachmentExpiration)
}
/**
@@ -70,18 +71,17 @@ 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) {
mh.memberArgumentHandler = async function(authorId, authorFull, isHelp, command = null, memberName = null, args = [], attachmentUrl = null, attachmentExpiration = null) {
if (!command && !memberName && !isHelp) {
throw new Error(enums.err.COMMAND_NOT_RECOGNIZED);
}
else if (isHelp) {
return memberHelper.sendHelpEnum(command);
return mh.sendHelpEnum(command);
}
else if (command === "list") {
return await memberHelper.getAllMembersInfo(authorId, authorFull);
return await mh.getAllMembersInfo(authorId, authorFull);
}
else if (!memberName && !isHelp) {
throw new Error(enums.err.NO_MEMBER);
@@ -92,10 +92,10 @@ memberHelper.memberArgumentHandler = async function(authorId, authorFull, isHelp
// ex: pf;member blah blah
if (command && memberName && (values.length > 0 || newAndRemoveCommands.includes(command) || attachmentUrl)) {
return await memberHelper.memberCommandHandler(authorId, command, memberName, values, attachmentUrl, attachmentExpiration);
return await mh.memberCommandHandler(authorId, command, memberName, values, attachmentUrl, attachmentExpiration);
}
else if (memberName && values.length === 0) {
return await memberHelper.sendCurrentValue(authorId, memberName, command);
return await mh.sendCurrentValue(authorId, memberName, command);
}
}
@@ -112,12 +112,12 @@ memberHelper.memberArgumentHandler = async function(authorId, authorFull, isHelp
* @returns {Promise<{EmbedBuilder, string[], string}>} A member info embed + info/errors.
* @throws {Error} When there's no member
*/
memberHelper.sendCurrentValue = async function(authorId, memberName, command= null) {
const member = await memberRepo.getMemberByName(authorId, memberName);
mh.sendCurrentValue = async function(authorId, memberName, command= null) {
const member = await mh.getMemberByName(authorId, memberName);
if (!member) throw new Error(enums.err.NO_MEMBER);
if (!command) {
return memberHelper.getMemberInfo(member);
return mh.getMemberInfo(member);
}
switch (command) {
@@ -138,7 +138,7 @@ memberHelper.sendCurrentValue = async function(authorId, memberName, command= nu
* @param {string} command - The command being called.
* @returns {string} - The help text associated with a command.
*/
memberHelper.sendHelpEnum = function(command) {
mh.sendHelpEnum = function(command) {
switch (command) {
case 'new':
return enums.help.NEW;
@@ -167,22 +167,25 @@ 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> | Promise <EmbedBuilder> | Promise<{EmbedBuilder, [string], string}>}
* @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.
*/
memberHelper.memberCommandHandler = async function(authorId, command, memberName, values, attachmentUrl = null, attachmentExpiration = null) {
mh.memberCommandHandler = async function(authorId, command, memberName, values, attachmentUrl = null, attachmentExpiration = null) {
switch (command) {
case 'new':
return await memberHelper.addNewMember(authorId, memberName, values, attachmentUrl, attachmentExpiration);
return await mh.addNewMember(authorId, memberName, values, attachmentUrl, attachmentExpiration);
case 'remove':
return await memberHelper.removeMember(authorId, memberName);
return await mh.removeMember(authorId, memberName);
case 'name':
return await memberHelper.updateName(authorId, memberName, values[0]);
return await mh.updateName(authorId, memberName, values[0]);
case 'displayname':
return await memberHelper.updateDisplayName(authorId, memberName, values[0]);
return await mh.updateDisplayName(authorId, memberName, values[0]);
case 'proxy':
return await memberHelper.updateProxy(authorId, memberName, values[0]);
return await mh.updateProxy(authorId, memberName, values[0]);
case 'propic':
return await memberHelper.updatePropic(authorId, memberName, values[0], attachmentUrl, attachmentExpiration);
return await mh.updatePropic(authorId, memberName, values[0], attachmentUrl, attachmentExpiration);
default:
throw new Error(enums.err.COMMAND_NOT_RECOGNIZED);
}
@@ -199,13 +202,13 @@ memberHelper.memberCommandHandler = async function(authorId, command, memberName
* @param {string | null} [attachmentExpiration] - The attachment expiry date, if any
* @returns {Promise<{EmbedBuilder, string[], string}>} A successful addition.
*/
memberHelper.addNewMember = async function (authorId, memberName, values, attachmentUrl = null, attachmentExpiration = null) {
mh.addNewMember = async function (authorId, memberName, values, attachmentUrl = null, attachmentExpiration = null) {
const displayName = values[0];
const proxy = values[1];
const propic = values[2] ?? attachmentUrl;
const memberObj = await memberHelper.addFullMember(authorId, memberName, displayName, proxy, propic, attachmentExpiration);
const memberInfoEmbed = memberHelper.getMemberInfo(memberObj.member);
const memberObj = await mh.addFullMember(authorId, memberName, displayName, proxy, propic, attachmentExpiration);
const memberInfoEmbed = mh.getMemberInfo(memberObj.member);
return {embed: memberInfoEmbed, errors: memberObj.errors, success: `${memberName} has been added successfully.`}
}
@@ -219,12 +222,12 @@ memberHelper.addNewMember = async function (authorId, memberName, values, attach
* @returns {Promise<string>} A successful update.
* @throws {RangeError} When the name doesn't exist.
*/
memberHelper.updateName = async function (authorId, memberName, name) {
mh.updateName = async function (authorId, memberName, name) {
const trimmedName = name.trim();
if (trimmedName === '') {
throw new RangeError(`Name ${enums.err.NO_VALUE}`);
}
return await memberHelper.updateMemberField(authorId, memberName, "name", trimmedName);
return await mh.updateMemberField(authorId, memberName, "name", trimmedName);
}
/**
@@ -237,7 +240,7 @@ memberHelper.updateName = async function (authorId, memberName, name) {
* @returns {Promise<string>} A successful update.
* @throws {RangeError} When the display name is too long or doesn't exist.
*/
memberHelper.updateDisplayName = async function (authorId, membername, displayname) {
mh.updateDisplayName = async function (authorId, membername, displayname) {
const trimmedName = displayname.trim();
if (trimmedName.length > 32) {
@@ -246,7 +249,7 @@ memberHelper.updateDisplayName = async function (authorId, membername, displayna
else if (trimmedName === '') {
throw new RangeError(`Display name ${enums.err.NO_VALUE}`);
}
return await memberHelper.updateMemberField(authorId, membername, "displayname", trimmedName);
return await mh.updateMemberField(authorId, membername, "displayname", trimmedName);
}
/**
@@ -258,11 +261,11 @@ memberHelper.updateDisplayName = async function (authorId, membername, displayna
* @param {string} proxy - The proxy to set
* @returns {Promise<string> } A successful update.
*/
memberHelper.updateProxy = async function (authorId, memberName, proxy) {
mh.updateProxy = async function (authorId, memberName, proxy) {
// Throws error if exists
await memberHelper.checkIfProxyExists(authorId, proxy);
await mh.checkIfProxyExists(authorId, proxy);
return await memberHelper.updateMemberField(authorId, memberName, "proxy", proxy);
return await mh.updateMemberField(authorId, memberName, "proxy", proxy);
}
/**
@@ -276,12 +279,12 @@ memberHelper.updateProxy = async function (authorId, memberName, proxy) {
* @param {string | null} attachmentExpiration - The attachment expiry date, if any
* @returns {Promise<string>} A successful update.
*/
memberHelper.updatePropic = async function (authorId, memberName, values, attachmentUrl = null, attachmentExpiration = null) {
mh.updatePropic = async function (authorId, memberName, values, attachmentUrl = null, attachmentExpiration = null) {
const imgUrl = values ?? attachmentUrl;
// Throws error if invalid
await utils.checkImageFormatValidity(imgUrl);
const expirationWarning = utils.setExpirationWarning(imgUrl, attachmentExpiration);
return await memberHelper.updateMemberField(authorId, memberName, "propic", imgUrl, expirationWarning);
return await mh.updateMemberField(authorId, memberName, "propic", imgUrl, attachmentExpiration);
}
/**
@@ -293,8 +296,13 @@ memberHelper.updatePropic = async function (authorId, memberName, values, attach
* @returns {Promise<string>} A successful removal.
* @throws {Error} When there is no member to remove.
*/
memberHelper.removeMember = async function (authorId, memberName) {
const destroyed = await memberRepo.removeMember(authorId, memberName);
mh.removeMember = async function (authorId, memberName) {
const destroyed = await database.members.destroy({
where: {
name: {[Op.iLike]: memberName},
userid: authorId
}
})
if (destroyed > 0) {
return `Member "${memberName}" has been deleted.`;
} else {
@@ -314,11 +322,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<{Members, string[]}>} A successful addition object, including errors if there are any.
* @returns {Promise<{model, 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 memberRepo.getMemberByName(authorId, memberName);
mh.addFullMember = async function (authorId, memberName, displayName = null, proxy = null, propic = null, attachmentExpiration = null) {
const existingMember = await mh.getMemberByName(authorId, memberName);
if (existingMember) {
throw new Error(`Can't add ${memberName}. ${enums.err.MEMBER_EXISTS}`);
}
@@ -348,7 +356,7 @@ memberHelper.addFullMember = async function (authorId, memberName, displayName =
let isValidProxy;
if (proxy && proxy.length > 0) {
try {
const proxyExists = await memberHelper.checkIfProxyExists(authorId, proxy);
const proxyExists = await mh.checkIfProxyExists(authorId, proxy);
isValidProxy = !proxyExists;
}
catch(e) {
@@ -357,22 +365,21 @@ memberHelper.addFullMember = async function (authorId, memberName, displayName =
}
}
let isValidPropic, expirationWarning;
let isValidPropic;
if (propic && propic.length > 0) {
try {
isValidPropic = await utils.checkImageFormatValidity(propic);
expirationWarning = utils.setExpirationWarning(propic, attachmentExpiration);
if (expirationWarning) {
errors.push(expirationWarning);
}
}
catch(e) {
errors.push(`Tried to set profile picture to \"${propic}\". ${e.message}. ${enums.err.SET_TO_NULL}`);
isValidPropic = false;
}
}
const member = await memberRepo.createMember({
if (isValidPropic && attachmentExpiration) {
errors.push(mh.setExpirationWarning(attachmentExpiration));
}
const member = await database.members.create({
name: memberName, userid: authorId, displayname: isValidDisplayName ? displayName : null, proxy: isValidProxy ? proxy : null, propic: isValidPropic ? propic : null
});
@@ -387,26 +394,51 @@ memberHelper.addFullMember = async function (authorId, memberName, displayName =
* @param {string} memberName - The member to update
* @param {string} columnName - The column name to update.
* @param {string} value - The value to update to.
* @param {string | null} [expirationWarning] - The attachment expiration warning (if any)
* @param {string | null} [attachmentExpiration] - The attachment expiration date (if any)
* @returns {Promise<string>} A successful update.
* @throws {Error} When no member row was updated.
*/
memberHelper.updateMemberField = async function (authorId, memberName, columnName, value, expirationWarning = null) {
const res = await memberRepo.updateMemberField(authorId, memberName, columnName, value);
if (res === 0) {
mh.updateMemberField = async function (authorId, memberName, columnName, value, attachmentExpiration = null) {
let fluxerPropicWarning;
// indicates that an attachment was uploaded on Fluxer directly
if (columnName === "propic" && attachmentExpiration) {
fluxerPropicWarning = mh.setExpirationWarning(value);
}
const res = await database.members.update({[columnName]: value}, {
where: {
name: {[Op.iLike]: memberName},
userid: authorId
}
})
if (res[0] === 0) {
throw new Error(`Can't update ${memberName}. ${enums.err.NO_MEMBER}.`);
} else {
return `Updated ${columnName} for ${memberName} to ${value}${expirationWarning ? `. ${expirationWarning}.` : '.'}`;
return `Updated ${columnName} for ${memberName} to ${value}${fluxerPropicWarning ?? ''}.`;
}
}
/**
* Sets the warning for an expiration date.
*
* @param {string} expirationString - An expiration date string.
* @returns {string} A description of the expiration, interpolating the expiration string.
*/
mh.setExpirationWarning = function (expirationString) {
let expirationDate = new Date(expirationString);
if (!isNaN(expirationDate.valueOf())) {
expirationDate = expirationDate.toDateString();
return `\n**NOTE:** Because this profile picture was uploaded via Fluxer, it will currently expire on *${expirationDate}*. To avoid this, upload the picture to another website like <https://imgbb.com/> and link to it directly`
}
}
/**
* Gets the details for a member.
*
* @param {{Member, string[]}} member - The member object
* @param {model} member - The member object
* @returns {EmbedBuilder} The member's info.
*/
memberHelper.getMemberInfo = function (member) {
mh.getMemberInfo = function (member) {
return new EmbedBuilder()
.setTitle(member.name)
.setDescription(`Details for ${member.name}`)
@@ -427,8 +459,8 @@ memberHelper.getMemberInfo = function (member) {
* @returns {Promise<EmbedBuilder>} The info for all members.
* @throws {Error} When there are no members for an author.
*/
memberHelper.getAllMembersInfo = async function (authorId, authorName) {
const members = await memberRepo.getMembersByAuthor(authorId);
mh.getAllMembersInfo = async function (authorId, authorName) {
const members = await mh.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,
@@ -438,6 +470,29 @@ 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.
*/
mh.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.
*/
mh.getMembersByAuthor = async function (authorId) {
return await database.members.findAll({where: {userid: authorId}});
}
/**
* Checks if proxy exists for a member.
*
@@ -446,12 +501,12 @@ memberHelper.getAllMembersInfo = async function (authorId, authorName) {
* @returns {Promise<boolean> } Whether the proxy exists.
* @throws {Error} When an empty proxy was provided, or no proxy exists.
*/
memberHelper.checkIfProxyExists = async function (authorId, proxy) {
mh.checkIfProxyExists = async function (authorId, proxy) {
const splitProxy = proxy.trim().split("text");
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 memberRepo.getMembersByAuthor(authorId);
const memberList = await mh.getMembersByAuthor(authorId);
const proxyExists = memberList.some(member => member.proxy === proxy);
if (proxyExists) {
throw new Error(enums.err.PROXY_EXISTS);
@@ -464,7 +519,7 @@ memberHelper.checkIfProxyExists = async function (authorId, proxy) {
*
* @returns {EmbedBuilder } An embed of member commands.
*/
memberHelper.getMemberCommandInfo = function() {
mh.getMemberCommandInfo = function() {
const fields = [
{name: `**new**`, value: enums.help.NEW, inline: false},
{name: `**remove**`, value: enums.help.REMOVE, inline: false},
@@ -481,4 +536,4 @@ memberHelper.getMemberCommandInfo = function() {
}
module.exports.memberHelper = memberHelper;
export const memberHelper = mh;

View File

@@ -1,4 +1,4 @@
const {memberRepo} = require('../repositories/memberRepo.js');
import {memberHelper} from "./memberHelper.js";
const msgh = {};
@@ -39,7 +39,7 @@ msgh.parseCommandArgs = function(content, commandName) {
* @returns {Promise<{model, string, bool}>} The proxy message object.
*/
msgh.parseProxyTags = async function (authorId, content, attachmentUrl = null){
const members = await memberRepo.getMembersByAuthor(authorId);
const members = await memberHelper.getMembersByAuthor(authorId);
// If an author has no members, no sense in searching for proxy
if (members.length === 0) {
return;
@@ -80,4 +80,4 @@ msgh.returnBufferFromText = function (text) {
return {text: text, file: undefined}
}
module.exports.messageHelper = msgh;
export const messageHelper = msgh;

View File

@@ -1,8 +1,8 @@
const {enums} = require('../enums');
import {enums} from '../enums.js'
const utils = {};
const u = {};
utils.debounce = function(func, delay) {
u.debounce = function(func, delay) {
let timeout = null;
return function (...args) {
clearTimeout(timeout);
@@ -18,7 +18,7 @@ utils.debounce = function(func, delay) {
* @returns {bool} - Whether the image is in a valid format
* @throws {Error} When loading the profile picture from a URL doesn't work, or it fails requirements.
*/
utils.checkImageFormatValidity = async function (imageUrl) {
u.checkImageFormatValidity = async function (imageUrl) {
const acceptableImages = ['image/png', 'image/jpg', 'image/jpeg', 'image/webp'];
let response, blobFile;
try {
@@ -34,24 +34,4 @@ utils.checkImageFormatValidity = async function (imageUrl) {
return true;
}
/**
* Sets the warning that a Fluxer-uploaded image will expire.
*
* @param {string | null} [imgUrl] - An image URL.
* @param {string | null} [expirationString] - An expiration date string.
* @returns {string | null} A description of the expiration, or null.
*/
utils.setExpirationWarning = function (imgUrl = null, expirationString = null) {
if (imgUrl && imgUrl.startsWith(enums.misc.FLUXER_ATTACHMENT_URL)) {
return enums.misc.ATTACHMENT_EXPIRATION_WARNING;
}
else if (expirationString) {
let expirationDate = new Date(expirationString);
if (!isNaN(expirationDate.valueOf())) {
return `${enums.misc.ATTACHMENT_EXPIRATION_WARNING}. Expiration date: *${expirationString}*.`;
}
}
return null;
}
module.exports.utils = utils;
export const utils = u;

View File

@@ -1,8 +1,8 @@
const {messageHelper} = require("./messageHelper.js");
const {Webhook, Channel, Message, Client} = require('@fluxerjs/core');
const {enums} = require("../enums.js");
import {messageHelper} from "./messageHelper.js";
import {Webhook, Channel, Message, Client} from '@fluxerjs/core';
import {enums} from "../enums.js";
const webhookHelper = {};
const wh = {};
const name = 'PluralFlux Proxy Webhook';
@@ -13,7 +13,7 @@ const name = 'PluralFlux Proxy Webhook';
* @param {Message} message - The full message object.
* @throws {Error} When the proxy message is not in a server.
*/
webhookHelper.sendMessageAsMember = async function(client, message) {
wh.sendMessageAsMember = async function(client, message) {
const attachmentUrl = message.attachments.size > 0 ? message.attachments.first().url : null;
const proxyMatch = await messageHelper.parseProxyTags(message.author.id, message.content, attachmentUrl);
// If the message doesn't match a proxy, just return.
@@ -27,7 +27,7 @@ webhookHelper.sendMessageAsMember = async function(client, message) {
if (proxyMatch.hasAttachment) {
return await message.reply(`${enums.misc.ATTACHMENT_SENT_BY} ${proxyMatch.member.displayname ?? proxyMatch.member.name}`)
}
await webhookHelper.replaceMessage(client, message, proxyMatch.message, proxyMatch.member);
await wh.replaceMessage(client, message, proxyMatch.message, proxyMatch.member);
}
/**
@@ -39,11 +39,11 @@ webhookHelper.sendMessageAsMember = async function(client, message) {
* @param {model} member - A member object from the database.
* @throws {Error} When there's no message to send.
*/
webhookHelper.replaceMessage = async function(client, message, text, member) {
wh.replaceMessage = async function(client, message, text, member) {
// attachment logic is not relevant yet, text length will always be over 0 right now
if (text.length > 0 || message.attachments.size > 0) {
const channel = client.channels.get(message.channelId);
const webhook = await webhookHelper.getOrCreateWebhook(client, channel);
const webhook = await wh.getOrCreateWebhook(client, channel);
const username = member.displayname ?? member.name;
if (text.length <= 2000) {
await webhook.send({content: text, username: username, avatar_url: member.propic})
@@ -68,10 +68,10 @@ webhookHelper.replaceMessage = async function(client, message, text, member) {
* @returns {Webhook} A webhook object.
* @throws {Error} When no webhooks are allowed in the channel.
*/
webhookHelper.getOrCreateWebhook = async function(client, channel) {
wh.getOrCreateWebhook = async function(client, channel) {
// If channel doesn't allow webhooks
if (!channel?.createWebhook) throw new Error(enums.err.NO_WEBHOOKS_ALLOWED);
let webhook = await webhookHelper.getWebhook(client, channel)
let webhook = await wh.getWebhook(client, channel)
if (!webhook) {
webhook = await channel.createWebhook({name: name});
}
@@ -85,7 +85,7 @@ webhookHelper.getOrCreateWebhook = async function(client, channel) {
* @param {Channel} channel - The channel the message was sent in.
* @returns {Webhook} A webhook object.
*/
webhookHelper.getWebhook = async function(client, channel) {
wh.getWebhook = async function(client, channel) {
const channelWebhooks = await channel?.fetchWebhooks() ?? [];
if (channelWebhooks.length === 0) {
return;
@@ -93,4 +93,4 @@ webhookHelper.getWebhook = async function(client, channel) {
return channelWebhooks.find((webhook) => webhook.name === name);
}
module.exports.webhookHelper = webhookHelper;
export const webhookHelper = wh;

View File

@@ -1,74 +0,0 @@
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({ 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 await members.save({
name: createObj.name, userid: createObj.userid, 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({
name: ILike(memberName),
userid: authorId
}, {[columnName]: value})
return updated.affected;
}
module.exports.memberRepo = memberRepo;

View File

@@ -56,15 +56,6 @@ 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");

View File

@@ -2,14 +2,16 @@ const {enums} = require('../../src/enums.js');
const {utils} = require("../../src/helpers/utils.js");
jest.mock('@fluxerjs/core', () => jest.fn());
jest.mock('../../src/repositories/memberRepo.js', () => {
jest.mock('../../src/database.js', () => {
return {
memberRepo: {
getMemberByName: jest.fn().mockResolvedValue(),
getMembersByAuthor: jest.fn().mockResolvedValue(),
removeMember: jest.fn().mockResolvedValue(),
createMember: jest.fn().mockResolvedValue(),
updateMemberField: jest.fn().mockResolvedValue(),
database: {
members: {
create: jest.fn().mockResolvedValue(),
update: jest.fn().mockResolvedValue(),
destroy: jest.fn().mockResolvedValue(),
findOne: jest.fn().mockResolvedValue(),
findAll: jest.fn().mockResolvedValue(),
}
}
}
});
@@ -23,8 +25,10 @@ jest.mock("../../src/helpers/utils.js", () => {
}
});
const {Op} = require('sequelize');
const {memberHelper} = require("../../src/helpers/memberHelper.js");
const {memberRepo} = require("../../src/repositories/memberRepo.js");
const {database} = require("../../src/database");
describe('MemberHelper', () => {
const authorId = "0001";
@@ -35,7 +39,7 @@ describe('MemberHelper', () => {
name: "somePerson",
displayname: "Some Person",
proxy: "--text",
propic: 'ono.png'
propic: attachmentUrl
}
beforeEach(() => {
@@ -266,29 +270,29 @@ describe('MemberHelper', () => {
['propic', `The profile picture for ${mockMember.name} is \"${mockMember.propic}\".`],
])('%s calls getMemberByName and returns value', async (command, expected) => {
// Arrange
memberRepo.getMemberByName.mockResolvedValue(mockMember);
jest.spyOn(memberHelper, 'getMemberByName').mockResolvedValue(mockMember);
// Act
const result = await memberHelper.sendCurrentValue(authorId, mockMember.name, command);
// Assert
expect(result).toEqual(expected);
expect(memberRepo.getMemberByName).toHaveBeenCalledTimes(1);
expect(memberRepo.getMemberByName).toHaveBeenCalledWith(authorId, mockMember.name);
expect(memberHelper.getMemberByName).toHaveBeenCalledTimes(1);
expect(memberHelper.getMemberByName).toHaveBeenCalledWith(authorId, mockMember.name);
})
test('returns error if no member found', async () => {
// Arrange
memberRepo.getMemberByName.mockResolvedValue(null);
jest.spyOn(memberHelper, 'getMemberByName').mockResolvedValue(null);
// Act
await expect(memberHelper.sendCurrentValue(authorId, mockMember.name, 'name')).rejects.toThrow(enums.err.NO_MEMBER);
// Assert
expect(memberRepo.getMemberByName).toHaveBeenCalledTimes(1);
expect(memberRepo.getMemberByName).toHaveBeenCalledWith(authorId, mockMember.name);
expect(memberHelper.getMemberByName).toHaveBeenCalledTimes(1);
expect(memberHelper.getMemberByName).toHaveBeenCalledWith(authorId, mockMember.name);
});
test('calls getMemberInfo with member if no command present', async () => {
// Arrange
memberRepo.getMemberByName.mockResolvedValue(mockMember);
jest.spyOn(memberHelper, 'getMemberByName').mockResolvedValue(mockMember);
jest.spyOn(memberHelper, 'getMemberInfo').mockResolvedValue('member info');
// Act
const result = await memberHelper.sendCurrentValue(authorId, mockMember.name, null);
@@ -305,13 +309,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}
memberRepo.getMemberByName.mockResolvedValue(empty);
jest.spyOn(memberHelper, 'getMemberByName').mockResolvedValue(empty);
// Act
const result = await memberHelper.sendCurrentValue(authorId, mockMember.name, command);
// Assert
expect(result).toEqual(expected);
expect(memberRepo.getMemberByName).toHaveBeenCalledTimes(1);
expect(memberRepo.getMemberByName).toHaveBeenCalledWith(authorId, mockMember.name);
expect(memberHelper.getMemberByName).toHaveBeenCalledTimes(1);
expect(memberHelper.getMemberByName).toHaveBeenCalledWith(authorId, mockMember.name);
})
})
@@ -442,13 +446,14 @@ describe('MemberHelper', () => {
describe('updatePropic', () => {
test.each([
[null, attachmentUrl, undefined, attachmentUrl],
[mockMember.propic, null, undefined, mockMember.propic],
[mockMember.propic, attachmentUrl, undefined, mockMember.propic],
[null, attachmentUrl, null, attachmentUrl],
[mockMember.propic, null, null, mockMember.propic],
[mockMember.propic, attachmentUrl, null, attachmentUrl],
[null, attachmentUrl, attachmentExpiration, attachmentUrl]
])('calls checkImageFormatValidity and updateMemberField and returns string', async (imgUrl, attachmentUrl, attachmentExpiration, expected) => {
// Arrange
jest.spyOn(memberHelper, 'updateMemberField').mockResolvedValue("Updated");
utils.setExpirationWarning = jest.fn().mockReturnValue(undefined);
// Act
const result = await memberHelper.updatePropic(authorId, mockMember.name, imgUrl, attachmentUrl, attachmentExpiration);
// Assert
@@ -456,57 +461,44 @@ describe('MemberHelper', () => {
expect(utils.checkImageFormatValidity).toHaveBeenCalledTimes(1);
expect(utils.checkImageFormatValidity).toHaveBeenCalledWith(expected);
expect(memberHelper.updateMemberField).toHaveBeenCalledTimes(1);
expect(memberHelper.updateMemberField).toHaveBeenCalledWith(authorId, mockMember.name, "propic", expected, undefined);
})
test('calls setExpirationWarning', async() => {
// Arrange
jest.spyOn(memberHelper, 'updateMemberField').mockResolvedValue("Updated");
utils.setExpirationWarning = jest.fn().mockReturnValue(enums.misc.ATTACHMENT_EXPIRATION_WARNING);
// Act
const result = await memberHelper.updatePropic(authorId, mockMember.name, null, attachmentUrl, attachmentExpiration);
// Assert
expect(result).toEqual("Updated");
expect(utils.setExpirationWarning).toHaveBeenCalledTimes(1);
expect(utils.setExpirationWarning).toHaveBeenCalledWith(attachmentUrl, attachmentExpiration);
expect(memberHelper.updateMemberField).toHaveBeenCalledTimes(1);
expect(memberHelper.updateMemberField).toHaveBeenCalledWith(authorId, mockMember.name, "propic", attachmentUrl, enums.misc.ATTACHMENT_EXPIRATION_WARNING);
expect(memberHelper.updateMemberField).toHaveBeenCalledWith(authorId, mockMember.name, "propic", expected, attachmentExpiration);
})
})
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(memberRepo.getMemberByName).toHaveBeenCalledWith(authorId, mockMember.name);
expect(memberRepo.getMemberByName).toHaveBeenCalledTimes(1);
expect(memberHelper.getMemberByName).toHaveBeenCalledWith(authorId, mockMember.name);
expect(memberHelper.getMemberByName).toHaveBeenCalledTimes(1);
})
test('if getMemberByName returns member, throw error', async () => {
// Arrange
memberRepo.getMemberByName.mockResolvedValue({name: mockMember.name});
memberHelper.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(memberRepo.createMember).not.toHaveBeenCalled();
expect(database.members.create).not.toHaveBeenCalled();
})
test('if name is not filled out, throw error', async () => {
// Arrange
memberRepo.getMemberByName.mockResolvedValue();
// Act
// Act & Assert
await expect(memberHelper.addFullMember(authorId, " ")).rejects.toThrow(`Name ${enums.err.NO_VALUE}. ${enums.err.NAME_REQUIRED}`);
// Assert
expect(memberRepo.createMember).not.toHaveBeenCalled();
expect(database.members.create).not.toHaveBeenCalled();
})
test('if displayname is over 32 characters, call memberRepo.createMember with null value', async () => {
test('if displayname is over 32 characters, call database.member.create with null value', async () => {
// Arrange
memberRepo.getMemberByName.mockResolvedValue();
memberHelper.getMemberByName.mockResolvedValue();
const tooLongDisplayName = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
const expectedMemberArgs = {
name: mockMember.name,
@@ -515,7 +507,7 @@ describe('MemberHelper', () => {
proxy: null,
propic: null
}
memberRepo.createMember = jest.fn().mockResolvedValue(expectedMemberArgs);
database.members.create = 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}`]
@@ -525,8 +517,8 @@ describe('MemberHelper', () => {
const res = await memberHelper.addFullMember(authorId, mockMember.name, tooLongDisplayName, null, null);
// Assert
expect(res).toEqual(expectedReturn);
expect(memberRepo.createMember).toHaveBeenCalledWith(expectedMemberArgs);
expect(memberRepo.createMember).toHaveBeenCalledTimes(1);
expect(database.members.create).toHaveBeenCalledWith(expectedMemberArgs);
expect(database.members.create).toHaveBeenCalledTimes(1);
})
test('if proxy, call checkIfProxyExists', async () => {
@@ -539,7 +531,7 @@ describe('MemberHelper', () => {
proxy: null,
propic: null
}
memberRepo.createMember = jest.fn().mockResolvedValue(expectedMemberArgs);
database.members.create = jest.fn().mockResolvedValue(expectedMemberArgs);
const expectedReturn = {member: expectedMemberArgs, errors: []}
// Act
@@ -548,8 +540,8 @@ describe('MemberHelper', () => {
expect(res).toEqual(expectedReturn);
expect(memberHelper.checkIfProxyExists).toHaveBeenCalledWith(authorId, mockMember.proxy);
expect(memberHelper.checkIfProxyExists).toHaveBeenCalledTimes(1);
expect(memberRepo.createMember).toHaveBeenCalledWith(expectedMemberArgs);
expect(memberRepo.createMember).toHaveBeenCalledTimes(1);
expect(database.members.create).toHaveBeenCalledWith(expectedMemberArgs);
expect(database.members.create).toHaveBeenCalledTimes(1);
})
test('if checkProxyExists throws error, call database.member.create with null value', async () => {
@@ -562,7 +554,7 @@ describe('MemberHelper', () => {
proxy: null,
propic: null
}
memberRepo.createMember = jest.fn().mockResolvedValue(expectedMemberArgs);
database.members.create = jest.fn().mockResolvedValue(expectedMemberArgs);
const expectedReturn = {
member: expectedMemberArgs,
errors: [`Tried to set proxy to \"${mockMember.proxy}\". error. ${enums.err.SET_TO_NULL}`]
@@ -572,8 +564,8 @@ describe('MemberHelper', () => {
const res = await memberHelper.addFullMember(authorId, mockMember.name, null, mockMember.proxy, null)
// Assert
expect(res).toEqual(expectedReturn);
expect(memberRepo.createMember).toHaveBeenCalledWith(expectedMemberArgs);
expect(memberRepo.createMember).toHaveBeenCalledTimes(1);
expect(database.members.create).toHaveBeenCalledWith(expectedMemberArgs);
expect(database.members.create).toHaveBeenCalledTimes(1);
})
test('if propic, call checkImageFormatValidity', async () => {
@@ -585,8 +577,7 @@ describe('MemberHelper', () => {
proxy: null,
propic: null
}
utils.setExpirationWarning = jest.fn().mockReturnValue();
memberRepo.createMember = jest.fn().mockResolvedValue(expectedMemberArgs);
database.members.create = jest.fn().mockResolvedValue(expectedMemberArgs);
const expectedReturn = {member: expectedMemberArgs, errors: []}
// Act
const res = await memberHelper.addFullMember(authorId, mockMember.name, null, null, mockMember.propic);
@@ -594,8 +585,8 @@ describe('MemberHelper', () => {
expect(res).toEqual(expectedReturn);
expect(utils.checkImageFormatValidity).toHaveBeenCalledWith(mockMember.propic);
expect(utils.checkImageFormatValidity).toHaveBeenCalledTimes(1);
expect(memberRepo.createMember).toHaveBeenCalledWith(expectedMemberArgs);
expect(memberRepo.createMember).toHaveBeenCalledTimes(1);
expect(database.members.create).toHaveBeenCalledWith(expectedMemberArgs);
expect(database.members.create).toHaveBeenCalledTimes(1);
})
test('if checkImageFormatValidity throws error, call database.member.create with null value', async () => {
@@ -608,7 +599,7 @@ describe('MemberHelper', () => {
proxy: null,
propic: null
}
memberRepo.createMember = jest.fn().mockResolvedValue(expectedMemberArgs);
database.members.create = jest.fn().mockResolvedValue(expectedMemberArgs);
const expectedReturn = {
member: expectedMemberArgs,
errors: [`Tried to set profile picture to \"${mockMember.propic}\". error. ${enums.err.SET_TO_NULL}`]
@@ -617,19 +608,8 @@ describe('MemberHelper', () => {
const res = await memberHelper.addFullMember(authorId, mockMember.name, null, null, mockMember.propic);
// Assert
expect(res).toEqual(expectedReturn);
expect(memberRepo.createMember).toHaveBeenCalledWith(expectedMemberArgs);
expect(memberRepo.createMember).toHaveBeenCalledTimes(1);
})
test('calls setExpirationWarning if attachmentExpiration exists', async () => {
// Arrange
utils.checkImageFormatValidity = jest.fn().mockResolvedValue(true);
utils.setExpirationWarning = jest.fn().mockReturnValue(`${enums.misc.ATTACHMENT_EXPIRATION_WARNING}`);
// Act
await memberHelper.addFullMember(authorId, mockMember.name, null, null, mockMember.propic, attachmentExpiration)
// Assert
expect(utils.setExpirationWarning).toHaveBeenCalledTimes(1);
expect(utils.setExpirationWarning).toHaveBeenCalledWith(mockMember.propic, attachmentExpiration);
expect(database.members.create).toHaveBeenCalledWith(expectedMemberArgs);
expect(database.members.create).toHaveBeenCalledTimes(1);
})
test('if all values are valid, call database.members.create', async () => {
@@ -642,45 +622,58 @@ describe('MemberHelper', () => {
proxy: mockMember.proxy,
propic: mockMember.propic
}
memberRepo.createMember = jest.fn().mockResolvedValue(expectedMemberArgs);
database.members.create = jest.fn().mockResolvedValue(expectedMemberArgs);
utils.checkImageFormatValidity = jest.fn().mockResolvedValue(true);
utils.setExpirationWarning = jest.fn().mockReturnValue();
const expectedReturn = {member: expectedMemberArgs, errors: []}
// Act
const res = await memberHelper.addFullMember(authorId, mockMember.name, mockMember.displayname, mockMember.proxy, mockMember.propic);
// Assert
expect(res).toEqual(expectedReturn);
expect(memberRepo.createMember).toHaveBeenCalledWith(expectedMemberArgs);
expect(memberRepo.createMember).toHaveBeenCalledTimes(1);
expect(database.members.create).toHaveBeenCalledWith(expectedMemberArgs);
expect(database.members.create).toHaveBeenCalledTimes(1);
})
})
describe('updateMemberField', () => {
const {database} = require('../../src/database.js');
beforeEach(() => {
utils.setExpirationWarning = jest.fn().mockReturnValue(`warning`);
memberRepo.updateMemberField = jest.fn().mockResolvedValue([1]);
jest.spyOn(memberHelper, "setExpirationWarning").mockReturnValue(' warning');
database.members = {
update: jest.fn().mockResolvedValue([1])
};
})
test('calls setExpirationWarning if attachmentExpiration', async () => {
await memberHelper.updateMemberField(authorId, mockMember.name, "propic", mockMember.propic, attachmentExpiration)
expect(memberHelper.setExpirationWarning).toHaveBeenCalledTimes(1);
expect(memberHelper.setExpirationWarning).toHaveBeenCalledWith(mockMember.propic);
})
test.each([
['name', mockMember.name, undefined, `Updated name for ${mockMember.name} to ${mockMember.name}.`],
['displayname', mockMember.displayname, undefined, `Updated displayname for ${mockMember.name} to ${mockMember.displayname}.`],
['proxy', mockMember.proxy, undefined, `Updated proxy for ${mockMember.name} to ${mockMember.proxy}.`],
['propic', mockMember.propic, undefined, `Updated propic for ${mockMember.name} to ${mockMember.propic}.`],
['propic', mockMember.propic,
'warning', `Updated propic for ${mockMember.name} to ${mockMember.propic}. warning.`]
])('calls database.members.update with correct column and value and return string', async (columnName, value, attachmentExpiration, expected) => {
['name', mockMember.name, null, `Updated name for ${mockMember.name} to ${mockMember.name}`],
['displayname', mockMember.displayname, null, `Updated name for ${mockMember.name} to ${mockMember.displayname}`],
['proxy', mockMember.proxy, null, `Updated name for ${mockMember.name} to ${mockMember.proxy}`],
['propic', mockMember.propic, null, `Updated name for ${mockMember.name} to ${mockMember.propic}`],
['propic', mockMember.propic, attachmentExpiration, `Updated name for ${mockMember.name} to ${mockMember.propic} warning}`]
])('calls database.members.update with correct column and value and return string', async (columnName, value, attachmentExpiration) => {
// Act
const res = await memberHelper.updateMemberField(authorId, mockMember.name, columnName, value, attachmentExpiration)
await memberHelper.updateMemberField(authorId, mockMember.name, columnName, value, attachmentExpiration)
// Assert
expect(res).toEqual(expected);
expect(memberRepo.updateMemberField).toHaveBeenCalledTimes(1);
expect(memberRepo.updateMemberField).toHaveBeenCalledWith(authorId, mockMember.name, columnName, value)
expect(database.members.update).toHaveBeenCalledTimes(1);
expect(database.members.update).toHaveBeenCalledWith({[columnName]: value}, {
where: {
name: {[Op.iLike]: mockMember.name},
userid: authorId
}
})
})
test('if database.members.update returns 0 rows changed, throw error', async () => {
// Arrange
memberRepo.updateMemberField = jest.fn().mockResolvedValue(0);
database.members = {
update: 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}.`);
})
@@ -689,7 +682,7 @@ describe('MemberHelper', () => {
describe('checkIfProxyExists', () => {
beforeEach(() => {
memberRepo.getMembersByAuthor.mockResolvedValue([mockMember]);
jest.spyOn(memberHelper, "getMembersByAuthor").mockResolvedValue([mockMember]);
})
test.each([
@@ -707,8 +700,8 @@ describe('MemberHelper', () => {
const res = await memberHelper.checkIfProxyExists(authorId, proxy)
// Assert
expect(res).toEqual(false)
expect(memberRepo.getMembersByAuthor).toHaveBeenCalledTimes(1);
expect(memberRepo.getMembersByAuthor).toHaveBeenCalledWith(authorId);
expect(memberHelper.getMembersByAuthor).toHaveBeenCalledTimes(1);
expect(memberHelper.getMembersByAuthor).toHaveBeenCalledWith(authorId);
})
test.each([
@@ -719,13 +712,13 @@ describe('MemberHelper', () => {
// Act & Assert
await expect(memberHelper.checkIfProxyExists(authorId, proxy)).rejects.toThrow(error);
expect(memberRepo.getMembersByAuthor).not.toHaveBeenCalled();
expect(memberHelper.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(memberRepo.getMembersByAuthor).toHaveBeenCalledTimes(1);
expect(memberRepo.getMembersByAuthor).toHaveBeenCalledWith(authorId);
expect(memberHelper.getMembersByAuthor).toHaveBeenCalledTimes(1);
expect(memberHelper.getMembersByAuthor).toHaveBeenCalledWith(authorId);
})
})

View File

@@ -2,16 +2,16 @@ const env = require('dotenv');
env.config();
jest.mock('../../src/repositories/memberRepo.js', () => {
jest.mock('../../src/helpers/memberHelper.js', () => {
return {
memberRepo: {
memberHelper: {
getMembersByAuthor: jest.fn()
}
}
})
const {memberHelper} = require("../../src/helpers/memberHelper.js");
const {messageHelper} = require("../../src/helpers/messageHelper.js");
const {memberRepo} = require("../../src/repositories/memberRepo");
describe('messageHelper', () => {
@@ -54,7 +54,7 @@ describe('messageHelper', () => {
const attachmentUrl = "../oya.png"
beforeEach(() => {
memberRepo.getMembersByAuthor = jest.fn().mockImplementation((specificAuthorId) => {
memberHelper.getMembersByAuthor = jest.fn().mockImplementation((specificAuthorId) => {
if (specificAuthorId === "1") return membersFor1;
if (specificAuthorId === "2") return membersFor2;
if (specificAuthorId === "3") return membersFor3;

View File

@@ -5,7 +5,6 @@ const {utils} = require("../../src/helpers/utils.js");
describe('utils', () => {
const attachmentUrl = 'oya.png';
const expirationString = new Date("2026-01-01").toDateString();
let blob;
beforeEach(() => {
@@ -61,28 +60,6 @@ describe('utils', () => {
})
})
describe('setExpirationWarning', () => {
test('sets warning if image Url starts with Fluxer host', () => {
// Act
const result = utils.setExpirationWarning(`${enums.misc.FLUXER_ATTACHMENT_URL}${attachmentUrl}`);
// Assert
expect(result).toEqual(enums.misc.ATTACHMENT_EXPIRATION_WARNING);
})
test('sets warning if expiration string exists', () => {
const result = utils.setExpirationWarning(null, expirationString);
// Assert
expect(result).toEqual(`${enums.misc.ATTACHMENT_EXPIRATION_WARNING}. Expiration date: *${expirationString}*.`);
})
test('returns null if img url does not start iwth fluxer host and no expiration', () => {
// Act
const result = utils.setExpirationWarning(attachmentUrl);
// Assert
expect(result).toBeNull();
})
})
afterEach(() => {
// restore the spy created with spyOn
jest.restoreAllMocks();

View File

@@ -1,16 +0,0 @@
{
"compilerOptions": {
"lib": [
"es2021"
],
"target": "es2021",
"module": "commonjs",
"moduleResolution": "node",
"outDir": "./database/build",
"rootDir": "./database",
"esModuleInterop": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"sourceMap": true
}
}

View File

@@ -1,7 +0,0 @@
FLUXER_BOT_TOKEN=<>
POSTGRES_PASSWORD=<>
POSTGRES_ENDPOINT=postgres
PGADMIN_DEFAULT_EMAIL: <>
PGADMIN_DEFAULT_PASSWORD: <>
PGADMIN_CONFIG_SERVER_MODE: 'False'
PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED: 'False'