Add timer REST stuff

This commit is contained in:
Elizabeth Hunt 2023-04-03 13:04:32 -06:00
parent 4dfc3129e3
commit 36412c9f58
Signed by: simponic
GPG Key ID: 52B3774857EB24B1
11 changed files with 236 additions and 30 deletions

View File

@ -21,5 +21,14 @@ module.exports = {
'@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-explicit-any': 'off', '@typescript-eslint/no-explicit-any': 'off',
'no-unused-vars': 'off',
'@typescript-eslint/no-unused-vars': [
'warn', // or "error"
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
caughtErrorsIgnorePattern: '^_',
},
],
}, },
}; };

View File

@ -0,0 +1,39 @@
/*
Warnings:
- You are about to drop the `_FriendToTimer` table. If the table is not empty, all the data it contains will be lost.
- Added the required column `created_by_id` to the `Timer` table without a default value. This is not possible if the table is not empty.
*/
-- DropForeignKey
ALTER TABLE "_FriendToTimer" DROP CONSTRAINT "_FriendToTimer_A_fkey";
-- DropForeignKey
ALTER TABLE "_FriendToTimer" DROP CONSTRAINT "_FriendToTimer_B_fkey";
-- AlterTable
ALTER TABLE "Timer" ADD COLUMN "created_by_id" INTEGER NOT NULL;
-- DropTable
DROP TABLE "_FriendToTimer";
-- CreateTable
CREATE TABLE "_referenced_friend_fk" (
"A" INTEGER NOT NULL,
"B" INTEGER NOT NULL
);
-- CreateIndex
CREATE UNIQUE INDEX "_referenced_friend_fk_AB_unique" ON "_referenced_friend_fk"("A", "B");
-- CreateIndex
CREATE INDEX "_referenced_friend_fk_B_index" ON "_referenced_friend_fk"("B");
-- AddForeignKey
ALTER TABLE "Timer" ADD CONSTRAINT "Timer_created_by_id_fkey" FOREIGN KEY ("created_by_id") REFERENCES "Friend"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "_referenced_friend_fk" ADD CONSTRAINT "_referenced_friend_fk_A_fkey" FOREIGN KEY ("A") REFERENCES "Friend"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "_referenced_friend_fk" ADD CONSTRAINT "_referenced_friend_fk_B_fkey" FOREIGN KEY ("B") REFERENCES "Timer"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "Timer" ALTER COLUMN "start" SET DEFAULT CURRENT_TIMESTAMP;

View File

@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "Timer" ALTER COLUMN "start" DROP NOT NULL;

View File

@ -19,30 +19,34 @@ model GodToken {
} }
model Friend { model Friend {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
name String @unique @db.Citext name String @unique @db.Citext
public_key String @db.Text public_key String @db.Text
timers Timer[] referenced_in Timer[] @relation(name: "referenced_friend_fk")
created_timers Timer[] @relation(name: "created_by_fk")
god_tokens GodToken[] god_tokens GodToken[]
TimerRefreshes TimerRefreshes[] TimerRefreshes TimerRefreshes[]
} }
model Timer { model Timer {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
start DateTime start DateTime? @default(now())
name String @unique name String @unique
created_by Friend @relation(name: "created_by_fk", fields: [created_by_id], references: [id])
created_by_id Int
timer_refreshes TimerRefreshes[] timer_refreshes TimerRefreshes[]
referenced_friends Friend[] referenced_friends Friend[] @relation(name: "referenced_friend_fk")
} }
model TimerRefreshes { model TimerRefreshes {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
start DateTime start DateTime
end DateTime end DateTime
refreshed_by Friend @relation(fields: [refreshed_by_id], references: [id]) refreshed_by Friend @relation(fields: [refreshed_by_id], references: [id])
refreshed_by_id Int refreshed_by_id Int
timer Timer @relation(fields: [timer_id], references: [id]) timer Timer @relation(fields: [timer_id], references: [id])

View File

@ -10,20 +10,12 @@ import {
UseGuards, UseGuards,
Req, Req,
} from '@nestjs/common'; } from '@nestjs/common';
import { IsNotEmpty } from 'class-validator';
import { readKey, readCleartextMessage, verify } from 'openpgp'; import { readKey, readCleartextMessage, verify } from 'openpgp';
import { AuthService } from './auth.service'; import { AuthService } from './auth.service';
import { AuthGuard } from './auth.guard'; import { AuthGuard } from './auth.guard';
export class LoginUserDTO { import { RetrieveFriendDTO, SignedGodTokenDTO } from '../dto/dtos';
@IsNotEmpty()
signature: string;
}
export class RetrieveTokenDTO {
@IsNotEmpty()
name: string;
}
@Controller('/auth') @Controller('/auth')
export class AuthController { export class AuthController {
@ -40,7 +32,7 @@ export class AuthController {
} }
@Get('/') @Get('/')
async retrieveGodToken(@Query() query: RetrieveTokenDTO) { async makeGodToken(@Query() query: RetrieveFriendDTO) {
const friend = await this.authService.findFriendByName(query.name); const friend = await this.authService.findFriendByName(query.name);
if (!friend) throw new NotFoundException('Friend not found with that name'); if (!friend) throw new NotFoundException('Friend not found with that name');
@ -50,7 +42,7 @@ export class AuthController {
@Post() @Post()
async verifyFriend( async verifyFriend(
@Res({ passthrough: true }) res, @Res({ passthrough: true }) res,
@Body() { signature }: LoginUserDTO, @Body() { signature }: SignedGodTokenDTO,
) { ) {
let signatureObj; let signatureObj;
try { try {

View File

@ -7,7 +7,7 @@ import { AuthService } from './auth.service';
@Module({ @Module({
imports: [PrismaModule], imports: [PrismaModule],
controllers: [AuthController], controllers: [AuthController],
exports: [AuthGuard], exports: [AuthGuard, AuthService],
providers: [AuthService, AuthGuard], providers: [AuthService, AuthGuard],
}) })
export class AuthModule {} export class AuthModule {}

16
server/src/dto/dtos.ts Normal file
View File

@ -0,0 +1,16 @@
import { IsNotEmpty } from 'class-validator';
export class SignedGodTokenDTO {
@IsNotEmpty()
signature: string;
}
export class RetrieveFriendDTO {
@IsNotEmpty()
name: string;
}
export class CreateTimerDTO {
@IsNotEmpty()
name: string;
}

View File

@ -1,4 +1,75 @@
import { Controller } from '@nestjs/common'; import {
Controller,
Get,
Post,
Body,
Req,
Query,
NotFoundException,
UseGuards,
BadRequestException,
} from '@nestjs/common';
import { TimerService } from './timer.service';
import { AuthService } from '../auth/auth.service';
import { CreateTimerDTO, RetrieveFriendDTO } from '../dto/dtos';
import { AuthGuard } from 'src/auth/auth.guard';
import { Prisma } from '@prisma/client';
@Controller('timer') @Controller('timers')
export class TimerController {} @UseGuards(AuthGuard)
export class TimerController {
constructor(
private readonly timerService: TimerService,
private readonly authService: AuthService,
) {}
@Get()
public async getAllTimers() {
return this.timerService.getAll();
}
@Get('/friend')
public async getFriendTimers(@Query() { name }: RetrieveFriendDTO) {
const friend = await this.authService.findFriendByName(name);
if (!friend) throw new NotFoundException('Friend not found with that name');
return this.timerService.friendTimers(friend);
}
@Post()
public async createTimer(@Body() { name }: CreateTimerDTO, @Req() req) {
const referencedFriendIds = Array.from(
new Set(
[...name.matchAll(/\@<(\d+)>/g)].map(([_match, id]) =>
parseInt(id, 10),
),
),
);
if (referencedFriendIds.length > 10)
throw new BadRequestException(
'Can link no more than 10 unique friends to timer',
);
try {
return await this.timerService.createTimerWithFriends(
{
name,
created_by: {
connect: {
id: req.friend.id,
},
},
},
referencedFriendIds,
);
} catch (e) {
if (
e instanceof Prisma.PrismaClientKnownRequestError &&
e.code === 'P2002'
)
throw new BadRequestException('Timer with name already exists');
throw e;
}
}
}

View File

@ -2,9 +2,12 @@ import { Module } from '@nestjs/common';
import { TimerGateway } from './timer.gateway'; import { TimerGateway } from './timer.gateway';
import { TimerController } from './timer.controller'; import { TimerController } from './timer.controller';
import { TimerService } from './timer.service'; import { TimerService } from './timer.service';
import { PrismaModule } from '../prisma/prisma.module';
import { AuthModule } from '../auth/auth.module';
@Module({ @Module({
imports: [PrismaModule, AuthModule],
providers: [TimerGateway, TimerService], providers: [TimerGateway, TimerService],
controllers: [TimerController] controllers: [TimerController],
}) })
export class TimerModule {} export class TimerModule {}

View File

@ -1,4 +1,72 @@
import { Friend, Prisma } from '@prisma/client';
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
@Injectable() @Injectable()
export class TimerService {} export class TimerService {
constructor(private readonly prismaService: PrismaService) {}
public getAll() {
return this.prismaService.timer.findMany({
select: {
id: true,
name: true,
start: true,
referenced_friends: {
select: {
name: true,
id: true,
},
},
created_by: {
select: {
name: true,
id: true,
},
},
},
});
}
public friendTimers(friend: Friend) {
return this.prismaService.timer.findMany({
select: {
id: true,
name: true,
start: true,
referenced_friends: {
where: {
id: friend.id,
},
select: {
id: true,
name: true,
},
},
created_by: {
select: {
name: true,
id: true,
},
},
},
});
}
public createTimerWithFriends(
timer: Prisma.TimerCreateInput,
friendIds: number[],
) {
if (friendIds.length > 0)
return this.prismaService.timer.create({
data: {
...timer,
referenced_friends: {
connect: friendIds.map((id) => ({ id })),
},
},
});
return this.prismaService.timer.create({ data: timer });
}
}