feat: mig to full cf wrangler api workers

This commit is contained in:
Jyotirmoy Bandyopadhayaya 2023-09-24 16:39:24 +05:30
parent 5c8c76aa5c
commit 61df1cbcd4
No known key found for this signature in database
GPG Key ID: 3C83E0D139DC0CBD
28 changed files with 410 additions and 622 deletions

View File

@ -8,6 +8,8 @@
"private": true,
"scripts": {
"dev": "dotnev -- turbo dev",
"build": "dotenv -- turbo build",
"start": "dotenv -- turbo start",
"prettier": "prettier --write \"**/*.{ts,tsx,js,jsx,json,css,scss,md}\"",
"prepare": "husky install",
"configure-husky": "npx husky install && npx husky add .husky/pre-commit \"npx --no-install lint-staged\""

View File

@ -12,4 +12,7 @@ uploads/*.mp4
uploads/*.mp3
uploads/*.zip
uploads/*.rar
uploads/*.pdf
uploads/*.pdf
# CF secrets
secrets.json

View File

@ -5,17 +5,21 @@ import {
IConfigController,
ConfigKeysTypes,
} from '../interfaces/config.interface';
import { Bindings, Variables } from "../types"
export default class ConfigController
extends ConfigService
implements IConfigController
{
public getAllConfig = async (
ctx: Context
ctx: Context<{
Bindings: Bindings,
Variables: Variables
}>
) => {
let config;
try {
config = await this.getAllConfigS();
config = await this.getAllConfigS(ctx);
} catch (error) {
return ctx.json({
error,
@ -31,7 +35,7 @@ export default class ConfigController
const { key, value } = await ctx.req.json();
if (!key || !value)
throw new Error("Invalid Request");
await this.setConfigS(key, value);
await this.setConfigS(ctx, key, value);
return ctx.json(
makeResponse({
message: 'Config updated successfully',
@ -52,7 +56,7 @@ export default class ConfigController
const { key } = ctx.req.param() as { key: ConfigKeysTypes };
if (!key)
throw new Error("Invalid Request");
const config = await this.getConfigS(key);
const config = await this.getConfigS(ctx, key);
ctx.json(makeResponse(config));
} catch (error) {
return ctx.json({

View File

@ -1,6 +1,4 @@
import { NextFunction, Response, Request } from 'express';
import GistService from '../services/gist.service';
import { ModRequest } from '../types';
import { makeResponse } from '../libs';
import {
GistRep,
@ -9,6 +7,13 @@ import {
} from '../interfaces/gists.interface';
import { Context } from 'hono';
type Bindings = {
SHX_BUCKET: R2Bucket
}
type Variables = {
user: any
}
export default class GistController
extends GistService
implements IGistController

View File

@ -1,17 +0,0 @@
import { Context } from 'hono';
import { makeResponse } from '../libs';
import { CustomError } from '../libs/error';
import InfoService from '../services/info.service';
export default class InfoController extends InfoService {
public static get = async (
ctx: Context
) => {
try {
const data = await this.getSystemInfo();
return ctx.json(makeResponse(data));
} catch (error) {
return ctx.json(error);
}
};
}

View File

@ -2,83 +2,32 @@ import { Context } from 'hono';
import Uploader from '../services/upload.service';
import { makeResponse } from '../libs';
import { IUploaderController, UploadRep } from '../interfaces/upload.interface';
import { Bindings, Variables } from '../types';
export default class UploadController
extends Uploader
implements IUploaderController
{
public upload = async (
ctx: Context
ctx: Context<{ Bindings: Bindings, Variables: Variables }>
) => {
try {
const { file } = await ctx.req.parseBody();
const { file } = await ctx.req.parseBody() as {
file: File;
}
if (!file) {
throw new Error('Please upload a file');
}
let data: UploadRep = await this.uploadS(file, await await ctx.get("user"));
data = {
...data,
url: data.upload_url,
};
return ctx.json(makeResponse(data));
} catch (error) {
return ctx.json(error)
}
};
public uploadImage = async (
ctx: Context
) => {
try {
const { file } = await ctx.req.parseBody();
if (!file) {
throw new Error('Please upload a image');
}
let data: UploadRep = await this.uploadImageS(file, await ctx.get("user"));
data = {
...data,
url: data.upload_url,
};
return ctx.json(makeResponse(data));
} catch (error) {
return ctx.json(error)
}
};
public uploadImageFromURL = async (
ctx: Context
) => {
try {
const { url } = await ctx.req.json();
if (!url) {
throw new Error('Please provide a url');
}
let data: UploadRep = await this.uploadImageViaURLS(url, await ctx.get("user"));
data = {
...data,
url: data.upload_url,
};
return ctx.json(makeResponse(data));
} catch (error) {
return ctx.json(error)
}
};
public uploadFileFromURL = async (
ctx: Context
) => {
try {
const { url } = await ctx.req.json();
if (!url) {
throw new Error('Please provide a url');
}
let data: UploadRep = await this.uploadFileViaURLS(url, await ctx.get("user"));
const file_name = Date.now() + "-" + file.name;
const shx_upload = await ctx.env.SHX_BUCKET.put(file_name, ctx.req.body);
console.log(shx_upload);
let data: UploadRep = await this.uploadS(file_name, await ctx.get("user"));
data = {
...data,
url: data.upload_url,
};
return ctx.json(makeResponse(data));
} catch (error) {
console.log(error)
return ctx.json(error)
}
};

View File

@ -1,123 +0,0 @@
import * as redis from 'redis';
import NodeCache from 'node-cache';
import { logger } from '../libs';
export type CacheEnvironment = 'inmemory' | 'redis';
export default class CacheClient {
private static _clientMode: CacheEnvironment;
private static _redisClient: redis.RedisClientType;
private static _nodeClient: NodeCache;
static get client() {
return this._clientMode === 'redis' ? this._redisClient : this._nodeClient;
}
static get env() {
return this._clientMode;
}
static init(forceEnv?: CacheEnvironment) {
const env = forceEnv || process.env.CACHE_ENV || 'inmemory';
if (!['inmemory', 'redis'].includes(env))
throw new Error(
"Invalid Caching Environment, expected - ['inmemory', 'redis'], received - " +
env
);
this._clientMode = env as CacheEnvironment;
const redisUrl = process.env.REDIS_URL || '';
if (env === 'redis') {
this._redisClient = redis.createClient({
url: redisUrl,
name: '<>',
});
this._redisClient.connect();
}
this._nodeClient = new NodeCache();
logger.info(`🪣 Caching Client initialized in '${env}' mode`);
}
static async set(key: string, value: any) {
if (this._clientMode === 'redis') {
await this._redisClient.set(key, value);
} else {
this._nodeClient.set(key, value);
}
}
static async get(key: string): Promise<string | null> {
if (this._clientMode === 'redis') {
return await this._redisClient.get(key);
} else {
return (this._nodeClient.get(key) as string) || null;
}
}
static async keys(keysample: string): Promise<string[]> {
if (this._clientMode === 'redis') {
return await this._redisClient.keys(keysample);
} else {
return this._nodeClient.keys().filter(key => key.includes(keysample));
}
}
static async delete(key: string) {
if (this._clientMode === 'redis') {
await this._redisClient.del(key);
} else {
this._nodeClient.del(key);
}
}
static async hset(key: string, field: string, value: any) {
if (this._clientMode === 'redis') {
await this._redisClient.HSET(key, field, value);
} else {
this._nodeClient.set(key + '.' + field, value);
}
}
static async hget(key: string, field: string) {
if (this._clientMode === 'redis') {
return await this._redisClient.HGET(key, field);
} else {
return (this._nodeClient.get(key + '.' + field) as string) || null;
}
}
static async hgetall(key: string) {
if (this._clientMode === 'redis') {
return await this._redisClient.HGETALL(key);
} else {
const keys = this._nodeClient.keys();
const filteredKeys = keys.filter(key => key.includes(key));
const values = filteredKeys.map(key => {
const value: any = this._nodeClient.get(key);
if (
typeof value === 'string' &&
value.startsWith('"[') &&
value.endsWith(']"')
)
return { [key.split('.')[1]]: JSON.parse(value) };
else if (
typeof value === 'string' &&
value.startsWith('"') &&
value.endsWith('"')
)
return { [key.split('.')[1]]: value.slice(1, -1) };
else if (
typeof value === 'string' &&
value.startsWith('"{') &&
value.endsWith('}"')
)
return { [key.split('.')[1]]: JSON.parse(value.slice(1, -1)) };
return { [key.split('.')[1]]: value };
});
return Object.assign({}, ...values);
}
}
}

View File

@ -1,11 +1,10 @@
import { GraphQLClient } from 'graphql-request';
import axios from 'axios';
import { logger } from '../libs';
export let client = new GraphQLClient('');
export const hgqlInit = async () => {
logger.info('🚀 GraphQL Client Initialized');
console.log('🚀 GraphQL Client Initialized');
let HASURA_URL: string = process.env.HASURA_GRAPHQL_ENDPOINT || '';
HASURA_URL += HASURA_URL.endsWith('/') ? 'v1/graphql' : '/v1/graphql';
@ -189,7 +188,7 @@ export const hgqlInit = async () => {
...config,
data: data,
}).then(res => {
logger.info(
console.log(
'🪄 Hasura Tables Metadata Tracked for ' + res.data.length + ' tables'
);
});
@ -197,7 +196,7 @@ export const hgqlInit = async () => {
...config,
data: data2,
}).then(res => {
logger.info(
console.log(
'🪄 Hasura Relationships Metadata Tracked for ' +
res.data.length +
' relationships'
@ -205,9 +204,9 @@ export const hgqlInit = async () => {
});
} catch (err: any) {
if (err.response?.data?.code == 'already-tracked') {
logger.info('🃏 Hasura Metadata Already Tracked');
console.log('🃏 Hasura Metadata Already Tracked');
} else {
logger.info('🚨 Hasura Metadata Tracking Failed');
console.log('🚨 Hasura Metadata Tracking Failed');
setTimeout(() => {
hgqlInit();

View File

@ -1,3 +1,2 @@
export * from './cache.factory';
export * from './gql_clent';
export * from './axios_client';

View File

@ -1,34 +0,0 @@
import multer from 'multer';
import path from 'path';
import { FileData } from '../types';
import { IUploadFactory, UploaderConfig } from '../interfaces/upload.interface';
export class UploadFactory implements IUploadFactory {
public getUploader(config?: UploaderConfig): any {
const storage = multer.memoryStorage();
return multer({
storage: storage,
fileFilter: (req, file: FileData, cb) => {
file.originalname = file.originalname.replace(/\s/g, '_');
const fileName =
file.originalname.split('.')[
file.originalname.split('.').length - 2
] +
'-' +
Date.now() +
path.extname(file.originalname);
file.newName = fileName;
if (config?.mimeFilters?.length) {
if (config.mimeFilters.includes(file.mimetype)) {
cb(null, true);
} else {
cb(null, false);
}
} else {
cb(null, true);
}
},
});
}
}

View File

@ -1,41 +1,53 @@
import 'dotenv/config';
import './configs';
import { Hono } from 'hono';
import { cors } from 'hono/cors';
import { logger as rlogger } from 'hono/logger';
import { poweredBy } from 'hono/powered-by'
import { serve } from '@hono/node-server';
import { hgqlInit } from './helpers';
import { notFoundHandler, logger, LogStream } from './libs';
import { notFoundHandler } from './libs';
import pkg from './package.json' assert { type: 'json' };
import routes from './routes';
import CacheClient, { CacheEnvironment } from './helpers/cache.factory';
import URLStoreController from './controllers/urlstore.controller';
import ConfigService from './services/config.service';
import { Bindings, Variables } from './types';
export const app = new Hono({
export type Env = {
ENVIRONMENT: 'development' | 'production';
SHX_BUCKET: R2Bucket;
SHX_SETTINGS: KVNamespace;
R2_BUCKET_ENDPOINT: string;
R2_CLIENT_ID: string;
R2_CLIENT_SECRET: string;
R2_BUCKET_NAME: number;
R2_BUCKET_FOLDER: string;
R2_BUCKET_URL: string;
R2_BUCKET_REGION: string;
HASURA_GRAPHQL_ADMIN_SECRET: string;
HASURA_GRAPHQL_ENDPOINT: string;
DATABASE_URL: string;
MASTER_KEY: string;
};
export const app = new Hono<{Bindings: Bindings, Variables: Variables}>({
strict: false
});
app.use('*', rlogger())
app.use('*', poweredBy())
logger.info('🚀 @' + pkg.author.name + '/' + pkg.name, 'v' + pkg.version);
console.log('🚀 @' + pkg.author.name + '/' + pkg.name, 'v' + pkg.version);
const isDev: boolean = process.env.NODE_ENV == 'production';
logger.info(isDev ? '🚀 Production Mode' : '🚀 Development Mode');
console.log(isDev ? '🚀 Production Mode' : '🚀 Development Mode');
const urlStoreController = new URLStoreController();
const logStream = new LogStream();
logger.info(`🔑 Master Key ${process.env.MASTER_KEY}`);
console.log(`🔑 Master Key ${process.env.MASTER_KEY}`);
hgqlInit();
CacheClient.init(process.env.CACHE_ENV as CacheEnvironment);
app.get('/health', (ctx) => {
return ctx.json({
@ -45,23 +57,18 @@ app.get('/health', (ctx) => {
});
});
logger.info('🦄 Base Route /');
console.log('🦄 Base Route /');
app.route('/', routes);
app.get('/:urlKey', urlStoreController.get);
app.all('*', notFoundHandler);
logger.info(`🚂 Server running on port ${process.env.PORT}`);
console.log(`🚂 Server running on port ${process.env.PORT}`);
const { initConfig } = new ConfigService();
await initConfig();
app.showRoutes()
serve({
fetch: app.fetch,
port: Number(process.env.PORT)
})
export { logger };
export default app

5
packages/api/init-config.sh Executable file
View File

@ -0,0 +1,5 @@
#!/bin/bash
# wrangler r2 bucket create shx
# wrangler kv:namespace create "SHX_SETTINGS"
wrangler secret:bulk secrets.json

View File

@ -15,7 +15,7 @@ export interface IConfigController {
}
export interface IConfigService {
initConfig(): Promise<boolean>;
initConfig(T): Promise<boolean>;
getAllConfigS(): Promise<Settings>;
setConfigS(key: ConfigKeysTypes, value: string): Promise<void>;
getConfigS(

View File

@ -6,15 +6,6 @@ export interface IUploaderController {
upload(
ctx: Context
);
uploadImage(
ctx: Context
);
uploadImageFromURL(
ctx: Context
);
uploadFileFromURL(
ctx: Context
);
getFile(
ctx: Context
);
@ -27,12 +18,7 @@ export interface IUploaderController {
}
export interface IUploaderService {
uploadImageS(file: any, meta: UserMeta): Promise<Uploads>;
uploadS(file: any, meta: UserMeta): Promise<Uploads>;
uploadImageViaURLS(url: string, meta: UserMeta): Promise<Uploads>;
uploadFileViaURLS(url: string, meta: UserMeta): Promise<Uploads>;
downloadImage(url: string): Promise<string>;
downloadFile(url: string): Promise<string>;
deleteFileS(fileID: string, delToken: string): Promise<Uploads>;
listFilesS(
searchQuery: any,

View File

@ -1,6 +1,5 @@
import { Apikeys } from '../graphql/types';
import { PaginationType } from '../types';
import { createLogger, format, transports } from 'winston';
export const makeResponse = (
data: any,
@ -167,32 +166,3 @@ export const encapDataKeys = (data: Apikeys[]) => {
});
return new_data;
};
const { combine, timestamp, label, printf } = format;
const formater = printf(({ level, message, label, timestamp }) => {
return `${timestamp} [${label}] ${level}: ${message}`;
});
export const logger = createLogger({
level: 'info',
format: combine(label({ label: '📢' }), timestamp(), formater),
transports: [
new transports.Console(),
new transports.File({ filename: 'error.log', level: 'error' }),
new transports.File({ filename: 'combined.log' }),
],
});
if (process.env.NODE_ENV !== 'production') {
logger.add(
new transports.Console({
format: format.simple(),
})
);
}
export class LogStream {
write(text: string) {
logger.info(text.replace(/\n$/, ''));
}
}

View File

@ -37,8 +37,9 @@
},
"scripts": {
"dev": "concurrently \"npm run dev:api\" \"npm run dev:hasura\"",
"deploy": "wrangler deploy --minify src/index.ts",
"dev:hasura": "cd hasura && hasura --skip-update-check --envfile ../.env console",
"dev:api": "dotenv -- cross-env NODE_ENV=development nodemon -x node --no-warnings --loader @esbuild-kit/esm-loader index.ts --signal SIGKILL --ignore node_modules",
"dev:api": "dotenv -- cross-env NODE_ENV=development wrangler dev index.ts",
"build": "tsc -p tsconfig.json",
"start": "node --es-module-specifier-resolution=node --loader ts-node/esm ./build/index.js",
"fetch:schemas": "bash bin/fetch-gql-schema.sh",
@ -47,6 +48,7 @@
"watch": "tsc -w"
},
"devDependencies": {
"@cloudflare/workers-types": "^4.20230914.0",
"@esbuild-kit/esm-loader": "^2.5.5",
"@graphql-codegen/cli": "^5.0.0",
"@graphql-codegen/typescript": "^4.0.0",
@ -66,6 +68,7 @@
"lint-staged": "^14.0.1",
"nodemon": "^3.0.1",
"prettier": "^2.8.2",
"typescript": "^5.1.6"
"typescript": "^5.1.6",
"wrangler": "^3.9.0"
}
}

View File

@ -3,16 +3,11 @@ import { Hono } from 'hono';
import apikey from './apikey.routes';
import config from './config.routes';
import gist from './gist.routes';
import info from './info.routes';
import settings from './settings.routes';
import upload from './upload.routes';
import url from './url.routes';
import pkg from '../package.json' assert { type: 'json' };
import { execSync } from 'child_process';
let gitHash = '';
if (!process.env.DOCKER_ENV)
gitHash = execSync('git rev-parse HEAD').toString().trim();
const router = new Hono();
@ -22,14 +17,12 @@ router.get('/', (ctx) => {
status: 'OK',
version: pkg.version,
source_code: pkg.repository,
git_commit_id: gitHash,
});
});
router.route('/apikey', apikey);
router.route('/config', config);
router.route('/gist', gist);
router.route('/info', info);
router.route('/settings', settings);
router.route('/upload', upload);
router.route('/url', url);

View File

@ -1,12 +0,0 @@
import { Hono } from 'hono';
import APIKeyAuth from '../middlewares/apikey_check';
import InfoController from '../controllers/info.controller';
const infoController = InfoController;
const apiKeyAuth = new APIKeyAuth();
const router = new Hono();
router.get('/sys', apiKeyAuth.check, infoController.get);
export default router;

View File

@ -3,11 +3,12 @@ import APIKeyAuth from '../middlewares/apikey_check';
import ConfigController from '../controllers/config.controller';
import { z } from 'zod';
import { zValidator } from '@hono/zod-validator';
import { Bindings, Variables } from '../types';
const configController = new ConfigController();
const apiKeyAuth = new APIKeyAuth();
const router = new Hono();
const router = new Hono<{Bindings: Bindings, Variables: Variables}>();
router.get('/', apiKeyAuth.check, configController.getAllConfig);

View File

@ -1,11 +1,12 @@
import { Hono } from 'hono';
import UploadController from '../controllers/upload.controller';
import APIKeyAuth from '../middlewares/apikey_check';
import { Bindings, Variables } from '../types';
const uploadController = new UploadController();
const apiKeyAuth = new APIKeyAuth();
const router = new Hono();
const router = new Hono<{ Bindings: Bindings, Variables: Variables }>();
router.post(
'/file',
@ -13,24 +14,6 @@ router.post(
uploadController.upload
);
router.post(
'/image',
apiKeyAuth.check,
uploadController.uploadImage
);
router.post(
'/image-from-url',
apiKeyAuth.check,
uploadController.uploadImageFromURL
);
router.post(
'/file-from-url',
apiKeyAuth.check,
uploadController.uploadFileFromURL
);
router.get(
'/',
apiKeyAuth.check,

View File

@ -0,0 +1,13 @@
{
"HASURA_GRAPHQL_ADMIN_SECRET": "xxxxxxxxxxxxxxxxxxxxxxxxxx",
"HASURA_GRAPHQL_ENDPOINT": "https://xxxxxxxx.hasura.app",
"R2_CLIENT_ID": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"R2_CLIENT_SECRET": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"R2_BUCKET_NAME": "xxxxxxxx",
"R2_BUCKET_REGION": "apac",
"R2_BUCKET_ENDPOINT": "https://xxxxxxxxxxxxxxxxxxxx.r2.cloudflarestorage.com/xxxxxxx",
"R2_BUCKET_URL": "https://xxxxxxxxxxxxxxxxxxxxxxxx",
"R2_BUCKET_FOLDER": "xxxxxxxxxxxxx",
"MASTER_KEY": "xxxxxxxxxxxxxxxxxxxxxxxxx",
"DATABASE_URL": "postgres://xxxxxxxxxxx:xxxxxxxxxxxxxxxxxxxx@xxxxxxxxxxxxxxxxxxxxxxxxxxxx/xxxxxxxxxxx"
}

View File

@ -1,4 +1,4 @@
import CacheClient from '../helpers/cache.factory';
import { Context } from 'hono';
import {
IConfigService,
ConfigKeysTypes,
@ -6,22 +6,31 @@ import {
ThemeType,
LanguageType,
} from '../interfaces/config.interface';
import { logger } from '../libs';
import { Bindings, Variables } from '../types';
import { Env } from '../index';
export default class ConfigService implements IConfigService {
public initConfig = async (): Promise<boolean> => {
const config = await CacheClient.keys('config');
let config = [
await Env.SHX_SETTINGS.get('theme'),
await SHX_SETTINGS.get('language'),
await SHX_SETTINGS.get('imageExtensions'),
await SHX_SETTINGS.get('fileExtensions'),
];
config = await Promise.all(config);
if (config && config.length > 0) {
logger.info('⚽ Config already initialized');
return true;
} else {
await this.setConfigS('theme', 'dark');
await this.setConfigS('language', 'en');
await this.setConfigS(
await this.setConfigS(ctx, 'theme', 'dark');
await this.setConfigS(ctx, 'language', 'en');
await this.setConfigS(ctx,
'imageExtensions',
JSON.stringify(['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg', 'ico'])
);
await this.setConfigS(
await this.setConfigS(ctx,
'fileExtensions',
JSON.stringify([
'png',
@ -48,21 +57,33 @@ export default class ConfigService implements IConfigService {
'html',
])
);
logger.info('⚽ Config initialized successfully');
console.log('⚽ Config initialized successfully');
return false;
}
};
public getAllConfigS = async (): Promise<Settings> => {
const settings: Partial<Settings | any> = await CacheClient.hgetall(
'config'
);
public getAllConfigS = async (ctx: Context<{Bindings: Bindings, Variables: Variables}>): Promise<Settings> => {
let config = [
await ctx.env.SHX_SETTINGS.get('theme'),
await ctx.env.SHX_SETTINGS.get('language'),
await ctx.env.SHX_SETTINGS.get('imageExtensions'),
await ctx.env.SHX_SETTINGS.get('fileExtensions'),
];
const [theme, language, imageExtensions, fileExtensions] = await Promise.all(config);
const settings: Partial<Settings | any> = {
theme: theme as ThemeType,
language: language as LanguageType,
imageExtensions,
fileExtensions,
}
settings.imageExtensions = JSON.parse(settings['imageExtensions']);
settings.fileExtensions = JSON.parse(settings['fileExtensions']);
return settings as Settings;
};
public setConfigS = async (
ctx: Context<{Bindings: Bindings, Variables: Variables}>,
key: ConfigKeysTypes,
value: string[] | string | boolean | number | ThemeType | LanguageType
): Promise<void> => {
@ -72,6 +93,7 @@ export default class ConfigService implements IConfigService {
};
public getConfigS = async (
ctx: Context<{Bindings: Bindings, Variables: Variables}>,
key: ConfigKeysTypes
): Promise<
| string[]

View File

@ -1,18 +0,0 @@
import os from 'node:os';
export default class InfoService {
static async getSystemInfo() {
const sys = {
platform: process.platform,
arch: process.arch,
nodeVersion: process.version,
uptime: process.uptime(),
memoryUsage: process.memoryUsage(),
cpuUsage: process.cpuUsage(),
kernelVersion: os.release(),
hostname: os.hostname(),
};
return sys;
}
}

View File

@ -1,16 +1,9 @@
import UploaderService from '../data/uploader.service';
import { gql } from 'graphql-request';
import { client } from '../helpers';
import sharp from 'sharp';
import { IListUploads, IUploaderService } from '../interfaces/upload.interface';
import { UserMeta } from '../types';
import axios from 'axios';
import fs from 'fs';
import { nanoid } from 'napi-nanoid';
import sanitize from 'sanitize-filename';
import ConfigService from './config.service';
import { CustomError } from '../libs/error';
import { logger } from '../libs';
import { Uploads } from '../graphql/types';
export default class Uploader implements IUploaderService {
@ -23,13 +16,6 @@ export default class Uploader implements IUploaderService {
}
public uploadS = async (file: any, meta: UserMeta): Promise<Uploads> => {
await this.uploaderService.uploadFile(
process.env.R2_BUCKET_FOLDER as string,
file.newName,
file.buffer,
file.mimetype,
'public-read'
);
const query = gql`
mutation uploadFile($file: uploads_insert_input!) {
insert_uploads_one(object: $file) {
@ -65,214 +51,6 @@ export default class Uploader implements IUploaderService {
return data.insert_uploads_one;
};
public uploadImageS = async (file: any, meta: UserMeta): Promise<Uploads> => {
const image = sharp(file.buffer);
await image.toFormat('jpeg');
const buffer = await image.toBuffer();
await this.uploaderService.uploadFile(
process.env.R2_BUCKET_FOLDER as string,
file.newName,
buffer,
file.mimetype,
'public-read'
);
const query = gql`
mutation uploadFile($file: uploads_insert_input!) {
insert_uploads_one(object: $file) {
fileID
uploaded_at
filename
upload_url
deleteToken
}
}
`;
const urlObj = {
url:
process.env.R2_BUCKET_URL +
'/' +
process.env.R2_BUCKET_NAME +
'/' +
process.env.R2_BUCKET_FOLDER +
'/' +
file.newName,
};
const variables = {
file: {
upload_url: urlObj.url,
filename: file.originalname,
uploader_ip: meta.ip,
apikeyUsed: meta.apiKeyID,
},
};
const data: {
insert_uploads_one: Uploads;
} = await client.request(query, variables);
return data.insert_uploads_one;
};
public uploadImageViaURLS = async (
url: string,
meta: UserMeta
): Promise<Uploads> => {
let filename = await this.downloadImage(url);
filename = sanitize(filename);
const rawImage = fs.readFileSync(`uploads/${filename}`);
const image = sharp(rawImage);
await image.toFormat('jpeg');
const buffer = await image.toBuffer();
await this.uploaderService.uploadFile(
process.env.R2_BUCKET_FOLDER as string,
filename,
buffer,
'image/jpeg',
'public-read'
);
const query = gql`
mutation uploadFile($file: uploads_insert_input!) {
insert_uploads_one(object: $file) {
fileID
uploaded_at
filename
upload_url
deleteToken
}
}
`;
const urlObj = {
url:
process.env.R2_BUCKET_URL +
'/' +
process.env.R2_BUCKET_NAME +
'/' +
process.env.R2_BUCKET_FOLDER +
'/' +
filename,
};
const variables = {
file: {
upload_url: urlObj.url,
filename: filename,
uploader_ip: meta.ip,
apikeyUsed: meta.apiKeyID,
},
};
const data: {
insert_uploads_one: Uploads;
} = await client.request(query, variables);
return data.insert_uploads_one;
};
public downloadImage = async (url: string): Promise<string> => {
let filename = nanoid() + url.split('/').pop();
filename = sanitize(filename);
const whitelistedExtensions = await this.configService.getConfigS(
'imageExtensions'
);
if (!whitelistedExtensions.includes(filename.split('.').pop() as string)) {
throw new CustomError({
message: 'Image extension not whitelisted',
statusCode: 400,
});
}
await axios({
url,
method: 'GET',
responseType: 'arraybuffer',
})
.then(res => {
return sharp(res.data).toFile(`uploads/${filename}`);
})
.catch(err => {
logger.info(`Couldn't process: ${err}`);
});
return filename;
};
public uploadFileViaURLS = async (
url: string,
meta: UserMeta
): Promise<Uploads> => {
let filename = await this.downloadFile(url);
filename = sanitize(filename);
await this.uploaderService.uploadFile(
process.env.R2_BUCKET_FOLDER as string,
filename,
fs.readFileSync(`uploads/${filename}`),
'application/octet-stream',
'public-read'
);
const query = gql`
mutation uploadFile($file: uploads_insert_input!) {
insert_uploads_one(object: $file) {
fileID
uploaded_at
filename
upload_url
deleteToken
}
}
`;
const urlObj = {
url:
process.env.R2_BUCKET_URL +
'/' +
process.env.R2_BUCKET_NAME +
'/' +
process.env.R2_BUCKET_FOLDER +
'/' +
filename,
};
const variables = {
file: {
upload_url: urlObj.url,
filename: filename,
uploader_ip: meta.ip,
apikeyUsed: meta.apiKeyID,
},
};
const data: {
insert_uploads_one: Uploads;
} = await client.request(query, variables);
return data.insert_uploads_one;
};
public downloadFile = async (url: string): Promise<string> => {
let filename = nanoid() + url.split('/').pop();
filename = sanitize(filename);
const whitelistedExtensions = await this.configService.getConfigS(
'fileExtensions'
);
if (!whitelistedExtensions.includes(filename.split('.').pop() as string)) {
throw new CustomError({
message: 'File extension not whitelisted',
statusCode: 400,
});
}
await axios({
url,
method: 'GET',
responseType: 'arraybuffer',
})
.then(res => {
return fs.writeFileSync(`uploads/${filename}`, res.data);
})
.catch(err => {
logger.info(`Couldn't process: ${err}`);
});
return filename;
};
public deleteFileS = async (
fileID: string,
delToken: string

View File

@ -1,7 +1,7 @@
{
"compilerOptions": {
"lib": ["es2018", "es5", "dom"],
"typeRoots": ["node_modules/@types", "types"],
"typeRoots": ["node_modules/@types", "types", "node_modules/@cloudflare/workers-types"],
"resolveJsonModule": true,
"esModuleInterop": true,
"target": "ES2017",

View File

@ -1,4 +1,3 @@
import { Request } from 'express';
export interface PaginationType {
page: number;
limit: number;
@ -7,13 +6,6 @@ export interface PaginationType {
filters?: { [k: string]: any };
}
export interface ModRequest extends Request {
file: any;
files: any;
user: UserMeta;
image: any;
}
export interface UserMeta {
apiKeyID: string;
ip: string;
@ -43,6 +35,12 @@ export interface SXCUFile {
ErrorMessage: string;
}
export interface FileData extends Express.Multer.File {
newName: string;
export type Bindings = {
SHX_BUCKET: R2Bucket,
SHX_SETTINGS: KVNamespace,
}
export type Variables = {
user: any
}

View File

@ -0,0 +1,13 @@
name = "shx-worker"
compatibility_date = "2023-01-01"
[vars]
MY_VARIABLE = "production_value"
[[kv_namespaces]]
binding = "SHX_SETTINGS"
id = "be1c00f6dc064c8aa5756fb58400d420"
[[r2_buckets]]
binding = "SHX_BUCKET"
bucket_name = "shx"

View File

@ -342,6 +342,9 @@ importers:
specifier: ^3.21.4
version: 3.21.4
devDependencies:
'@cloudflare/workers-types':
specifier: ^4.20230914.0
version: 4.20230914.0
'@esbuild-kit/esm-loader':
specifier: ^2.5.5
version: 2.5.5
@ -402,6 +405,9 @@ importers:
typescript:
specifier: ^5.1.6
version: 5.1.6
wrangler:
specifier: ^3.9.0
version: 3.9.0
packages:
@ -3383,6 +3389,61 @@ packages:
resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==}
dev: true
/@cloudflare/kv-asset-handler@0.2.0:
resolution: {integrity: sha512-MVbXLbTcAotOPUj0pAMhVtJ+3/kFkwJqc5qNOleOZTv6QkZZABDMS21dSrSlVswEHwrpWC03e4fWytjqKvuE2A==}
dependencies:
mime: 3.0.0
dev: true
/@cloudflare/workerd-darwin-64@1.20230904.0:
resolution: {integrity: sha512-/GDlmxAFbDtrQwP4zOXFbqOfaPvkDxdsCoEa+KEBcAl5uR98+7WW5/b8naBHX+t26uS7p4bLlImM8J5F1ienRQ==}
engines: {node: '>=16'}
cpu: [x64]
os: [darwin]
requiresBuild: true
dev: true
optional: true
/@cloudflare/workerd-darwin-arm64@1.20230904.0:
resolution: {integrity: sha512-x8WXNc2xnDqr5y1iirnNdyx8GZY3rL5xiF7ebK3mKQeB+jFjkhO71yuPTkDCzUWtOvw1Wfd4jbwy4wxacMX4mQ==}
engines: {node: '>=16'}
cpu: [arm64]
os: [darwin]
requiresBuild: true
dev: true
optional: true
/@cloudflare/workerd-linux-64@1.20230904.0:
resolution: {integrity: sha512-V58xyMS3oDpKO8Dpdh0r0BXm99OzoGgvWe9ufttVraj/1NTMGELwb6i9ySb8k3F1J9m/sO26+TV7pQc/bGC1VQ==}
engines: {node: '>=16'}
cpu: [x64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@cloudflare/workerd-linux-arm64@1.20230904.0:
resolution: {integrity: sha512-VrDaW+pjb5IAKEnNWtEaFiG377kXKmk5Fu0Era4W+jKzPON2BW/qRb/4LNHXQ4yxg/2HLm7RiUTn7JZtt1qO6A==}
engines: {node: '>=16'}
cpu: [arm64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@cloudflare/workerd-windows-64@1.20230904.0:
resolution: {integrity: sha512-/R/dE8uy+8J2YeXfDhI8/Bg7YUirdbbjH5/l/Vv00ZRE0lC3nPLcYeyBXSwXIQ6/Xht3gN+lksLQgKd0ZWRd+Q==}
engines: {node: '>=16'}
cpu: [x64]
os: [win32]
requiresBuild: true
dev: true
optional: true
/@cloudflare/workers-types@4.20230914.0:
resolution: {integrity: sha512-OVeN4lFVu1O0PJGZ2d0FwpK8lelFcr33qYOgCh77ErEYmEBO4adwnIxcIsdQbFbhF0ffN6joiVcljD4zakdaeQ==}
dev: true
/@colors/colors@1.5.0:
resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==}
engines: {node: '>=0.1.90'}
@ -3520,6 +3581,24 @@ packages:
'@esbuild-kit/core-utils': 3.2.2
get-tsconfig: 4.7.0
/@esbuild-plugins/node-globals-polyfill@0.2.3(esbuild@0.17.19):
resolution: {integrity: sha512-r3MIryXDeXDOZh7ih1l/yE9ZLORCd5e8vWg02azWRGj5SPTuoh69A2AIyn0Z31V/kHBfZ4HgWJ+OK3GTTwLmnw==}
peerDependencies:
esbuild: '*'
dependencies:
esbuild: 0.17.19
dev: true
/@esbuild-plugins/node-modules-polyfill@0.2.2(esbuild@0.17.19):
resolution: {integrity: sha512-LXV7QsWJxRuMYvKbiznh+U1ilIop3g2TeKRzUxOG5X3YITc8JyyTa90BmLwqqv0YnX4v32CSlG+vsziZp9dMvA==}
peerDependencies:
esbuild: '*'
dependencies:
esbuild: 0.17.19
escape-string-regexp: 4.0.0
rollup-plugin-node-polyfills: 0.2.1
dev: true
/@esbuild/android-arm64@0.17.19:
resolution: {integrity: sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==}
engines: {node: '>=12'}
@ -9029,6 +9108,12 @@ packages:
is-shared-array-buffer: 1.0.2
dev: false
/as-table@1.0.55:
resolution: {integrity: sha512-xvsWESUJn0JN421Xb9MQw6AsMHRCUknCe0Wjlxvjud80mU4E6hQf1A6NzQKcYNmYw62MfzEtXc+badstZP3JpQ==}
dependencies:
printable-characters: 1.0.42
dev: true
/asap@2.0.6:
resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==}
dev: true
@ -9364,6 +9449,10 @@ packages:
inherits: 2.0.4
readable-stream: 3.6.2
/blake3-wasm@2.1.5:
resolution: {integrity: sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==}
dev: true
/bn.js@4.12.0:
resolution: {integrity: sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==}
dev: true
@ -9663,6 +9752,15 @@ packages:
upper-case-first: 2.0.2
dev: true
/capnp-ts@0.7.0:
resolution: {integrity: sha512-XKxXAC3HVPv7r674zP0VC3RTXz+/JKhfyw94ljvF80yynK6VkTnqE3jMuN8b3dUVmmc43TjyxjW4KTsmB3c86g==}
dependencies:
debug: 4.3.4(supports-color@8.1.1)
tslib: 2.6.2
transitivePeerDependencies:
- supports-color
dev: true
/cardinal@2.1.1:
resolution: {integrity: sha512-JSr5eOgoEymtYHBjNWyjrMqet9Am2miJhlfKNdqLp6zoeAh0KN5dRAcxlecj5mAJrmQomgiOBj35xHLrFjqBpw==}
hasBin: true
@ -10379,6 +10477,10 @@ packages:
resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==}
dev: false
/data-uri-to-buffer@2.0.2:
resolution: {integrity: sha512-ND9qDTLc6diwj+Xe5cdAgVTbLVdXbtxTJRXRhli8Mowuaan+0EJOtdqJ0QCHNSSPyoXGx9HX2/VMnKeC34AChA==}
dev: true
/dataloader@2.2.2:
resolution: {integrity: sha512-8YnDaaf7N3k/q5HnTJVuzSyLETjoZjVmHc4AeKAzOvKHEFQKcn64OKBfzHYtE9zGjctNM7V9I0MfnUVLpi7M5g==}
dev: true
@ -11463,6 +11565,10 @@ packages:
- supports-color
dev: true
/estree-walker@0.6.1:
resolution: {integrity: sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==}
dev: true
/esutils@2.0.3:
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
engines: {node: '>=0.10.0'}
@ -11539,6 +11645,11 @@ packages:
strip-final-newline: 3.0.0
dev: true
/exit-hook@2.2.1:
resolution: {integrity: sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw==}
engines: {node: '>=6'}
dev: true
/expand-template@2.0.3:
resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==}
engines: {node: '>=6'}
@ -12132,6 +12243,13 @@ packages:
engines: {node: '>=8'}
dev: true
/get-source@2.0.12:
resolution: {integrity: sha512-X5+4+iD+HoSeEED+uwrQ07BOQr0kEDFMVqqpBuI+RaZBpBpHCuXxo70bjar6f0b0u/DQJsJ7ssurpP0V60Az+w==}
dependencies:
data-uri-to-buffer: 2.0.2
source-map: 0.6.1
dev: true
/get-stream@4.1.0:
resolution: {integrity: sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==}
engines: {node: '>=6'}
@ -13941,6 +14059,12 @@ packages:
hasBin: true
dev: true
/magic-string@0.25.9:
resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==}
dependencies:
sourcemap-codec: 1.4.8
dev: true
/make-dir@2.1.0:
resolution: {integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==}
engines: {node: '>=6'}
@ -14106,6 +14230,12 @@ packages:
hasBin: true
dev: true
/mime@3.0.0:
resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==}
engines: {node: '>=10.0.0'}
hasBin: true
dev: true
/mimic-fn@2.1.0:
resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
engines: {node: '>=6'}
@ -14130,6 +14260,28 @@ packages:
hasBin: true
dev: false
/miniflare@3.20230918.0:
resolution: {integrity: sha512-Dd29HB7ZlT1CXB2tPH8nW6fBOOXi/m7qFZHjKm2jGS+1OaGfrv0PkT5UspWW5jQi8rWI87xtordAUiIJkwWqRw==}
engines: {node: '>=16.13'}
dependencies:
acorn: 8.10.0
acorn-walk: 8.2.0
capnp-ts: 0.7.0
exit-hook: 2.2.1
glob-to-regexp: 0.4.1
source-map-support: 0.5.21
stoppable: 1.1.0
undici: 5.23.0
workerd: 1.20230904.0
ws: 8.13.0
youch: 3.3.2
zod: 3.21.4
transitivePeerDependencies:
- bufferutil
- supports-color
- utf-8-validate
dev: true
/minimalistic-assert@1.0.1:
resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==}
dev: true
@ -14234,7 +14386,6 @@ packages:
/mustache@4.2.0:
resolution: {integrity: sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==}
hasBin: true
dev: false
/mute-stream@0.0.8:
resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==}
@ -14558,6 +14709,11 @@ packages:
dependencies:
whatwg-url: 5.0.0
/node-forge@1.3.1:
resolution: {integrity: sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==}
engines: {node: '>= 6.13.0'}
dev: true
/node-int64@0.4.0:
resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==}
dev: true
@ -15200,6 +15356,10 @@ packages:
resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==}
dev: true
/path-to-regexp@6.2.1:
resolution: {integrity: sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==}
dev: true
/path-type@4.0.0:
resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
engines: {node: '>=8'}
@ -15516,6 +15676,10 @@ packages:
engines: {node: '>= 0.8'}
dev: true
/printable-characters@1.0.42:
resolution: {integrity: sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ==}
dev: true
/process-nextick-args@2.0.1:
resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==}
dev: true
@ -16179,6 +16343,27 @@ packages:
inherits: 2.0.4
dev: true
/rollup-plugin-inject@3.0.2:
resolution: {integrity: sha512-ptg9PQwzs3orn4jkgXJ74bfs5vYz1NCZlSQMBUA0wKcGp5i5pA1AO3fOUEte8enhGUC+iapTCzEWw2jEFFUO/w==}
deprecated: This package has been deprecated and is no longer maintained. Please use @rollup/plugin-inject.
dependencies:
estree-walker: 0.6.1
magic-string: 0.25.9
rollup-pluginutils: 2.8.2
dev: true
/rollup-plugin-node-polyfills@0.2.1:
resolution: {integrity: sha512-4kCrKPTJ6sK4/gLL/U5QzVT8cxJcofO0OU74tnB19F40cmuAKSzH5/siithxlofFEjwvw1YAhPmbvGNA6jEroA==}
dependencies:
rollup-plugin-inject: 3.0.2
dev: true
/rollup-pluginutils@2.8.2:
resolution: {integrity: sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==}
dependencies:
estree-walker: 0.6.1
dev: true
/run-async@2.4.1:
resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==}
engines: {node: '>=0.12.0'}
@ -16304,6 +16489,13 @@ packages:
resolution: {integrity: sha512-MuCAyrGZcTLfQoH2XoBlQ8C6bzwN88XT/0slOGz0pn8+gIP85BOAfYa44ZXQUTOwRwPU0QvgU+V+OSajl/59Xg==}
dev: true
/selfsigned@2.1.1:
resolution: {integrity: sha512-GSL3aowiF7wa/WtSFwnUrludWFoNhftq8bUkH9pkzjpN2XSPOAYEgg6e0sS9s0rZwgJzJiQRPU18A6clnoW5wQ==}
engines: {node: '>=10'}
dependencies:
node-forge: 1.3.1
dev: true
/semver@5.7.2:
resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==}
hasBin: true
@ -16626,6 +16818,11 @@ packages:
engines: {node: '>= 8'}
dev: true
/sourcemap-codec@1.4.8:
resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==}
deprecated: Please use @jridgewell/sourcemap-codec instead
dev: true
/space-separated-tokens@1.1.5:
resolution: {integrity: sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==}
dev: true
@ -16674,6 +16871,13 @@ packages:
resolution: {integrity: sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==}
dev: true
/stacktracey@2.1.8:
resolution: {integrity: sha512-Kpij9riA+UNg7TnphqjH7/CzctQ/owJGNbFkfEeve4Z4uxT5+JapVLFXcsurIfN34gnTWZNJ/f7NMG0E8JDzTw==}
dependencies:
as-table: 1.0.55
get-source: 2.0.12
dev: true
/statuses@1.4.0:
resolution: {integrity: sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==}
engines: {node: '>= 0.6'}
@ -16691,6 +16895,11 @@ packages:
internal-slot: 1.0.5
dev: true
/stoppable@1.1.0:
resolution: {integrity: sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==}
engines: {node: '>=4', npm: '>=6'}
dev: true
/store2@2.14.2:
resolution: {integrity: sha512-siT1RiqlfQnGqgT/YzXVUNsom9S0H1OX+dpdGN1xkyYATo4I6sep5NmsRD/40s3IIOvlCq6akxkqG82urIZW1w==}
dev: true
@ -17677,7 +17886,6 @@ packages:
engines: {node: '>=14.0'}
dependencies:
busboy: 1.6.0
dev: false
/unfetch@4.2.0:
resolution: {integrity: sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA==}
@ -18121,6 +18329,45 @@ packages:
resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==}
dev: true
/workerd@1.20230904.0:
resolution: {integrity: sha512-t9znszH0rQGK4mJGvF9L3nN0qKEaObAGx0JkywFtAwH8OkSn+YfQbHNZE+YsJ4qa1hOz1DCNEk08UDFRBaYq4g==}
engines: {node: '>=16'}
hasBin: true
requiresBuild: true
optionalDependencies:
'@cloudflare/workerd-darwin-64': 1.20230904.0
'@cloudflare/workerd-darwin-arm64': 1.20230904.0
'@cloudflare/workerd-linux-64': 1.20230904.0
'@cloudflare/workerd-linux-arm64': 1.20230904.0
'@cloudflare/workerd-windows-64': 1.20230904.0
dev: true
/wrangler@3.9.0:
resolution: {integrity: sha512-Ho1A76KxbqfcRgCsuN6xGar3BVPyn4oVWM9zx0HvEVhT9wQ7n/LvB6GlPdXKABqEBYhVe/oTH72S5TgWl0DgaA==}
engines: {node: '>=16.13.0'}
hasBin: true
dependencies:
'@cloudflare/kv-asset-handler': 0.2.0
'@esbuild-plugins/node-globals-polyfill': 0.2.3(esbuild@0.17.19)
'@esbuild-plugins/node-modules-polyfill': 0.2.2(esbuild@0.17.19)
blake3-wasm: 2.1.5
chokidar: 3.5.3
esbuild: 0.17.19
miniflare: 3.20230918.0
nanoid: 3.3.6
path-to-regexp: 6.2.1
selfsigned: 2.1.1
source-map: 0.6.1
source-map-support: 0.5.21
xxhash-wasm: 1.0.2
optionalDependencies:
fsevents: 2.3.3
transitivePeerDependencies:
- bufferutil
- supports-color
- utf-8-validate
dev: true
/wrap-ansi@4.0.0:
resolution: {integrity: sha512-uMTsj9rDb0/7kk1PbcbCcwvHUxp60fGDB/NNXpVa0Q+ic/e7y5+BwTxKfQ33VYgDppSwi/FBzpetYzo8s6tfbg==}
engines: {node: '>=6'}
@ -18259,6 +18506,10 @@ packages:
engines: {node: '>=0.4'}
dev: true
/xxhash-wasm@1.0.2:
resolution: {integrity: sha512-ibF0Or+FivM9lNrg+HGJfVX8WJqgo+kCLDc4vx6xMeTce7Aj+DLttKbxxRR/gNLSAelRc1omAPlJ77N/Jem07A==}
dev: true
/y18n@4.0.3:
resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==}
dev: true
@ -18374,5 +18625,13 @@ packages:
engines: {node: '>=12.20'}
dev: true
/youch@3.3.2:
resolution: {integrity: sha512-9cwz/z7abtcHOIuH45nzmUFCZbyJA1nLqlirKvyNRx4wDMhqsBaifAJzBej7L4fsVPjFxYq3NK3GAcfvZsydFw==}
dependencies:
cookie: 0.5.0
mustache: 4.2.0
stacktracey: 2.1.8
dev: true
/zod@3.21.4:
resolution: {integrity: sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==}