forked from PluralFlux/PluralFlux
Compare commits
29 Commits
refactor-a
...
sync-on-de
| Author | SHA1 | Date | |
|---|---|---|---|
| 8ec327a149 | |||
| ce57d15b29 | |||
| 2114362dbb | |||
| d1c1754212 | |||
| 7d438b1492 | |||
| c16b397cfa | |||
| e862d7c178 | |||
| e40c1266c2 | |||
| b8e155bcc5 | |||
| 2551ab4343 | |||
| 135962267d | |||
| 24802f1b75 | |||
| dbdb9fc38c | |||
| a3e0aa73b4 | |||
| 690344934b | |||
| bf4f55c91b | |||
| df80eca0ec | |||
| b3566010a2 | |||
| 0eee2988ce | |||
| a86260cc4a | |||
| 506e3ef9dd | |||
| 6a33fb592a | |||
| 2f255cefd1 | |||
| c54016de77 | |||
| 31da15eaeb | |||
| 43f4302dbc | |||
| af3da44946 | |||
| af57e2e6a3 | |||
| 77191566e3 |
46
.gitea/workflows/build-dev.yml
Normal file
46
.gitea/workflows/build-dev.yml
Normal file
@@ -0,0 +1,46 @@
|
||||
name: Build Dev instance
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["develop", "Develop"]
|
||||
pull_request:
|
||||
branches: ["develop", "Develop"]
|
||||
|
||||
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 up -d pluralflux-dev
|
||||
46
.gitea/workflows/build-main.yml
Normal file
46
.gitea/workflows/build-main.yml
Normal file
@@ -0,0 +1,46 @@
|
||||
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 up -d pluralflux-prod
|
||||
26
.gitea/workflows/sync-from-mirror.yaml
Normal file
26
.gitea/workflows/sync-from-mirror.yaml
Normal file
@@ -0,0 +1,26 @@
|
||||
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
|
||||
63
compose.yaml
63
compose.yaml
@@ -1,21 +1,34 @@
|
||||
services:
|
||||
main:
|
||||
build: .
|
||||
container_name: pluralflux
|
||||
pluralflux-dev:
|
||||
image: engineering.sanya.gay/pluralflux/pluralflux-dev:latest
|
||||
container_name: pluralflux-dev
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- pluralflux-dev-net
|
||||
env_file: "variables.env"
|
||||
pluralflux-prod:
|
||||
image: engineering.sanya.gay/pluralflux/pluralflux:latest
|
||||
container_name: pluralflux-prod
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- pluralflux-net
|
||||
postgres:
|
||||
env_file: "variables-prod.env"
|
||||
postgres-dev:
|
||||
image: postgres:latest
|
||||
container_name: pluralflux-postgres
|
||||
environment:
|
||||
POSTGRES_PASSWORD_FILE: /run/secrets/postgres_pwd
|
||||
secrets:
|
||||
- postgres_pwd
|
||||
container_name: pluralflux-dev-postgres
|
||||
env_file: "variables.env"
|
||||
volumes:
|
||||
- pgdataDev:/var/lib/postgresql
|
||||
- ./pgBackup:/mnt/pgBackup
|
||||
networks:
|
||||
- pluralflux-dev-net
|
||||
postgres-prod:
|
||||
image: postgres:latest
|
||||
container_name: pluralflux-prod-postgres
|
||||
env_file: "variables-prod.env"
|
||||
volumes:
|
||||
- pgdata:/var/lib/postgresql
|
||||
ports:
|
||||
- "5432:5432"
|
||||
- ./pgBackup/prod/:/mnt/pgBackup/prod
|
||||
networks:
|
||||
- pluralflux-net
|
||||
pgadmin:
|
||||
@@ -23,24 +36,24 @@ services:
|
||||
container_name: pluralflux-pgadmin
|
||||
ports:
|
||||
- "5050:80"
|
||||
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
|
||||
env_file: "variables.env"
|
||||
depends_on:
|
||||
- postgres
|
||||
- postgres-dev
|
||||
- postgres-prod
|
||||
networks:
|
||||
- pluralflux-net
|
||||
|
||||
- pluralflux-dev-net
|
||||
volumes:
|
||||
- pgadmindata:/var/lib/pgadmin
|
||||
- ./pgBackup:/mnt/host
|
||||
# uncomment the above line if you plan to restore / backup dump files from PGAdmin UI
|
||||
networks:
|
||||
pluralflux-net:
|
||||
driver: bridge
|
||||
pluralflux-dev-net:
|
||||
driver: bridge
|
||||
|
||||
volumes:
|
||||
pgdata:
|
||||
|
||||
secrets:
|
||||
postgres_pwd:
|
||||
file: ./secrets/postgres-password.txt
|
||||
pgdataDev:
|
||||
pgadmindata:
|
||||
pgdata:
|
||||
6
secrets.env
Normal file
6
secrets.env
Normal file
@@ -0,0 +1,6 @@
|
||||
FLUXER_BOT_TOKEN=<your bot token here>
|
||||
POSTGRES_PASSWORD=<your postgres password here>
|
||||
PGADMIN_DEFAULT_EMAIL: <default postgres admin login>
|
||||
PGADMIN_DEFAULT_PASSWORD: <your postgres password here>
|
||||
PGADMIN_CONFIG_SERVER_MODE: 'False'
|
||||
PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED: 'False'
|
||||
@@ -44,7 +44,10 @@ helperEnums.help = {
|
||||
}
|
||||
|
||||
helperEnums.misc = {
|
||||
ATTACHMENT_SENT_BY: "Attachment sent by:"
|
||||
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/"
|
||||
|
||||
}
|
||||
|
||||
export const enums = helperEnums;
|
||||
@@ -283,8 +283,8 @@ mh.updatePropic = async function (authorId, memberName, values, attachmentUrl =
|
||||
const imgUrl = values ?? attachmentUrl;
|
||||
// Throws error if invalid
|
||||
await utils.checkImageFormatValidity(imgUrl);
|
||||
|
||||
return await mh.updateMemberField(authorId, memberName, "propic", imgUrl, attachmentExpiration);
|
||||
const expirationWarning = utils.setExpirationWarning(imgUrl, attachmentExpiration);
|
||||
return await mh.updateMemberField(authorId, memberName, "propic", imgUrl, expirationWarning);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -369,15 +369,15 @@ mh.addFullMember = async function (authorId, memberName, displayName = null, pro
|
||||
if (propic && propic.length > 0) {
|
||||
try {
|
||||
isValidPropic = await utils.checkImageFormatValidity(propic);
|
||||
|
||||
}
|
||||
catch(e) {
|
||||
errors.push(`Tried to set profile picture to \"${propic}\". ${e.message}. ${enums.err.SET_TO_NULL}`);
|
||||
isValidPropic = false;
|
||||
}
|
||||
}
|
||||
if (isValidPropic && attachmentExpiration) {
|
||||
errors.push(mh.setExpirationWarning(attachmentExpiration));
|
||||
const expirationWarning = utils.setExpirationWarning(propic, attachmentExpiration);
|
||||
if (expirationWarning) {
|
||||
errors.push(expirationWarning);
|
||||
}
|
||||
const member = await database.members.create({
|
||||
name: memberName, userid: authorId, displayname: isValidDisplayName ? displayName : null, proxy: isValidProxy ? proxy : null, propic: isValidPropic ? propic : null
|
||||
@@ -394,17 +394,11 @@ mh.addFullMember = async function (authorId, memberName, displayName = null, pro
|
||||
* @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} [attachmentExpiration] - The attachment expiration date (if any)
|
||||
* @param {string | null} [expirationWarning] - The attachment expiration warning (if any)
|
||||
* @returns {Promise<string>} A successful update.
|
||||
* @throws {Error} When no member row was updated.
|
||||
*/
|
||||
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);
|
||||
}
|
||||
mh.updateMemberField = async function (authorId, memberName, columnName, value, expirationWarning = null) {
|
||||
const res = await database.members.update({[columnName]: value}, {
|
||||
where: {
|
||||
name: {[Op.iLike]: memberName},
|
||||
@@ -414,21 +408,7 @@ mh.updateMemberField = async function (authorId, memberName, columnName, value,
|
||||
if (res[0] === 0) {
|
||||
throw new Error(`Can't update ${memberName}. ${enums.err.NO_MEMBER}.`);
|
||||
} else {
|
||||
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`
|
||||
return `Updated ${columnName} for ${memberName} to ${value}${expirationWarning ? `. ${expirationWarning}.` : '.'}`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -34,4 +34,24 @@ u.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.
|
||||
*/
|
||||
u.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;
|
||||
}
|
||||
|
||||
export const utils = u;
|
||||
|
||||
@@ -39,7 +39,7 @@ describe('MemberHelper', () => {
|
||||
name: "somePerson",
|
||||
displayname: "Some Person",
|
||||
proxy: "--text",
|
||||
propic: attachmentUrl
|
||||
propic: 'ono.png'
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -446,14 +446,13 @@ describe('MemberHelper', () => {
|
||||
|
||||
describe('updatePropic', () => {
|
||||
test.each([
|
||||
[null, attachmentUrl, null, attachmentUrl],
|
||||
[mockMember.propic, null, null, mockMember.propic],
|
||||
[mockMember.propic, attachmentUrl, null, attachmentUrl],
|
||||
[null, attachmentUrl, attachmentExpiration, attachmentUrl]
|
||||
[null, attachmentUrl, undefined, attachmentUrl],
|
||||
[mockMember.propic, null, undefined, mockMember.propic],
|
||||
[mockMember.propic, attachmentUrl, undefined, mockMember.propic],
|
||||
])('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
|
||||
@@ -461,7 +460,21 @@ 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, attachmentExpiration);
|
||||
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);
|
||||
})
|
||||
})
|
||||
|
||||
@@ -612,6 +625,17 @@ describe('MemberHelper', () => {
|
||||
expect(database.members.create).toHaveBeenCalledTimes(1);
|
||||
})
|
||||
|
||||
test('calls setExpirationWarning if attachmentExpiration exists', async () => {
|
||||
// Arrange
|
||||
utils.checkImageFormatValidity = jest.fn().mockResolvedValue(true);
|
||||
jest.spyOn(memberHelper, 'setExpirationWarning').mockReturnValue(`${enums.misc.ATTACHMENT_EXPIRATION_WARNING}`);
|
||||
// Act
|
||||
await memberHelper.addFullMember(authorId, mockMember.name, null, null, mockMember.propic, attachmentExpiration)
|
||||
// Assert
|
||||
expect(memberHelper.setExpirationWarning).toHaveBeenCalledTimes(1);
|
||||
expect(memberHelper.setExpirationWarning).toHaveBeenCalledWith(mockMember.propic, attachmentExpiration);
|
||||
})
|
||||
|
||||
test('if all values are valid, call database.members.create', async () => {
|
||||
// Arrange
|
||||
jest.spyOn(memberHelper, 'checkIfProxyExists').mockResolvedValue(false);
|
||||
@@ -644,22 +668,18 @@ describe('MemberHelper', () => {
|
||||
};
|
||||
})
|
||||
|
||||
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, 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) => {
|
||||
['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) => {
|
||||
// Act
|
||||
await memberHelper.updateMemberField(authorId, mockMember.name, columnName, value, attachmentExpiration)
|
||||
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: {
|
||||
|
||||
@@ -5,6 +5,7 @@ const {utils} = require("../../src/helpers/utils.js");
|
||||
describe('utils', () => {
|
||||
|
||||
const attachmentUrl = 'oya.png';
|
||||
const expirationString = new Date("2026-01-01").toDateString();
|
||||
let blob;
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -60,6 +61,28 @@ 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();
|
||||
|
||||
Reference in New Issue
Block a user