add follow
This commit is contained in:
parent
4d3428e56c
commit
0c27a6f513
@ -43,6 +43,21 @@ model ChatMessage {
|
||||
@@map("chat_messages")
|
||||
}
|
||||
|
||||
model Follow {
|
||||
id String @id @default(uuid())
|
||||
follower User @relation(name: "followers", fields: [followerId], references: [id], onDelete: Cascade)
|
||||
followerId String @map("follower_id")
|
||||
following User @relation(name: "followings", fields: [followingId], references: [id], onDelete: Cascade)
|
||||
followingId String @map("following_id")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
||||
@@unique([followingId, followerId])
|
||||
@@index([followerId])
|
||||
@@index([followingId])
|
||||
@@map("follows")
|
||||
}
|
||||
|
||||
model SocialLink {
|
||||
id String @id @default(uuid())
|
||||
title String
|
||||
@ -74,6 +89,8 @@ model User {
|
||||
totpSecret String? @map("totp_secret")
|
||||
stream Stream?
|
||||
chatMessages ChatMessage[]
|
||||
followers Follow[] @relation(name: "followers")
|
||||
followings Follow[] @relation(name: "followings")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
||||
|
||||
@ -10,6 +10,7 @@ import { VerificationModule } from '@/src/module/auth/verification/verification.
|
||||
import { CategoryModule } from '@/src/module/category/category.module'
|
||||
import { ChatModule } from '@/src/module/chat/chat.module'
|
||||
import { CronModule } from '@/src/module/cron/cron.module'
|
||||
import { FollowModule } from '@/src/module/follow/follow.module'
|
||||
import { LiveKitModule } from '@/src/module/libs/livekit/livekit.module'
|
||||
import { MailModule } from '@/src/module/libs/mail/mail.module'
|
||||
import { StorageModule } from '@/src/module/libs/storage/storage.module'
|
||||
@ -58,6 +59,7 @@ import { RedisModule } from './redis/redis.module'
|
||||
WebhookModule,
|
||||
CategoryModule,
|
||||
ChatModule,
|
||||
FollowModule,
|
||||
],
|
||||
})
|
||||
export class CoreModule {}
|
||||
|
||||
@ -87,6 +87,16 @@ input FilterInput {
|
||||
take: Float
|
||||
}
|
||||
|
||||
type FollowModel {
|
||||
createdAt: DateTime!
|
||||
follower: UserModel!
|
||||
followerId: ID!
|
||||
following: UserModel!
|
||||
followingId: ID!
|
||||
id: ID!
|
||||
updatedAt: DateTime!
|
||||
}
|
||||
|
||||
input GenerateStreamTokenInput {
|
||||
channelId: String!
|
||||
userId: String!
|
||||
@ -124,6 +134,7 @@ type Mutation {
|
||||
deactivateAccount(data: DeactivateAccountInput!): AuthModel!
|
||||
disableTotp: Boolean!
|
||||
enableTotp(data: EnableTotpInput!): Boolean!
|
||||
followChannel(channelId: String!): FollowModel!
|
||||
generateStreamToken(data: GenerateStreamTokenInput!): GenerateTokenModel!
|
||||
loginUser(data: LoginInput!): AuthModel!
|
||||
logoutUser: Boolean!
|
||||
@ -135,6 +146,7 @@ type Mutation {
|
||||
resetPassword(data: ResetPasswordInput!): Boolean!
|
||||
sendChatMessage(data: SendMessageInput!): ChatMessageModel!
|
||||
setNewPassword(data: NewPasswordInput!): Boolean!
|
||||
unfollowChannel(channelId: String!): FollowModel!
|
||||
updateSocialLink(data: SocialLinkInput!, id: String!): SocialLinkModel!
|
||||
verifyAccount(data: VerificationInput!): AuthModel!
|
||||
}
|
||||
@ -151,6 +163,8 @@ type Query {
|
||||
findCategoryBySlug(slug: String!): CategoryModel!
|
||||
findCurrentSession: SessionModel!
|
||||
findMessagesByStream(streamId: String!): [ChatMessageModel!]!
|
||||
findMyFollowers: [FollowModel!]!
|
||||
findMyFollowings: [FollowModel!]!
|
||||
findProfile: UserModel!
|
||||
findRandomCategories: [CategoryModel!]!
|
||||
findRandomStreams: [StreamModel!]!
|
||||
|
||||
8
backend/src/module/follow/follow.module.ts
Normal file
8
backend/src/module/follow/follow.module.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { Module } from '@nestjs/common'
|
||||
import { FollowService } from './follow.service'
|
||||
import { FollowResolver } from './follow.resolver'
|
||||
|
||||
@Module({
|
||||
providers: [FollowResolver, FollowService],
|
||||
})
|
||||
export class FollowModule {}
|
||||
35
backend/src/module/follow/follow.resolver.ts
Normal file
35
backend/src/module/follow/follow.resolver.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import { FollowModel } from '@/src/module/follow/model/follow.model'
|
||||
import { Authorization } from '@/src/shared/decorators/auth.decorator'
|
||||
import { Authorized } from '@/src/shared/decorators/authorized.decorator'
|
||||
import { Args, Mutation, Query, Resolver } from '@nestjs/graphql'
|
||||
import { User } from '@prisma/generated'
|
||||
import { FollowService } from './follow.service'
|
||||
|
||||
@Resolver('Follow')
|
||||
export class FollowResolver {
|
||||
constructor(private readonly followService: FollowService) {}
|
||||
|
||||
@Authorization()
|
||||
@Query(() => [FollowModel], { name: 'findMyFollowers' })
|
||||
public async findMyFollowers(@Authorized() user: User) {
|
||||
return this.followService.findMyFollowers(user)
|
||||
}
|
||||
|
||||
@Authorization()
|
||||
@Query(() => [FollowModel], { name: 'findMyFollowings' })
|
||||
public async findMyFollowings(@Authorized() user: User) {
|
||||
return this.followService.findMyFollowings(user)
|
||||
}
|
||||
|
||||
@Authorization()
|
||||
@Mutation(() => FollowModel, { name: 'followChannel' })
|
||||
public async follow(@Authorized() user: User, @Args('channelId') channelId: string) {
|
||||
return this.followService.follow(user, channelId)
|
||||
}
|
||||
|
||||
@Authorization()
|
||||
@Mutation(() => FollowModel, { name: 'unfollowChannel' })
|
||||
public async unfollow(@Authorized() user: User, @Args('channelId') channelId: string) {
|
||||
return this.followService.unfollow(user, channelId)
|
||||
}
|
||||
}
|
||||
94
backend/src/module/follow/follow.service.ts
Normal file
94
backend/src/module/follow/follow.service.ts
Normal file
@ -0,0 +1,94 @@
|
||||
import { PrismaService } from '@/src/core/prisma/prisma.service'
|
||||
import { ConflictException, Injectable, NotFoundException } from '@nestjs/common'
|
||||
import { User } from '@prisma/generated'
|
||||
|
||||
@Injectable()
|
||||
export class FollowService {
|
||||
constructor(
|
||||
private readonly prismaService: PrismaService,
|
||||
) {
|
||||
}
|
||||
|
||||
public async findMyFollowers(user: User) {
|
||||
return this.prismaService.follow.findMany({
|
||||
where: { followingId: user.id },
|
||||
orderBy: { createdAt: 'desc' },
|
||||
include: { follower: true, following: true },
|
||||
})
|
||||
}
|
||||
|
||||
public async findMyFollowings(user: User) {
|
||||
return this.prismaService.follow.findMany({
|
||||
where: { followerId: user.id },
|
||||
orderBy: { createdAt: 'desc' },
|
||||
include: { follower: true, following: true },
|
||||
})
|
||||
}
|
||||
|
||||
public async follow(user: User, channelId: string) {
|
||||
const channel = await this.prismaService.user.findUnique({
|
||||
where: { id: channelId },
|
||||
})
|
||||
|
||||
if (!channel) {
|
||||
throw new NotFoundException('Пользователь не найден')
|
||||
}
|
||||
if (channel.id === user.id) {
|
||||
throw new ConflictException('Нельзя подписаться на себя')
|
||||
}
|
||||
|
||||
const existingFollow = await this.prismaService.follow.findFirst({
|
||||
where: {
|
||||
followerId: user.id,
|
||||
followingId: channel.id,
|
||||
},
|
||||
})
|
||||
|
||||
if (existingFollow) {
|
||||
throw new ConflictException('Подписка уже существует')
|
||||
}
|
||||
|
||||
return this.prismaService.follow.create({
|
||||
data: {
|
||||
followerId: user.id,
|
||||
followingId: channel.id,
|
||||
},
|
||||
include: {
|
||||
following: true,
|
||||
follower: true,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
public async unfollow(user: User, channelId: string) {
|
||||
const channel = await this.prismaService.user.findUnique({
|
||||
where: { id: channelId },
|
||||
})
|
||||
|
||||
if (!channel) {
|
||||
throw new NotFoundException('Пользователь не найден')
|
||||
}
|
||||
if (channel.id === user.id) {
|
||||
throw new ConflictException('Нельзя отписаться от себя')
|
||||
}
|
||||
|
||||
const existingFollow = await this.prismaService.follow.findFirst({
|
||||
where: {
|
||||
followerId: user.id,
|
||||
followingId: channel.id,
|
||||
},
|
||||
})
|
||||
|
||||
if (!existingFollow) {
|
||||
throw new ConflictException('Подписки не существует')
|
||||
}
|
||||
|
||||
return this.prismaService.follow.delete({
|
||||
where: { id: existingFollow.id },
|
||||
include: {
|
||||
following: true,
|
||||
follower: true,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
27
backend/src/module/follow/model/follow.model.ts
Normal file
27
backend/src/module/follow/model/follow.model.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { UserModel } from '@/src/module/auth/account/models/user.model'
|
||||
import { Field, ID, ObjectType } from '@nestjs/graphql'
|
||||
import { Follow } from '@prisma/generated'
|
||||
|
||||
@ObjectType()
|
||||
export class FollowModel implements Follow {
|
||||
@Field(() => ID)
|
||||
public id: string
|
||||
|
||||
@Field(() => UserModel)
|
||||
public following: UserModel
|
||||
|
||||
@Field(() => ID)
|
||||
public followingId: string
|
||||
|
||||
@Field(() => UserModel)
|
||||
public follower: UserModel
|
||||
|
||||
@Field(() => ID)
|
||||
public followerId: string
|
||||
|
||||
@Field(() => Date)
|
||||
public createdAt: Date
|
||||
|
||||
@Field(() => Date)
|
||||
public updatedAt: Date
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user