Skip to content

Commit 42c5fa5

Browse files
authored
Merge pull request #475 from CyferShepard/unstable
Release V1.1.7
2 parents 54185bb + f1a214c commit 42c5fa5

68 files changed

Lines changed: 4573 additions & 686 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/docker-image.yml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,21 @@ jobs:
3939
username: ${{ secrets.DOCKER_USERNAME }}
4040
password: ${{ secrets.DOCKER_TOKEN }}
4141

42+
- name: Log in to GitHub Container Registry
43+
uses: docker/login-action@v3
44+
with:
45+
registry: ghcr.io
46+
username: ${{ github.actor }}
47+
password: ${{ secrets.GITHUB_TOKEN }}
48+
4249
- name: Build Docker image
4350
uses: docker/build-push-action@v5
4451
with:
4552
context: .
4653
push: true
47-
tags: ${{ steps.meta.outputs.tags }}
54+
tags: |
55+
${{ steps.meta.outputs.tags }}
56+
ghcr.io/${{ steps.meta.outputs.tags }}
4857
platforms: linux/amd64,linux/arm64,linux/arm/v7
4958
cache-from: type=gha
5059
cache-to: type=gha,mode=max

.github/workflows/docker-latest.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,13 @@ jobs:
4444
with:
4545
username: ${{ secrets.DOCKER_USERNAME }}
4646
password: ${{ secrets.DOCKER_TOKEN }}
47+
48+
- name: Log in to GitHub Container Registry
49+
uses: docker/login-action@v3
50+
with:
51+
registry: ghcr.io
52+
username: ${{ github.actor }}
53+
password: ${{ secrets.GITHUB_TOKEN }}
4754

4855
- name: Build Docker image
4956
uses: docker/build-push-action@v5
@@ -53,4 +60,6 @@ jobs:
5360
tags: |
5461
cyfershepard/jellystat:latest
5562
cyfershepard/jellystat:${{ env.VERSION }}
63+
ghcr.io/cyfershepard/jellystat:latest
64+
ghcr.io/cyfershepard/jellystat:${{ env.VERSION }}
5665
platforms: linux/amd64,linux/arm64,linux/arm/v7

Dockerfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# Stage 1: Build the application
2-
FROM node:slim AS builder
2+
FROM node:22.21.1-bookworm-slim AS builder
33

44
WORKDIR /app
55

@@ -14,7 +14,7 @@ COPY entry.sh ./
1414
RUN npm run build
1515

1616
# Stage 2: Create the production image
17-
FROM node:slim
17+
FROM node:22.21.1-bookworm-slim
1818

1919
RUN apt-get update && \
2020
apt-get install -yqq --no-install-recommends wget && \

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
| POSTGRES_PASSWORD `REQUIRED` | `null` | `postgres` | Password that will be used in postgres database |
3131
| POSTGRES_IP `REQUIRED` | `null` | `jellystat-db` or `192.168.0.5` | Hostname/IP of postgres instance |
3232
| POSTGRES_PORT `REQUIRED` | `null` | `5432` | Port Postgres is running on |
33+
| POSTGRES_SSL_ENABLED | `null` | `true` | Enable SSL connections to Postgres
34+
| POSTGRES_SSL_REJECT_UNAUTHORIZED | `null` | `false` | Verify Postgres SSL certificates when POSTGRES_SSL_ENABLED=true
3335
| JS_LISTEN_IP | `0.0.0.0`| `0.0.0.0` or `::` | Enable listening on specific IP or `::` for IPv6 |
3436
| JWT_SECRET `REQUIRED` | `null` | `my-secret-jwt-key` | JWT Key to be used to encrypt JWT tokens for authentication |
3537
| TZ `REQUIRED` | `null` | `Etc/UTC` | Server timezone (Can be found at https://en.wikipedia.org/wiki/List_of_tz_database_time_zones#List) |

backend/classes/backup.js

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ const fs = require("fs");
33
const path = require("path");
44
const configClass = require("./config");
55

6-
const moment = require("moment");
6+
const dayjs = require("dayjs");
77
const Logging = require("./logging");
88

99
const taskstate = require("../logging/taskstate");
@@ -34,7 +34,7 @@ async function backup(refLog) {
3434
if (config.error) {
3535
refLog.logData.push({ color: "red", Message: "Backup Failed: Failed to get config" });
3636
refLog.logData.push({ color: "red", Message: "Backup Failed with errors" });
37-
Logging.updateLog(refLog.uuid, refLog.logData, taskstate.FAILED);
37+
await Logging.updateLog(refLog.uuid, refLog.logData, taskstate.FAILED);
3838
return;
3939
}
4040

@@ -50,7 +50,7 @@ async function backup(refLog) {
5050
// Get data from each table and append it to the backup file
5151

5252
try {
53-
let now = moment();
53+
let now = dayjs();
5454
const backuppath = "./" + backupfolder;
5555

5656
if (!fs.existsSync(backuppath)) {
@@ -61,7 +61,7 @@ async function backup(refLog) {
6161
console.error("No write permissions for the folder:", backuppath);
6262
refLog.logData.push({ color: "red", Message: "Backup Failed: No write permissions for the folder: " + backuppath });
6363
refLog.logData.push({ color: "red", Message: "Backup Failed with errors" });
64-
Logging.updateLog(refLog.uuid, refLog.logData, taskstate.FAILED);
64+
await Logging.updateLog(refLog.uuid, refLog.logData, taskstate.FAILED);
6565
await pool.end();
6666
return;
6767
}
@@ -73,18 +73,18 @@ async function backup(refLog) {
7373
if (filteredTables.length === 0) {
7474
refLog.logData.push({ color: "red", Message: "Backup Failed: No tables to backup" });
7575
refLog.logData.push({ color: "red", Message: "Backup Failed with errors" });
76-
Logging.updateLog(refLog.uuid, refLog.logData, taskstate.FAILED);
76+
await Logging.updateLog(refLog.uuid, refLog.logData, taskstate.FAILED);
7777
await pool.end();
7878
return;
7979
}
8080

81-
// const backupPath = `../backup-data/backup_${now.format('yyyy-MM-DD HH-mm-ss')}.json`;
82-
const directoryPath = path.join(__dirname, "..", backupfolder, `backup_${now.format("yyyy-MM-DD HH-mm-ss")}.json`);
81+
// const backupPath = `../backup-data/backup_${now.format('YYYY-MM-DD HH-mm-ss')}.json`;
82+
const directoryPath = path.join(__dirname, "..", backupfolder, `backup_${now.format("YYYY-MM-DD HH-mm-ss")}.json`);
8383
refLog.logData.push({ color: "yellow", Message: "Begin Backup " + directoryPath });
8484
const stream = fs.createWriteStream(directoryPath, { flags: "a" });
85-
stream.on("error", (error) => {
85+
stream.on("error", async (error) => {
8686
refLog.logData.push({ color: "red", Message: "Backup Failed: " + error });
87-
Logging.updateLog(refLog.uuid, refLog.logData, taskstate.FAILED);
87+
await Logging.updateLog(refLog.uuid, refLog.logData, taskstate.FAILED);
8888
return;
8989
});
9090
const backup_data = [];
@@ -152,7 +152,7 @@ async function backup(refLog) {
152152
} catch (error) {
153153
console.log(error);
154154
refLog.logData.push({ color: "red", Message: "Backup Failed: " + error });
155-
Logging.updateLog(refLog.uuid, refLog.logData, taskstate.FAILED);
155+
await Logging.updateLog(refLog.uuid, refLog.logData, taskstate.FAILED);
156156
}
157157

158158
await pool.end();

backend/classes/config.js

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,17 @@ class Config {
1515
return { state: 0, error: "Config Details Not Found" };
1616
}
1717

18+
const _config = config[0];
19+
1820
return {
19-
JF_HOST: process.env.JF_HOST ?? config[0].JF_HOST,
20-
JF_API_KEY: process.env.JF_API_KEY ?? config[0].JF_API_KEY,
21-
APP_USER: config[0].APP_USER,
22-
APP_PASSWORD: config[0].APP_PASSWORD,
23-
REQUIRE_LOGIN: config[0].REQUIRE_LOGIN,
24-
settings: config[0].settings,
25-
api_keys: config[0].api_keys,
21+
JF_HOST: process.env.JF_HOST ?? _config.JF_HOST,
22+
JF_EXTERNAL_HOST: _config.settings?.EXTERNAL_URL,
23+
JF_API_KEY: process.env.JF_API_KEY ?? _config.JF_API_KEY,
24+
APP_USER: _config.APP_USER,
25+
APP_PASSWORD: _config.APP_PASSWORD,
26+
REQUIRE_LOGIN: _config.REQUIRE_LOGIN,
27+
settings: _config.settings,
28+
api_keys: _config.api_keys,
2629
state: state,
2730
IS_JELLYFIN: (process.env.IS_EMBY_API || "false").toLowerCase() === "false",
2831
};

backend/classes/db-helper.js

Lines changed: 120 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ function wrapField(field) {
1414
field.includes("AVG") ||
1515
field.includes("DISTINCT") ||
1616
field.includes("json_agg") ||
17-
field.includes("CASE")
17+
field.includes("CASE") ||
18+
field.includes("REGEXP_REPLACE")
1819
) {
1920
return field;
2021
}
@@ -203,6 +204,124 @@ async function query({
203204
client.release();
204205
}
205206
}
207+
208+
function buildFilterList(query, filtersArray, filterFields) {
209+
if (filtersArray.length > 0) {
210+
query.where = query.where || [];
211+
filtersArray.forEach((filter) => {
212+
const findField = filterFields.find((item) => item.field === filter.field);
213+
const column = findField?.column || "a.ActivityDateInserted";
214+
const isColumn = findField?.isColumn || false;
215+
const applyToCTE = findField?.applyToCTE || false;
216+
if (filter.min) {
217+
query.where.push({
218+
column: column,
219+
operator: ">=",
220+
value: `$${query.values.length + 1}`,
221+
});
222+
223+
query.values.push(filter.min);
224+
225+
if (applyToCTE) {
226+
if (query.cte) {
227+
if (!query.cte.where) {
228+
query.cte.where = [];
229+
}
230+
query.cte.where.push({
231+
column: column,
232+
operator: ">=",
233+
value: `$${query.values.length + 1}`,
234+
});
235+
236+
query.values.push(filter.min);
237+
}
238+
}
239+
}
240+
241+
if (filter.in) {
242+
const values = filter.in.split(",");
243+
const valuesPlaceholders = values.map((_, i) => `$${query.values.length + i + 1}`).join(", ");
244+
query.where.push({
245+
column: column,
246+
operator: "in",
247+
value: `(${valuesPlaceholders})`,
248+
});
249+
250+
if (applyToCTE) {
251+
if (query.cte) {
252+
if (!query.cte.where) {
253+
query.cte.where = [];
254+
}
255+
const valuesPlaceholdersCTE = values.map((_, i) => `$${query.values.length + values.length + i + 1}`).join(", ");
256+
query.cte.where.push({
257+
column: column,
258+
operator: "in",
259+
value: `(${valuesPlaceholdersCTE})`,
260+
});
261+
}
262+
}
263+
query.values.push(...values);
264+
if (applyToCTE && query.cte) {
265+
query.values.push(...values);
266+
}
267+
}
268+
269+
if (filter.max) {
270+
query.where.push({
271+
column: column,
272+
operator: "<=",
273+
value: `$${query.values.length + 1}`,
274+
});
275+
276+
query.values.push(filter.max);
277+
278+
if (applyToCTE) {
279+
if (query.cte) {
280+
if (!query.cte.where) {
281+
query.cte.where = [];
282+
}
283+
query.cte.where.push({
284+
column: column,
285+
operator: "<=",
286+
value: `$${query.values.length + 1}`,
287+
});
288+
289+
query.values.push(filter.max);
290+
}
291+
}
292+
}
293+
294+
if (filter.value) {
295+
const whereClause = {
296+
operator: "LIKE",
297+
value: `$${query.values.length + 1}`,
298+
};
299+
300+
query.values.push(`%${filter.value.toLowerCase()}%`);
301+
302+
if (isColumn) {
303+
whereClause.column = column;
304+
} else {
305+
whereClause.field = column;
306+
}
307+
query.where.push(whereClause);
308+
309+
if (applyToCTE) {
310+
if (query.cte) {
311+
if (!query.cte.where) {
312+
query.cte.where = [];
313+
}
314+
whereClause.value = `$${query.values.length + 1}`;
315+
query.cte.where.push(whereClause);
316+
317+
query.values.push(`%${filter.value.toLowerCase()}%`);
318+
}
319+
}
320+
}
321+
});
322+
}
323+
}
206324
module.exports = {
207325
query,
326+
buildFilterList,
208327
};

backend/classes/emby-api.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,23 @@ class EmbyAPI {
143143
}
144144
}
145145

146+
async getUserById(userid) {
147+
if (!this.configReady) {
148+
const success = await this.#fetchConfig();
149+
if (!success) {
150+
return null;
151+
}
152+
}
153+
154+
try {
155+
const users = await this.getUsers();
156+
return users.find((user) => user.Id === userid) || null;
157+
} catch (error) {
158+
this.#errorHandler(error);
159+
return null;
160+
}
161+
}
162+
146163
async getItemsByID({ ids, params }) {
147164
if (!this.configReady) {
148165
return [];

0 commit comments

Comments
 (0)