add follow
This commit is contained in:
parent
4d3428e56c
commit
0c27a6f513
@ -40,7 +40,22 @@ model ChatMessage {
|
|||||||
createdAt DateTime @default(now()) @map("created_at")
|
createdAt DateTime @default(now()) @map("created_at")
|
||||||
updatedAt DateTime @updatedAt @map("updated_at")
|
updatedAt DateTime @updatedAt @map("updated_at")
|
||||||
|
|
||||||
@@map("chat_messages ")
|
@@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 {
|
model SocialLink {
|
||||||
@ -74,6 +89,8 @@ model User {
|
|||||||
totpSecret String? @map("totp_secret")
|
totpSecret String? @map("totp_secret")
|
||||||
stream Stream?
|
stream Stream?
|
||||||
chatMessages ChatMessage[]
|
chatMessages ChatMessage[]
|
||||||
|
followers Follow[] @relation(name: "followers")
|
||||||
|
followings Follow[] @relation(name: "followings")
|
||||||
createdAt DateTime @default(now()) @map("created_at")
|
createdAt DateTime @default(now()) @map("created_at")
|
||||||
updatedAt DateTime @updatedAt @map("updated_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 { CategoryModule } from '@/src/module/category/category.module'
|
||||||
import { ChatModule } from '@/src/module/chat/chat.module'
|
import { ChatModule } from '@/src/module/chat/chat.module'
|
||||||
import { CronModule } from '@/src/module/cron/cron.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 { LiveKitModule } from '@/src/module/libs/livekit/livekit.module'
|
||||||
import { MailModule } from '@/src/module/libs/mail/mail.module'
|
import { MailModule } from '@/src/module/libs/mail/mail.module'
|
||||||
import { StorageModule } from '@/src/module/libs/storage/storage.module'
|
import { StorageModule } from '@/src/module/libs/storage/storage.module'
|
||||||
@ -58,6 +59,7 @@ import { RedisModule } from './redis/redis.module'
|
|||||||
WebhookModule,
|
WebhookModule,
|
||||||
CategoryModule,
|
CategoryModule,
|
||||||
ChatModule,
|
ChatModule,
|
||||||
|
FollowModule,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class CoreModule {}
|
export class CoreModule {}
|
||||||
|
|||||||
@ -87,6 +87,16 @@ input FilterInput {
|
|||||||
take: Float
|
take: Float
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type FollowModel {
|
||||||
|
createdAt: DateTime!
|
||||||
|
follower: UserModel!
|
||||||
|
followerId: ID!
|
||||||
|
following: UserModel!
|
||||||
|
followingId: ID!
|
||||||
|
id: ID!
|
||||||
|
updatedAt: DateTime!
|
||||||
|
}
|
||||||
|
|
||||||
input GenerateStreamTokenInput {
|
input GenerateStreamTokenInput {
|
||||||
channelId: String!
|
channelId: String!
|
||||||
userId: String!
|
userId: String!
|
||||||
@ -124,6 +134,7 @@ type Mutation {
|
|||||||
deactivateAccount(data: DeactivateAccountInput!): AuthModel!
|
deactivateAccount(data: DeactivateAccountInput!): AuthModel!
|
||||||
disableTotp: Boolean!
|
disableTotp: Boolean!
|
||||||
enableTotp(data: EnableTotpInput!): Boolean!
|
enableTotp(data: EnableTotpInput!): Boolean!
|
||||||
|
followChannel(channelId: String!): FollowModel!
|
||||||
generateStreamToken(data: GenerateStreamTokenInput!): GenerateTokenModel!
|
generateStreamToken(data: GenerateStreamTokenInput!): GenerateTokenModel!
|
||||||
loginUser(data: LoginInput!): AuthModel!
|
loginUser(data: LoginInput!): AuthModel!
|
||||||
logoutUser: Boolean!
|
logoutUser: Boolean!
|
||||||
@ -135,6 +146,7 @@ type Mutation {
|
|||||||
resetPassword(data: ResetPasswordInput!): Boolean!
|
resetPassword(data: ResetPasswordInput!): Boolean!
|
||||||
sendChatMessage(data: SendMessageInput!): ChatMessageModel!
|
sendChatMessage(data: SendMessageInput!): ChatMessageModel!
|
||||||
setNewPassword(data: NewPasswordInput!): Boolean!
|
setNewPassword(data: NewPasswordInput!): Boolean!
|
||||||
|
unfollowChannel(channelId: String!): FollowModel!
|
||||||
updateSocialLink(data: SocialLinkInput!, id: String!): SocialLinkModel!
|
updateSocialLink(data: SocialLinkInput!, id: String!): SocialLinkModel!
|
||||||
verifyAccount(data: VerificationInput!): AuthModel!
|
verifyAccount(data: VerificationInput!): AuthModel!
|
||||||
}
|
}
|
||||||
@ -151,6 +163,8 @@ type Query {
|
|||||||
findCategoryBySlug(slug: String!): CategoryModel!
|
findCategoryBySlug(slug: String!): CategoryModel!
|
||||||
findCurrentSession: SessionModel!
|
findCurrentSession: SessionModel!
|
||||||
findMessagesByStream(streamId: String!): [ChatMessageModel!]!
|
findMessagesByStream(streamId: String!): [ChatMessageModel!]!
|
||||||
|
findMyFollowers: [FollowModel!]!
|
||||||
|
findMyFollowings: [FollowModel!]!
|
||||||
findProfile: UserModel!
|
findProfile: UserModel!
|
||||||
findRandomCategories: [CategoryModel!]!
|
findRandomCategories: [CategoryModel!]!
|
||||||
findRandomStreams: [StreamModel!]!
|
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