Compare commits

...

11 Commits

Author SHA1 Message Date
Bas950
255f840275 chore: release v0.0.39 2024-12-19 20:33:57 +01:00
Bas950
12089fe773 chore: lint 2024-12-19 20:33:50 +01:00
Bas950
4b43e03ff0 chore: remove ip lookup 2024-12-19 20:33:08 +01:00
Bas950
d16ec114b0 chore: release v0.0.38 2024-12-19 20:27:29 +01:00
Bas950
8ea8904752 chore: release v1.0.5 2024-12-19 20:26:07 +01:00
Bas950
7fabdb8fe4 feat: new schema version 2024-12-19 20:25:17 +01:00
Bas van Zanten
2554b20b34 feat: extension version gauge (#1074)
* feat: extension version gauge

* chore: lint
2024-12-19 20:22:44 +01:00
Florian Metz
9e72d2cc7f chore: release v1.0.6 2024-12-19 10:47:55 +01:00
Florian Metz
4af1ff22f1 fix: show displayName 2024-12-19 10:47:15 +01:00
Bas950
f339035463 chore: release v0.0.18 2024-11-17 21:02:17 +01:00
Bas950
b67226dcdd feat: fix presence endpoints 2024-11-17 21:02:07 +01:00
17 changed files with 380 additions and 217 deletions

View File

@@ -1,14 +1,3 @@
declare module "ip-location-api" {
export function lookup(ip: string): Promise<{
latitude: number;
longitude: number;
country: string;
} | null>;
export function updateDb(options: { fields?: string[]; dataDir?: string; tmpDataDir?: string; smallMemory?: boolean; autoUpdate?: string }): Promise<void>;
export function reload(options: { fields?: string[]; dataDir?: string; tmpDataDir?: string; smallMemory?: boolean; autoUpdate?: string }): Promise<void>;
}
declare namespace NodeJS {
export interface ProcessEnv {
METRICS_DATABASE_URL?: string;

View File

@@ -1,7 +1,7 @@
{
"name": "@premid/api-master",
"type": "module",
"version": "0.0.37",
"version": "0.0.39",
"private": true,
"description": "PreMiD's api master",
"license": "MPL-2.0",
@@ -24,7 +24,6 @@
"debug": "^4.3.6",
"drizzle-orm": "^0.33.0",
"ioredis": "^5.3.2",
"ip-location-api": "^2.0.1",
"ky": "^1.7.2",
"p-limit": "^6.1.0",
"postgres": "^3.4.4",

View File

@@ -1,46 +1,3 @@
import { join } from "node:path";
import process from "node:process";
import { lookup, reload } from "ip-location-api";
import { mainLog } from "../index.js";
const fields = ["latitude", "longitude", "country"];
const dataDir = join(process.cwd(), "data");
const tmpDataDir = join(process.cwd(), "tmp");
const smallMemory = true;
let initialized = false;
export async function lookupIp(ip: string): Promise<{ latitude: number; longitude: number; country: string } | undefined> {
if (!initialized) {
reloadIpLocationApi();
return undefined;
}
try {
return await lookup(ip) ?? undefined;
}
catch {
return undefined;
}
}
let reloading: Promise<void> | undefined;
let log: debug.Debugger | undefined;
export async function reloadIpLocationApi() {
log ??= mainLog.extend("IP-Location-API");
if (reloading)
return reloading;
reloading = new Promise((resolve, reject) => {
log?.("Reloading IP location API");
reload({ fields, dataDir, tmpDataDir, smallMemory, autoUpdate: "0 9 * * *" }).then(() => {
log?.("IP location API reloaded");
initialized = true;
reloading = undefined;
resolve();
}).catch(reject);
});
return reloading;
export async function lookupIp(_ip: string): Promise<{ latitude: number; longitude: number; country: string } | undefined> {
return undefined;
}

View File

@@ -0,0 +1,54 @@
import process from "node:process";
import pLimit from "p-limit";
import type { Gauge } from "prom-client";
import { mainLog, redis } from "../index.js";
export const updateExtensionVersionGaugeLimit = pLimit(1);
let log: debug.Debugger | undefined;
const scanCount = Number.parseInt(process.env.SCAN_COUNT || "1000", 10);
export async function updateExtensionVersionGauge(gauge: Gauge) {
await updateExtensionVersionGaugeLimit(async () => {
log ??= mainLog.extend("Extension-Version-Updates");
log?.("Starting extension version gauge update");
const pattern = "pmd-api.heartbeatUpdates.*";
let cursor: string = "0";
const versionCounts = new Map<string, number>();
do {
const [newCursor, keys] = await redis.scan(cursor, "MATCH", pattern, "COUNT", scanCount);
cursor = newCursor;
//* Use pipelining for batch Redis operations
const pipeline = redis.pipeline();
keys.forEach(key => pipeline.hmget(key, "extension_version"));
const hashes = await pipeline.exec();
if (!hashes) {
log?.("No hashes found");
return;
}
hashes.forEach(([err, hash]) => {
if (err || !Array.isArray(hash))
return;
const [version] = hash;
if (version && typeof version === "string")
versionCounts.set(version, (versionCounts.get(version) || 0) + 1);
});
} while (cursor !== "0");
log?.("Updating extension version gauge");
//* Batch update the gauge
gauge.reset();
for (const [version, count] of versionCounts) {
gauge.set({ version }, count);
}
log?.("Extension version gauge update completed");
});
}

View File

@@ -4,7 +4,6 @@ import debug from "debug";
import { clearOldSessions } from "./functions/clearOldSessions.js";
import createRedis from "./functions/createRedis.js";
import "./tracing.js";
import { reloadIpLocationApi } from "./functions/lookupIp.js";
import { cleanupOldUserData } from "./functions/cleanupOldUserData.js";
import { setupServer } from "./functions/setupServer.js";
@@ -16,8 +15,6 @@ export const mainLog = debug("api-master");
debug("Starting cron jobs");
void reloadIpLocationApi();
void new CronJob(
// Every 5 seconds
"*/5 * * * * *",

View File

@@ -1,6 +1,7 @@
import process from "node:process";
import { Counter, Gauge, Registry, collectDefaultMetrics } from "prom-client";
import { updateActivePresenceGauge, updateActivePresenceGaugeLimit } from "./functions/updateActivePresenceGauge.js";
import { updateExtensionVersionGauge, updateExtensionVersionGaugeLimit } from "./functions/updateVersionGauge.js";
import { redis } from "./index.js";
const scanCount = Number.parseInt(process.env.SCAN_COUNT || "1000", 10);
@@ -37,5 +38,17 @@ export const activePresencesCounter = new Gauge({
},
});
const versionCounter = new Gauge({
name: "extension_version",
help: "The version of the extension with the amount of users using it",
labelNames: ["version"],
async collect() {
this.reset();
updateExtensionVersionGaugeLimit.clearQueue();
await updateExtensionVersionGauge(this);
},
});
register.registerMetric(activeSessionsCounter);
register.registerMetric(activePresencesCounter);
register.registerMetric(versionCounter);

View File

@@ -1,7 +1,7 @@
{
"name": "@premid/api-worker",
"type": "module",
"version": "0.0.17",
"version": "0.0.18",
"private": true,
"description": "PreMiD's api",
"license": "MPL-2.0",

View File

@@ -13,6 +13,7 @@ import { Socket } from "../classes/Socket.js";
import { resolvers } from "../graphql/resolvers/v5/index.js";
import { sessionKeepAlive } from "../routes/sessionKeepAlive.js";
import { featureFlags } from "../constants.js";
import { presences } from "../routes/presences.js";
import createRedis from "./createRedis.js";
export interface FastifyContext {
@@ -89,6 +90,7 @@ export default async function createServer() {
});
app.post("/v5/session-keep-alive", sessionKeepAlive);
app.get("/v5/presence/:service/:file", presences);
return app;
}

View File

@@ -0,0 +1,34 @@
import { Presence } from "@premid/db";
import { type } from "arktype";
import type { FastifyReply, FastifyRequest } from "fastify";
const schema = type({
service: "string.trim",
file: "'metadata.json'|'presence.js'|'iframe.js'",
});
export async function presences(request: FastifyRequest, reply: FastifyReply) {
const out = schema(request.params);
if (out instanceof type.errors)
return reply.status(400).send({ code: "INVALID_PARAMS", message: out.message });
const service = decodeURIComponent(out.service);
const { file } = out;
const presence = await Presence.findOne({ "metadata.service": service });
if (!presence)
return reply.status(404).send({ code: "PRESENCE_NOT_FOUND", message: "The presence was not found" });
switch (file) {
case "metadata.json":
return reply.status(200).type("application/json").send(presence.metadata);
case "presence.js":
return reply.status(200).type("application/javascript").send(presence.presenceJs);
case "iframe.js":
if (!presence.iframeJs)
return reply.status(404).send({ code: "IFRAME_NOT_FOUND", message: "The presence does not have an iframe" });
return reply.status(200).type("application/javascript").send(presence.iframeJs);
}
}

View File

@@ -1,7 +1,7 @@
{
"name": "@premid/discord-bot",
"type": "module",
"version": "1.0.5",
"version": "1.0.6",
"private": true,
"description": "PreMiD's discord bot",
"license": "MPL-2.0",

View File

@@ -15,7 +15,7 @@ client.on(Events.GuildMemberAdd, async (member) => {
created: member.user.createdTimestamp,
discriminator: member.user.discriminator,
userId: member.id,
username: member.user.username,
username: member.user.displayName ?? member.user.username,
},
},
{ upsert: true },

View File

@@ -17,7 +17,7 @@ client.on(Events.GuildMemberUpdate, async (oldMember, newMember) => {
created: newMember.user.createdTimestamp,
discriminator: newMember.user.discriminator,
userId: newMember.id,
username: newMember.user.username,
username: newMember.user.displayName ?? newMember.user.username,
},
},
{ upsert: true },
@@ -28,7 +28,7 @@ client.on(Events.GuildMemberUpdate, async (oldMember, newMember) => {
{
$set: {
userId: newMember.id,
name: newMember.user.username,
name: newMember.user.displayName ?? newMember.user.username,
tag: newMember.user.discriminator,
avatar: newMember.user.displayAvatarURL({
extension: "png",

View File

@@ -108,7 +108,7 @@ client.once(Events.ClientReady, async () => {
update: {
$set: {
userId: member.id,
name: member.user.username,
name: member.user.displayName ?? member.user.username,
tag: member.user.discriminator,
avatar: member.user.displayAvatarURL({
extension: "png",

View File

@@ -1,7 +1,7 @@
{
"name": "@premid/schema-server",
"type": "module",
"version": "1.0.4",
"version": "1.0.5",
"private": true,
"description": "A small service to serve the JSON schemas for PreMiD",
"license": "MPL-2.0",

View File

@@ -0,0 +1,252 @@
{
"$schema": "http://json-schema.org/draft-07/schema",
"$id": "https://schemas.premid.app/metadata/1.12",
"title": "Metadata",
"type": "object",
"description": "Metadata that describes a presence.",
"definitions": {
"user": {
"type": "object",
"description": "User information.",
"properties": {
"name": {
"type": "string",
"description": "The name of the user."
},
"id": {
"type": "string",
"description": "The Discord snowflake of the user.",
"pattern": "^\\d+$"
}
},
"additionalProperties": false,
"required": [
"name",
"id"
]
}
},
"properties": {
"$schema": {
"$comment": "This is required otherwise the schema will fail itself when it is applied to a document via $schema. This is optional so that validators that use this schema don't fail if the metadata doesn't have the $schema property.",
"type": "string",
"description": "The metadata schema URL."
},
"author": {
"$ref": "#/definitions/user",
"description": "The author of this presence."
},
"contributors": {
"type": "array",
"description": "Any extra contributors to this presence.",
"items": {
"$ref": "#/definitions/user"
}
},
"service": {
"type": "string",
"description": "The service this presence is for."
},
"altnames": {
"type": "array",
"description": "Alternative names for the service.",
"items": {
"type": "string",
"description": "An alternative name."
},
"minItems": 1
},
"description": {
"type": "object",
"description": "A description of the presence in multiple languages.",
"propertyNames": {
"type": "string",
"description": "The language key. The key must be languagecode(_REGIONCODE).",
"pattern": "^[a-z]{2}(?:_(?:[A-Z]{2}|[0-9]{1,3}))?$"
},
"patternProperties": {
"^[a-z]{2}(?:_(?:[A-Z]{2}|[0-9]{1,3}))?$": {
"type": "string",
"description": "The description of the presence in the key's language."
}
},
"additionalProperties": false,
"required": [
"en"
]
},
"url": {
"type": [
"string",
"array"
],
"description": "The service's website URL, or an array of URLs. Protocols should not be added.",
"pattern": "^(([a-z0-9-]+\\.)*[0-9a-z_-]+(\\.[a-z]+)+|(\\d{1,3}\\.){3}\\d{1,3}|localhost)$",
"items": {
"type": "string",
"description": "One of the service's website URLs.",
"pattern": "^(([a-z0-9-]+\\.)*[0-9a-z_-]+(\\.[a-z]+)+|(\\d{1,3}\\.){3}\\d{1,3}|localhost)$"
},
"minItems": 2
},
"version": {
"type": "string",
"description": "The SemVer version of the presence. Must just be major.minor.patch.",
"pattern": "^\\d+\\.\\d+\\.\\d+$"
},
"apiVersion": {
"type": "integer",
"description": "The Presence System version this Presence supports.",
"minimum": 1,
"maximum": 2
},
"logo": {
"type": "string",
"description": "The logo of the service this presence is for.",
"pattern": "^https?://.+\\.(png|jpe?g|gif|webp)$"
},
"thumbnail": {
"type": "string",
"description": "A thumbnail of the service this presence is for.",
"pattern": "^https?://.+\\.(png|jpe?g|gif|webp)$"
},
"color": {
"type": "string",
"description": "The theme color of the service this presence is for. Must be either a 6 digit or a 3 digit hex code.",
"pattern": "^#([A-Fa-f0-9]{3}){1,2}$"
},
"tags": {
"type": [
"array"
],
"description": "The tags for the presence.",
"items": {
"type": "string",
"description": "A tag.",
"pattern": "^[^A-Z\\s!\"#$%&'()*+,./:;<=>?@\\[\\\\\\]^_`{|}~]+$"
},
"minItems": 1
},
"category": {
"type": "string",
"description": "The category the presence falls under.",
"enum": [
"anime",
"games",
"music",
"socials",
"videos",
"other"
]
},
"iframe": {
"type": "boolean",
"description": "Whether or not the presence should run in IFrames."
},
"readLogs": {
"type": "boolean",
"description": "Whether or not the extension should be reading logs."
},
"regExp": {
"type": "string",
"description": "A regular expression used to match URLs for the presence to inject into."
},
"iFrameRegExp": {
"type": "string",
"description": "A regular expression used to match IFrames for the presence to inject into."
},
"button": {
"type": "boolean",
"description": "Controls whether the presence is automatically added when the extension is installed. For partner presences only."
},
"warning": {
"type": "boolean",
"description": "Shows a warning saying that it requires additional steps for the presence to function correctly."
},
"settings": {
"type": "array",
"description": "An array of settings the user can change in the presence.",
"items": {
"type": "object",
"description": "A setting.",
"properties": {
"id": {
"type": "string",
"description": "The ID of the setting."
},
"title": {
"type": "string",
"description": "The title of the setting. Required only if `multiLanguage` is disabled."
},
"icon": {
"type": "string",
"description": "The icon of the setting. Required only if `multiLanguage` is disabled.",
"pattern": "^fa([bsdrlt]|([-](brands|solid|duotone|regular|light|thin))) fa-[0-9a-z-]+$"
},
"if": {
"type": "object",
"description": "Restrict showing this setting if another setting is the defined value.",
"propertyNames": {
"type": "string",
"description": "The ID of the setting."
},
"patternProperties": {
"": {
"type": [
"string",
"number",
"boolean"
],
"description": "The value of the setting."
}
},
"additionalProperties": false
},
"placeholder": {
"type": "string",
"description": "The placeholder for settings that require input. Shown when the input is empty."
},
"value": {
"type": [
"string",
"number",
"boolean"
],
"description": "The default value of the setting. Not compatible with `values`."
},
"values": {
"type": "array",
"description": "The default values of the setting. Not compatible with `value`.",
"items": {
"type": [
"string",
"number",
"boolean"
],
"description": "The value of the setting."
}
},
"multiLanguage": {
"type": "boolean",
"description": "When true, strings from the `general.json` file are available for use, plus the <service>.json file. False is not allowed."
}
},
"additionalProperties": false
}
}
},
"additionalProperties": false,
"required": [
"author",
"service",
"description",
"url",
"version",
"apiVersion",
"logo",
"thumbnail",
"color",
"tags",
"category"
]
}

View File

@@ -62,15 +62,15 @@ const PresenceMetadataSettingSchema = new Schema<PresenceMetadataSetting>({
placeholder: { type: String },
title: { type: String },
value: Schema.Types.Mixed,
values: { type: [String] },
values: { type: [Schema.Types.Mixed], default: undefined },
});
const PresenceMetadataSchema = new Schema<PresenceMetadata>({
$schema: { required: true, type: String },
altnames: { type: [String] },
altnames: { type: [String], default: undefined },
author: { required: true, type: PresenceMetadataContributorSchema },
category: { required: true, type: String },
color: { required: true, type: String },
contributors: { type: [PresenceMetadataContributorSchema] },
contributors: { type: [PresenceMetadataContributorSchema], default: undefined },
description: { required: true, type: Schema.Types.Mixed },
iFrameRegExp: { type: String },
iframe: { type: Boolean },
@@ -78,10 +78,10 @@ const PresenceMetadataSchema = new Schema<PresenceMetadata>({
readLogs: { type: Boolean },
regExp: { type: String },
service: { required: true, type: String },
settings: { type: [PresenceMetadataSettingSchema] },
settings: { type: [PresenceMetadataSettingSchema], default: undefined },
tags: { required: true, type: [String] },
thumbnail: { required: true, type: String },
url: { required: true, type: [String] },
url: { required: true, type: Schema.Types.Mixed },
version: { required: true, type: String },
apiVersion: { required: true, type: Number },
});

154
pnpm-lock.yaml generated
View File

@@ -71,9 +71,6 @@ importers:
ioredis:
specifier: ^5.3.2
version: 5.4.1
ip-location-api:
specifier: ^2.0.1
version: 2.0.1(debug@4.3.7)
ky:
specifier: ^1.7.2
version: 1.7.2
@@ -1888,12 +1885,6 @@ packages:
resolution: {integrity: sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@fast-csv/format@5.0.0':
resolution: {integrity: sha512-IyMpHwYIOGa2f0BJi6Wk55UF0oBA5urdIydoEDYxPo88LFbeb3Yr4rgpu98OAO1glUWheSnNtUgS80LE+/dqmw==}
'@fast-csv/parse@5.0.0':
resolution: {integrity: sha512-ecF8tCm3jVxeRjEB6VPzmA+1wGaJ5JgaUX2uesOXdXD6qQp0B3EdshOIed4yT1Xlj/F2f8v4zHSo0Oi31L697g==}
'@fastify/accept-negotiator@1.1.0':
resolution: {integrity: sha512-OIHZrb2ImZ7XG85HXOONLcJWGosv7sIvM2ifAPQVhg9Lv7qdmMBNVaai4QTdyuaqbKM5eO6sLSQOYI7wEQeCJQ==}
engines: {node: '>=14'}
@@ -3176,7 +3167,6 @@ packages:
'@rollup/rollup-linux-arm64-gnu@4.21.2':
resolution: {integrity: sha512-69CF19Kp3TdMopyteO/LJbWufOzqqXzkrv4L2sP8kfMaAQ6iwky7NoXTp7bD6/irKgknDKM0P9E/1l5XxVQAhw==}
cpu: [arm64]
os: [linux]
'@rollup/rollup-linux-arm64-musl@4.18.1':
@@ -4362,9 +4352,6 @@ packages:
resolution: {integrity: sha512-iOJg8pr7wq2tg/zSlCCHMi3hMm5JTOxLTagf3zxhcenHsFp+c6uOs6K7W5UE7A4QIJGtqh/ZovFNMP4mOPJynQ==}
engines: {node: '>=16.20.1'}
buffer-crc32@0.2.13:
resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==}
buffer-crc32@1.0.0:
resolution: {integrity: sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==}
engines: {node: '>=8.0.0'}
@@ -4763,9 +4750,6 @@ packages:
typescript:
optional: true
countries-list@3.1.1:
resolution: {integrity: sha512-nPklKJ5qtmY5MdBKw1NiBAoyx5Sa7p2yPpljZyQ7gyCN1m+eMFs9I6CT37Mxt8zvR5L3VzD3DJBE4WQzX3WF4A==}
crawler-user-agents@1.0.143:
resolution: {integrity: sha512-tVnQF0rrrzmiwHL/ASloGdW4wQ5Cjfd1ujiUWfeDjqXR6vTxrN4bIWnDd1tgUhDWx3z0msiktWjI51r3Dnw0CA==}
@@ -4894,9 +4878,6 @@ packages:
dataloader@2.2.2:
resolution: {integrity: sha512-8YnDaaf7N3k/q5HnTJVuzSyLETjoZjVmHc4AeKAzOvKHEFQKcn64OKBfzHYtE9zGjctNM7V9I0MfnUVLpi7M5g==}
dayjs@1.11.13:
resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==}
db0@0.1.4:
resolution: {integrity: sha512-Ft6eCwONYxlwLjBXSJxw0t0RYtA5gW9mq8JfBXn9TtC0nDPlqePAhpv9v4g9aONBi6JI1OXHTKKkUYGd+BOrCA==}
peerDependencies:
@@ -5598,10 +5579,6 @@ packages:
fast-content-type-parse@1.1.0:
resolution: {integrity: sha512-fBHHqSTFLVnR61C+gltJuE5GkVQMV0S2nqUO8TJ+5Z3qAKG8vAx4FKai1s5jq/inV1+sREynIWSuQ6HgoSXpDQ==}
fast-csv@5.0.1:
resolution: {integrity: sha512-Q43zC4NdQD5MAWOVQOF8KA+D6ddvTJjX2ib8zqysm74jZhtk6+dc8C75/OqRV6Y9CLc4kgvbC3PLG8YL4YZfgw==}
engines: {node: '>=10.0.0'}
fast-decode-uri-component@1.0.1:
resolution: {integrity: sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==}
@@ -6216,14 +6193,6 @@ packages:
resolution: {integrity: sha512-2YZsvl7jopIa1gaePkeMtd9rAcSjOOjPtpcLlOeusyO+XH2SK5ZcT+UCrElPP+WVIInh2TzeI4XW9ENaSLVVHA==}
engines: {node: '>=12.22.0'}
ip-address@9.0.5:
resolution: {integrity: sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==}
engines: {node: '>= 12'}
ip-location-api@2.0.1:
resolution: {integrity: sha512-g6INF2j3+Je89rj0ENaN9w1YzzKx11Hf4Q7aooCw9wcJ5GHFvwCgtlPJWqZ32wrCv3/sLvSbLEwOh/kauTFT9w==}
engines: {node: '>=14.8.0'}
ipaddr.js@1.9.1:
resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==}
engines: {node: '>= 0.10'}
@@ -6461,9 +6430,6 @@ packages:
resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
hasBin: true
jsbn@1.1.0:
resolution: {integrity: sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==}
jsdoc-type-pratt-parser@4.1.0:
resolution: {integrity: sha512-Hicd6JK5Njt2QB6XYFS7ok9e37O8AYk3jTcppG4YVQnYjOemymvTcmc7OWsmq/Qqj5TdRFO5/x/tIPmBeRtGHg==}
engines: {node: '>=12.0.0'}
@@ -6624,33 +6590,12 @@ packages:
lodash.defaults@4.2.0:
resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==}
lodash.escaperegexp@4.1.2:
resolution: {integrity: sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==}
lodash.groupby@4.6.0:
resolution: {integrity: sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw==}
lodash.isarguments@3.1.0:
resolution: {integrity: sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==}
lodash.isboolean@3.0.3:
resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==}
lodash.isequal@4.5.0:
resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==}
lodash.isfunction@3.0.9:
resolution: {integrity: sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==}
lodash.isnil@4.0.0:
resolution: {integrity: sha512-up2Mzq3545mwVnMhTDMdfoG1OurpA/s5t88JmQX809eH3C8491iu2sfKhTfhQtKY78oPNhiaHJUpT/dUDAAtng==}
lodash.isplainobject@4.0.6:
resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==}
lodash.isundefined@3.0.1:
resolution: {integrity: sha512-MXB1is3s899/cD8jheYYE2V9qTHwKvt+npCwpD+1Sxm3Q3cECXCiYHjeHWXNwr6Q0SOBPrYUDxendrO6goVTEA==}
lodash.kebabcase@4.1.1:
resolution: {integrity: sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==}
@@ -7455,9 +7400,6 @@ packages:
resolution: {integrity: sha512-U94a+eXHzct7vAd19GH3UQ2dH4Satbng0MyYTMaQatL0pvYYL5CTPR25HBhKtecl+4bfu1/i3vC6k0hydO5Vcw==}
engines: {node: '>=14.16'}
pend@1.2.0:
resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==}
perfect-debounce@1.0.0:
resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==}
@@ -8268,9 +8210,6 @@ packages:
sponge-case@1.0.1:
resolution: {integrity: sha512-dblb9Et4DAtiZ5YSUZHLl4XhH4uK80GhAZrVXdN4O2P4gQ40Wa5UIOPUHlA/nFd2PLblBZWUioLMMAVrgpoYcA==}
sprintf-js@1.1.3:
resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==}
stable-hash@0.0.4:
resolution: {integrity: sha512-LjdcbuBeLcdETCrPn9i8AYAZ1eCtu4ECAWtP7UleOiZ9LzVxRzzUZEoZ8zB24nhkQnDWyET0I+3sWokSDS3E7g==}
@@ -9389,10 +9328,6 @@ packages:
resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
engines: {node: '>=12'}
yauzl@3.1.3:
resolution: {integrity: sha512-JCCdmlJJWv7L0q/KylOekyRaUrdEoUxWkWVcgorosTROCFWiS9p2NNPE9Yb91ak7b1N5SxAZEliWpspbZccivw==}
engines: {node: '>=12'}
yocto-queue@0.1.0:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'}
@@ -10869,23 +10804,6 @@ snapshots:
'@eslint/object-schema@2.1.4': {}
'@fast-csv/format@5.0.0':
dependencies:
lodash.escaperegexp: 4.1.2
lodash.isboolean: 3.0.3
lodash.isequal: 4.5.0
lodash.isfunction: 3.0.9
lodash.isnil: 4.0.0
'@fast-csv/parse@5.0.0':
dependencies:
lodash.escaperegexp: 4.1.2
lodash.groupby: 4.6.0
lodash.isfunction: 3.0.9
lodash.isnil: 4.0.0
lodash.isundefined: 3.0.1
lodash.uniq: 4.5.0
'@fastify/accept-negotiator@1.1.0':
optional: true
@@ -14226,7 +14144,7 @@ snapshots:
pathe: 1.1.2
sirv: 2.0.4
tinyrainbow: 1.2.0
vitest: 2.0.5(@types/node@20.16.5)(@vitest/ui@2.0.5)(happy-dom@15.7.4)(sass@1.78.0)(terser@5.33.0)
vitest: 2.0.5(@types/node@22.6.1)(@vitest/ui@2.0.5)(happy-dom@15.0.0)(sass@1.78.0)(terser@5.33.0)
'@vitest/utils@2.0.5':
dependencies:
@@ -14469,7 +14387,7 @@ snapshots:
'@vueuse/shared': 10.11.1(vue@3.5.4(typescript@5.6.2))
vue-demi: 0.14.10(vue@3.5.4(typescript@5.6.2))
optionalDependencies:
axios: 1.7.7(debug@4.3.7)
axios: 1.7.7
change-case: 4.1.2
focus-trap: 7.5.4
transitivePeerDependencies:
@@ -14482,7 +14400,7 @@ snapshots:
'@vueuse/shared': 11.0.3(vue@3.5.4(typescript@5.5.4))
vue-demi: 0.14.10(vue@3.5.4(typescript@5.5.4))
optionalDependencies:
axios: 1.7.7(debug@4.3.7)
axios: 1.7.7
focus-trap: 7.5.4
fuse.js: 7.0.0
transitivePeerDependencies:
@@ -14843,13 +14761,14 @@ snapshots:
'@fastify/error': 3.4.1
fastq: 1.17.1
axios@1.7.7(debug@4.3.7):
axios@1.7.7:
dependencies:
follow-redirects: 1.15.9(debug@4.3.7)
follow-redirects: 1.15.9
form-data: 4.0.0
proxy-from-env: 1.1.0
transitivePeerDependencies:
- debug
optional: true
b4a@1.6.6: {}
@@ -14980,8 +14899,6 @@ snapshots:
bson@6.8.0: {}
buffer-crc32@0.2.13: {}
buffer-crc32@1.0.0: {}
buffer-from@1.1.2: {}
@@ -15466,8 +15383,6 @@ snapshots:
optionalDependencies:
typescript: 5.6.2
countries-list@3.1.1: {}
crawler-user-agents@1.0.143: {}
crc-32@1.2.2: {}
@@ -15606,8 +15521,6 @@ snapshots:
dataloader@2.2.2: {}
dayjs@1.11.13: {}
db0@0.1.4: {}
de-indent@1.0.2: {}
@@ -16511,11 +16424,6 @@ snapshots:
fast-content-type-parse@1.1.0: {}
fast-csv@5.0.1:
dependencies:
'@fast-csv/format': 5.0.0
'@fast-csv/parse': 5.0.0
fast-decode-uri-component@1.0.1: {}
fast-deep-equal@3.1.3: {}
@@ -16690,9 +16598,8 @@ snapshots:
dependencies:
tabbable: 6.2.0
follow-redirects@1.15.9(debug@4.3.7):
optionalDependencies:
debug: 4.3.7
follow-redirects@1.15.9:
optional: true
fontaine@0.5.0(webpack-sources@3.2.3):
dependencies:
@@ -17287,23 +17194,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
ip-address@9.0.5:
dependencies:
jsbn: 1.1.0
sprintf-js: 1.1.3
ip-location-api@2.0.1(debug@4.3.7):
dependencies:
axios: 1.7.7(debug@4.3.7)
countries-list: 3.1.1
cron: 3.1.7
dayjs: 1.11.13
fast-csv: 5.0.1
ip-address: 9.0.5
yauzl: 3.1.3
transitivePeerDependencies:
- debug
ipaddr.js@1.9.1: {}
ipaddr.js@2.2.0: {}
@@ -17529,8 +17419,6 @@ snapshots:
dependencies:
argparse: 2.0.1
jsbn@1.1.0: {}
jsdoc-type-pratt-parser@4.1.0: {}
jsesc@0.5.0: {}
@@ -17692,24 +17580,10 @@ snapshots:
lodash.defaults@4.2.0: {}
lodash.escaperegexp@4.1.2: {}
lodash.groupby@4.6.0: {}
lodash.isarguments@3.1.0: {}
lodash.isboolean@3.0.3: {}
lodash.isequal@4.5.0: {}
lodash.isfunction@3.0.9: {}
lodash.isnil@4.0.0: {}
lodash.isplainobject@4.0.6: {}
lodash.isundefined@3.0.1: {}
lodash.kebabcase@4.1.1: {}
lodash.memoize@4.1.2: {}
@@ -18943,8 +18817,6 @@ snapshots:
peek-readable@5.2.0: {}
pend@1.2.0: {}
perfect-debounce@1.0.0: {}
pg-int8@1.0.1: {}
@@ -19260,7 +19132,8 @@ snapshots:
forwarded: 0.2.0
ipaddr.js: 1.9.1
proxy-from-env@1.1.0: {}
proxy-from-env@1.1.0:
optional: true
pump@3.0.0:
dependencies:
@@ -19802,8 +19675,6 @@ snapshots:
dependencies:
tslib: 2.7.0
sprintf-js@1.1.3: {}
stable-hash@0.0.4: {}
stack-trace@0.0.10: {}
@@ -21178,11 +21049,6 @@ snapshots:
y18n: 5.0.8
yargs-parser: 21.1.1
yauzl@3.1.3:
dependencies:
buffer-crc32: 0.2.13
pend: 1.2.0
yocto-queue@0.1.0: {}
yocto-queue@1.1.1: {}