Compare commits

..

10 Commits

Author SHA1 Message Date
aba378ab44 Add additional properties
All checks were successful
Gitea Actions Demo / build (push) Successful in 2m9s
2025-06-17 18:45:31 -06:00
5a7bad327f Add initial baby-names service 2025-06-17 17:17:54 -06:00
a4c75cfbd2 Update claude summary prompt 2025-05-29 10:45:27 -06:00
ef01d3d475 Update to Claude Opus 4 2025-05-22 16:13:01 -06:00
79a0b436f1 Add markdown response to claude prompt 2025-05-15 13:47:47 -06:00
1b3e52d9cf Return 200 on claude summary 2025-04-25 10:57:01 -06:00
089a7e7975 Add claude module 2025-04-25 10:45:29 -06:00
04e4342097 Remove excess line in script output 2025-04-24 13:48:51 -06:00
1dfed5cee8 Allow larger body sizes 2025-04-24 13:41:00 -06:00
84efbd295c Set response headers 2025-04-24 13:25:38 -06:00
14 changed files with 569 additions and 19 deletions

View File

@@ -20,6 +20,7 @@
"test:e2e": "jest --config ./test/jest-e2e.json"
},
"dependencies": {
"@anthropic-ai/sdk": "^0.40.0",
"@liaoliaots/nestjs-redis": "^9.0.5",
"@nestjs/bull": "^10.0.1",
"@nestjs/cache-manager": "^2.1.1",
@@ -37,6 +38,7 @@
"bull": "^4.11.5",
"cache-manager": "^5.3.1",
"cache-manager-redis-yet": "^4.1.2",
"cookie-parser": "^1.4.7",
"fp-ts": "^2.16.3",
"haversine-ts": "^1.2.0",
"hbs": "^4.2.0",
@@ -59,6 +61,7 @@
"@nestjs/testing": "^10.0.0",
"@types/axios": "^0.14.0",
"@types/bcrypt": "^5.0.0",
"@types/cookie-parser": "^1.4.9",
"@types/express": "^4.17.17",
"@types/jest": "^29.5.2",
"@types/multer": "^1.4.10",

View File

@@ -32,6 +32,8 @@ import { ContactModule } from './contact/contact.module';
import { HoardingModule } from './hoarding/hoarding.module';
import { NamesModule } from './names/names.module';
import { AssemblyAiModule } from './assembly-ai/assembly-ai.module';
import { ClaudeModule } from './claude/claude.module';
import { BabyNamesModule } from './baby-names/baby-names.module';
@Module({
imports: [
@@ -110,6 +112,8 @@ import { AssemblyAiModule } from './assembly-ai/assembly-ai.module';
HoardingModule,
NamesModule,
AssemblyAiModule,
ClaudeModule,
BabyNamesModule,
],
controllers: [AppController],
providers: [

View File

@@ -1,5 +1,6 @@
import { Body, Controller, Post } from '@nestjs/common';
import { ApiResponse } from '@nestjs/swagger';
import { Body, Controller, Post, Res } from '@nestjs/common';
import { ApiBody, ApiConsumes, ApiResponse } from '@nestjs/swagger';
import { Response } from 'express';
interface Utterance {
speaker: string;
@@ -24,14 +25,22 @@ export class AssemblyAiController {
description: 'Converts utterances to a script format',
type: String,
})
@ApiBody({
description: 'Array of utterances or an object containing utterances',
type: String,
})
async utteranceToScript(
@Body() body: { utterances: Utterance[] } | Utterance[],
@Res() res: Response,
) {
const utterances = Array.isArray(body) ? body : body.utterances;
return utterances.map(
(utterance) => `
${utterance.speaker}:
return res.header('Content-Type', 'text/plain; charset=utf-8').send(
utterances
.map(
(utterance) => `Speaker ${utterance.speaker}:
${utterance.text}`,
)
.join('\n\n'),
);
}
}

View File

@@ -0,0 +1,92 @@
import {
Body,
Controller,
Get,
Post,
Redirect,
Render,
Req,
Res,
} from '@nestjs/common';
import { ApiConsumes, ApiTags } from '@nestjs/swagger';
import { BabyNamesService } from './baby-names.service';
import { Request, Response } from 'express';
@Controller('baby-names')
@ApiTags('baby-names')
export class BabyNamesController {
constructor(private readonly babyNamesService: BabyNamesService) {}
@Get()
@Render('baby-names/index')
async index(@Req() request: Request) {
const key = request.cookies['baby-names-key'] || null;
return { previousKey: key };
}
@Get('form')
@Render('baby-names/form')
async form(@Req() request: Request) {
const { key } = request.query;
if (!key) {
throw new Error("Missing 'key' field");
}
const currentIndex = await this.babyNamesService.getCurrentNumber(
key as string,
);
const name = this.babyNamesService.nameList[currentIndex];
const synonyms = this.babyNamesService.getSynonyms(name);
return {
key: key,
name,
index: currentIndex,
message: request.query.message || null,
synonyms: synonyms.join(', '),
};
}
@Post()
@ApiConsumes('multipart/form-data')
@Redirect('/baby-names', 302)
async submit(
@Body()
body: {
key: string;
name: string;
nameindex: string;
opinion: string;
pronunciation: string;
spelling: string;
comment: string;
},
@Res({ passthrough: true }) res: Response,
) {
const { key, name, opinion, nameindex, pronunciation, spelling, comment } =
body;
if (!key) {
throw new Error("Missing 'key' field");
}
await this.babyNamesService.addUserScore(key, name, {
opinion: parseInt(opinion, 10),
pronunciation: parseInt(pronunciation, 10),
spelling: parseInt(spelling, 10),
comment: comment || '',
});
await this.babyNamesService.writeUserNumber(
key,
parseInt(nameindex, 10) + 1,
);
res.cookie('baby-names-key', key, { maxAge: 30 * 24 * 60 * 60 * 1000 }); // 30 days
return {
url: `/baby-names/form?key=${key}&message=Logged ${name} as ${opinion}`,
};
}
@Get('data/names.json')
async getNamesData() {
return {
names: this.babyNamesService.nameList,
nameCount: this.babyNamesService.nameCountMap,
};
}
}

View File

@@ -0,0 +1,13 @@
import { Module } from '@nestjs/common';
import { BabyNamesController } from './baby-names.controller';
import { KvService } from 'src/kv/kv.service';
import { MinioService } from 'src/minio/minio.service';
import { BullModule } from '@nestjs/bull';
import { BabyNamesService } from './baby-names.service';
@Module({
providers: [KvService, MinioService, BabyNamesService],
imports: [],
controllers: [BabyNamesController],
})
export class BabyNamesModule {}

View File

@@ -0,0 +1,133 @@
import { Injectable } from '@nestjs/common';
import axios from 'axios';
import { KvService } from 'src/kv/kv.service';
import { MinioService } from 'src/minio/minio.service';
export interface Name {
name: string;
year: number;
gender: string;
count: number;
}
export interface NameMap {
[key: string]: Name[];
}
export type NameList = string[];
export interface NameCountMap {
[key: string]: number;
}
interface NameSynonyms {
name: string;
gender: string;
synonyms: string[];
}
export interface NameSynonymsMap {
[key: string]: NameSynonyms;
}
interface UserScoreMap {
[key: string]: object;
}
@Injectable()
export class BabyNamesService {
public nameList: NameList = [];
public nameCountMap: NameCountMap = {};
public nameSynonymsMap: NameSynonymsMap = {};
public userScoreMap: UserScoreMap = {};
constructor(private readonly minioService: MinioService) {
this.refreshNames();
}
private async refreshNames() {
this.nameCountMap = (
await axios.get('https://cache.sh/baby-name-data/namecount.json')
).data;
this.nameList = (
await axios.get('https://cache.sh/baby-name-data/namecount-list.json')
).data;
this.nameSynonymsMap = (
await axios.get('https://cache.sh/baby-name-data/btn_synonyms.json')
).data;
this.nameSynonymsMap = Object.keys(this.nameSynonymsMap).reduce(
(acc, key) => {
acc[key.toLowerCase()] = this.nameSynonymsMap[key];
return acc;
},
{} as NameSynonymsMap,
);
}
public async getCurrentNumber(userKey: string): Promise<number> {
const currentKey = await this.minioService
.getBuffer(
this.minioService.defaultBucketName,
`baby-names/${userKey}-current`,
)
.then((buffer) => buffer.toString())
.catch(() => null);
if (currentKey === null) {
await this.writeUserNumber(userKey, 0);
}
const currentNumber = parseInt(currentKey || '0', 10);
return currentNumber + 1;
}
public async writeUserNumber(userKey: string, number: number): Promise<void> {
await this.minioService.uploadBuffer(
this.minioService.defaultBucketName,
`baby-names/${userKey}-current`,
Buffer.from(number.toString()),
);
}
public async getUserScores(userKey: string): Promise<UserScoreMap> {
const scoresKey = await this.minioService
.getBuffer(
this.minioService.defaultBucketName,
`baby-names/${userKey}-scores`,
)
.then((buffer) => buffer.toString())
.catch(() => null);
if (scoresKey === null) {
return {};
}
return JSON.parse(scoresKey);
}
public async saveUserScores(
userKey: string,
scores: UserScoreMap,
): Promise<void> {
await this.minioService.uploadBuffer(
this.minioService.defaultBucketName,
`baby-names/${userKey}-scores`,
Buffer.from(JSON.stringify(scores)),
);
}
public async addUserScore(
userKey: string,
name: string,
score: object,
): Promise<void> {
const scores = await this.getUserScores(userKey);
scores[name] = score;
await this.saveUserScores(userKey, scores);
}
public getSynonyms(name: string): string[] {
const entry = this.nameSynonymsMap[name.toLowerCase()];
if (entry) {
return entry.synonyms;
}
return [];
}
}

View File

@@ -0,0 +1,66 @@
import Anthropic from '@anthropic-ai/sdk';
import { Body, Controller, Logger, Post, Res, Headers } from '@nestjs/common';
import { ApiBody, ApiConsumes, ApiResponse } from '@nestjs/swagger';
import { Response } from 'express';
@Controller('claude')
export class ClaudeController {
private readonly logger = new Logger(ClaudeController.name);
private readonly claude: Anthropic;
constructor() {
this.claude = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY,
});
}
@Post('script-to-meeting-summary')
@ApiResponse({
status: 200,
description: 'Converts a script to a meeting summary',
type: String,
})
@ApiBody({
description: 'Meeting transcript in script format',
type: String,
})
@ApiConsumes('text/plain')
async scriptToMeetingSummary(
@Body() body: string,
@Res() res: Response,
@Headers('x-api-key') apiKey: string,
) {
if (apiKey !== process.env.CLAUDE_ENDPOINT_KEY) {
this.logger.warn('Unauthorized access attempt');
return res.status(401).send('Unauthorized');
}
const instructionPrefix = `You are being contacted by an API which is making this call.
Do not respond with anything other than the text that should be returned to the user.
The meeting transcript follows the line separating these instructions which is a line of = symbols.
Return a two paragraph or less summary, bullet points of the topics, and bullet points of any action items.
Your response MUST be in markdown format.
Lists should be formatted with '- ' for bullet points.
==========
`;
this.logger.log(`Received script to summarize: ${body.length} characters`);
const response = await this.claude.messages.create({
model: 'claude-opus-4-20250514',
messages: [
{
role: 'user',
content: `${instructionPrefix}${body}`,
},
],
max_tokens: 1000,
});
return res
.header('Content-Type', 'text/plain; charset=utf-8')
.status(200)
.send((response.content[0] as any).text);
}
}

View File

@@ -0,0 +1,7 @@
import { Module } from '@nestjs/common';
import { ClaudeController } from './claude.controller';
@Module({
controllers: [ClaudeController]
})
export class ClaudeModule {}

View File

@@ -6,11 +6,7 @@ import { BullModule } from '@nestjs/bull';
@Module({
providers: [KvService, MinioService],
imports: [
BullModule.registerQueue({
name: 'kv',
}),
],
imports: [],
controllers: [KvController],
})
export class KvModule {}

View File

@@ -1,6 +1,4 @@
import { InjectQueue } from '@nestjs/bull';
import { Injectable, Logger } from '@nestjs/common';
import { Queue } from 'bull';
import { UploadedObjectInfo } from 'minio/dist/main/internal/type';
import { MinioService } from 'src/minio/minio.service';
@@ -12,10 +10,7 @@ export class KvService {
private readonly kvMetadataPath = `${this.kvPrefix}/${this.kvMetadataFileName}`;
private readonly logger: Logger = new Logger(KvService.name);
constructor(
private readonly minioService: MinioService,
@InjectQueue('kv') private kvProcessingQueue: Queue,
) {}
constructor(private readonly minioService: MinioService) {}
public generateFilePath(namespace: string, key: string): string {
return `${this.kvPrefix}/${namespace}/${key}`;
@@ -123,7 +118,6 @@ export class KvService {
};
await this.setMetadataFile(Buffer.from(JSON.stringify(metadata)));
this.logger.verbose(`Claimed namespace ${namespace}`);
this.kvProcessingQueue.add('namespaceModeration', metadata[namespace]);
return metadata[namespace];
}

View File

@@ -3,6 +3,7 @@ import { AppModule } from './app.module';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import { NestExpressApplication } from '@nestjs/platform-express';
import { join } from 'path';
import * as cookieParser from 'cookie-parser';
async function bootstrap() {
const app = await NestFactory.create<NestExpressApplication>(AppModule);
@@ -18,7 +19,9 @@ async function bootstrap() {
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('api', app, document);
app.enableCors({ origin: '*' });
app.useBodyParser('text');
app.useBodyParser('json', { limit: '50mb' });
app.useBodyParser('text', { limit: '50mb' });
app.use(cookieParser());
app.disable('x-powered-by');
await app.listen(3000);
}

129
views/baby-names/form.hbs Normal file
View File

@@ -0,0 +1,129 @@
<form action='/baby-names' method='post'>
{{#if message}}
<div>
<i>{{message}}</i>
</div>
{{/if}}
<div>
<label for='key'>Your key:</label>
<input type='text' name='key' value='{{key}}' required />
</div>
<div>
<h1>{{name}} <span style='color:gray'>{{lastName}}</span></h1>
</div>
<div>
<h3>{{synonyms}}</h3>
</div>
<input type='hidden' name='name' value='{{name}}' />
<input type='hidden' name='nameindex' value='{{index}}' />
<div
style='display: flex; flex-direction: column; gap: 1em; margin-bottom: 1em;'
>
{{! Opinion }}
<div>
<label for='opinion'>How do you feel about this name?</label>
<div style='width: 300px;'>
<input
type='range'
id='opinion'
name='opinion'
min='-2'
max='2'
step='1'
list='opinion-ticks'
style='width: 100%;'
required
/>
<datalist id='opinion-ticks'>
<option value='-2' label='Hate it'></option>
<option value='-1' label='Dislike it'></option>
<option value='0' label='Neutral'></option>
<option value='1' label='Like it'></option>
<option value='2' label='Love it'></option>
</datalist>
<div
style='display: flex; justify-content: space-between; font-size: 0.9em; margin-top: 0.2em;'
>
<span>Hate it</span>
<span>Dislike it</span>
<span>Neutral</span>
<span>Like it</span>
<span>Love it</span>
</div>
</div>
</div>
{{! Pronunciation }}
<div>
<label for='pronunciation'>Is this pronounceable?</label>
<div style='width: 300px;'>
<input
type='range'
id='pronunciation'
name='pronunciation'
min='-2'
max='2'
step='1'
list='pronunciation-ticks'
style='width: 100%;'
required
/>
<datalist id='pronunciation-ticks'>
<option value='-2' label='Very Hard'></option>
<option value='0' label='Normal'></option>
<option value='2' label='Very Easy'></option>
</datalist>
<div
style='display: flex; justify-content: space-between; font-size: 0.9em; margin-top: 0.2em;'
>
<span>Very Hard</span>
<span></span>
<span>Normal</span>
<span></span>
<span>Very Easy</span>
</div>
</div>
</div>
{{! Spelling }}
<div>
<label for='spelling'>How easy is this to spell?</label>
<div style='width: 300px;'>
<input
type='range'
id='spelling'
name='spelling'
min='-2'
max='2'
step='1'
list='spelling-ticks'
style='width: 100%;'
required
/>
<datalist id='spelling-ticks'>
<option value='-2' label='Very Hard'></option>
<option value='0' label='Normal'></option>
<option value='2' label='Very Easy'></option>
</datalist>
<div
style='display: flex; justify-content: space-between; font-size: 0.9em; margin-top: 0.2em;'
>
<span>Very Hard</span>
<span></span>
<span>Normal</span>
<span></span>
<span>Very Easy</span>
</div>
</div>
</div>
<div>
</div>
<div>
<textarea
name='comment'
rows='4'
cols='50'
placeholder='Any additional comments?'
></textarea>
</div>
</div>
<button type='submit'>Submit Opinion</button>
</form>

View File

@@ -0,0 +1,13 @@
<form action='/baby-names/form' method='get'>
<input type='text' name='key' placeholder='Enter your key' required />
<button type='submit'>Log In</button>
</form>
{{#if previousKey}}
<i>or</i>
<form action='/baby-names/form' method='get'>
<input type='hidden' name='key' value='{{previousKey}}' />
<button type='submit'>Continue as {{previousKey}}</button>
</form>
{{/if}}

View File

@@ -45,6 +45,19 @@
ora "5.4.1"
rxjs "7.8.1"
"@anthropic-ai/sdk@^0.40.0":
version "0.40.0"
resolved "https://registry.yarnpkg.com/@anthropic-ai/sdk/-/sdk-0.40.0.tgz#7a98cf38ec59ccb68aa23d098ffa97525fd265ae"
integrity sha512-NkIgtZxa4nWBzXtDuYe6Wumu1+cgEuEQtol8b1bVSjtS7Dvqex6iNdxaddV8PkRC8Z8mhbNw2m7j+9AD9uRRKw==
dependencies:
"@types/node" "^18.11.18"
"@types/node-fetch" "^2.6.4"
abort-controller "^3.0.0"
agentkeepalive "^4.2.1"
form-data-encoder "1.7.2"
formdata-node "^4.3.2"
node-fetch "^2.6.7"
"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.16.7", "@babel/code-frame@^7.25.7":
version "7.25.7"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.25.7.tgz#438f2c524071531d643c6f0188e1e28f130cebc7"
@@ -1048,6 +1061,11 @@
dependencies:
"@types/node" "*"
"@types/cookie-parser@^1.4.9":
version "1.4.9"
resolved "https://registry.yarnpkg.com/@types/cookie-parser/-/cookie-parser-1.4.9.tgz#f0e79c766a58ee7369a52e7509b3840222f68ed2"
integrity sha512-tGZiZ2Gtc4m3wIdLkZ8mkj1T6CEHb35+VApbL2T14Dew8HA7c+04dmKqsKRNC+8RJPm16JEK0tFSwdZqubfc4g==
"@types/cookiejar@^2.1.5":
version "2.1.5"
resolved "https://registry.yarnpkg.com/@types/cookiejar/-/cookiejar-2.1.5.tgz#14a3e83fa641beb169a2dd8422d91c3c345a9a78"
@@ -1171,6 +1189,14 @@
dependencies:
"@types/express" "*"
"@types/node-fetch@^2.6.4":
version "2.6.12"
resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.12.tgz#8ab5c3ef8330f13100a7479e2cd56d3386830a03"
integrity sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==
dependencies:
"@types/node" "*"
form-data "^4.0.0"
"@types/node@*":
version "22.7.5"
resolved "https://registry.yarnpkg.com/@types/node/-/node-22.7.5.tgz#cfde981727a7ab3611a481510b473ae54442b92b"
@@ -1183,6 +1209,13 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.63.tgz#1788fa8da838dbb5f9ea994b834278205db6ca2b"
integrity sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==
"@types/node@^18.11.18":
version "18.19.87"
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.87.tgz#690f000cc51e3c7f48bc00f7e86fac6eb550b709"
integrity sha512-OIAAu6ypnVZHmsHCeJ+7CCSub38QNBS9uceMQeg7K5Ur0Jr+wG9wEOEvvMbhp09pxD5czIUy/jND7s7Tb6Nw7A==
dependencies:
undici-types "~5.26.4"
"@types/node@^20.3.1":
version "20.16.11"
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.16.11.tgz#9b544c3e716b1577ac12e70f9145193f32750b33"
@@ -1568,6 +1601,13 @@ agent-base@6:
dependencies:
debug "4"
agentkeepalive@^4.2.1:
version "4.6.0"
resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.6.0.tgz#35f73e94b3f40bf65f105219c623ad19c136ea6a"
integrity sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==
dependencies:
humanize-ms "^1.2.1"
airtable@^0.12.2:
version "0.12.2"
resolved "https://registry.yarnpkg.com/airtable/-/airtable-0.12.2.tgz#e53e66db86744f9bc684faa58881d6c9c12f0e6f"
@@ -2310,6 +2350,14 @@ convert-source-map@^2.0.0:
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a"
integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==
cookie-parser@^1.4.7:
version "1.4.7"
resolved "https://registry.yarnpkg.com/cookie-parser/-/cookie-parser-1.4.7.tgz#e2125635dfd766888ffe90d60c286404fa0e7b26"
integrity sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==
dependencies:
cookie "0.7.2"
cookie-signature "1.0.6"
cookie-signature@1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
@@ -2320,6 +2368,11 @@ cookie@0.6.0:
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.6.0.tgz#2798b04b071b0ecbff0dbb62a505a8efa4e19051"
integrity sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==
cookie@0.7.2:
version "0.7.2"
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.2.tgz#556369c472a2ba910f2979891b526b3436237ed7"
integrity sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==
cookiejar@^2.1.4:
version "2.1.4"
resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.4.tgz#ee669c1fea2cf42dc31585469d193fef0d65771b"
@@ -3086,6 +3139,11 @@ fork-ts-checker-webpack-plugin@9.0.2:
semver "^7.3.5"
tapable "^2.2.1"
form-data-encoder@1.7.2:
version "1.7.2"
resolved "https://registry.yarnpkg.com/form-data-encoder/-/form-data-encoder-1.7.2.tgz#1f1ae3dccf58ed4690b86d87e4f57c654fbab040"
integrity sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==
form-data@^4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.1.tgz#ba1076daaaa5bfd7e99c1a6cb02aa0a5cff90d48"
@@ -3095,6 +3153,14 @@ form-data@^4.0.0:
combined-stream "^1.0.8"
mime-types "^2.1.12"
formdata-node@^4.3.2:
version "4.4.1"
resolved "https://registry.yarnpkg.com/formdata-node/-/formdata-node-4.4.1.tgz#23f6a5cb9cb55315912cbec4ff7b0f59bbd191e2"
integrity sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==
dependencies:
node-domexception "1.0.0"
web-streams-polyfill "4.0.0-beta.3"
formidable@^2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/formidable/-/formidable-2.1.2.tgz#fa973a2bec150e4ce7cac15589d7a25fc30ebd89"
@@ -3416,6 +3482,13 @@ human-signals@^2.1.0:
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0"
integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==
humanize-ms@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed"
integrity sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==
dependencies:
ms "^2.0.0"
iconv-lite@0.4.24, iconv-lite@^0.4.24:
version "0.4.24"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
@@ -4548,7 +4621,7 @@ ms@2.0.0:
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==
ms@2.1.3, ms@^2.1.1, ms@^2.1.3:
ms@2.1.3, ms@^2.0.0, ms@^2.1.1, ms@^2.1.3:
version "2.1.3"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
@@ -4622,6 +4695,11 @@ node-addon-api@^5.0.0:
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-5.1.0.tgz#49da1ca055e109a23d537e9de43c09cca21eb762"
integrity sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==
node-domexception@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5"
integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==
node-emoji@1.11.0:
version "1.11.0"
resolved "https://registry.yarnpkg.com/node-emoji/-/node-emoji-1.11.0.tgz#69a0150e6946e2f115e9d7ea4df7971e2628301c"
@@ -5861,6 +5939,11 @@ uid@2.0.2:
dependencies:
"@lukeed/csprng" "^1.0.0"
undici-types@~5.26.4:
version "5.26.5"
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617"
integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==
undici-types@~6.19.2:
version "6.19.8"
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02"
@@ -5989,6 +6072,11 @@ web-encoding@^1.1.5:
optionalDependencies:
"@zxing/text-encoding" "0.9.0"
web-streams-polyfill@4.0.0-beta.3:
version "4.0.0-beta.3"
resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz#2898486b74f5156095e473efe989dcf185047a38"
integrity sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==
webidl-conversions@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"