first commit
This commit is contained in:
22
src/app.controller.spec.ts
Normal file
22
src/app.controller.spec.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { AppController } from './app.controller';
|
||||
import { AppService } from './app.service';
|
||||
|
||||
describe('AppController', () => {
|
||||
let appController: AppController;
|
||||
|
||||
beforeEach(async () => {
|
||||
const app: TestingModule = await Test.createTestingModule({
|
||||
controllers: [AppController],
|
||||
providers: [AppService],
|
||||
}).compile();
|
||||
|
||||
appController = app.get<AppController>(AppController);
|
||||
});
|
||||
|
||||
describe('root', () => {
|
||||
it('should return "Hello World!"', () => {
|
||||
expect(appController.getHello()).toBe('Hello World!');
|
||||
});
|
||||
});
|
||||
});
|
||||
12
src/app.controller.ts
Normal file
12
src/app.controller.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { Controller, Get } from '@nestjs/common';
|
||||
import { AppService } from './app.service';
|
||||
|
||||
@Controller()
|
||||
export class AppController {
|
||||
constructor(private readonly appService: AppService) {}
|
||||
|
||||
@Get()
|
||||
getHello(): string {
|
||||
return this.appService.getHello();
|
||||
}
|
||||
}
|
||||
28
src/app.module.ts
Normal file
28
src/app.module.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { ServeStaticModule } from '@nestjs/serve-static';
|
||||
import { join } from 'path';
|
||||
import { AppController } from './app.controller';
|
||||
import { AppService } from './app.service';
|
||||
import { PrismaModule } from './prisma/prisma.module';
|
||||
import { EventsModule } from './events/events.module';
|
||||
import { TicketsModule } from './tickets/tickets.module';
|
||||
import { AuthModule } from './auth/auth.module';
|
||||
import { MailModule } from './mail/mail.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
ConfigModule.forRoot({ isGlobal: true }),
|
||||
ServeStaticModule.forRoot({
|
||||
rootPath: join(__dirname, '..', 'client', 'dist')
|
||||
}),
|
||||
PrismaModule,
|
||||
EventsModule,
|
||||
TicketsModule,
|
||||
AuthModule,
|
||||
MailModule,
|
||||
],
|
||||
controllers: [AppController],
|
||||
providers: [AppService],
|
||||
})
|
||||
export class AppModule {}
|
||||
8
src/app.service.ts
Normal file
8
src/app.service.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
@Injectable()
|
||||
export class AppService {
|
||||
getHello(): string {
|
||||
return 'Hello World!';
|
||||
}
|
||||
}
|
||||
18
src/assets/atiw-out-logo.svg
Normal file
18
src/assets/atiw-out-logo.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 8.4 KiB |
19
src/auth/auth.controller.ts
Normal file
19
src/auth/auth.controller.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { Controller, Post, HttpCode, HttpStatus, Body } from '@nestjs/common';
|
||||
import { AuthService } from './auth.service';
|
||||
|
||||
@Controller()
|
||||
export class AuthController {
|
||||
constructor(private readonly authService: AuthService) {}
|
||||
|
||||
@Post('login')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
async login(@Body() body: { password: string }) {
|
||||
return this.authService.login(body.password);
|
||||
}
|
||||
|
||||
@Post('logout')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
async logout() {
|
||||
return { success: true };
|
||||
}
|
||||
}
|
||||
25
src/auth/auth.module.ts
Normal file
25
src/auth/auth.module.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { AuthService } from './auth.service';
|
||||
import { AuthController } from './auth.controller';
|
||||
import { JwtModule } from '@nestjs/jwt';
|
||||
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||
import { PassportModule } from '@nestjs/passport';
|
||||
import { JwtStrategy } from './jwt.strategy';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
ConfigModule,
|
||||
PassportModule,
|
||||
JwtModule.registerAsync({
|
||||
imports: [ConfigModule],
|
||||
useFactory: async (configService: ConfigService) => ({
|
||||
secret: configService.get<string>('JWT_SECRET') || 'secret',
|
||||
signOptions: { expiresIn: '1d' },
|
||||
}),
|
||||
inject: [ConfigService],
|
||||
}),
|
||||
],
|
||||
controllers: [AuthController],
|
||||
providers: [AuthService, JwtStrategy],
|
||||
})
|
||||
export class AuthModule {}
|
||||
22
src/auth/auth.service.ts
Normal file
22
src/auth/auth.service.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { Injectable, UnauthorizedException } from '@nestjs/common';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
|
||||
@Injectable()
|
||||
export class AuthService {
|
||||
constructor(
|
||||
private jwtService: JwtService,
|
||||
private configService: ConfigService,
|
||||
) {}
|
||||
|
||||
async login(password: string) {
|
||||
const envPassword = this.configService.get<string>('PASSWORD');
|
||||
if (password !== envPassword) {
|
||||
throw new UnauthorizedException('Incorrect password');
|
||||
}
|
||||
const payload = { username: 'admin', sub: 'admin' };
|
||||
return {
|
||||
accessToken: this.jwtService.sign(payload),
|
||||
};
|
||||
}
|
||||
}
|
||||
19
src/auth/jwt.strategy.ts
Normal file
19
src/auth/jwt.strategy.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { ExtractJwt, Strategy } from 'passport-jwt';
|
||||
import { PassportStrategy } from '@nestjs/passport';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
|
||||
@Injectable()
|
||||
export class JwtStrategy extends PassportStrategy(Strategy) {
|
||||
constructor(configService: ConfigService) {
|
||||
super({
|
||||
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
|
||||
ignoreExpiration: false,
|
||||
secretOrKey: configService.get<string>('JWT_SECRET') || 'secret',
|
||||
});
|
||||
}
|
||||
|
||||
async validate(payload: any) {
|
||||
return { userId: payload.sub, username: payload.username };
|
||||
}
|
||||
}
|
||||
5
src/events/dto/create-event.dto.ts
Normal file
5
src/events/dto/create-event.dto.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export class CreateEventDto {
|
||||
name: string;
|
||||
date: string;
|
||||
location: string;
|
||||
}
|
||||
4
src/events/dto/update-event.dto.ts
Normal file
4
src/events/dto/update-event.dto.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { PartialType } from '@nestjs/mapped-types';
|
||||
import { CreateEventDto } from './create-event.dto';
|
||||
|
||||
export class UpdateEventDto extends PartialType(CreateEventDto) {}
|
||||
1
src/events/entities/event.entity.ts
Normal file
1
src/events/entities/event.entity.ts
Normal file
@@ -0,0 +1 @@
|
||||
export class Event {}
|
||||
18
src/events/events.controller.ts
Normal file
18
src/events/events.controller.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Controller, Get, Post, Body } from '@nestjs/common';
|
||||
import { EventsService } from './events.service';
|
||||
import { CreateEventDto } from './dto/create-event.dto';
|
||||
|
||||
@Controller('events')
|
||||
export class EventsController {
|
||||
constructor(private readonly eventsService: EventsService) {}
|
||||
|
||||
@Post()
|
||||
create(@Body() createEventDto: CreateEventDto) {
|
||||
return this.eventsService.create(createEventDto);
|
||||
}
|
||||
|
||||
@Get()
|
||||
findAll() {
|
||||
return this.eventsService.findAll();
|
||||
}
|
||||
}
|
||||
11
src/events/events.module.ts
Normal file
11
src/events/events.module.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { EventsService } from './events.service';
|
||||
import { EventsController } from './events.controller';
|
||||
import { PrismaModule } from '../prisma/prisma.module';
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule],
|
||||
controllers: [EventsController],
|
||||
providers: [EventsService],
|
||||
})
|
||||
export class EventsModule {}
|
||||
16
src/events/events.service.ts
Normal file
16
src/events/events.service.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { CreateEventDto } from './dto/create-event.dto';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
|
||||
@Injectable()
|
||||
export class EventsService {
|
||||
constructor(private prisma: PrismaService) {}
|
||||
|
||||
create(createEventDto: CreateEventDto) {
|
||||
return this.prisma.event.create({ data: createEventDto });
|
||||
}
|
||||
|
||||
findAll() {
|
||||
return this.prisma.event.findMany();
|
||||
}
|
||||
}
|
||||
10
src/mail/mail.module.ts
Normal file
10
src/mail/mail.module.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { MailService } from './mail.service';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
|
||||
@Module({
|
||||
imports: [ConfigModule],
|
||||
providers: [MailService],
|
||||
exports: [MailService],
|
||||
})
|
||||
export class MailModule {}
|
||||
89
src/mail/mail.service.ts
Normal file
89
src/mail/mail.service.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import * as nodemailer from 'nodemailer';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import * as path from 'path';
|
||||
|
||||
@Injectable()
|
||||
export class MailService {
|
||||
private transporter: nodemailer.Transporter;
|
||||
|
||||
constructor(private configService: ConfigService) {
|
||||
this.transporter = nodemailer.createTransport({
|
||||
host: this.configService.get<string>('SMTP_HOST'),
|
||||
port: this.configService.get<number>('SMTP_PORT'),
|
||||
secure: this.configService.get<string>('SMTP_SECURE') === 'true', // true for 465, false for other ports
|
||||
auth: {
|
||||
user: this.configService.get<string>('SMTP_USER'),
|
||||
pass: this.configService.get<string>('SMTP_PASS'),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async sendTicket(
|
||||
email: string,
|
||||
name: string,
|
||||
ticketId: string,
|
||||
eventName: string,
|
||||
qrCodeBuffer: Buffer, // New parameter for QR code image buffer
|
||||
) {
|
||||
const logoPath = path.join(process.cwd(), 'dist/assets/atiw-out-logo.svg');
|
||||
const firstName = name.split(' ')[0];
|
||||
|
||||
await this.transporter.sendMail({
|
||||
from: this.configService.get<string>('SMTP_FROM') || '"Event Team" <noreply@example.com>',
|
||||
to: email,
|
||||
subject: `Dein Ticket fürs ATIW OUT!`,
|
||||
html: `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
body { font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; background-color: #f4f4f5; margin: 0; padding: 0; }
|
||||
.container { max-width: 600px; margin: 32px auto; background-color: #ffffff; border-radius: 8px; overflow: hidden; border: 1px solid rgba(0, 0, 0, 0.2); padding: 32px; }
|
||||
.header { background-color: #ffffff; text-align: center; }
|
||||
.logo { height: 150px;}
|
||||
.content {text-align: center; color: #333333; }
|
||||
.greeting { font-size: 32px; color: #18181b; }
|
||||
.text { font-size: 16px; line-height: 1.5; color: #52525b;}
|
||||
.qr-container { background-color: #f4f4f5; padding: 24px; border-radius: 12px; display: inline-block; margin: 32px 0px; }
|
||||
.footer { background-color: #fafafa; padding: 24px; text-align: center; font-size: 14px; color: #a1a1aa; border-top: 1px solid #e4e4e7; }
|
||||
.highlight { color: #2563eb; font-weight: 600; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<img src="cid:logo" alt="Logo" class="logo" />
|
||||
</div>
|
||||
<div class="content">
|
||||
<h2 class="greeting">Moin ${firstName},</h2>
|
||||
<p class="text">Hier ist dein Ticket für das <strong>ATIW OUT</strong>!</p>
|
||||
<p class="text">Zeig diesen QR Code einfach am <strong>09.12.</strong> am Eingang vor.</p>
|
||||
|
||||
<div class="qr-container">
|
||||
<img src="cid:qrcode" width="200" height="200" alt="Dein Ticket QR Code" style="display: block;"/>
|
||||
</div>
|
||||
|
||||
<p class="text">Wir wünschen dir viel Spaß!</p>
|
||||
<p class="text">~FI231 & FS231</p>
|
||||
<p class="text" style="margin-top: 32px;">Bei Fragen oder Problemen komm bitte<br>zum Klassenraum <strong>E.07</strong> und frag nach <strong>Jason</strong></p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`,
|
||||
attachments: [
|
||||
{
|
||||
filename: 'qrcode.png',
|
||||
content: qrCodeBuffer,
|
||||
cid: 'qrcode', // Content ID to link with img src
|
||||
},
|
||||
{
|
||||
filename: 'logo.svg',
|
||||
path: logoPath,
|
||||
cid: 'logo',
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
||||
12
src/main.ts
Normal file
12
src/main.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
import { AppModule } from './app.module';
|
||||
import { ValidationPipe } from '@nestjs/common';
|
||||
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create(AppModule);
|
||||
app.setGlobalPrefix('api');
|
||||
app.enableCors();
|
||||
app.useGlobalPipes(new ValidationPipe());
|
||||
await app.listen(process.env.PORT ?? 3000);
|
||||
}
|
||||
bootstrap();
|
||||
9
src/prisma/prisma.module.ts
Normal file
9
src/prisma/prisma.module.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { Global, Module } from '@nestjs/common';
|
||||
import { PrismaService } from './prisma.service';
|
||||
|
||||
@Global()
|
||||
@Module({
|
||||
providers: [PrismaService],
|
||||
exports: [PrismaService],
|
||||
})
|
||||
export class PrismaModule {}
|
||||
13
src/prisma/prisma.service.ts
Normal file
13
src/prisma/prisma.service.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
@Injectable()
|
||||
export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
|
||||
async onModuleInit() {
|
||||
await this.$connect();
|
||||
}
|
||||
|
||||
async onModuleDestroy() {
|
||||
await this.$disconnect();
|
||||
}
|
||||
}
|
||||
15
src/tickets/dto/create-ticket.dto.ts
Normal file
15
src/tickets/dto/create-ticket.dto.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { IsString, IsEmail, IsIn } from 'class-validator';
|
||||
|
||||
export class CreateTicketDto {
|
||||
@IsString()
|
||||
eventId: string;
|
||||
|
||||
@IsString()
|
||||
attendeeName: string;
|
||||
|
||||
@IsEmail()
|
||||
attendeeEmail: string;
|
||||
|
||||
@IsIn(['Klassenbester', '1er Schüler', 'Partyborner'])
|
||||
ticketType: string;
|
||||
}
|
||||
4
src/tickets/dto/update-ticket.dto.ts
Normal file
4
src/tickets/dto/update-ticket.dto.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { PartialType } from '@nestjs/mapped-types';
|
||||
import { CreateTicketDto } from './create-ticket.dto';
|
||||
|
||||
export class UpdateTicketDto extends PartialType(CreateTicketDto) {}
|
||||
1
src/tickets/entities/ticket.entity.ts
Normal file
1
src/tickets/entities/ticket.entity.ts
Normal file
@@ -0,0 +1 @@
|
||||
export class Ticket {}
|
||||
33
src/tickets/tickets.controller.ts
Normal file
33
src/tickets/tickets.controller.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { Controller, Get, Post, Body, Param, Delete, Put, Query } from '@nestjs/common';
|
||||
import { TicketsService } from './tickets.service';
|
||||
import { CreateTicketDto } from './dto/create-ticket.dto';
|
||||
|
||||
@Controller('tickets')
|
||||
export class TicketsController {
|
||||
constructor(private readonly ticketsService: TicketsService) {}
|
||||
|
||||
@Post()
|
||||
create(@Body() createTicketDto: CreateTicketDto) {
|
||||
return this.ticketsService.create(createTicketDto);
|
||||
}
|
||||
|
||||
@Get()
|
||||
findAll(@Query('eventId') eventId: string) {
|
||||
return this.ticketsService.findAll(eventId);
|
||||
}
|
||||
|
||||
@Post(':id/resend')
|
||||
resendEmail(@Param('id') id: string) {
|
||||
return this.ticketsService.resendEmail(id);
|
||||
}
|
||||
|
||||
@Put(':id/status')
|
||||
updateStatus(@Param('id') id: string, @Body('status') status: string) {
|
||||
return this.ticketsService.updateStatus(id, status);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
remove(@Param('id') id: string) {
|
||||
return this.ticketsService.remove(id);
|
||||
}
|
||||
}
|
||||
12
src/tickets/tickets.module.ts
Normal file
12
src/tickets/tickets.module.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TicketsService } from './tickets.service';
|
||||
import { TicketsController } from './tickets.controller';
|
||||
import { PrismaModule } from '../prisma/prisma.module';
|
||||
import { MailModule } from '../mail/mail.module';
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule, MailModule],
|
||||
controllers: [TicketsController],
|
||||
providers: [TicketsService],
|
||||
})
|
||||
export class TicketsModule {}
|
||||
101
src/tickets/tickets.service.ts
Normal file
101
src/tickets/tickets.service.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { CreateTicketDto } from './dto/create-ticket.dto';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { MailService } from '../mail/mail.service';
|
||||
import * as QRCode from 'qrcode'; // Import qrcode library
|
||||
|
||||
@Injectable()
|
||||
export class TicketsService {
|
||||
constructor(
|
||||
private prisma: PrismaService,
|
||||
private mailService: MailService,
|
||||
) {}
|
||||
|
||||
async create(createTicketDto: CreateTicketDto) {
|
||||
const ticket = await this.prisma.ticket.create({
|
||||
data: createTicketDto,
|
||||
include: { event: true },
|
||||
});
|
||||
|
||||
try {
|
||||
// Generate QR code buffer from ticket ID
|
||||
const qrCodeBuffer = await QRCode.toBuffer(ticket.id, {
|
||||
errorCorrectionLevel: 'H', // High error correction
|
||||
type: 'png', // Output as PNG image
|
||||
width: 250, // QR code width
|
||||
color: {
|
||||
dark: '#000000', // Black dots
|
||||
light: '#FFFFFF', // White background
|
||||
},
|
||||
});
|
||||
|
||||
await this.mailService.sendTicket(
|
||||
ticket.attendeeEmail,
|
||||
ticket.attendeeName,
|
||||
ticket.id,
|
||||
ticket.event.name,
|
||||
qrCodeBuffer, // Pass the QR code buffer
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Failed to send ticket email:', error);
|
||||
// Optionally throw error or just log it.
|
||||
// For now, we log it so the ticket creation doesn't fail if email fails (unless strict requirement).
|
||||
}
|
||||
|
||||
return ticket;
|
||||
}
|
||||
|
||||
findAll(eventId: string) {
|
||||
return this.prisma.ticket.findMany({
|
||||
where: { eventId },
|
||||
});
|
||||
}
|
||||
|
||||
async resendEmail(id: string) {
|
||||
const ticket = await this.prisma.ticket.findUnique({
|
||||
where: { id },
|
||||
include: { event: true },
|
||||
});
|
||||
|
||||
if (!ticket) {
|
||||
throw new Error('Ticket not found');
|
||||
}
|
||||
|
||||
try {
|
||||
const qrCodeBuffer = await QRCode.toBuffer(ticket.id, {
|
||||
errorCorrectionLevel: 'H',
|
||||
type: 'png',
|
||||
width: 250,
|
||||
color: {
|
||||
dark: '#000000',
|
||||
light: '#FFFFFF',
|
||||
},
|
||||
});
|
||||
|
||||
await this.mailService.sendTicket(
|
||||
ticket.attendeeEmail,
|
||||
ticket.attendeeName,
|
||||
ticket.id,
|
||||
ticket.event.name,
|
||||
qrCodeBuffer,
|
||||
);
|
||||
return { success: true, message: 'Email resent successfully' };
|
||||
} catch (error) {
|
||||
console.error('Failed to resend ticket email:', error);
|
||||
throw new Error('Failed to send email');
|
||||
}
|
||||
}
|
||||
|
||||
updateStatus(id: string, status: string) {
|
||||
return this.prisma.ticket.update({
|
||||
where: { id },
|
||||
data: { status },
|
||||
});
|
||||
}
|
||||
|
||||
remove(id: string) {
|
||||
return this.prisma.ticket.delete({
|
||||
where: { id },
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user