add social links

This commit is contained in:
Sergey Krylov 2025-07-07 06:16:58 +03:00
parent c63592448a
commit 5d347acf88
7 changed files with 224 additions and 14 deletions

View File

@ -8,6 +8,19 @@ datasource db {
url = env("POSTGRES_URI")
}
model SocialLink {
id String @id @default(uuid())
title String
url String
position Int
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
userId String? @map("user_id")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@map("social_links")
}
model User {
id String @id @default(uuid())
email String @unique
@ -22,6 +35,7 @@ model User {
isTotpEnabled Boolean @default(false) @map("is_totp_enabled")
isDeactivated Boolean @default(false) @map("is_deactivated")
deactivatedAt DateTime? @map("deactivated_at")
socialLink SocialLink[]
totpSecret String? @map("totp_secret")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")

View File

@ -69,6 +69,7 @@ type Mutation {
changeProfileAvatar(avatar: Upload!): Boolean!
changeProfileInfo(data: ChangeProfileInfoInput!): UserModel!
clearSessionCookie: Boolean!
createSocialLink(data: SocialLinkInput!): SocialLinkModel!
createUser(data: CreateUserInput!): UserModel!
deactivateAccount(data: DeactivateAccountInput!): AuthModel!
disableTotp: Boolean!
@ -77,8 +78,11 @@ type Mutation {
logoutUser: Boolean!
removeProfileAvatar: Boolean!
removeSession(id: String!): Boolean!
removeSocialLink(id: String!): Boolean!
reorderSocialLink(list: [SocialLinkOrderInput!]!): Boolean!
resetPassword(data: ResetPasswordInput!): Boolean!
setNewPassword(data: NewPasswordInput!): Boolean!
updateSocialLink(data: SocialLinkInput!, id: String!): SocialLinkModel!
verifyAccount(data: VerificationInput!): UserModel!
}
@ -92,6 +96,7 @@ type Query {
findCurrentSession: SessionModel!
findProfile: UserModel!
findSessionsByUser: [SessionModel!]!
findSocialLinks: [SocialLinkModel!]!
generateTotpSecret: TotpModel!
}
@ -112,6 +117,26 @@ type SessionModel {
userId: String!
}
input SocialLinkInput {
title: String!
url: String!
}
type SocialLinkModel {
createdAt: DateTime!
id: ID!
position: Float!
title: String!
updatedAt: DateTime!
url: String!
userId: ID!
}
input SocialLinkOrderInput {
id: String!
position: Float!
}
type TotpModel {
qrcodeUrl: String!
secret: String!
@ -134,6 +159,7 @@ type UserModel {
isVerified: Boolean!
name: String!
password: String!
socialLink: [SocialLinkModel!]!
totpSecret: String
updatedAt: DateTime!
}

View File

@ -1,3 +1,4 @@
import { SocialLinkModel } from '@/src/module/auth/profile/inputs/models/social-link.model';
import { Field, ID, ObjectType } from '@nestjs/graphql'
import { User } from '@prisma/generated'
@ -42,6 +43,9 @@ export class UserModel implements User {
@Field(() => String, { nullable: true })
totpSecret: string
@Field(() => [SocialLinkModel])
socialLink: SocialLinkModel[]
@Field(() => Date)
createdAt: Date

View File

@ -0,0 +1,27 @@
import { UserModel } from '@/src/module/auth/account/models/user.model'
import { Field, ID, ObjectType } from '@nestjs/graphql'
import { SocialLink } from '@prisma/generated'
@ObjectType()
export class SocialLinkModel implements SocialLink {
@Field(() => ID)
id: string
@Field(() => ID)
userId: UserModel['id']
@Field(() => String)
title: string
@Field(() => String)
url: string
@Field(() => Number)
position: number
@Field(() => Date)
updatedAt: Date
@Field(() => Date)
createdAt: Date
}

View File

@ -0,0 +1,29 @@
import { Field, InputType } from '@nestjs/graphql'
import { IsNotEmpty, IsNumber, IsString, IsUrl } from 'class-validator'
@InputType()
export class SocialLinkInput {
@Field(() => String)
@IsString()
@IsNotEmpty()
title: string
@Field(() => String)
@IsString()
@IsNotEmpty()
@IsUrl()
url: string
}
@InputType()
export class SocialLinkOrderInput {
@Field(() => String)
@IsString()
@IsNotEmpty()
id: string
@Field(() => Number)
@IsNumber()
@IsNotEmpty()
position: number
}

View File

@ -1,9 +1,14 @@
import { UserModel } from '@/src/module/auth/account/models/user.model'
import { ChangeProfileInfoInput } from '@/src/module/auth/profile/inputs/change-profile-info.input'
import { SocialLinkModel } from '@/src/module/auth/profile/inputs/models/social-link.model'
import {
SocialLinkInput,
SocialLinkOrderInput,
} from '@/src/module/auth/profile/inputs/social-link.input'
import { Authorization } from '@/src/shared/decorators/auth.decorator'
import { Authorized } from '@/src/shared/decorators/authorized.decorator'
import { FileValidationPipe } from '@/src/shared/pipes/file-validation.pipe'
import { Args, Mutation, Resolver } from '@nestjs/graphql'
import { Args, Mutation, Query, Resolver } from '@nestjs/graphql'
import { ProfileService } from './profile.service'
import { User } from '@/prisma/generated'
import * as Upload from 'graphql-upload/Upload.js'
@ -36,4 +41,44 @@ export class ProfileResolver {
) {
return this.profileService.changeInfo(user, input)
}
@Authorization()
@Mutation(() => SocialLinkModel, { name: 'createSocialLink' })
public async createSocialLink(
@Authorized() user: User,
@Args('data') input: SocialLinkInput,
) {
return this.profileService.createSocialLink(user, input)
}
@Authorization()
@Mutation(() => Boolean, { name: 'reorderSocialLink' })
public async reorderSocialLinks(
@Args('list', { type: () => [SocialLinkOrderInput] }) list: SocialLinkOrderInput[],
) {
return this.profileService.reorderSocialLinks(list)
}
@Authorization()
@Mutation(() => SocialLinkModel, { name: 'updateSocialLink' })
public async updateSocialLink(
@Args('id') id: string,
@Args('data') input: SocialLinkInput,
) {
return this.profileService.updateSocialLink(id, input)
}
@Authorization()
@Mutation(() => Boolean, { name: 'removeSocialLink' })
public async removeSocialLink(
@Args('id') id: string,
) {
return this.profileService.removeSocialLink(id)
}
@Authorization()
@Query(() => [SocialLinkModel], { name: 'findSocialLinks' })
public async findSocialLink(@Authorized() user: User) {
return this.profileService.findSocialLink(user)
}
}

View File

@ -1,9 +1,13 @@
import { PrismaService } from '@/src/core/prisma/prisma.service'
import { ChangeProfileInfoInput } from '@/src/module/auth/profile/inputs/change-profile-info.input'
import {
SocialLinkInput,
SocialLinkOrderInput,
} from '@/src/module/auth/profile/inputs/social-link.input'
import { ConflictException, Injectable } from '@nestjs/common'
import sharp from 'sharp'
import { StorageService } from '../../libs/storage/storage.service'
import { User } from '@/prisma/generated'
import { SocialLink, User } from '@/prisma/generated'
import * as Upload from 'graphql-upload/Upload.js'
@Injectable()
@ -72,4 +76,65 @@ export class ProfileService {
data: { bio, displayName, name },
})
}
public async createSocialLink(user: User, input: SocialLinkInput) {
const { title, url } = input
const lastSocialLink = await this.prismaService.socialLink.findFirst({
where: { userId: user.id },
orderBy: { position: 'desc' },
})
return this.prismaService.socialLink.create({
data: {
url,
title,
position: lastSocialLink ? lastSocialLink.position + 1 : 1,
user: {
connect: {
id: user.id,
},
},
},
})
}
public async findSocialLink(user: User) {
return this.prismaService.socialLink.findMany({
where: { userId: user.id },
})
}
public async reorderSocialLinks(list: SocialLinkOrderInput[]) {
if (list.length === 0) {
return
}
const updatePromises = list.map((socialLink) => {
return this.prismaService.socialLink.update({
where: { id: socialLink.id },
data: { position: Number(socialLink.position) },
})
})
await Promise.all(updatePromises)
return true
}
public async updateSocialLink(id: SocialLink['id'], input: SocialLinkInput) {
const { title, url } = input
return this.prismaService.socialLink.update({
where: { id },
data: { title, url },
})
}
public async removeSocialLink(id: SocialLink['id']) {
await this.prismaService.socialLink.delete({
where: { id },
})
return true
}
}