Compare commits

...

37 Commits

Author SHA1 Message Date
Bas950
3f65f678b1 chore: release v0.0.25 2024-09-16 20:58:24 +02:00
Bas950
a71b66540b chore: release v0.0.14 2024-09-16 20:58:07 +02:00
Bas van Zanten
e675f74983 feat: update tracing (#1067) 2024-09-16 20:18:35 +02:00
Florian Metz
e9e6639492 chore: release v0.0.13 2024-09-15 03:09:23 +02:00
Florian Metz
3258179040 chore: release v0.0.24 2024-09-15 03:09:13 +02:00
Florian Metz
086d476af2 chore: update hash 2024-09-15 03:09:04 +02:00
Florian Metz
146bf9e270 chore: release v0.0.23 2024-09-15 02:48:56 +02:00
Florian Metz
a02f25ba29 chore: test 2024-09-15 02:48:41 +02:00
Florian Metz
416b65f0d4 chore: release v0.0.12 2024-09-15 02:41:31 +02:00
Florian Metz
f8e9fc832d chore: test 2024-09-15 02:41:16 +02:00
Florian Metz
86b0f07216 chore: test 2024-09-15 02:31:38 +02:00
Florian Metz
9eb5c03877 chore: release v0.0.11 2024-09-15 02:25:50 +02:00
Florian Metz
e63e1270aa chore: release v0.0.22 2024-09-15 02:25:38 +02:00
Florian Metz
f730e71bbf chore: test 2024-09-15 02:25:10 +02:00
Bas950
8b68bf85c8 chore: release v0.0.10 2024-09-13 17:27:44 +02:00
Bas950
e4c794a9ad chore: 202 on disabled flag 2024-09-13 17:27:38 +02:00
Bas950
6e8258d76f chore: release v0.0.21 2024-09-13 15:08:16 +02:00
Bas950
56b796c621 chore: use ky 2024-09-13 15:08:08 +02:00
Bas950
0de59c48b4 chore: release v0.0.20 2024-09-13 14:37:31 +02:00
Bas950
60056e069d chore: update log 2024-09-13 14:37:24 +02:00
Bas950
b6bad90919 chore: release v0.0.9 2024-09-13 14:33:34 +02:00
Bas950
ee21bb9dec chore: release v0.0.20 2024-09-13 14:31:39 +02:00
Bas950
6efac4fef1 feat: use scienceId 2024-09-13 14:31:27 +02:00
Bas950
93424793bd chore: release v0.0.19 2024-09-13 13:46:33 +02:00
Bas950
affcb6a0cf chore: add reason 2024-09-13 13:46:27 +02:00
Bas950
bb56949dfb chore: release v0.0.18 2024-09-13 13:02:31 +02:00
Bas950
c06fe04b65 chore: fix time 2024-09-13 13:02:26 +02:00
Florian Metz
ef976341ba chore: release v0.0.17 2024-09-13 12:33:19 +02:00
Florian Metz
38893891af chore: why does it not abort 2024-09-13 12:33:10 +02:00
Florian Metz
63eeeefda7 chore: release v0.0.16 2024-09-13 12:05:42 +02:00
Florian Metz
056db21cb0 chore: add p-limit dependency for session cleanup 2024-09-13 12:05:37 +02:00
Bas950
d8dc08c6c3 chore: release v0.0.15 2024-09-13 11:55:36 +02:00
Bas950
634391b6e3 chore: always return the key 2024-09-13 11:55:32 +02:00
Florian Metz
c46cf6975a chore: release v0.0.14 2024-09-13 11:52:23 +02:00
Florian Metz
68c6b4fcdc chore: add p-limit dependency for session cleanup 2024-09-13 11:52:00 +02:00
Florian Metz
55fa07d5b5 chore: release v0.0.13 2024-09-13 11:38:49 +02:00
Florian Metz
903c238b33 chore: add timeout to headless session deletion 2024-09-13 11:38:40 +02:00
21 changed files with 634 additions and 102 deletions

4
.gitignore vendored
View File

@@ -3,6 +3,7 @@ out
dist
tmp
lib
data
.vscode
.env
@@ -22,4 +23,5 @@ src/update.ini
!eslint.config.js
coverage
*.tsbuildinfo
*.tsbuildinfo
.DS_Store

10
apps/api-master/environment.d.ts vendored Normal file
View File

@@ -0,0 +1,10 @@
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 }): Promise<void>;
export function reload(options: { fields?: string[]; dataDir?: string; tmpDataDir?: string }): Promise<void>;
}

View File

@@ -1,7 +1,7 @@
{
"name": "@premid/api-master",
"type": "module",
"version": "0.0.12",
"version": "0.0.25",
"private": true,
"description": "PreMiD's api master",
"license": "MPL-2.0",
@@ -14,7 +14,6 @@
"dev": "node --watch --env-file .env --enable-source-maps ."
},
"dependencies": {
"@discordjs/rest": "^2.3.0",
"@envelop/sentry": "^9.0.0",
"@opentelemetry/api": "^1.9.0",
"@opentelemetry/exporter-prometheus": "^0.52.1",
@@ -22,7 +21,10 @@
"@sentry/node": "^8.17.0",
"cron": "^3.1.7",
"debug": "^4.3.6",
"ioredis": "^5.3.2"
"ioredis": "^5.3.2",
"ip-location-api": "^1.0.0",
"ky": "^1.7.2",
"p-limit": "^6.1.0"
},
"devDependencies": {
"@types/debug": "^4.1.12"

View File

@@ -1,4 +1,5 @@
import { REST } from "@discordjs/rest";
import pLimit from "p-limit";
import ky, { HTTPError, TimeoutError } from "ky";
import { mainLog, redis } from "../index.js";
let inProgress = false;
@@ -10,66 +11,54 @@ export async function clearOldSessions() {
inProgress = true;
const now = Date.now();
const pattern = "pmd-api.sessions.*";
let cursor = "0";
let totalSessions = 0;
let cleared = 0;
const batchSize = 100;
let keysToDelete = [];
let keysToDelete: string[] = [];
mainLog("Starting session cleanup");
const limit = pLimit(100); // Create a limit of 100 concurrent operations
do {
//* Use hscan to iterate through sessions
const [nextCursor, result] = await redis.hscan("pmd-api.sessions", cursor, "COUNT", batchSize);
cursor = nextCursor;
totalSessions += result.length / 2;
const [newCursor, keys] = await redis.scan(cursor, "MATCH", pattern, "COUNT", 1000); //* Use SCAN with COUNT for memory efficiency
for (let i = 0; i < result.length; i += 2) {
const key = result[i];
const value = result[i + 1];
cursor = newCursor;
totalSessions += keys.length;
if (!key || !value) {
continue;
}
const deletePromises: Promise<string>[] = [];
const session = JSON.parse(value) as {
for (const key of keys) {
const session = await redis.hgetall(key) as unknown as {
token: string;
session: string;
lastUpdated: number;
};
//* If the session is younger than 30 seconds, skip it
if (now - session.lastUpdated < 30000)
continue;
//* Mark the session for deletion
try {
const discord = new REST({ version: "10", authPrefix: "Bearer" });
discord.setToken(session.token);
await discord.post("/users/@me/headless-sessions/delete", {
body: {
token: session.session,
},
});
}
catch (error) {
mainLog(`Failed to delete session: %O`, (typeof error === "object" && error && "message" in error ? error.message : error));
}
deletePromises.push(limit(() => deleteSession(session, key)));
}
keysToDelete.push(key);
cleared++;
//* Delete in batches to avoid memory bloat
if (keysToDelete.length >= batchSize) {
await redis.hdel("pmd-api.sessions", ...keysToDelete);
keysToDelete = [];
const results = await Promise.allSettled(deletePromises);
results.forEach((result) => {
if (result.status === "fulfilled" && result.value) {
keysToDelete.push(result.value);
cleared++;
}
});
if (keysToDelete.length >= batchSize) {
await redis.del(...keysToDelete);
keysToDelete = [];
}
} while (cursor !== "0");
//* Delete any remaining keys
if (keysToDelete.length > 0) {
await redis.hdel("pmd-api.sessions", ...keysToDelete);
await redis.del(...keysToDelete);
}
if (totalSessions === 0) {
@@ -81,3 +70,31 @@ export async function clearOldSessions() {
inProgress = false;
}
async function deleteSession(session: { token: string; session: string }, key: string): Promise<string> {
try {
await ky.post("https://discord.com/api/v10/users/@me/headless-sessions/delete", {
json: {
token: session.session,
},
headers: {
Authorization: `Bearer ${session.token}`,
},
retry: 3,
timeout: 5000,
});
}
catch (error) {
if (error instanceof TimeoutError) {
mainLog(`Session deletion aborted due to timeout for key ${key}`);
}
else if (error instanceof HTTPError) {
mainLog(`Failed to delete session for key ${key}: [${error.name}] ${error.message} ${JSON.stringify(await error.response.json())}`);
}
else {
mainLog(`Failed to delete session for key ${key}: Unknown error`);
}
}
return key;
}

View File

@@ -0,0 +1,83 @@
import type { ServerResponse } from "node:http";
import type { Attributes } from "@opentelemetry/api";
import { ValueType, diag } from "@opentelemetry/api";
import type { PrometheusExporter, PrometheusSerializer } from "@opentelemetry/exporter-prometheus";
import { AggregationTemporality, DataPointType, type GaugeMetricData, InstrumentType } from "@opentelemetry/sdk-metrics";
const registeredMetrics = new Map<string, ClearableGaugeMetric>();
//* Custom gauge metric class
export class ClearableGaugeMetric {
private data: Map<string, { value: number; attributes: Attributes }>;
private name: string;
private description: string;
constructor(name: string, description: string) {
this.data = new Map();
this.name = name;
this.description = description;
registeredMetrics.set(name, this);
}
set(key: string, value: number, attributes: Attributes) {
this.data.set(key, { value, attributes });
}
clear() {
this.data.clear();
}
toMetricData(): GaugeMetricData {
return {
descriptor: {
name: this.name,
description: this.description,
unit: "",
type: InstrumentType.GAUGE,
valueType: ValueType.INT,
},
dataPointType: DataPointType.GAUGE,
dataPoints: Array.from(this.data.values()).map(({ value, attributes }) => ({
value,
attributes,
startTime: [0, 0],
endTime: [0, 0],
})),
aggregationTemporality: AggregationTemporality.CUMULATIVE,
};
}
get hasData() {
return this.data.size > 0;
}
}
export function updatePrometheusMetrics(prometheusExporter: PrometheusExporter) {
// @ts-expect-error We are modifying a private method
prometheusExporter._exportMetrics = function (this: PrometheusExporter, response: ServerResponse) {
response.statusCode = 200;
response.setHeader("content-type", "text/plain");
this.collect().then(
(collectionResult) => {
const { resourceMetrics, errors } = collectionResult;
if (errors.length) {
diag.error(
"PrometheusExporter: metrics collection errors",
...errors,
);
}
for (const metric of registeredMetrics.values()) {
if (metric.hasData) {
resourceMetrics.scopeMetrics[0]!.metrics.push(metric.toMetricData());
}
}
response.end((this as unknown as { _serializer: PrometheusSerializer })._serializer.serialize(resourceMetrics));
},
(err) => {
response.end(`# failed to export metrics: ${err}`);
},
);
}.bind(prometheusExporter);
}

View File

@@ -0,0 +1,46 @@
import { join } from "node:path";
import process from "node:process";
import { lookup, reload, updateDb } 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");
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");
updateDb({ fields, dataDir, tmpDataDir }).then(async () => {
await reload({ fields, dataDir, tmpDataDir });
log?.("IP location API reloaded");
initialized = true;
reloading = undefined;
resolve();
}).catch(reject);
});
return reloading;
}

View File

@@ -1,13 +1,13 @@
import { redis } from "../index.js";
import { counter } from "../tracing.js";
import { activeSessionsCounter } from "../tracing.js";
let activeActivities = 0;
counter.add(0);
export async function setCounter() {
activeSessionsCounter.add(0);
export async function setSessionCounter() {
const length = await redis.hlen("pmd-api.sessions");
if (length === activeActivities)
return;
const diff = length - activeActivities;
activeActivities = length;
counter.add(diff);
activeSessionsCounter.add(diff);
}

View File

@@ -0,0 +1,64 @@
import { redis } from "../index.js";
import { activeIpsGauge, activePresenceGauge } from "../tracing.js";
import { lookupIp } from "./lookupIp.js";
//* Function to update the gauge with per-service counts
export async function updateActivePresenceGauge() {
const pattern = "pmd-api.heartbeatUpdates.*";
let cursor: string = "0";
const serviceCounts = new Map<string, number>();
const ips = new Map<string, {
presences: string[];
sessions: number;
}>();
do {
const [newCursor, keys] = await redis.scan(cursor, "MATCH", pattern, "COUNT", 1000); //* Use SCAN with COUNT for memory efficiency
cursor = newCursor;
for (const key of keys) {
const hash = await redis.hgetall(key);
const service = hash.service;
const version = hash.version; //* Get version from hash
const ip = hash.ip_address;
if (service && version) {
serviceCounts.set(`${service}:${version}`, (serviceCounts.get(`${service}:${version}`) || 0) + 1);
}
else {
serviceCounts.set("none", (serviceCounts.get("none") || 0) + 1);
}
if (ip) {
const presenceName = service && version ? `${service}:${version}` : undefined;
const ipData = ips.get(ip) || { presences: [], sessions: 0 };
ipData.presences = [...new Set<string>([...ipData.presences, presenceName].filter(Boolean) as string[])];
ipData.sessions++;
ips.set(ip, ipData);
}
}
} while (cursor !== "0");
// Clear previous data
activePresenceGauge.clear();
activeIpsGauge.clear();
// Set new data
for (const [serviceVersion, count] of serviceCounts.entries()) {
const [presence_name, version] = serviceVersion.split(":");
activePresenceGauge.set(serviceVersion, count, {
presence_name,
version,
});
}
await Promise.all(Array.from(ips).map(async ([ip, { presences, sessions }]) => {
const parsed = await lookupIp(ip);
if (parsed) {
activeIpsGauge.set(ip, sessions, {
country: parsed.country,
ip,
latitude: parsed.latitude,
longitude: parsed.longitude,
presence_names: presences,
});
}
}));
}

View File

@@ -3,14 +3,18 @@ import { CronJob } from "cron";
import debug from "debug";
import { clearOldSessions } from "./functions/clearOldSessions.js";
import createRedis from "./functions/createRedis.js";
import { setCounter } from "./functions/setCounter.js";
import { setSessionCounter } from "./functions/setSessionCounter.js";
import "./tracing.js";
import { updateActivePresenceGauge } from "./functions/updateActivePresenceGauge.js";
import { reloadIpLocationApi } from "./functions/lookupIp.js";
export const redis = createRedis();
export const mainLog = debug("api-master");
debug("Starting cron job to clear old sessions");
reloadIpLocationApi();
debug("Starting cron jobs");
void new CronJob(
// Every 5 seconds
@@ -26,7 +30,27 @@ void new CronJob(
// Every second
"* * * * * *",
() => {
setCounter();
setSessionCounter();
},
undefined,
true,
);
void new CronJob(
// Every 5 seconds
"*/5 * * * * *",
() => {
updateActivePresenceGauge();
},
undefined,
true,
);
void new CronJob(
// Every day at 9am
"0 9 * * *",
() => {
reloadIpLocationApi();
},
undefined,
true,

View File

@@ -1,6 +1,7 @@
import { ValueType } from "@opentelemetry/api";
import { PrometheusExporter } from "@opentelemetry/exporter-prometheus";
import { MeterProvider } from "@opentelemetry/sdk-metrics";
import { ClearableGaugeMetric, updatePrometheusMetrics } from "./functions/clearableGaugeMetric.js";
const prometheusExporter = new PrometheusExporter();
@@ -10,9 +11,21 @@ const provider = new MeterProvider({
const meter = provider.getMeter("nice");
export const counter = meter.createUpDownCounter("active_activites", {
description: "Number of active activities",
export const activeSessionsCounter = meter.createUpDownCounter("active_sessions", {
description: "Number of active sessions",
valueType: ValueType.INT,
});
export const activePresenceGauge = new ClearableGaugeMetric(
"active_presences",
"Per presence name+version, active number of users",
);
export const activeIpsGauge = new ClearableGaugeMetric(
"active_ips",
"Per ip, list of presences and the number of sessions",
);
updatePrometheusMetrics(prometheusExporter);
prometheusExporter.startServer();

View File

@@ -3,6 +3,7 @@
"compilerOptions": {
"composite": true,
"rootDir": "src",
"types": ["./environment.d.ts"],
"outDir": "dist"
},
"include": ["src/**/*"]

View File

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

View File

@@ -0,0 +1,10 @@
import process from "node:process";
import { defu } from "defu";
const disabledFlags = process.env.DISABLED_FEATURE_FLAGS?.split(",") ?? [];
const flags = Object.fromEntries(disabledFlags.map(flag => [flag, false]));
export const featureFlags = defu(flags, {
WebSocketManager: true,
SessionKeepAlive: true,
});

View File

@@ -1,13 +1,10 @@
import { readFile } from "node:fs/promises";
import { resolve } from "node:path";
import process from "node:process";
import { useSentry } from "@envelop/sentry";
import { maxAliasesPlugin } from "@escape.tech/graphql-armor-max-aliases";
import { maxDepthPlugin } from "@escape.tech/graphql-armor-max-depth";
import { maxDirectivesPlugin } from "@escape.tech/graphql-armor-max-directives";
import { maxTokensPlugin } from "@escape.tech/graphql-armor-max-tokens";
import fastifyWebsocket from "@fastify/websocket";
import { defu } from "defu";
import fastify from "fastify";
import { createSchema, createYoga } from "graphql-yoga";
@@ -15,6 +12,7 @@ import type { FastifyReply, FastifyRequest } from "fastify";
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 createRedis from "./createRedis.js";
export interface FastifyContext {
@@ -48,7 +46,7 @@ export default async function createServer() {
maxDepthPlugin(),
maxDirectivesPlugin(),
maxTokensPlugin(),
useSentry(),
/* useSentry(), */
],
schema: createSchema<FastifyContext>({
resolvers,
@@ -87,15 +85,7 @@ export default async function createServer() {
});
app.get("/v5/feature-flags", async (request, reply) => {
const disabledFlags = process.env.DISABLED_FEATURE_FLAGS?.split(",") ?? [];
const flags = Object.fromEntries(disabledFlags.map(flag => [flag, false]));
const test = defu(flags, {
WebSocketManager: true,
SessionKeepAlive: true,
});
void reply.send(test);
void reply.send(featureFlags);
});
app.post("/v5/session-keep-alive", sessionKeepAlive);

View File

@@ -1,4 +1,5 @@
import { type } from "arktype";
import { GraphQLError } from "graphql";
import { redis } from "../../../../functions/createServer.js";
import type { MutationResolvers } from "../../../../generated/graphql-v5.js";
@@ -15,7 +16,7 @@ const mutation: MutationResolvers["addScience"] = async (_parent, input) => {
const out = addScienceSchema(input);
if (out instanceof type.errors)
throw new Error(out.summary);
throw new GraphQLError(out.summary);
await redis.hset(
"pmd-api.scienceUpdates",

View File

@@ -1,15 +1,17 @@
import { type } from "arktype";
import { GraphQLError } from "graphql";
import type { MutationResolvers } from "../../../../generated/graphql-v5.js";
import { redis } from "../../../../functions/createServer.js";
const heartbeatSchema = type({
identifier: "string.uuid & string.lower",
presences: {
"identifier": "string.uuid & string.lower",
"presence?": {
service: "string.trim",
version: "string.semver",
language: "string.trim",
since: "number.epoch",
},
extension: {
"extension": {
"version": "string.semver",
"language": "string.trim",
"connected?": {
@@ -19,19 +21,29 @@ const heartbeatSchema = type({
},
});
const mutation: MutationResolvers["heartbeat"] = async (_parent, input) => {
const mutation: MutationResolvers["heartbeat"] = async (_parent, input, context) => {
const out = heartbeatSchema(input);
if (out instanceof type.errors)
throw new Error(out.summary);
throw new GraphQLError(out.summary);
// ! Disabled for now
/* await redis.setex(
`pmd-api.heartbeatUpdates.${data.identifier}`,
// 5 minutes
300,
JSON.stringify(data)
); */
//* Get the user's IP address from Cloudflare headers or fallback to the request IP
const userIp = context.request.headers.get("cf-connecting-ip") || context.request.ip;
// * Use Redis Hash with 'service' in the key to store heartbeat data
const redisKey = `pmd-api.heartbeatUpdates.${out.identifier}`;
await redis.hset(redisKey, {
service: out.presence?.service,
version: out.presence?.version,
language: out.presence?.language,
since: out.presence?.since.toString(),
extension_version: out.extension.version,
extension_language: out.extension.language,
extension_connected_app: out.extension.connected?.app?.toString(),
extension_connected_discord: out.extension.connected?.discord?.toString(),
ip_address: userIp,
});
await redis.expire(redisKey, 300);
return {
__typename: "HeartbeatResult",

View File

@@ -3,8 +3,6 @@ import process from "node:process";
import * as Sentry from "@sentry/node";
import { connect } from "mongoose";
import "./tracing.js";
// eslint-disable-next-line perfectionist/sort-imports
import createServer from "./functions/createServer.js";
// TODO SETUP SENTRY

View File

@@ -4,17 +4,25 @@ import { type } from "arktype";
import { Routes } from "discord-api-types/v10";
import type { FastifyReply, FastifyRequest } from "fastify";
import { redis } from "../functions/createServer.js";
import { featureFlags } from "../constants.js";
const schema = type({
token: "string.trim",
session: "string.trim",
version: "string.semver & string.trim",
scienceId: "string.trim",
});
export async function sessionKeepAlive(request: FastifyRequest, reply: FastifyReply) {
//* Get the 2 headers
if (!featureFlags.SessionKeepAlive)
return reply.status(202).send();
//* Get the headers
const out = schema({
token: request.headers["x-token"],
session: request.headers["x-session"],
version: request.headers["x-version"] ?? "2.6.8",
scienceId: request.headers["x-science-id"] ?? request.headers["x-token"],
});
if (out instanceof type.errors)
@@ -23,17 +31,15 @@ export async function sessionKeepAlive(request: FastifyRequest, reply: FastifyRe
if (!await isTokenValid(out.token))
return reply.status(400).send({ code: "INVALID_TOKEN", message: "The token is invalid" });
await redis.hset(
"pmd-api.sessions",
out.token,
JSON.stringify({
session: out.session,
token: out.token,
lastUpdated: Date.now(),
}),
);
const redisKey = `pmd-api.sessions.${out.scienceId}`;
await redis.hset(redisKey, {
session: out.session,
token: out.token,
lastUpdated: Date.now(),
});
await redis.expire(redisKey, 300); // 5 minutes
const interval = Number.parseInt(process.env.SESSION_KEEP_ALIVE_INTERVAL ?? "5000");
const interval = Number.parseInt(process.env.SESSION_KEEP_ALIVE_INTERVAL ?? "5000"); // 5 seconds
return reply.status(200).send({
code: "OK",

View File

@@ -38,5 +38,10 @@
"prettier": "^3.2.5",
"typescript": "^5.5.4",
"vitest": "^2.0.2"
},
"pnpm": {
"patchedDependencies": {
"ip-location-api@1.0.0": "patches/ip-location-api@1.0.0.patch"
}
}
}

View File

@@ -0,0 +1,62 @@
diff --git a/browser/country/README.md b/browser/country/README.md
deleted file mode 100644
index ac8fc934b4998f2a2cb7a92bf68bbdadd9e3d36d..0000000000000000000000000000000000000000
diff --git a/browser/country-extra/README.md b/browser/country-extra/README.md
deleted file mode 100644
index 71e7237722915b2697b56ccb14171524eb4b40fb..0000000000000000000000000000000000000000
diff --git a/browser/geocode/README.md b/browser/geocode/README.md
deleted file mode 100644
index 9d9a2205061f332b363b82d7561a0e3829d5bf2c..0000000000000000000000000000000000000000
diff --git a/browser/geocode-extra/README.md b/browser/geocode-extra/README.md
deleted file mode 100644
index 38e17eebdd8532d07b460fbe7f385f36625ece9d..0000000000000000000000000000000000000000
diff --git a/src/db.mjs b/src/db.mjs
index 378b8a22084f860cc89720d1783a235c034717b2..cbffe1eaa84a94df536059d4b4af3f8f5ceb0ca7 100644
--- a/src/db.mjs
+++ b/src/db.mjs
@@ -33,7 +33,12 @@ export const update = async () => {
if(refreshTmpDir || !fsSync.existsSync(setting.tmpDataDir)){
// refresh tmp folder
await rimraf(setting.tmpDataDir)
- await fs.mkdir(setting.tmpDataDir)
+ await fs.mkdir(setting.tmpDataDir, {recursive: true})
+ }
+
+ // When specifying a custom dataDir, it doesn't always exists
+ if (!fsSync.existsSync(setting.dataDir)){
+ await fs.mkdir(setting.dataDir, {recursive: true})
}
console.log('Downloading database')
diff --git a/src/main.mjs b/src/main.mjs
index d001aca60902bc7fe41271c6fa7a0b6648607b15..5b2c125d8e7590afee82c794bf771accd656b2b7 100644
--- a/src/main.mjs
+++ b/src/main.mjs
@@ -3,7 +3,7 @@ import fs from 'fs/promises'
import fsSync from 'fs'
import path from 'path'
import { exec } from 'child_process'
-
+import { fileURLToPath } from "url"
import { countries, continents } from 'countries-list'
import { setting, setSetting, getSettingCmd } from './setting.mjs'
@@ -14,6 +14,9 @@ const v6db = setting.v6
const locFieldHash = setting.locFieldHash
const mainFieldHash = setting.mainFieldHash
+const __filename = fileURLToPath(import.meta.url)
+const __dirname = path.dirname(__filename)
+
//---------------------------------------
// Database lookup
//---------------------------------------
@@ -235,7 +238,7 @@ export const updateDb = (_setting) => {
// However, db.js import many external modules, it makes slow down the startup time and uses more memory.
// Therefore, we use exec() to run the script in the other process.
return new Promise((resolve, reject) => {
- var cmd = 'node ' + path.resolve(__dirname, '..', 'script', 'updatedb.js')
+ var cmd = 'node ' + path.resolve(__dirname, '..', 'script', 'updatedb.mjs')
var arg
if(_setting){
var oldSetting = Object.assign({}, setting)

212
pnpm-lock.yaml generated
View File

@@ -4,6 +4,11 @@ settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
patchedDependencies:
ip-location-api@1.0.0:
hash: rjznwunaa6otlvuetmywn36scu
path: patches/ip-location-api@1.0.0.patch
importers:
.:
@@ -53,9 +58,6 @@ importers:
apps/api-master:
dependencies:
'@discordjs/rest':
specifier: ^2.3.0
version: 2.4.0
'@envelop/sentry':
specifier: ^9.0.0
version: 9.0.0(@envelop/core@5.0.2)(@sentry/node@8.30.0)(graphql@16.9.0)
@@ -80,6 +82,15 @@ importers:
ioredis:
specifier: ^5.3.2
version: 5.4.1
ip-location-api:
specifier: ^1.0.0
version: 1.0.0(patch_hash=rjznwunaa6otlvuetmywn36scu)(debug@4.3.7)
ky:
specifier: ^1.7.2
version: 1.7.2
p-limit:
specifier: ^6.1.0
version: 6.1.0
devDependencies:
'@types/debug':
specifier: ^4.1.12
@@ -174,7 +185,7 @@ importers:
devDependencies:
vitepress:
specifier: 1.3.1
version: 1.3.1(@algolia/client-search@5.4.0)(@types/node@22.5.4)(change-case@4.1.2)(postcss@8.4.45)(sass@1.78.0)(search-insights@2.17.2)(terser@5.32.0)(typescript@5.6.2)
version: 1.3.1(@algolia/client-search@5.4.0)(@types/node@22.5.4)(axios@1.7.7)(change-case@4.1.2)(postcss@8.4.45)(sass@1.78.0)(search-insights@2.17.2)(terser@5.32.0)(typescript@5.6.2)
apps/pd:
dependencies:
@@ -265,7 +276,7 @@ importers:
version: 3.13.1(magicast@0.3.5)(rollup@4.21.2)(webpack-sources@3.2.3)
'@nuxt/scripts':
specifier: ^0.8.5
version: 0.8.5(@nuxt/devtools@1.4.2(rollup@4.21.2)(vite@5.4.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0))(vue@3.5.4(typescript@5.5.4))(webpack-sources@3.2.3))(@unocss/webpack@0.62.3(rollup@4.21.2)(webpack@5.94.0(esbuild@0.23.1)))(@vue/compiler-core@3.5.4)(fuse.js@7.0.0)(ioredis@5.4.1)(magicast@0.3.5)(nuxt@3.13.1(@parcel/watcher@2.4.1)(@types/node@22.5.4)(eslint@9.9.1(jiti@1.21.6))(ioredis@5.4.1)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.21.2)(sass@1.78.0)(terser@5.32.0)(typescript@5.5.4)(vite@5.4.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0))(vue-tsc@2.1.6(typescript@5.5.4))(webpack-sources@3.2.3))(postcss@8.4.45)(rollup@4.21.2)(typescript@5.5.4)(vite@5.4.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0))(vue@3.5.4(typescript@5.5.4))(webpack-sources@3.2.3)(webpack@5.94.0(esbuild@0.23.1))
version: 0.8.5(@nuxt/devtools@1.4.2(rollup@4.21.2)(vite@5.4.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0))(vue@3.5.4(typescript@5.5.4))(webpack-sources@3.2.3))(@unocss/webpack@0.62.3(rollup@4.21.2)(webpack@5.94.0(esbuild@0.23.1)))(@vue/compiler-core@3.5.4)(axios@1.7.7)(fuse.js@7.0.0)(ioredis@5.4.1)(magicast@0.3.5)(nuxt@3.13.1(@parcel/watcher@2.4.1)(@types/node@22.5.4)(eslint@9.9.1(jiti@1.21.6))(ioredis@5.4.1)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.21.2)(sass@1.78.0)(terser@5.32.0)(typescript@5.5.4)(vite@5.4.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0))(vue-tsc@2.1.6(typescript@5.5.4))(webpack-sources@3.2.3))(postcss@8.4.45)(rollup@4.21.2)(typescript@5.5.4)(vite@5.4.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0))(vue@3.5.4(typescript@5.5.4))(webpack-sources@3.2.3)(webpack@5.94.0(esbuild@0.23.1))
'@nuxtjs/device':
specifier: ^3.2.2
version: 3.2.2(@parcel/watcher@2.4.1)(@types/node@22.5.4)(@vitest/ui@2.0.5(vitest@2.0.5))(h3@1.12.0)(ioredis@5.4.1)(jiti@1.21.6)(magicast@0.3.5)(nitropack@2.9.7(magicast@0.3.5)(webpack-sources@3.2.3))(nuxi@3.13.1)(optionator@0.9.4)(playwright-core@1.47.0)(rollup@4.21.2)(sass@1.78.0)(terser@5.32.0)(vite@5.4.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0))(vue-router@4.4.4(vue@3.5.4(typescript@5.5.4)))(vue-tsc@2.1.6(typescript@5.5.4))(vue@3.5.4(typescript@5.5.4))(webpack-sources@3.2.3)
@@ -1691,6 +1702,12 @@ 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'}
@@ -4024,6 +4041,9 @@ packages:
avvio@8.3.2:
resolution: {integrity: sha512-st8e519GWHa/azv8S87mcJvZs4WsgTBjOw/Ih1CP6u+8SZvcOeAYNG6JbsIrAUUJJ7JfmrnOkR8ipDS+u9SIRQ==}
axios@1.7.7:
resolution: {integrity: sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==}
b4a@1.6.6:
resolution: {integrity: sha512-5Tk1HLk6b6ctmjIkAcU/Ujv/1WqiDl0F0JdRCR80VsOcUlHcu7pWeWRlOqQLHfDEsVx9YH/aif5AG4ehoCtTmg==}
@@ -4119,6 +4139,9 @@ 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'}
@@ -4511,6 +4534,9 @@ 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==}
@@ -4639,6 +4665,9 @@ 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:
@@ -5236,6 +5265,10 @@ 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==}
@@ -5378,6 +5411,15 @@ packages:
focus-trap@7.5.4:
resolution: {integrity: sha512-N7kHdlgsO/v+iD/dMoJKtsSqs5Dz/dXZVebRgJw23LDk+jMi/974zyiOYDziY2JPp8xivq9BmUGwIJMiuSBi7w==}
follow-redirects@1.15.9:
resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==}
engines: {node: '>=4.0'}
peerDependencies:
debug: '*'
peerDependenciesMeta:
debug:
optional: true
fontaine@0.5.0:
resolution: {integrity: sha512-vPDSWKhVAfTx4hRKT777+N6Szh2pAosAuzLpbppZ6O3UdD/1m6OlHjNcC3vIbgkRTIcLjzySLHXzPeLO2rE8cA==}
@@ -5830,6 +5872,14 @@ 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@1.0.0:
resolution: {integrity: sha512-kx2597JI+SzijYEZ0X8zG3u77wVlcCwoJ9feHt11+COa39o4gzPJC6D62P0O6AAwrT0yUPI7JSo10e5tpFBzUA==}
engines: {node: '>=14.8.0'}
ipaddr.js@1.9.1:
resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==}
engines: {node: '>= 0.10'}
@@ -6063,6 +6113,9 @@ 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'}
@@ -6143,6 +6196,10 @@ packages:
kolorist@1.8.0:
resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==}
ky@1.7.2:
resolution: {integrity: sha512-OzIvbHKKDpi60TnF9t7UUVAF1B4mcqc02z5PIvrm08Wyb+yOcz63GRvEuVxNT18a9E1SrNouhB4W2NNLeD7Ykg==}
engines: {node: '>=18'}
launch-editor@2.9.1:
resolution: {integrity: sha512-Gcnl4Bd+hRO9P9icCP/RVVT2o8SFlPXofuCxvA2SaZuH45whSvf5p8x5oih5ftLiVhEI4sp5xDY+R+b3zJBh5w==}
@@ -6216,12 +6273,33 @@ 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==}
@@ -6855,6 +6933,10 @@ packages:
resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
p-limit@6.1.0:
resolution: {integrity: sha512-H0jc0q1vOzlEk0TqAKXKZxdl7kX3OFUzCnNVUnq5Pc3DGo0kpeaMuPqxQn235HibwBEb0/pm9dgKTjXy66fBkg==}
engines: {node: '>=18'}
p-locate@4.1.0:
resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==}
engines: {node: '>=8'}
@@ -7003,6 +7085,9 @@ 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==}
@@ -7331,6 +7416,9 @@ packages:
resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}
engines: {node: '>= 0.10'}
proxy-from-env@1.1.0:
resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
pump@3.0.0:
resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==}
@@ -7799,6 +7887,9 @@ 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==}
@@ -8876,6 +8967,10 @@ 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'}
@@ -10235,6 +10330,23 @@ 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
@@ -11132,7 +11244,7 @@ snapshots:
- supports-color
- webpack-sources
'@nuxt/devtools-ui-kit@1.4.1(@nuxt/devtools@1.4.2(rollup@4.21.2)(vite@5.4.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0))(vue@3.5.4(typescript@5.5.4))(webpack-sources@3.2.3))(@unocss/webpack@0.62.3(rollup@4.21.2)(webpack@5.94.0(esbuild@0.23.1)))(@vue/compiler-core@3.5.4)(fuse.js@7.0.0)(magicast@0.3.5)(nuxt@3.13.1(@parcel/watcher@2.4.1)(@types/node@22.5.4)(eslint@9.9.1(jiti@1.21.6))(ioredis@5.4.1)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.21.2)(sass@1.78.0)(terser@5.32.0)(typescript@5.5.4)(vite@5.4.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0))(vue-tsc@2.1.6(typescript@5.5.4))(webpack-sources@3.2.3))(postcss@8.4.45)(rollup@4.21.2)(vite@5.4.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0))(vue@3.5.4(typescript@5.5.4))(webpack-sources@3.2.3)(webpack@5.94.0(esbuild@0.23.1))':
'@nuxt/devtools-ui-kit@1.4.1(@nuxt/devtools@1.4.2(rollup@4.21.2)(vite@5.4.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0))(vue@3.5.4(typescript@5.5.4))(webpack-sources@3.2.3))(@unocss/webpack@0.62.3(rollup@4.21.2)(webpack@5.94.0(esbuild@0.23.1)))(@vue/compiler-core@3.5.4)(axios@1.7.7)(fuse.js@7.0.0)(magicast@0.3.5)(nuxt@3.13.1(@parcel/watcher@2.4.1)(@types/node@22.5.4)(eslint@9.9.1(jiti@1.21.6))(ioredis@5.4.1)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.21.2)(sass@1.78.0)(terser@5.32.0)(typescript@5.5.4)(vite@5.4.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0))(vue-tsc@2.1.6(typescript@5.5.4))(webpack-sources@3.2.3))(postcss@8.4.45)(rollup@4.21.2)(vite@5.4.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0))(vue@3.5.4(typescript@5.5.4))(webpack-sources@3.2.3)(webpack@5.94.0(esbuild@0.23.1))':
dependencies:
'@iconify-json/carbon': 1.2.1
'@iconify-json/logos': 1.2.0
@@ -11148,7 +11260,7 @@ snapshots:
'@unocss/preset-mini': 0.62.3
'@unocss/reset': 0.62.3
'@vueuse/core': 11.0.3(vue@3.5.4(typescript@5.5.4))
'@vueuse/integrations': 11.0.3(focus-trap@7.5.4)(fuse.js@7.0.0)(vue@3.5.4(typescript@5.5.4))
'@vueuse/integrations': 11.0.3(axios@1.7.7)(focus-trap@7.5.4)(fuse.js@7.0.0)(vue@3.5.4(typescript@5.5.4))
'@vueuse/nuxt': 11.0.3(magicast@0.3.5)(nuxt@3.13.1(@parcel/watcher@2.4.1)(@types/node@22.5.4)(eslint@9.9.1(jiti@1.21.6))(ioredis@5.4.1)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.21.2)(sass@1.78.0)(terser@5.32.0)(typescript@5.5.4)(vite@5.4.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0))(vue-tsc@2.1.6(typescript@5.5.4))(webpack-sources@3.2.3))(rollup@4.21.2)(vue@3.5.4(typescript@5.5.4))(webpack-sources@3.2.3)
defu: 6.1.4
focus-trap: 7.5.4
@@ -11526,10 +11638,10 @@ snapshots:
- supports-color
- webpack-sources
'@nuxt/scripts@0.8.5(@nuxt/devtools@1.4.2(rollup@4.21.2)(vite@5.4.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0))(vue@3.5.4(typescript@5.5.4))(webpack-sources@3.2.3))(@unocss/webpack@0.62.3(rollup@4.21.2)(webpack@5.94.0(esbuild@0.23.1)))(@vue/compiler-core@3.5.4)(fuse.js@7.0.0)(ioredis@5.4.1)(magicast@0.3.5)(nuxt@3.13.1(@parcel/watcher@2.4.1)(@types/node@22.5.4)(eslint@9.9.1(jiti@1.21.6))(ioredis@5.4.1)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.21.2)(sass@1.78.0)(terser@5.32.0)(typescript@5.5.4)(vite@5.4.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0))(vue-tsc@2.1.6(typescript@5.5.4))(webpack-sources@3.2.3))(postcss@8.4.45)(rollup@4.21.2)(typescript@5.5.4)(vite@5.4.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0))(vue@3.5.4(typescript@5.5.4))(webpack-sources@3.2.3)(webpack@5.94.0(esbuild@0.23.1))':
'@nuxt/scripts@0.8.5(@nuxt/devtools@1.4.2(rollup@4.21.2)(vite@5.4.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0))(vue@3.5.4(typescript@5.5.4))(webpack-sources@3.2.3))(@unocss/webpack@0.62.3(rollup@4.21.2)(webpack@5.94.0(esbuild@0.23.1)))(@vue/compiler-core@3.5.4)(axios@1.7.7)(fuse.js@7.0.0)(ioredis@5.4.1)(magicast@0.3.5)(nuxt@3.13.1(@parcel/watcher@2.4.1)(@types/node@22.5.4)(eslint@9.9.1(jiti@1.21.6))(ioredis@5.4.1)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.21.2)(sass@1.78.0)(terser@5.32.0)(typescript@5.5.4)(vite@5.4.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0))(vue-tsc@2.1.6(typescript@5.5.4))(webpack-sources@3.2.3))(postcss@8.4.45)(rollup@4.21.2)(typescript@5.5.4)(vite@5.4.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0))(vue@3.5.4(typescript@5.5.4))(webpack-sources@3.2.3)(webpack@5.94.0(esbuild@0.23.1))':
dependencies:
'@nuxt/devtools-kit': 1.4.1(magicast@0.3.5)(rollup@4.21.2)(vite@5.4.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0))(webpack-sources@3.2.3)
'@nuxt/devtools-ui-kit': 1.4.1(@nuxt/devtools@1.4.2(rollup@4.21.2)(vite@5.4.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0))(vue@3.5.4(typescript@5.5.4))(webpack-sources@3.2.3))(@unocss/webpack@0.62.3(rollup@4.21.2)(webpack@5.94.0(esbuild@0.23.1)))(@vue/compiler-core@3.5.4)(fuse.js@7.0.0)(magicast@0.3.5)(nuxt@3.13.1(@parcel/watcher@2.4.1)(@types/node@22.5.4)(eslint@9.9.1(jiti@1.21.6))(ioredis@5.4.1)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.21.2)(sass@1.78.0)(terser@5.32.0)(typescript@5.5.4)(vite@5.4.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0))(vue-tsc@2.1.6(typescript@5.5.4))(webpack-sources@3.2.3))(postcss@8.4.45)(rollup@4.21.2)(vite@5.4.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0))(vue@3.5.4(typescript@5.5.4))(webpack-sources@3.2.3)(webpack@5.94.0(esbuild@0.23.1))
'@nuxt/devtools-ui-kit': 1.4.1(@nuxt/devtools@1.4.2(rollup@4.21.2)(vite@5.4.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0))(vue@3.5.4(typescript@5.5.4))(webpack-sources@3.2.3))(@unocss/webpack@0.62.3(rollup@4.21.2)(webpack@5.94.0(esbuild@0.23.1)))(@vue/compiler-core@3.5.4)(axios@1.7.7)(fuse.js@7.0.0)(magicast@0.3.5)(nuxt@3.13.1(@parcel/watcher@2.4.1)(@types/node@22.5.4)(eslint@9.9.1(jiti@1.21.6))(ioredis@5.4.1)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.21.2)(sass@1.78.0)(terser@5.32.0)(typescript@5.5.4)(vite@5.4.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0))(vue-tsc@2.1.6(typescript@5.5.4))(webpack-sources@3.2.3))(postcss@8.4.45)(rollup@4.21.2)(vite@5.4.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0))(vue@3.5.4(typescript@5.5.4))(webpack-sources@3.2.3)(webpack@5.94.0(esbuild@0.23.1))
'@nuxt/kit': 3.13.1(magicast@0.3.5)(rollup@4.21.2)(webpack-sources@3.2.3)
'@types/google.maps': 3.58.0
'@types/stripe-v3': 3.1.33
@@ -13739,24 +13851,26 @@ snapshots:
- '@vue/composition-api'
- vue
'@vueuse/integrations@10.11.1(change-case@4.1.2)(focus-trap@7.5.4)(vue@3.5.4(typescript@5.6.2))':
'@vueuse/integrations@10.11.1(axios@1.7.7)(change-case@4.1.2)(focus-trap@7.5.4)(vue@3.5.4(typescript@5.6.2))':
dependencies:
'@vueuse/core': 10.11.1(vue@3.5.4(typescript@5.6.2))
'@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)
change-case: 4.1.2
focus-trap: 7.5.4
transitivePeerDependencies:
- '@vue/composition-api'
- vue
'@vueuse/integrations@11.0.3(focus-trap@7.5.4)(fuse.js@7.0.0)(vue@3.5.4(typescript@5.5.4))':
'@vueuse/integrations@11.0.3(axios@1.7.7)(focus-trap@7.5.4)(fuse.js@7.0.0)(vue@3.5.4(typescript@5.5.4))':
dependencies:
'@vueuse/core': 11.0.3(vue@3.5.4(typescript@5.5.4))
'@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)
focus-trap: 7.5.4
fuse.js: 7.0.0
transitivePeerDependencies:
@@ -14117,6 +14231,14 @@ snapshots:
'@fastify/error': 3.4.1
fastq: 1.17.1
axios@1.7.7(debug@4.3.7):
dependencies:
follow-redirects: 1.15.9(debug@4.3.7)
form-data: 4.0.0
proxy-from-env: 1.1.0
transitivePeerDependencies:
- debug
b4a@1.6.6: {}
babel-plugin-syntax-trailing-function-commas@7.0.0-beta.0: {}
@@ -14244,6 +14366,8 @@ snapshots:
bson@6.8.0: {}
buffer-crc32@0.2.13: {}
buffer-crc32@1.0.0: {}
buffer-from@1.1.2: {}
@@ -14719,6 +14843,8 @@ snapshots:
optionalDependencies:
typescript: 5.6.2
countries-list@3.1.1: {}
crawler-user-agents@1.0.143: {}
crc-32@1.2.2: {}
@@ -14857,6 +14983,8 @@ snapshots:
dataloader@2.2.2: {}
dayjs@1.11.13: {}
db0@0.1.4: {}
de-indent@1.0.2: {}
@@ -15695,6 +15823,11 @@ 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: {}
@@ -15865,6 +15998,10 @@ snapshots:
dependencies:
tabbable: 6.2.0
follow-redirects@1.15.9(debug@4.3.7):
optionalDependencies:
debug: 4.3.7
fontaine@0.5.0(webpack-sources@3.2.3):
dependencies:
'@capsizecss/metrics': 2.2.0
@@ -16449,6 +16586,22 @@ snapshots:
transitivePeerDependencies:
- supports-color
ip-address@9.0.5:
dependencies:
jsbn: 1.1.0
sprintf-js: 1.1.3
ip-location-api@1.0.0(patch_hash=rjznwunaa6otlvuetmywn36scu)(debug@4.3.7):
dependencies:
axios: 1.7.7(debug@4.3.7)
countries-list: 3.1.1
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: {}
@@ -16671,6 +16824,8 @@ snapshots:
dependencies:
argparse: 2.0.1
jsbn@1.1.0: {}
jsdoc-type-pratt-parser@4.1.0: {}
jsesc@0.5.0: {}
@@ -16731,6 +16886,8 @@ snapshots:
kolorist@1.8.0: {}
ky@1.7.2: {}
launch-editor@2.9.1:
dependencies:
picocolors: 1.1.0
@@ -16828,10 +16985,24 @@ 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: {}
@@ -17895,6 +18066,10 @@ snapshots:
dependencies:
yocto-queue: 1.1.1
p-limit@6.1.0:
dependencies:
yocto-queue: 1.1.1
p-locate@4.1.0:
dependencies:
p-limit: 2.3.0
@@ -18037,6 +18212,8 @@ snapshots:
peek-readable@5.2.0: {}
pend@1.2.0: {}
perfect-debounce@1.0.0: {}
pg-int8@1.0.1: {}
@@ -18345,6 +18522,8 @@ snapshots:
forwarded: 0.2.0
ipaddr.js: 1.9.1
proxy-from-env@1.1.0: {}
pump@3.0.0:
dependencies:
end-of-stream: 1.4.4
@@ -18878,6 +19057,8 @@ snapshots:
dependencies:
tslib: 2.6.3
sprintf-js@1.1.3: {}
stable-hash@0.0.4: {}
stackback@0.0.2: {}
@@ -19745,7 +19926,7 @@ snapshots:
sass: 1.78.0
terser: 5.32.0
vitepress@1.3.1(@algolia/client-search@5.4.0)(@types/node@22.5.4)(change-case@4.1.2)(postcss@8.4.45)(sass@1.78.0)(search-insights@2.17.2)(terser@5.32.0)(typescript@5.6.2):
vitepress@1.3.1(@algolia/client-search@5.4.0)(@types/node@22.5.4)(axios@1.7.7)(change-case@4.1.2)(postcss@8.4.45)(sass@1.78.0)(search-insights@2.17.2)(terser@5.32.0)(typescript@5.6.2):
dependencies:
'@docsearch/css': 3.6.1
'@docsearch/js': 3.6.1(@algolia/client-search@5.4.0)(search-insights@2.17.2)
@@ -19756,7 +19937,7 @@ snapshots:
'@vue/devtools-api': 7.4.5
'@vue/shared': 3.5.4
'@vueuse/core': 10.11.1(vue@3.5.4(typescript@5.6.2))
'@vueuse/integrations': 10.11.1(change-case@4.1.2)(focus-trap@7.5.4)(vue@3.5.4(typescript@5.6.2))
'@vueuse/integrations': 10.11.1(axios@1.7.7)(change-case@4.1.2)(focus-trap@7.5.4)(vue@3.5.4(typescript@5.6.2))
focus-trap: 7.5.4
mark.js: 8.11.1
minisearch: 7.1.0
@@ -20169,6 +20350,11 @@ 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: {}