mirror of
https://github.com/Drop-OSS/drop.git
synced 2025-11-10 04:22:09 +10:00
feat: update checker based gh releases
This commit is contained in:
@ -1,6 +1,6 @@
|
|||||||
import tailwindcss from "@tailwindcss/vite";
|
import tailwindcss from "@tailwindcss/vite";
|
||||||
|
|
||||||
const dropVersion = "0.3";
|
const dropVersion = "v0.3.0";
|
||||||
|
|
||||||
// https://nuxt.com/docs/api/configuration/nuxt-config
|
// https://nuxt.com/docs/api/configuration/nuxt-config
|
||||||
export default defineNuxtConfig({
|
export default defineNuxtConfig({
|
||||||
|
|||||||
@ -37,6 +37,7 @@
|
|||||||
"nuxt": "^3.16.2",
|
"nuxt": "^3.16.2",
|
||||||
"nuxt-security": "2.2.0",
|
"nuxt-security": "2.2.0",
|
||||||
"prisma": "^6.7.0",
|
"prisma": "^6.7.0",
|
||||||
|
"semver": "^7.7.1",
|
||||||
"stream-mime-type": "^2.0.0",
|
"stream-mime-type": "^2.0.0",
|
||||||
"turndown": "^7.2.0",
|
"turndown": "^7.2.0",
|
||||||
"unstorage": "^1.15.0",
|
"unstorage": "^1.15.0",
|
||||||
@ -53,6 +54,7 @@
|
|||||||
"@types/bcryptjs": "^3.0.0",
|
"@types/bcryptjs": "^3.0.0",
|
||||||
"@types/luxon": "^3.6.2",
|
"@types/luxon": "^3.6.2",
|
||||||
"@types/node": "^22.13.16",
|
"@types/node": "^22.13.16",
|
||||||
|
"@types/semver": "^7.7.0",
|
||||||
"@types/turndown": "^5.0.5",
|
"@types/turndown": "^5.0.5",
|
||||||
"autoprefixer": "^10.4.20",
|
"autoprefixer": "^10.4.20",
|
||||||
"eslint": "^9.24.0",
|
"eslint": "^9.24.0",
|
||||||
|
|||||||
4
server/h3.d.ts
vendored
4
server/h3.d.ts
vendored
@ -1 +1,5 @@
|
|||||||
export type MinimumRequestObject = { headers: Headers };
|
export type MinimumRequestObject = { headers: Headers };
|
||||||
|
|
||||||
|
export type TaskReturn<T = unknown> =
|
||||||
|
| { success: true; data: T; error?: never }
|
||||||
|
| { success: false; data?: never; error: { message: string } };
|
||||||
|
|||||||
@ -1,6 +1,12 @@
|
|||||||
class SystemConfig {
|
class SystemConfig {
|
||||||
private libraryFolder = process.env.LIBRARY ?? "./.data/library";
|
private libraryFolder = process.env.LIBRARY ?? "./.data/library";
|
||||||
private dataFolder = process.env.DATA ?? "./.data/data";
|
private dataFolder = process.env.DATA ?? "./.data/data";
|
||||||
|
private dropVersion = "v0.3.0";
|
||||||
|
private checkForUpdates =
|
||||||
|
process.env.CHECK_FOR_UPDATES !== undefined &&
|
||||||
|
process.env.CHECK_FOR_UPDATES.toLocaleLowerCase() === "true"
|
||||||
|
? true
|
||||||
|
: false;
|
||||||
|
|
||||||
getLibraryFolder() {
|
getLibraryFolder() {
|
||||||
return this.libraryFolder;
|
return this.libraryFolder;
|
||||||
@ -9,6 +15,14 @@ class SystemConfig {
|
|||||||
getDataFolder() {
|
getDataFolder() {
|
||||||
return this.dataFolder;
|
return this.dataFolder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getDropVersion() {
|
||||||
|
return this.dropVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
shouldCheckForUpdates() {
|
||||||
|
return this.checkForUpdates;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const systemConfig = new SystemConfig();
|
export const systemConfig = new SystemConfig();
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import type {
|
|||||||
} from "./types";
|
} from "./types";
|
||||||
import { ObjectTransactionalHandler } from "../objects/transactional";
|
import { ObjectTransactionalHandler } from "../objects/transactional";
|
||||||
import { PriorityListIndexed } from "../utils/prioritylist";
|
import { PriorityListIndexed } from "../utils/prioritylist";
|
||||||
|
import { systemConfig } from "../config/sys-conf";
|
||||||
|
|
||||||
export class MissingMetadataProviderConfig extends Error {
|
export class MissingMetadataProviderConfig extends Error {
|
||||||
private providerName: string;
|
private providerName: string;
|
||||||
@ -25,7 +26,7 @@ export class MissingMetadataProviderConfig extends Error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: add useragent to all outbound api calls (best practice)
|
// TODO: add useragent to all outbound api calls (best practice)
|
||||||
export const DropUserAgent = "Drop/0.2";
|
export const DropUserAgent = `Drop/${systemConfig.getDropVersion()}`;
|
||||||
|
|
||||||
export abstract class MetadataProvider {
|
export abstract class MetadataProvider {
|
||||||
abstract name(): string;
|
abstract name(): string;
|
||||||
|
|||||||
@ -9,6 +9,7 @@ Design goals:
|
|||||||
import type { Notification } from "~/prisma/client";
|
import type { Notification } from "~/prisma/client";
|
||||||
import prisma from "../db/database";
|
import prisma from "../db/database";
|
||||||
|
|
||||||
|
// TODO: document notification action format
|
||||||
export type NotificationCreateArgs = Pick<
|
export type NotificationCreateArgs = Pick<
|
||||||
Notification,
|
Notification,
|
||||||
"title" | "description" | "actions" | "nonce"
|
"title" | "description" | "actions" | "nonce"
|
||||||
@ -61,14 +62,18 @@ class NotificationSystem {
|
|||||||
throw new Error("No nonce in notificationCreateArgs");
|
throw new Error("No nonce in notificationCreateArgs");
|
||||||
const notification = await prisma.notification.upsert({
|
const notification = await prisma.notification.upsert({
|
||||||
where: {
|
where: {
|
||||||
nonce: notificationCreateArgs.nonce,
|
userId_nonce: {
|
||||||
|
nonce: notificationCreateArgs.nonce,
|
||||||
|
userId,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
update: {
|
update: {
|
||||||
userId: userId,
|
// we don't need to update the userid right?
|
||||||
|
// userId: userId,
|
||||||
...notificationCreateArgs,
|
...notificationCreateArgs,
|
||||||
},
|
},
|
||||||
create: {
|
create: {
|
||||||
userId: userId,
|
userId,
|
||||||
...notificationCreateArgs,
|
...notificationCreateArgs,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -84,13 +89,34 @@ class NotificationSystem {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const res: Promise<void>[] = [];
|
||||||
for (const user of users) {
|
for (const user of users) {
|
||||||
await this.push(user.id, notificationCreateArgs);
|
res.push(this.push(user.id, notificationCreateArgs));
|
||||||
}
|
}
|
||||||
|
// wait for all notifications to pass
|
||||||
|
await Promise.all(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
async systemPush(notificationCreateArgs: NotificationCreateArgs) {
|
async systemPush(notificationCreateArgs: NotificationCreateArgs) {
|
||||||
return await this.push("system", notificationCreateArgs);
|
await this.push("system", notificationCreateArgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
async pushAllAdmins(notificationCreateArgs: NotificationCreateArgs) {
|
||||||
|
const users = await prisma.user.findMany({
|
||||||
|
where: {
|
||||||
|
admin: true,
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const res: Promise<void>[] = [];
|
||||||
|
for (const user of users) {
|
||||||
|
res.push(this.push(user.id, notificationCreateArgs));
|
||||||
|
}
|
||||||
|
// wait for all notifications to pass
|
||||||
|
await Promise.all(res);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,5 +3,8 @@ export default defineNitroPlugin(async (_nitro) => {
|
|||||||
await Promise.all([
|
await Promise.all([
|
||||||
runTask("cleanup:invitations"),
|
runTask("cleanup:invitations"),
|
||||||
runTask("cleanup:sessions"),
|
runTask("cleanup:sessions"),
|
||||||
|
// TODO: maybe implement some sort of rate limit thing to prevent this from calling github api a bunch in the event of crashloop or whatever?
|
||||||
|
// probably will require custom task scheduler for object cleanup anyway, so something to thing about
|
||||||
|
runTask("check:update"),
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|||||||
143
server/tasks/check/update.ts
Normal file
143
server/tasks/check/update.ts
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
import { type } from "arktype";
|
||||||
|
import { systemConfig } from "../../internal/config/sys-conf";
|
||||||
|
import * as semver from "semver";
|
||||||
|
import type { TaskReturn } from "../../h3";
|
||||||
|
import notificationSystem from "../../internal/notifications";
|
||||||
|
|
||||||
|
const latestRelease = type({
|
||||||
|
url: "string", // api url for specific release
|
||||||
|
html_url: "string", // user facing url
|
||||||
|
id: "number", // release id
|
||||||
|
tag_name: "string", // tag used for release
|
||||||
|
name: "string", // release name
|
||||||
|
draft: "boolean",
|
||||||
|
prerelease: "boolean",
|
||||||
|
created_at: "string",
|
||||||
|
published_at: "string",
|
||||||
|
});
|
||||||
|
|
||||||
|
export default defineTask<TaskReturn>({
|
||||||
|
meta: {
|
||||||
|
name: "check:update",
|
||||||
|
},
|
||||||
|
async run() {
|
||||||
|
if (systemConfig.shouldCheckForUpdates()) {
|
||||||
|
console.log("[Task check:update]: Checking for update");
|
||||||
|
try {
|
||||||
|
const response = await fetch(
|
||||||
|
"https://api.github.com/repos/Drop-OSS/drop/releases/latest",
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
console.log("[Task check:update]: Failed to check for update", {
|
||||||
|
status: response.status,
|
||||||
|
body: response.body,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
result: {
|
||||||
|
success: false,
|
||||||
|
error: {
|
||||||
|
message: "" + response.status,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const resJson = await response.json();
|
||||||
|
const body = latestRelease(resJson);
|
||||||
|
if (body instanceof type.errors) {
|
||||||
|
console.error(body.summary);
|
||||||
|
console.log("GitHub Api response", resJson);
|
||||||
|
return {
|
||||||
|
result: {
|
||||||
|
success: false,
|
||||||
|
error: {
|
||||||
|
message: body.summary,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// const currVerStr = systemConfig.getDropVersion()
|
||||||
|
const currVerStr = "v0.1";
|
||||||
|
|
||||||
|
const latestVer = semver.coerce(body.tag_name);
|
||||||
|
const currVer = semver.coerce(currVerStr);
|
||||||
|
if (latestVer === null) {
|
||||||
|
const msg = "Github Api returned invalid semver tag";
|
||||||
|
console.log("[Task check:update]:", msg);
|
||||||
|
return {
|
||||||
|
result: {
|
||||||
|
success: false,
|
||||||
|
error: {
|
||||||
|
message: msg,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} else if (currVer === null) {
|
||||||
|
const msg = "Drop provided a invalid semver tag";
|
||||||
|
console.log("[Task check:update]:", msg);
|
||||||
|
return {
|
||||||
|
result: {
|
||||||
|
success: false,
|
||||||
|
error: {
|
||||||
|
message: msg,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (semver.gt(latestVer, currVer)) {
|
||||||
|
console.log("[Task check:update]: Update available");
|
||||||
|
notificationSystem.pushAllAdmins({
|
||||||
|
nonce: `drop-update-available-${currVer}-to-${latestVer}`,
|
||||||
|
title: `Update available to v${latestVer}`,
|
||||||
|
description: `A new version of Drop is available v${latestVer}`,
|
||||||
|
actions: [`View|${body.html_url}`],
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.log("[Task check:update]: no update available");
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("[Task check:update]: Done");
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
if (typeof e === "string") {
|
||||||
|
return {
|
||||||
|
result: {
|
||||||
|
success: false,
|
||||||
|
error: {
|
||||||
|
message: e,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} else if (e instanceof Error) {
|
||||||
|
return {
|
||||||
|
result: {
|
||||||
|
success: false,
|
||||||
|
error: {
|
||||||
|
message: e.message,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
result: {
|
||||||
|
success: false,
|
||||||
|
error: {
|
||||||
|
message: "unknown cause, please check console",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
result: {
|
||||||
|
success: true,
|
||||||
|
data: undefined,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
@ -1830,6 +1830,11 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.20.2.tgz#97d26e00cd4a0423b4af620abecf3e6f442b7975"
|
resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.20.2.tgz#97d26e00cd4a0423b4af620abecf3e6f442b7975"
|
||||||
integrity sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==
|
integrity sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==
|
||||||
|
|
||||||
|
"@types/semver@^7.7.0":
|
||||||
|
version "7.7.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.7.0.tgz#64c441bdae033b378b6eef7d0c3d77c329b9378e"
|
||||||
|
integrity sha512-k107IF4+Xr7UHjwDc7Cfd6PRQfbdkiRabXGRjo07b4WyPahFBZCZ1sE+BNxYIJPPg73UkfOsVOLwqVc/6ETrIA==
|
||||||
|
|
||||||
"@types/turndown@^5.0.5":
|
"@types/turndown@^5.0.5":
|
||||||
version "5.0.5"
|
version "5.0.5"
|
||||||
resolved "https://registry.yarnpkg.com/@types/turndown/-/turndown-5.0.5.tgz#614de24fc9ace4d8c0d9483ba81dc8c1976dd26f"
|
resolved "https://registry.yarnpkg.com/@types/turndown/-/turndown-5.0.5.tgz#614de24fc9ace4d8c0d9483ba81dc8c1976dd26f"
|
||||||
|
|||||||
Reference in New Issue
Block a user