Skip to content

Commit e8e3b09

Browse files
committed
feat: add subscription plan and payment
1 parent 077f1ef commit e8e3b09

24 files changed

+2755
-17946
lines changed

package-lock.json

Lines changed: 0 additions & 15920 deletions
This file was deleted.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
"@nestjs/mongoose": "^10.1.0",
3939
"@nestjs/passport": "^10.0.3",
4040
"@nestjs/platform-express": "^10.3.0",
41-
"@nestjs/schedule": "^4.1.1",
41+
"@nestjs/schedule": "^4.1.2",
4242
"@nestjs/serve-static": "^4.0.2",
4343
"@nestjs/swagger": "^7.1.17",
4444
"@nestjs/terminus": "^10.2.3",
@@ -66,6 +66,7 @@
6666
"passport": "^0.6.0",
6767
"passport-jwt": "^4.0.1",
6868
"passport-local": "^1.0.0",
69+
"passport-strategy": "^1.0.0",
6970
"reflect-metadata": "^0.2.1",
7071
"rxjs": "^7.8.1",
7172
"source-map-support": "^0.5.21",

src/app.module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import ServiceRegistryModule from './modules/service-registry/service-registry.m
1414
import { SubscriptionsModule } from './modules/subscriptions/subscriptions.module';
1515
import { UserModule } from './modules/users/user.module';
1616
import { SharedModule } from './shared/shared.module';
17+
import { InvoiceModule } from './modules/invoices/invoice.module';
1718

1819
@Module({
1920
imports: [
@@ -28,6 +29,7 @@ import { SharedModule } from './shared/shared.module';
2829
ServicesConfigModule,
2930
SubscriptionsModule,
3031
LogModule,
32+
InvoiceModule,
3133
],
3234
controllers: [],
3335
providers: [],

src/modules/auth/auth.module.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import AuthController from './auth.controller';
88
import AuthService from './auth.service';
99
import JwtStrategy from './strategies/jwt.strategy';
1010
import LocalStrategy from './strategies/local.strategy';
11+
import { Nip98Strategy } from './strategies/nip-98.strategy';
12+
import { Nip98AuthGuard } from './guards/nip98-auth.guard';
1113

1214
@Module({
1315
imports: [
@@ -20,7 +22,8 @@ import LocalStrategy from './strategies/local.strategy';
2022
}),
2123
}),
2224
],
23-
providers: [AuthService, LocalStrategy, JwtStrategy],
25+
providers: [AuthService, LocalStrategy, JwtStrategy, Nip98Strategy, Nip98AuthGuard],
2426
controllers: [AuthController],
27+
exports: [Nip98AuthGuard,Nip98Strategy],
2528
})
2629
export default class AuthModule {}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { Injectable, UnauthorizedException } from '@nestjs/common';
2+
import { AuthGuard } from '@nestjs/passport';
3+
4+
@Injectable()
5+
export class Nip98AuthGuard extends AuthGuard('nip98') {
6+
handleRequest(err: any, user: any) {
7+
if (err || !user) {
8+
throw err || new UnauthorizedException();
9+
}
10+
return user;
11+
}
12+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import { Injectable, UnauthorizedException } from '@nestjs/common';
2+
import { PassportStrategy } from '@nestjs/passport';
3+
import { Event, verifyEvent } from 'nostr-tools';
4+
import { Request } from 'express';
5+
import { Strategy } from 'passport-strategy';
6+
7+
@Injectable()
8+
export class Nip98Strategy extends PassportStrategy(Strategy, 'nip98') {
9+
public Scheme = 'Nostr';
10+
11+
constructor() {
12+
super();
13+
}
14+
15+
authenticate(req: unknown) {
16+
const request = req as Request
17+
const authHeader = request.headers['authorization'];
18+
19+
if (!authHeader) {
20+
return this.fail(new UnauthorizedException('Missing Authorization header'), 401);
21+
}
22+
23+
if (authHeader.slice(0, 5) !== this.Scheme) {
24+
return this.fail(new UnauthorizedException('Invalid auth scheme'), 401);
25+
}
26+
27+
const token = authHeader.slice(6);
28+
29+
const bToken = Buffer.from(token, 'base64').toString('utf-8');
30+
31+
if (!bToken || bToken.length === 0 || bToken[0] != '{') {
32+
return this.fail(new UnauthorizedException('Invalid token'), 401);
33+
}
34+
35+
const ev = JSON.parse(bToken) as Event;
36+
37+
const isValidEvent = verifyEvent(ev);
38+
if (!isValidEvent) {
39+
return this.fail(new UnauthorizedException('Invalid event'), 401);
40+
}
41+
42+
if (ev.kind != 27_235) {
43+
return this.fail(new UnauthorizedException('Invalid nostr event, wrong kind'), 401);
44+
}
45+
46+
const now = Date.now();
47+
const diffTime = Math.abs(ev.created_at * 1000 - now);
48+
49+
// if (diffTime < 1 * 60 * 1000) { // 1 min
50+
// return this.fail(new UnauthorizedException('Invalid nostr event, timestamp out of range'), 401);
51+
// }
52+
53+
const urlTag = ev.tags[0]?.[1];
54+
const methodTag = ev.tags[1]?.[1];
55+
const a = new URL(urlTag!).pathname;
56+
console.log(new URL(urlTag!).pathname == request.path);
57+
if (!urlTag || new URL(urlTag).pathname !== request.path) {
58+
return this.fail(new UnauthorizedException('Invalid nostr event, URL tag invalid'), 401);
59+
}
60+
61+
if (!methodTag || methodTag.toLowerCase() !== request.method.toLowerCase()) {
62+
return { success: false, message: 'Invalid nostr event, method tag invalid' };
63+
}
64+
65+
this.success({ pubkey: ev.pubkey });
66+
}
67+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { PickType } from '@nestjs/swagger';
2+
import { InvoiceDto } from './invoice.dto';
3+
4+
export class CreateInvoice extends PickType(InvoiceDto, [
5+
'checkoutSessionId',
6+
'status',
7+
'subscriptionId',
8+
'totalAmount',
9+
'unit',
10+
] as const) {}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { ApiProperty } from '@nestjs/swagger';
2+
import { IsEnum } from 'class-validator';
3+
4+
import { AbstractDto } from '../../../../src/common/dto/abstract.dto';
5+
import { NumberField, StringField, StringFieldOptional } from '../../../../src/decorators';
6+
import { InvoiceStatusEnum } from '../enums/invoice-status.enum';
7+
8+
export class InvoiceDto extends AbstractDto {
9+
@StringFieldOptional()
10+
subscriptionId?: string;
11+
12+
@StringField()
13+
checkoutSessionId: string;
14+
15+
@ApiProperty()
16+
@IsEnum(() => InvoiceStatusEnum)
17+
status: InvoiceStatusEnum;
18+
19+
@NumberField()
20+
totalAmount: number;
21+
22+
@StringField()
23+
unit: string;
24+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import { PartialType } from '@nestjs/swagger';
2+
import { CreateInvoice } from './create-invoice.dto';
3+
4+
export class UpdateInvoice extends PartialType(CreateInvoice) {}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { Column, Entity } from 'typeorm';
2+
3+
import { AbstractEntity } from '../../../../src/common/abstract.entity';
4+
import { InvoiceStatusEnum } from '../enums/invoice-status.enum';
5+
import { InvoiceDto } from '../dto/invoice.dto';
6+
7+
@Entity('invoices')
8+
export class InvoiceEntity extends AbstractEntity<InvoiceDto> {
9+
dtoClass = InvoiceDto;
10+
11+
@Column('string')
12+
checkoutSessionId: string;
13+
14+
@Column('string')
15+
subscriptionId: string;
16+
17+
@Column({ enum: InvoiceStatusEnum, type: 'enum', default: InvoiceStatusEnum.CREATED })
18+
status: InvoiceStatusEnum;
19+
20+
@Column('number')
21+
totalAmount: number;
22+
23+
@Column('string')
24+
unit: string;
25+
26+
constructor(item?: Partial<Omit<InvoiceEntity, 'id'>>) {
27+
super();
28+
29+
if (!item) {
30+
return;
31+
}
32+
33+
this.assign(item);
34+
}
35+
36+
assign(item: Partial<Omit<InvoiceEntity, 'id'>>): void {
37+
this.checkoutSessionId = item.checkoutSessionId ?? this.checkoutSessionId;
38+
this.subscriptionId = item.subscriptionId ?? this.subscriptionId;
39+
this.status = item.status ?? this.status;
40+
this.totalAmount = item.totalAmount ?? this.totalAmount;
41+
this.unit = item.unit ?? this.unit;
42+
}
43+
}

0 commit comments

Comments
 (0)