Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
Warnings:

- A unique constraint covering the columns `[inviteCode]` on the table `user` will be added. If there are existing duplicate values, this will fail.
- Added the required column `inviteCode` to the `user` table without a default value. This is not possible if the table is not empty.

*/
-- CreateEnum
CREATE TYPE "Status" AS ENUM ('REQ_UID1', 'REQ_UID2', 'FRIEND');

-- AlterTable
ALTER TABLE "user" ADD COLUMN "inviteCode" CHAR(7) NOT NULL;

-- CreateTable
CREATE TABLE "friendship" (
"id" TEXT NOT NULL,
"user1Id" TEXT NOT NULL,
"user2Id" TEXT NOT NULL,
"status" "Status" NOT NULL,

CONSTRAINT "friendship_pkey" PRIMARY KEY ("id")
);

-- CreateIndex
CREATE UNIQUE INDEX "friendship_user1Id_user2Id_key" ON "friendship"("user1Id", "user2Id");

-- CreateIndex
CREATE UNIQUE INDEX "user_inviteCode_key" ON "user"("inviteCode");

-- AddForeignKey
ALTER TABLE "friendship" ADD CONSTRAINT "friendship_user1Id_fkey" FOREIGN KEY ("user1Id") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "friendship" ADD CONSTRAINT "friendship_user2Id_fkey" FOREIGN KEY ("user2Id") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE CASCADE;
33 changes: 19 additions & 14 deletions server/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -25,34 +25,39 @@ model User {

firstName String
lastName String
inviteCode String @unique @db.Char(7)
profilePictureUrl String?
timetables Timetable[]
settings Settings?

friendshipsAsUser1 Friendship[] @relation("FriendshipUser1")
friendshipsAsUser2 Friendship[] @relation("FriendshipUser2")

createdAt DateTime @default(now())
lastLogin DateTime @default(now())

@@unique([authProvider, authSubject])
@@map("user")
}

// model FriendRequest {
// id String @id @default(uuid())
// fromId String
// toId String
model Friendship {
id String @id @default(uuid())
user1Id String
user2Id String
status Status

// @@unique([fromId, toId])
// @@map("friend_request")
// }
user1 User @relation("FriendshipUser1", fields: [user1Id], references: [id], onDelete: Cascade)
user2 User @relation("FriendshipUser2", fields: [user2Id], references: [id], onDelete: Cascade)

// model Friendship {
// id String @id @default(uuid())
// user1Id String
// user2Id String
@@unique([user1Id, user2Id])
@@map("friendship")
}

// @@unique([user1Id, user2Id])
// @@map("friendship")
// }
enum Status {
REQ_UID1
REQ_UID2
FRIEND
}

model Settings {
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
Expand Down
2 changes: 2 additions & 0 deletions server/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { ConfigModule } from '@nestjs/config';
import config from './config';
import { UserModule } from './user/user.module';
import { AuthModule } from './auth/auth.module';
import { FriendshipModule } from './friendship/friendship.module';
import { TimetableModule } from './timetable/timetable.module';

@Module({
Expand All @@ -15,6 +16,7 @@ import { TimetableModule } from './timetable/timetable.module';
expandVariables: true,
}),
UserModule,
FriendshipModule,
TimetableModule,
AuthModule,
],
Expand Down
4 changes: 4 additions & 0 deletions server/src/auth/auth.service.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Injectable } from '@nestjs/common';
import { PrismaService } from 'src/prisma/prisma.service';
import { AuthProvider, Prisma, User } from 'src/generated/prisma/client';
import { UserService } from '../user/user.service';
import { Term } from 'src/timetable/types';
import { GraphqlService } from 'src/graphql/graphql.service';

Expand All @@ -17,6 +18,7 @@ export class AuthService {
constructor(
private readonly prisma: PrismaService,
private readonly graphql: GraphqlService,
private readonly user: UserService,
) {}

private readonly TIMETABLE_DEFAULT_NAME = 'My Timetable';
Expand Down Expand Up @@ -58,6 +60,7 @@ export class AuthService {
authSubject: subject,
firstName: params.firstName,
lastName: params.lastName,
inviteCode: await this.user.generateUniqueInviteCode(),
isGuest: params.isGuest,
settings: { create: {} },
},
Expand Down Expand Up @@ -87,6 +90,7 @@ export class AuthService {
firstName: params.firstName,
lastName: params.lastName,
isGuest: params.isGuest,
inviteCode: await this.user.generateUniqueInviteCode(),
settings: { create: {} },
timetables: {
create: availableTerms.map((availableTerm) => {
Expand Down
18 changes: 18 additions & 0 deletions server/src/auth/non-guest.guard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import {
CanActivate,
ExecutionContext,
ForbiddenException,
Injectable,
} from '@nestjs/common';
import { AuthenticatedRequest } from './auth.controller';

@Injectable()
export class NonGuestGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const req = context.switchToHttp().getRequest<AuthenticatedRequest>();
if (req.user?.isGuest) {
throw new ForbiddenException('Guests cannot use social features');
}
return true;
}
}
99 changes: 99 additions & 0 deletions server/src/friendship/friendship.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import {
Controller,
Get,
Body,
Post,
Req,
UseGuards,
HttpCode,
HttpStatus,
} from '@nestjs/common';
import { FriendshipService } from './friendship.service';
import {
CreateFriendRequestDto,
CancelFriendRequestDto,
AcceptFriendRequestDto,
RejectFriendRequestDto,
RemoveFriendDto,
} from './types';
import { AuthenticatedGuard } from 'src/auth/authenticated.guard';
import { NonGuestGuard } from 'src/auth/non-guest.guard';
import { AuthenticatedRequest } from 'src/auth/auth.controller';

@Controller('friendship')
@UseGuards(AuthenticatedGuard, NonGuestGuard)
export class FriendshipController {
constructor(private readonly friendshipService: FriendshipService) {}

@Post()
@HttpCode(HttpStatus.NO_CONTENT)
async createFriendRequest(
@Req() req: AuthenticatedRequest,
@Body() body: CreateFriendRequestDto,
) {
await this.friendshipService.createRelationship(
req.user.id,
body.requesteeCode,
);
}

@Post('cancel')
@HttpCode(HttpStatus.NO_CONTENT)
async cancelFriendRequest(
@Req() req: AuthenticatedRequest,
@Body() body: CancelFriendRequestDto,
) {
await this.friendshipService.cancelFriendRequest(
req.user.id,
body.requesteeId,
);
}

@Get('requests/outgoing')
async getOutgoingFriendRequests(@Req() req: AuthenticatedRequest) {
return await this.friendshipService.getUserFriendRequests(req.user.id);
}

@Get('requests/incoming')
async getIncomingFriendRequests(@Req() req: AuthenticatedRequest) {
return await this.friendshipService.getFriendRequestsToUser(req.user.id);
}

@Get()
async getUserFriends(@Req() req: AuthenticatedRequest) {
return await this.friendshipService.getUserFriendships(req.user.id);
}

@Post('accept')
@HttpCode(HttpStatus.NO_CONTENT)
async acceptFriendRequest(
@Req() req: AuthenticatedRequest,
@Body() body: AcceptFriendRequestDto,
) {
await this.friendshipService.acceptFriendRequest(
req.user.id,
body.requestorId,
);
}

@Post('reject')
@HttpCode(HttpStatus.NO_CONTENT)
async rejectFriendRequest(
@Req() req: AuthenticatedRequest,
@Body() body: RejectFriendRequestDto,
) {
await this.friendshipService.rejectFriendRequest(
req.user.id,
body.requestorId,
);
}

@Post('remove')
@HttpCode(HttpStatus.NO_CONTENT)
async removeFriend(
@Req() req: AuthenticatedRequest,
@Body() body: RemoveFriendDto,
) {
await this.friendshipService.deleteFriendship(req.user.id, body.friendId);
}
}
10 changes: 10 additions & 0 deletions server/src/friendship/friendship.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Module } from '@nestjs/common';
import { PrismaService } from 'src/prisma/prisma.service';
import { FriendshipController } from './friendship.controller';
import { FriendshipService } from './friendship.service';

@Module({
providers: [FriendshipService, PrismaService],
controllers: [FriendshipController],
})
export class FriendshipModule {}
Loading
Loading