Add contact, upgrade versions, add docs
This commit is contained in:
@@ -15,3 +15,5 @@ S3_SECRET_KEY="localminio"
|
|||||||
S3_BUCKET="devbucket"
|
S3_BUCKET="devbucket"
|
||||||
|
|
||||||
FOCO_LIVE_AIRTABLE_APIKEY=
|
FOCO_LIVE_AIRTABLE_APIKEY=
|
||||||
|
|
||||||
|
MAILGUN_SEND_KEY_HOOLI=
|
||||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@@ -37,3 +37,6 @@ lerna-debug.log*
|
|||||||
!.vscode/extensions.json
|
!.vscode/extensions.json
|
||||||
|
|
||||||
data/
|
data/
|
||||||
|
|
||||||
|
# Ignore Bruno for now
|
||||||
|
api.us.dev/
|
@@ -41,7 +41,8 @@
|
|||||||
"haversine-ts": "^1.2.0",
|
"haversine-ts": "^1.2.0",
|
||||||
"hbs": "^4.2.0",
|
"hbs": "^4.2.0",
|
||||||
"ioredis": "^5.3.2",
|
"ioredis": "^5.3.2",
|
||||||
"minio": "^7.1.3",
|
"mailgun.js": "^10.2.3",
|
||||||
|
"minio": "^8.0.1",
|
||||||
"open-graph-scraper": "^6.3.0",
|
"open-graph-scraper": "^6.3.0",
|
||||||
"prom-client": "^15.0.0",
|
"prom-client": "^15.0.0",
|
||||||
"ramda": "^0.29.0",
|
"ramda": "^0.29.0",
|
||||||
|
@@ -10,7 +10,6 @@ import { DomainrproxyModule } from './domainrproxy/domainrproxy.module';
|
|||||||
import configuration from './config/configuration';
|
import configuration from './config/configuration';
|
||||||
import { CacheInterceptor, CacheModule } from '@nestjs/cache-manager';
|
import { CacheInterceptor, CacheModule } from '@nestjs/cache-manager';
|
||||||
import { IrcbotModule } from './ircbot/ircbot.module';
|
import { IrcbotModule } from './ircbot/ircbot.module';
|
||||||
import { OgScraperModule } from './ogscraper/ogscraper.module';
|
|
||||||
import { PrometheusModule } from '@willsoto/nestjs-prometheus';
|
import { PrometheusModule } from '@willsoto/nestjs-prometheus';
|
||||||
import { MinioModule } from './minio/minio.module';
|
import { MinioModule } from './minio/minio.module';
|
||||||
import { KvModule } from './kv/kv.module';
|
import { KvModule } from './kv/kv.module';
|
||||||
@@ -28,6 +27,8 @@ import { RedisModule, RedisModuleOptions } from '@liaoliaots/nestjs-redis';
|
|||||||
import { AdsbExchangeModule } from './adsb-exchange/adsb-exchange.module';
|
import { AdsbExchangeModule } from './adsb-exchange/adsb-exchange.module';
|
||||||
import { APP_INTERCEPTOR } from '@nestjs/core';
|
import { APP_INTERCEPTOR } from '@nestjs/core';
|
||||||
import { JunkDrawerModule } from './junk-drawer/junk-drawer.module';
|
import { JunkDrawerModule } from './junk-drawer/junk-drawer.module';
|
||||||
|
import { EmailModule } from './email/email.module';
|
||||||
|
import { ContactModule } from './contact/contact.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -91,7 +92,6 @@ import { JunkDrawerModule } from './junk-drawer/junk-drawer.module';
|
|||||||
UsersModule,
|
UsersModule,
|
||||||
DomainrproxyModule,
|
DomainrproxyModule,
|
||||||
IrcbotModule,
|
IrcbotModule,
|
||||||
OgScraperModule,
|
|
||||||
MinioModule,
|
MinioModule,
|
||||||
KvModule,
|
KvModule,
|
||||||
RedirectsModule,
|
RedirectsModule,
|
||||||
@@ -102,6 +102,8 @@ import { JunkDrawerModule } from './junk-drawer/junk-drawer.module';
|
|||||||
JobsModule,
|
JobsModule,
|
||||||
AdsbExchangeModule,
|
AdsbExchangeModule,
|
||||||
JunkDrawerModule,
|
JunkDrawerModule,
|
||||||
|
EmailModule,
|
||||||
|
ContactModule,
|
||||||
],
|
],
|
||||||
controllers: [AppController],
|
controllers: [AppController],
|
||||||
providers: [
|
providers: [
|
||||||
@@ -111,5 +113,6 @@ import { JunkDrawerModule } from './junk-drawer/junk-drawer.module';
|
|||||||
useClass: CacheInterceptor,
|
useClass: CacheInterceptor,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
exports: [PrometheusModule],
|
||||||
})
|
})
|
||||||
export class AppModule {}
|
export class AppModule {}
|
||||||
|
@@ -26,6 +26,20 @@ class HashDto {
|
|||||||
rounds?: number;
|
rounds?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class LoginDto {
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'The username to authenticate',
|
||||||
|
default: 'admin',
|
||||||
|
})
|
||||||
|
username: string;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'The password to authenticate',
|
||||||
|
default: 'password',
|
||||||
|
})
|
||||||
|
password: string;
|
||||||
|
}
|
||||||
|
|
||||||
@ApiTags('auth')
|
@ApiTags('auth')
|
||||||
@Controller('auth')
|
@Controller('auth')
|
||||||
export class AuthController {
|
export class AuthController {
|
||||||
@@ -33,6 +47,7 @@ export class AuthController {
|
|||||||
|
|
||||||
@HttpCode(HttpStatus.OK)
|
@HttpCode(HttpStatus.OK)
|
||||||
@Post('login')
|
@Post('login')
|
||||||
|
@ApiBody({ type: LoginDto })
|
||||||
signIn(@Body() signInDto: Record<string, any>) {
|
signIn(@Body() signInDto: Record<string, any>) {
|
||||||
return this.authService.signIn(signInDto.username, signInDto.password);
|
return this.authService.signIn(signInDto.username, signInDto.password);
|
||||||
}
|
}
|
||||||
|
@@ -44,4 +44,7 @@ export default () => ({
|
|||||||
bucketName: process.env.JUNK_DRAWER_BUCKET_NAME ?? 'junk-drawer',
|
bucketName: process.env.JUNK_DRAWER_BUCKET_NAME ?? 'junk-drawer',
|
||||||
rootPath: process.env.JUNK_DRAWER_ROOT_PATH ?? '',
|
rootPath: process.env.JUNK_DRAWER_ROOT_PATH ?? '',
|
||||||
},
|
},
|
||||||
|
mailgun: {
|
||||||
|
hooliKey: process.env.MAILGUN_SEND_KEY_HOOLI ?? '',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
85
src/contact/contact.controller.ts
Normal file
85
src/contact/contact.controller.ts
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
import {
|
||||||
|
Body,
|
||||||
|
Controller,
|
||||||
|
Get,
|
||||||
|
NotFoundException,
|
||||||
|
Param,
|
||||||
|
Post,
|
||||||
|
UseInterceptors,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { FileInterceptor } from '@nestjs/platform-express';
|
||||||
|
import { ApiConsumes, ApiParam, ApiQuery, ApiTags } from '@nestjs/swagger';
|
||||||
|
import { EmailService } from 'src/email/email.service';
|
||||||
|
import { PowService } from 'src/pow/pow.service';
|
||||||
|
|
||||||
|
interface Destination {
|
||||||
|
email: string;
|
||||||
|
powChallenge: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
type Destinations = {
|
||||||
|
[key: string]: Destination;
|
||||||
|
};
|
||||||
|
|
||||||
|
const destinations: Destinations = {
|
||||||
|
focolive: {
|
||||||
|
// email: 'hello@fortcollinslive.com',
|
||||||
|
email: 'mailtest@chip.bz',
|
||||||
|
powChallenge: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
@Controller('contact')
|
||||||
|
@ApiTags('contact')
|
||||||
|
export class ContactController {
|
||||||
|
constructor(
|
||||||
|
private readonly powService: PowService,
|
||||||
|
private readonly emailService: EmailService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
@Get(':destination/challenge')
|
||||||
|
@ApiParam({
|
||||||
|
name: 'destination',
|
||||||
|
required: true,
|
||||||
|
description: 'The destination to send the email to, the contact "class"',
|
||||||
|
})
|
||||||
|
async getChallenge(@Param('destination') destination: string) {
|
||||||
|
const dest = destinations[destination];
|
||||||
|
if (!dest) {
|
||||||
|
throw new NotFoundException();
|
||||||
|
}
|
||||||
|
if (dest.powChallenge) {
|
||||||
|
return {
|
||||||
|
challenge: await this.powService.generateChallenge(),
|
||||||
|
difficulty: this.powService.getDifficulty(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Post(':destination')
|
||||||
|
@ApiConsumes('multipart/form-data')
|
||||||
|
@UseInterceptors(FileInterceptor('file'))
|
||||||
|
async sendEmail(
|
||||||
|
@Param('destination') destination: string,
|
||||||
|
@Body('challenge') challenge: string,
|
||||||
|
@Body('proof') proof: string,
|
||||||
|
@Body('subject') subject: string,
|
||||||
|
@Body('text') text: string,
|
||||||
|
) {
|
||||||
|
const dest = destinations[destination];
|
||||||
|
if (!dest) {
|
||||||
|
throw new NotFoundException();
|
||||||
|
}
|
||||||
|
if (dest.powChallenge) {
|
||||||
|
if (!(await this.powService.verifyChallenge(challenge, proof))) {
|
||||||
|
throw new NotFoundException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const result = await this.emailService.sendEmail(
|
||||||
|
[dest.email],
|
||||||
|
subject,
|
||||||
|
text,
|
||||||
|
);
|
||||||
|
this.powService.markChallengeAsComplete(challenge);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
12
src/contact/contact.module.ts
Normal file
12
src/contact/contact.module.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { ContactController } from './contact.controller';
|
||||||
|
import { EmailService } from 'src/email/email.service';
|
||||||
|
import { PowService } from 'src/pow/pow.service';
|
||||||
|
import { PowModule } from 'src/pow/pow.module';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [PowModule],
|
||||||
|
controllers: [ContactController],
|
||||||
|
providers: [EmailService],
|
||||||
|
})
|
||||||
|
export class ContactModule {}
|
@@ -9,7 +9,7 @@ import {
|
|||||||
} from '@nestjs/swagger';
|
} from '@nestjs/swagger';
|
||||||
import { DomainrParsedStatusResult } from './domainrproxy.service';
|
import { DomainrParsedStatusResult } from './domainrproxy.service';
|
||||||
|
|
||||||
@ApiTags('Domainr')
|
@ApiTags('domainr')
|
||||||
@Controller('domainrproxy')
|
@Controller('domainrproxy')
|
||||||
export class DomainrproxyController {
|
export class DomainrproxyController {
|
||||||
constructor(private readonly proxyService: DomainrproxyService) {}
|
constructor(private readonly proxyService: DomainrproxyService) {}
|
||||||
|
8
src/email/email.module.ts
Normal file
8
src/email/email.module.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { EmailService } from './email.service';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
providers: [EmailService],
|
||||||
|
exports: [EmailService],
|
||||||
|
})
|
||||||
|
export class EmailModule {}
|
36
src/email/email.service.ts
Normal file
36
src/email/email.service.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import Mailgun from 'mailgun.js';
|
||||||
|
import * as formdata from 'form-data';
|
||||||
|
import { IMailgunClient } from 'mailgun.js/Interfaces';
|
||||||
|
import { ConfigService } from '@nestjs/config';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class EmailService {
|
||||||
|
private readonly mailgun: IMailgunClient | undefined;
|
||||||
|
private static readonly emailFromDomain = 'hello.hooli.co';
|
||||||
|
|
||||||
|
constructor(private readonly configService: ConfigService) {
|
||||||
|
const mailgun = new Mailgun(formdata);
|
||||||
|
const hooliKey = this.configService.get<string>('mailgun.hooliKey');
|
||||||
|
if (!hooliKey) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.mailgun = mailgun.client({
|
||||||
|
username: 'api',
|
||||||
|
key: hooliKey,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async sendEmail(to: string[], subject: string, text: string) {
|
||||||
|
if (!this.mailgun) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return this.mailgun.messages.create(EmailService.emailFromDomain, {
|
||||||
|
from: `HooliMail <hooli-mail@${EmailService.emailFromDomain}>`,
|
||||||
|
to,
|
||||||
|
subject,
|
||||||
|
text,
|
||||||
|
html: text,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@@ -10,7 +10,6 @@ import {
|
|||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { FileService } from './file.service';
|
import { FileService } from './file.service';
|
||||||
import { Post } from '@nestjs/common';
|
import { Post } from '@nestjs/common';
|
||||||
import { UploadedObjectInfo } from 'minio';
|
|
||||||
import { FileInterceptor } from '@nestjs/platform-express';
|
import { FileInterceptor } from '@nestjs/platform-express';
|
||||||
import { ApiConsumes, ApiTags } from '@nestjs/swagger';
|
import { ApiConsumes, ApiTags } from '@nestjs/swagger';
|
||||||
import { InjectMetric } from '@willsoto/nestjs-prometheus';
|
import { InjectMetric } from '@willsoto/nestjs-prometheus';
|
||||||
|
@@ -1,7 +1,8 @@
|
|||||||
import { Injectable, Logger } from '@nestjs/common';
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@nestjs/config';
|
||||||
import { Cron } from '@nestjs/schedule';
|
import { Cron } from '@nestjs/schedule';
|
||||||
import { ItemBucketMetadata, UploadedObjectInfo } from 'minio';
|
import { ItemBucketMetadata } from 'minio';
|
||||||
|
import { UploadedObjectInfo } from 'minio/dist/main/internal/type';
|
||||||
import { MinioService } from 'src/minio/minio.service';
|
import { MinioService } from 'src/minio/minio.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
|
@@ -83,7 +83,6 @@ export class JunkDrawerController {
|
|||||||
@Body('description') description: string,
|
@Body('description') description: string,
|
||||||
@Body('private-ish') privateIsh: boolean,
|
@Body('private-ish') privateIsh: boolean,
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
console.log(privateIsh);
|
|
||||||
const uniqueSlug = generateUniqueSlug({
|
const uniqueSlug = generateUniqueSlug({
|
||||||
random: privateIsh,
|
random: privateIsh,
|
||||||
});
|
});
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import { InjectQueue } from '@nestjs/bull';
|
import { InjectQueue } from '@nestjs/bull';
|
||||||
import { Injectable, Logger } from '@nestjs/common';
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
import { Queue } from 'bull';
|
import { Queue } from 'bull';
|
||||||
import { UploadedObjectInfo } from 'minio';
|
import { UploadedObjectInfo } from 'minio/dist/main/internal/type';
|
||||||
import { MinioService } from 'src/minio/minio.service';
|
import { MinioService } from 'src/minio/minio.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
|
@@ -2,7 +2,9 @@ import { CACHE_MANAGER } from '@nestjs/cache-manager';
|
|||||||
import { Cache } from 'cache-manager';
|
import { Cache } from 'cache-manager';
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@nestjs/config';
|
||||||
import { Client, ItemBucketMetadata, UploadedObjectInfo } from 'minio';
|
import { Client, ItemBucketMetadata } from 'minio';
|
||||||
|
import { UploadedObjectInfo } from 'minio/dist/main/internal/type';
|
||||||
|
import { open, readFileSync } from 'fs';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class MinioService {
|
export class MinioService {
|
||||||
@@ -30,12 +32,8 @@ export class MinioService {
|
|||||||
filePath: string,
|
filePath: string,
|
||||||
metadata?: ItemBucketMetadata,
|
metadata?: ItemBucketMetadata,
|
||||||
): Promise<UploadedObjectInfo> {
|
): Promise<UploadedObjectInfo> {
|
||||||
return await this.client.fPutObject(
|
const file = readFileSync(filePath);
|
||||||
bucketName,
|
return this.uploadBuffer(bucketName, objectName, file, metadata);
|
||||||
objectName,
|
|
||||||
filePath,
|
|
||||||
metadata,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async uploadBuffer(
|
public async uploadBuffer(
|
||||||
@@ -44,10 +42,11 @@ export class MinioService {
|
|||||||
buffer: Buffer,
|
buffer: Buffer,
|
||||||
metadata?: ItemBucketMetadata,
|
metadata?: ItemBucketMetadata,
|
||||||
): Promise<UploadedObjectInfo> {
|
): Promise<UploadedObjectInfo> {
|
||||||
return await this.client.putObject(
|
return this.client.putObject(
|
||||||
bucketName,
|
bucketName,
|
||||||
objectName,
|
objectName,
|
||||||
buffer,
|
buffer,
|
||||||
|
buffer.length,
|
||||||
metadata,
|
metadata,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -1,34 +0,0 @@
|
|||||||
import { Body, Controller, Post } from '@nestjs/common';
|
|
||||||
import { ApiProperty, ApiTags } from '@nestjs/swagger';
|
|
||||||
const ogs = require('open-graph-scraper');
|
|
||||||
import { SuccessResult } from 'open-graph-scraper';
|
|
||||||
import { OgScraperService } from './ogscraper.service';
|
|
||||||
import { InjectMetric } from '@willsoto/nestjs-prometheus';
|
|
||||||
import { Histogram } from 'prom-client';
|
|
||||||
|
|
||||||
class ScrapeOgDto {
|
|
||||||
@ApiProperty({
|
|
||||||
description: 'URL of the page to fetch Open Graph metadata of',
|
|
||||||
example:
|
|
||||||
'https://qz.com/1903322/why-pivot-tables-are-the-spreadsheets-most-powerful-tool',
|
|
||||||
})
|
|
||||||
url: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Controller('ogscraper')
|
|
||||||
@ApiTags('open-graph-scraper')
|
|
||||||
export class OgScraperController {
|
|
||||||
constructor(
|
|
||||||
private readonly ogScraperService: OgScraperService,
|
|
||||||
@InjectMetric('generation_time')
|
|
||||||
public generationTime: Histogram<string>,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
@Post('')
|
|
||||||
async scrapeOg(@Body() body: ScrapeOgDto): Promise<SuccessResult> {
|
|
||||||
const end = this.generationTime.startTimer();
|
|
||||||
const response = await this.ogScraperService.getOg(body.url);
|
|
||||||
end();
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,28 +0,0 @@
|
|||||||
import { Module } from '@nestjs/common';
|
|
||||||
import { OgScraperController } from './ogscraper.controller';
|
|
||||||
import { OgScraperService } from './ogscraper.service';
|
|
||||||
import {
|
|
||||||
PrometheusModule,
|
|
||||||
makeHistogramProvider,
|
|
||||||
} from '@willsoto/nestjs-prometheus';
|
|
||||||
|
|
||||||
@Module({
|
|
||||||
imports: [
|
|
||||||
PrometheusModule.register({
|
|
||||||
customMetricPrefix: 'ogscraper',
|
|
||||||
defaultMetrics: {
|
|
||||||
enabled: false,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
controllers: [OgScraperController],
|
|
||||||
providers: [
|
|
||||||
OgScraperService,
|
|
||||||
makeHistogramProvider({
|
|
||||||
name: 'generation_time',
|
|
||||||
help: 'Open Graph Scraping response times',
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
exports: [OgScraperService],
|
|
||||||
})
|
|
||||||
export class OgScraperModule {}
|
|
@@ -1,20 +0,0 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
|
||||||
import { ConfigService } from '@nestjs/config';
|
|
||||||
const ogs = require('open-graph-scraper');
|
|
||||||
import { SuccessResult } from 'open-graph-scraper';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class OgScraperService {
|
|
||||||
constructor(public readonly configService: ConfigService) {}
|
|
||||||
|
|
||||||
async getOg(url: string): Promise<SuccessResult> {
|
|
||||||
return ogs({
|
|
||||||
url,
|
|
||||||
fetchOptions: {
|
|
||||||
headers: {
|
|
||||||
'user-agent': this.configService.get<string>('userAgent') || '',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,12 +1,29 @@
|
|||||||
import { Body, Controller, Get, Post, Query, Render } from '@nestjs/common';
|
import {
|
||||||
|
BadRequestException,
|
||||||
|
Body,
|
||||||
|
Controller,
|
||||||
|
Get,
|
||||||
|
Post,
|
||||||
|
Query,
|
||||||
|
Render,
|
||||||
|
} from '@nestjs/common';
|
||||||
import { PowService } from './pow.service';
|
import { PowService } from './pow.service';
|
||||||
import { ApiBody, ApiQuery, ApiTags } from '@nestjs/swagger';
|
import { ApiBody, ApiProperty, ApiQuery, ApiTags } from '@nestjs/swagger';
|
||||||
|
import { ConfigService } from '@nestjs/config';
|
||||||
|
|
||||||
|
class SolveDto {
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'The challenge to solve',
|
||||||
|
})
|
||||||
|
challenge: string;
|
||||||
|
}
|
||||||
|
|
||||||
@ApiTags('pow')
|
@ApiTags('pow')
|
||||||
@Controller('pow')
|
@Controller('pow')
|
||||||
export class PowController {
|
export class PowController {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly powService: PowService,
|
private readonly powService: PowService,
|
||||||
|
private readonly configService: ConfigService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@Get('')
|
@Get('')
|
||||||
@@ -23,8 +40,12 @@ export class PowController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Post('challenge')
|
@Post('challenge')
|
||||||
@ApiBody({ schema: { properties: { challenge: { type: 'string' }, proof: { type: 'string' } } } })
|
@ApiBody({
|
||||||
async verifyChallenge(@Body() body: { challenge: string, proof: string }) {
|
schema: {
|
||||||
|
properties: { challenge: { type: 'string' }, proof: { type: 'string' } },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
async verifyChallenge(@Body() body: { challenge: string; proof: string }) {
|
||||||
return this.powService.verifyChallenge(body.challenge, body.proof);
|
return this.powService.verifyChallenge(body.challenge, body.proof);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,4 +55,12 @@ export class PowController {
|
|||||||
return this.powService.markChallengeAsComplete(body.challenge);
|
return this.powService.markChallengeAsComplete(body.challenge);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Post('challenge/solve')
|
||||||
|
async solveChallenge(@Body() body: SolveDto) {
|
||||||
|
if (this.configService.get<boolean>('isProduction')) {
|
||||||
|
throw new BadRequestException('This endpoint is disabled in production');
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.powService.performChallenge(body.challenge);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,44 +1,34 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { PowService } from './pow.service';
|
import { PowService } from './pow.service';
|
||||||
import { CacheModule } from '@nestjs/cache-manager';
|
|
||||||
import { PowController } from './pow.controller';
|
import { PowController } from './pow.controller';
|
||||||
import { PrometheusModule, makeGaugeProvider } from '@willsoto/nestjs-prometheus';
|
import { makeGaugeProvider } from '@willsoto/nestjs-prometheus';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [],
|
||||||
CacheModule.register({
|
providers: [
|
||||||
ttl: 5,
|
PowService,
|
||||||
max: 10,
|
|
||||||
}),
|
|
||||||
PrometheusModule.register({
|
|
||||||
customMetricPrefix: 'pow',
|
|
||||||
defaultMetrics: {
|
|
||||||
enabled: false,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
providers: [PowService,
|
|
||||||
makeGaugeProvider({
|
makeGaugeProvider({
|
||||||
name: 'challenges_generated',
|
name: 'pow_challenges_generated',
|
||||||
help: 'The total number of POW challenges generated',
|
help: 'The total number of POW challenges generated',
|
||||||
}),
|
}),
|
||||||
makeGaugeProvider({
|
makeGaugeProvider({
|
||||||
name: 'challenges_completed',
|
name: 'pow_challenges_completed',
|
||||||
help: 'The total number of POW challenges completed',
|
help: 'The total number of POW challenges completed',
|
||||||
}),
|
}),
|
||||||
makeGaugeProvider({
|
makeGaugeProvider({
|
||||||
name: 'successful_verifies',
|
name: 'pow_successful_verifies',
|
||||||
help: 'The total number of successful POW challenge verifications',
|
help: 'The total number of successful POW challenge verifications',
|
||||||
}),
|
}),
|
||||||
makeGaugeProvider({
|
makeGaugeProvider({
|
||||||
name: 'failed_verifies',
|
name: 'pow_failed_verifies',
|
||||||
help: 'The total number of failed POW challenge verifications',
|
help: 'The total number of failed POW challenge verifications',
|
||||||
}),
|
}),
|
||||||
makeGaugeProvider({
|
makeGaugeProvider({
|
||||||
name: 'difficulty',
|
name: 'pow_difficulty',
|
||||||
help: 'The current POW difficulty',
|
help: 'The current POW difficulty',
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
controllers: [PowController]
|
controllers: [PowController],
|
||||||
|
exports: [PowService],
|
||||||
})
|
})
|
||||||
export class PowModule {}
|
export class PowModule {}
|
||||||
|
@@ -4,7 +4,6 @@ import { InjectMetric } from '@willsoto/nestjs-prometheus';
|
|||||||
import { Cache } from 'cache-manager';
|
import { Cache } from 'cache-manager';
|
||||||
import { randomBytes, createHash } from 'crypto';
|
import { randomBytes, createHash } from 'crypto';
|
||||||
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class PowService {
|
export class PowService {
|
||||||
private readonly logger = new Logger(PowService.name);
|
private readonly logger = new Logger(PowService.name);
|
||||||
@@ -12,18 +11,18 @@ export class PowService {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(CACHE_MANAGER) private cacheManager: Cache,
|
@Inject(CACHE_MANAGER) private cacheManager: Cache,
|
||||||
@InjectMetric('challenges_generated') private challengesGenerated: any,
|
@InjectMetric('pow_challenges_generated') private challengesGenerated: any,
|
||||||
@InjectMetric('challenges_completed') private challengesCompleted: any,
|
@InjectMetric('pow_challenges_completed') private challengesCompleted: any,
|
||||||
@InjectMetric('successful_verifies') private successfulVerifies: any,
|
@InjectMetric('pow_successful_verifies') private successfulVerifies: any,
|
||||||
@InjectMetric('failed_verifies') private failedVerifies: any,
|
@InjectMetric('pow_failed_verifies') private failedVerifies: any,
|
||||||
@InjectMetric('difficulty') private powDifficulty: any,
|
@InjectMetric('pow_difficulty') private powDifficulty: any,
|
||||||
) {
|
) {
|
||||||
this.powDifficulty.set(this.difficulty);
|
this.powDifficulty.set(this.difficulty);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate a proof of work challenge, stored to redis for verification within
|
* Generate a proof of work challenge, stored to redis for verification within
|
||||||
* the next 10 seconds.
|
* the next 60 seconds.
|
||||||
*/
|
*/
|
||||||
async generateChallenge() {
|
async generateChallenge() {
|
||||||
const challenge = this.generateRandom256BitString();
|
const challenge = this.generateRandom256BitString();
|
||||||
@@ -38,7 +37,6 @@ export class PowService {
|
|||||||
return randomString;
|
return randomString;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
hashAndCheck(string: string) {
|
hashAndCheck(string: string) {
|
||||||
return this.hashPassesDifficulty(this.hashString(string), this.difficulty);
|
return this.hashPassesDifficulty(this.hashString(string), this.difficulty);
|
||||||
}
|
}
|
||||||
@@ -101,5 +99,4 @@ export class PowService {
|
|||||||
getDifficulty() {
|
getDifficulty() {
|
||||||
return this.difficulty;
|
return this.difficulty;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user