diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 843ab6ec65d66bb61977552344f7db80ac9a7543..71499cecfa4281fbe9e737b5d552eb06676a5daf 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -13,7 +13,7 @@ model Group { code String @unique tokens Int @default(0) - unlocks UnlockEntry[] + unlocks Entry[] } model Agent { @@ -23,28 +23,17 @@ model Agent { name String entries Entry[] - unlocks UnlockEntry[] } -model Entry { - id String @id @default(uuid()) - content String - - createdAt DateTime @default(now()) - - agent Agent @relation(fields: [agentId], references: [id]) - agentId String -} - - // Types: 'picture', 'location', 'text' -model UnlockEntry { +model Entry { id String @id @default(uuid()) type String + private Boolean content String? - path String? + image String? lat String? lon String? diff --git a/src/app.module.ts b/src/app.module.ts index 848fccbe6771e3633b5da1c235990f9ccbd4e2ed..46d666f1f3cf41da70a154233dd4a44636ad83be 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -3,6 +3,7 @@ import { PrismaModule } from './prisma/prisma.module'; import { TelegramModule } from './telegram/telegram.module'; import { GraphQLModule } from '@nestjs/graphql'; import { ResolverModule } from './resolvers/resolver.module'; +import { AuthGuard } from './auth/auth.guard'; @Module({ imports: [ @@ -12,7 +13,11 @@ import { ResolverModule } from './resolvers/resolver.module'; GraphQLModule.forRoot({ autoSchemaFile: true, playground: true, + context: ({ request }) => ({ + req: request, + }), }), ], + providers: [AuthGuard], }) export class AppModule {} diff --git a/src/auth/auth.guard.ts b/src/auth/auth.guard.ts new file mode 100644 index 0000000000000000000000000000000000000000..b1e3bb5dfd095c95a369b0c800f7b7a710ee9fbd --- /dev/null +++ b/src/auth/auth.guard.ts @@ -0,0 +1,34 @@ +import { CanActivate, ExecutionContext, Injectable } from "@nestjs/common"; +import { GqlExecutionContext } from "@nestjs/graphql"; +import { Request } from "express"; +import { Observable } from "rxjs"; +import { PrismaService } from "src/prisma/prisma.service"; + +@Injectable() +export class AuthGuard implements CanActivate { + + constructor(private prismaService: PrismaService) {} + + async canActivate(context: ExecutionContext): Promise<boolean> { + const ctx = GqlExecutionContext.create(context); + + const req: Request = ctx.getContext().req; + + const token = req.headers.authorization; + + if (!token) + return false; + + const group = await this.prismaService.group.findFirst({ + where: { code: token }, + }); + + if (!group) + return false; + + req.group = group; + + return true; + } + +} \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index 617bfd97a3919a952ef27bc85e1a330c3e6f0ae4..4c1caf3cb744d2b805111f16c9d645ec356106d9 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,4 +1,11 @@ import { NestFactory } from '@nestjs/core'; +import { Group } from '@prisma/client'; + +declare module 'express' { + interface Request { + group: Group; + } +} import * as dotenv from 'dotenv'; dotenv.config(); diff --git a/src/resolvers/agent.resolver.ts b/src/resolvers/agent.resolver.ts index b18bea9e886ddce6aca13a585e62315b097a0ef9..3138bf72b79c3ecd89597538447cd5617bc6d284 100644 --- a/src/resolvers/agent.resolver.ts +++ b/src/resolvers/agent.resolver.ts @@ -1,21 +1,20 @@ -import { Args, Parent, Query, ResolveField, Resolver } from '@nestjs/graphql'; +import { Args, Context, Parent, Query, ResolveField, Resolver } from '@nestjs/graphql'; import { Agent } from './models/agent.model'; import { PrismaService } from '../prisma/prisma.service'; import { GraphQLString } from 'graphql'; -import { NotFoundException } from '@nestjs/common'; +import { NotFoundException, UseGuards } from '@nestjs/common'; +import { AuthGuard } from 'src/auth/auth.guard'; +import { Request } from 'express'; @Resolver(() => Agent) +@UseGuards(AuthGuard) export class AgentResolver { constructor(private prismaService: PrismaService) {} @Query(() => [Agent]) - listAgents() { - return this.prismaService.agent.findMany({ - include: { - unlocks: true, - entries: true, - }, - }); + listAgents(@Context() { req }: { req: Request }) { + console.log(req.group) + return this.prismaService.agent.findMany(); } @Query(() => Agent) @@ -31,23 +30,17 @@ export class AgentResolver { return agent; } - @ResolveField('unlocks') - async unlocks(@Parent() entry: Agent) { - const result = await this.prismaService.agent.findFirst({ - where: { id: entry.id }, - select: { unlocks: true }, - }); - - return result.unlocks; - } - @ResolveField('entries') async entries(@Parent() entry: Agent) { const result = await this.prismaService.agent.findFirst({ - where: { id: entry.id }, - select: { entries: true }, + where: { + id: entry.id, + }, + include: { + entries: true, + }, }); - return result.entries; + } } diff --git a/src/resolvers/entry.resolver.ts b/src/resolvers/entry.resolver.ts index 3918bb864bbb9fd9b1d4982928f46e30c020f38a..70bc535a51d4ba1fd9630569a349d0d18886a5c3 100644 --- a/src/resolvers/entry.resolver.ts +++ b/src/resolvers/entry.resolver.ts @@ -1,18 +1,16 @@ -import { Parent, Query, ResolveField, Resolver } from '@nestjs/graphql'; +import { Args, Context, Mutation, Parent, ResolveField, Resolver } from '@nestjs/graphql'; import { Entry } from './models/entry.model'; import { PrismaService } from '../prisma/prisma.service'; +import { GraphQLString } from 'graphql'; +import { BadRequestException, NotFoundException, UseGuards } from '@nestjs/common'; +import { Request } from 'express'; +import { AuthGuard } from 'src/auth/auth.guard'; @Resolver(() => Entry) +@UseGuards(AuthGuard) export class EntryResolver { constructor(private prismaService: PrismaService) {} - @Query(() => [Entry]) - listEntries() { - return this.prismaService.entry.findMany({ - take: 200, - }); - } - @ResolveField('agent') async agent(@Parent() entry: Entry) { const result = await this.prismaService.entry.findFirst({ @@ -22,4 +20,36 @@ export class EntryResolver { return result.agent; } + + @Mutation(() => Entry) + async unlockEntry( + @Args({ name: 'id', type: () => GraphQLString }) id: string, + @Context() { req }: { req: Request }, + ) { + const unlock = await this.prismaService.entry.findFirst({ + where: { + id, + private: true, + }, + }); + + if (!unlock) + throw new NotFoundException(); + + if (req.group.tokens <= 0) + throw new BadRequestException('No remaining tokens'); + + await this.prismaService.group.update({ + where: { id: req.group.id }, + data: { + tokens: req.group.tokens - 1, + unlocks: { + connect: { id }, + }, + }, + }); + + return unlock; + + } } diff --git a/src/resolvers/group.resolver.ts b/src/resolvers/group.resolver.ts index 95ab769f8e8160cc61efeaa1c0f28dd9848c79ab..781044bd0ac58da831cf399659238a52199c4fbc 100644 --- a/src/resolvers/group.resolver.ts +++ b/src/resolvers/group.resolver.ts @@ -1,5 +1,6 @@ import { Args, + Context, Mutation, Parent, Query, @@ -10,9 +11,12 @@ import { Group } from './models/group.model'; import { PrismaService } from '../prisma/prisma.service'; import { GraphQLString } from 'graphql'; import seedWords from 'mnemonic-words'; -import { NotFoundException } from '@nestjs/common'; +import { NotFoundException, UseGuards } from '@nestjs/common'; +import { AuthGuard } from 'src/auth/auth.guard'; +import { Request } from 'express'; @Resolver(() => Group) +@UseGuards(AuthGuard) export class GroupResolver { constructor(private prismaService: PrismaService) {} @@ -41,16 +45,9 @@ export class GroupResolver { @Query(() => Group) async getGroup( - @Args({ name: 'code', type: () => GraphQLString }) code: string, + @Context() { req }: { req: Request }, ) { - const group = this.prismaService.group.findFirst({ - where: { code }, - include: { unlocks: true }, - }); - - if (!group) throw new NotFoundException(); - - return group; + return req.group; } @ResolveField('unlocks') diff --git a/src/resolvers/models/agent.model.ts b/src/resolvers/models/agent.model.ts index 04b30d0271d58881f89bca7f6f1f80e7fc0d1266..432195cf8bc558d247d9bcfc15121a4c4a7a205d 100644 --- a/src/resolvers/models/agent.model.ts +++ b/src/resolvers/models/agent.model.ts @@ -1,5 +1,4 @@ import { Field, ObjectType } from '@nestjs/graphql'; -import { UnlockableEntry } from './unlockable-entry.model'; import { Entry } from './entry.model'; @ObjectType() @@ -13,9 +12,6 @@ export class Agent { @Field() name: string; - @Field(() => [UnlockableEntry]) - unlocks: UnlockableEntry[]; - @Field(() => [Entry]) entries: Entry[]; } diff --git a/src/resolvers/models/entry.model.ts b/src/resolvers/models/entry.model.ts index 419b506bd6e27e1d453cf992bc2d38bd044b38c6..1be72c1dd50bbecfffce091d75e388f77ee1b60b 100644 --- a/src/resolvers/models/entry.model.ts +++ b/src/resolvers/models/entry.model.ts @@ -7,7 +7,22 @@ export class Entry { id: string; @Field() - content: string; + private: boolean; + + @Field() + locked: boolean; + + @Field() + type: string; + + @Field({ nullable: true }) + content?: string; + @Field({ nullable: true }) + path?: string; + @Field({ nullable: true }) + lat?: string; + @Field({ nullable: true }) + lon?: string; @Field(() => GraphQLISODateTime) createdAt: Date; diff --git a/src/resolvers/models/group.model.ts b/src/resolvers/models/group.model.ts index 9dae2c384657ac5a2aad9fb53fe7d8ac4412eb38..0679cc8d47f18571e9d752cd5ba6cbc0db7a0bb4 100644 --- a/src/resolvers/models/group.model.ts +++ b/src/resolvers/models/group.model.ts @@ -1,5 +1,5 @@ import { Field, Int, ObjectType } from '@nestjs/graphql'; -import { UnlockedEntry } from './unlocked-entry.model'; +import { Entry } from './entry.model'; @ObjectType() export class Group { @@ -15,6 +15,6 @@ export class Group { @Field(() => Int) tokens: number; - @Field(() => [UnlockedEntry]) - unlocks: UnlockedEntry[]; + @Field(() => [Entry]) + unlocks: Entry[]; } diff --git a/src/resolvers/models/unlockable-entry.model.ts b/src/resolvers/models/unlockable-entry.model.ts deleted file mode 100644 index 4bdeb9de2c79d9c56d343d6c5114acd3b10f8558..0000000000000000000000000000000000000000 --- a/src/resolvers/models/unlockable-entry.model.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Field, GraphQLISODateTime, ObjectType } from '@nestjs/graphql'; -import { Agent } from './agent.model'; - -@ObjectType() -export class UnlockableEntry { - @Field() - id: string; - - @Field() - type: string; - - @Field(() => GraphQLISODateTime) - createdAt: Date; - - @Field(() => Agent) - agent: Agent; -} diff --git a/src/resolvers/models/unlocked-entry.model.ts b/src/resolvers/models/unlocked-entry.model.ts deleted file mode 100644 index 575b85d185bcd32117197e9577ee8d756a670967..0000000000000000000000000000000000000000 --- a/src/resolvers/models/unlocked-entry.model.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Field, GraphQLISODateTime, ObjectType } from '@nestjs/graphql'; -import { Agent } from './agent.model'; - -@ObjectType() -export class UnlockedEntry { - @Field() - id: string; - - @Field() - type: string; - - @Field({ nullable: true }) - content?: string; - @Field({ nullable: true }) - path?: string; - @Field({ nullable: true }) - lat?: string; - @Field({ nullable: true }) - lon?: string; - - @Field(() => GraphQLISODateTime) - createdAt: Date; - - @Field(() => Agent) - agent: Agent; -} diff --git a/src/resolvers/resolver.module.ts b/src/resolvers/resolver.module.ts index 82ae25992af3235d29ea021d04391cd078621110..982faa9fc87b2211af680d48b3800b138d477e85 100644 --- a/src/resolvers/resolver.module.ts +++ b/src/resolvers/resolver.module.ts @@ -2,16 +2,12 @@ import { Module } from '@nestjs/common'; import { GroupResolver } from './group.resolver'; import { EntryResolver } from './entry.resolver'; import { AgentResolver } from './agent.resolver'; -import { UnlockableEntryResolver } from './unlockable-list.resolver'; -import { UnlockedEntryResolver } from './unlocked-entry.resolver'; @Module({ providers: [ GroupResolver, EntryResolver, - UnlockableEntryResolver, AgentResolver, - UnlockedEntryResolver, ], }) export class ResolverModule {} diff --git a/src/resolvers/unlockable-list.resolver.ts b/src/resolvers/unlockable-list.resolver.ts deleted file mode 100644 index aa2d0c1fa5b64131cb39842717a133c486d20cd1..0000000000000000000000000000000000000000 --- a/src/resolvers/unlockable-list.resolver.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Parent, ResolveField, Resolver } from '@nestjs/graphql'; -import { UnlockableEntry } from './models/unlockable-entry.model'; -import { PrismaService } from '../prisma/prisma.service'; - -@Resolver(() => UnlockableEntry) -export class UnlockableEntryResolver { - constructor(private prismaService: PrismaService) {} - - @ResolveField('agent') - async agent(@Parent() entry: UnlockableEntry) { - const result = await this.prismaService.unlockEntry.findFirst({ - where: { id: entry.id }, - select: { agent: true }, - }); - - return result.agent; - } -} diff --git a/src/resolvers/unlocked-entry.resolver.ts b/src/resolvers/unlocked-entry.resolver.ts deleted file mode 100644 index 1ae876c5dbd92c81aa8f442d5b4b3977fae54761..0000000000000000000000000000000000000000 --- a/src/resolvers/unlocked-entry.resolver.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { Args, Mutation, Query, Resolver } from '@nestjs/graphql'; -import { UnlockedEntry } from './models/unlocked-entry.model'; -import { UnlockableEntry } from './models/unlockable-entry.model'; -import { PrismaService } from '../prisma/prisma.service'; -import { GraphQLString } from 'graphql'; -import { BadRequestException, NotFoundException } from '@nestjs/common'; - -@Resolver(() => UnlockedEntry) -export class UnlockedEntryResolver { - constructor(private prismaService: PrismaService) {} - - @Query(() => [UnlockableEntry]) - async listUnlockables() { - return this.prismaService.unlockEntry.findMany({ - take: 100, - select: { - id: true, - createdAt: true, - agent: true, - }, - }); - } - - @Mutation(() => UnlockedEntry) - async unlockEntry( - @Args({ name: 'id', type: () => GraphQLString }) id: string, - @Args({ name: 'groupCode', type: () => GraphQLString }) - groupCode: string, - ) { - const unlock = await this.prismaService.unlockEntry.findFirst({ - where: { id }, - }); - - if (!unlock) throw new NotFoundException(); - - const group = await this.prismaService.group.findFirst({ - where: { code: groupCode }, - }); - - if (!group) throw new NotFoundException(); - - if (group.tokens <= 0) - throw new BadRequestException('No remaining tokens'); - - await this.prismaService.group.update({ - where: { code: groupCode }, - data: { - tokens: group.tokens - 1, - unlocks: { - connect: { id }, - }, - }, - }); - - return unlock; - } -} diff --git a/src/telegram/telegram.service.ts b/src/telegram/telegram.service.ts index ea9ce358b7c5e332e05d4e966fcb11a56a78e8fb..16674c1d586823132f6075858fb1e83a197f30f0 100644 --- a/src/telegram/telegram.service.ts +++ b/src/telegram/telegram.service.ts @@ -60,6 +60,8 @@ export class TelegramService { data: { agentId: agent.id, content: match[1], + private: false, + type: 'text', }, }); @@ -108,7 +110,7 @@ export class TelegramService { where: { agentId: agent.id }, }); - await this.prismaService.unlockEntry.deleteMany({ + await this.prismaService.entry.deleteMany({ where: { agentId: agent.id }, }); @@ -137,7 +139,7 @@ export class TelegramService { where: { agentId: agent.id }, }); - const unlockEntries = await this.prismaService.unlockEntry.count({ + const unlockEntries = await this.prismaService.entry.count({ where: { agentId: agent.id }, }); @@ -175,11 +177,12 @@ export class TelegramService { await new Promise((resolve) => pipe.on('finish', resolve)); - await this.prismaService.unlockEntry.create({ + await this.prismaService.entry.create({ data: { type: 'picture', agentId: agent.id, - path: `${id}.jpg`, + image: `${id}.jpg`, + private: true, }, }); @@ -188,11 +191,12 @@ export class TelegramService { `Accepted Photo`, )); } else if (metadata.type === 'text') { - await this.prismaService.unlockEntry.create({ + await this.prismaService.entry.create({ data: { type: 'text', agentId: agent.id, content: msg.text, + private: true, }, }); @@ -201,12 +205,13 @@ export class TelegramService { 'Accepted Text', )); } else if (metadata.type === 'location') { - await this.prismaService.unlockEntry.create({ + await this.prismaService.entry.create({ data: { agentId: agent.id, type: 'location', lat: msg.location.latitude.toString(), lon: msg.location.longitude.toString(), + private: true, }, });