Files
comroots-backend/src/resolvers/page.ts
T
2026-06-24 14:10:53 +02:00

360 lines
11 KiB
TypeScript

import { Arg, Ctx, FieldResolver, Int, Mutation, Query, Resolver, Root, UseMiddleware } from 'type-graphql'
import { Page, PageFollow, User } from '@entities'
import { Context } from '@types'
import { isAuth } from '@middlewares'
import { In } from 'typeorm'
import { s3 } from '@configs'
import { PageInfo, PageResponse, PhotoResponse, UploadedFileResponse } from '@graphql-types'
import _ from 'lodash'
import { GraphQLUpload, FileUpload } from 'graphql-upload'
import { v4 } from 'uuid'
import { getExtensionFromFilename, saveAttributes } from '@utils'
@Resolver(Page)
export class PageResolver {
@FieldResolver(() => String)
avatarUrl(
@Root() page: Page
): Promise<string | null> {
return s3.getSignedUrlPromise('getObject', { Bucket: process.env.S3_BUCKET, Key: page.avatar })
}
@FieldResolver(() => String)
coverPhotoUrl(
@Root() page: Page
): Promise<string | null> {
return s3.getSignedUrlPromise('getObject', { Bucket: process.env.S3_BUCKET, Key: page.coverPhoto })
}
@Query(() => Boolean, { nullable: true })
@FieldResolver(() => Boolean, { nullable: true })
async followStatus(
@Root() root: Page,
@Arg('pageId', () => Int, { nullable: true }) pageId: number,
@Ctx() { req, followLoader }: Context
): Promise<boolean | null> {
const { userId } = req.session
if (pageId) {
return userId ?
followLoader.load({ pageId, userId })
:
null
}
else {
return userId ?
followLoader.load({ pageId: root.id, userId })
:
null
}
}
@FieldResolver(() => Boolean, { nullable: true })
async ownerStatus(
@Root() root: Page,
@Ctx() { req, ownerStatusLoader }: Context
): Promise<boolean | null> {
const { userId } = req.session
return userId ?
ownerStatusLoader.load({ pageId: root.id, userId })
:
null
}
@FieldResolver(() => Int)
async followerNumber(
@Root() page: Page,
@Ctx() { followerNumberLoader }: Context
): Promise<number> {
return followerNumberLoader.load(page.id)
}
@Query(() => Page)
async page(
@Arg('pageName', () => String) pageName: string,
): Promise<Page | null> {
return Page.findOneBy({ pageName })
}
@UseMiddleware(isAuth)
@Query(() => [Page])
async myPages(
@Ctx() { orm, req }: Context
): Promise<Page[]> {
const myPageIds: { pageId: number }[] = await orm.query(`
SELECT "pageId" FROM page_owners_user WHERE "userId" = ${req.session.userId};
`)
return Page.findBy({ id: In(myPageIds.map(item => item.pageId)) })
}
@UseMiddleware(isAuth)
@Mutation(() => PageResponse)
async createPage(
@Arg('pageName') pageName: string,
@Ctx() { req }: Context
): Promise<PageResponse> {
const { userId } = req.session
const p = await Page.findOne({ where: { pageName } })
if (p) {
return {
errors: [{
field: 'pageName',
message: 'Page already exists.'
}]
}
}
if (!/^[A-Za-z0-9_-]*$/.test(pageName)) {
return {
errors: [{
field: 'pageName',
message: 'Page\'s name must contain only letters, numbers, underscores and dashes.'
}]
}
}
const page = await Page.create({
pageName,
owners: [(await User.findOneBy({ id: userId }))!],
}).save()
await PageFollow.create({ pageId: page.id, userId }).save()
return { page }
}
@UseMiddleware(isAuth)
@Mutation(() => Boolean)
async deletePage(
@Arg('id', () => Int) id: number,
@Ctx() { orm, req }: Context
): Promise<boolean> {
const page = await Page.findOne({ where: { id } })
if (!page) {
throw new Error('Page not found.')
}
const ownerProof: { userId: number, pageId: number }[] = await orm.query(`
SELECT * FROM page_owners_user WHERE "userId" = ${req.session.userId} AND "pageId" = ${id};
`)
if (ownerProof.length == 0) {
throw new Error('You are not an owner of this page.')
}
try {
const result = await s3.listObjectsV2({ Bucket: process.env.S3_BUCKET, Prefix: `pages/${id}` }).promise()
result?.Contents?.forEach(async c => {
if (c.Key) {
await s3.deleteObject({ Bucket: process.env.S3_BUCKET, Key: c.Key }).promise()
}
})
await page.remove()
return true
}
catch (e) {
console.error(e)
return false
}
}
@UseMiddleware(isAuth)
@Mutation(() => Boolean)
async follow(
@Arg('pageId', () => Int) pageId: number,
@Ctx() { orm, req }: Context
) {
const { userId } = req.session
const follow = await PageFollow.findOne({ where: { pageId, userId } })
if (!follow) {
const page = await Page.findOne({ where: { id: pageId } })
if (!page) {
return false
}
await PageFollow.create({ pageId, userId }).save()
}
return true
}
@UseMiddleware(isAuth)
@Mutation(() => Boolean)
async unfollow(
@Arg('pageId', () => Int) pageId: number,
@Ctx() { req }: Context
) {
const { userId } = req.session
const follow = await PageFollow.findOne({ where: { pageId, userId } })
if (follow) {
await follow.remove()
}
return true
}
@Mutation(() => UploadedFileResponse, { nullable: true })
@UseMiddleware(isAuth)
async uploadPageAvatar(
@Arg('pageId', () => Int) pageId: number,
@Arg('upload', () => GraphQLUpload) upload: FileUpload,
@Ctx() { req, orm }: Context
): Promise<UploadedFileResponse> {
const { userId } = req.session
const { createReadStream, filename, mimetype, encoding } = upload as FileUpload
const stream = createReadStream()
const ext = getExtensionFromFilename(filename)
const ownerProof = await orm.query(`
SELECT * FROM page_owners_user WHERE "pageId" = ${pageId} AND "userId" = ${userId};
`)
if (ownerProof.length == 0) {
throw new Error('You are not an owner of this page.')
}
const uploaded = await s3.upload({
Bucket: process.env.S3_BUCKET,
Body: stream,
Key: `pages/${pageId}/photos/avatars/${v4()}.${ext}`,
}).promise()
return {
filename,
mimetype,
encoding,
url: await s3.getSignedUrlPromise('getObject', { Bucket: process.env.S3_BUCKET, Key: uploaded.Key })
}
}
@Mutation(() => Boolean)
@UseMiddleware(isAuth)
async changePageAvatar(
@Arg('pageId', () => Int) pageId: number,
@Arg('key', () => String) key: string,
@Ctx() { req, orm }: Context
): Promise<boolean> {
const { userId } = req.session
const ownerProof = await orm.query(`
SELECT * FROM page_owners_user WHERE "pageId" = ${pageId} AND "userId" = ${userId};
`)
if (ownerProof.length == 0) {
throw new Error('You are not an owner of this page.')
}
await Page.update({ id: pageId }, { avatar: key })
return true
}
@Mutation(() => UploadedFileResponse, { nullable: true })
@UseMiddleware(isAuth)
async uploadPageCoverPhoto(
@Arg('pageId', () => Int) pageId: number,
@Arg('upload', () => GraphQLUpload) upload: FileUpload,
@Ctx() { req, orm }: Context
): Promise<UploadedFileResponse> {
const { userId } = req.session
const { createReadStream, filename, mimetype, encoding } = upload as FileUpload
const stream = createReadStream()
const ext = getExtensionFromFilename(filename)
const ownerProof = await orm.query(`
SELECT * FROM page_owners_user WHERE "pageId" = ${pageId} AND "userId" = ${userId};
`)
if (ownerProof.length == 0) {
throw new Error('You are not an owner of this page.')
}
const uploaded = await s3.upload({
Bucket: process.env.S3_BUCKET,
Body: stream,
Key: `pages/${pageId}/photos/coverPhotos/${v4()}.${ext}`,
}).promise()
return {
filename,
mimetype,
encoding,
url: await s3.getSignedUrlPromise('getObject', { Bucket: process.env.S3_BUCKET, Key: uploaded.Key })
}
}
@Mutation(() => Boolean)
@UseMiddleware(isAuth)
async changePageCoverPhoto(
@Arg('pageId', () => Int) pageId: number,
@Arg('key', () => String) key: string,
@Ctx() { req, orm }: Context
): Promise<boolean> {
const { userId } = req.session
const ownerProof = await orm.query(`
SELECT * FROM page_owners_user WHERE "pageId" = ${pageId} AND "userId" = ${userId};
`)
if (ownerProof.length == 0) {
throw new Error('You are not an owner of this page.')
}
await Page.update({ id: pageId }, { coverPhoto: key })
return true
}
@Query(() => [PhotoResponse])
@UseMiddleware(isAuth)
async pageAvatars(
@Arg('pageId', () => Int) pageId: number,
@Ctx() { req, orm }: Context
): Promise<PhotoResponse[]> {
const { userId } = req.session
const ownerProof = await orm.query(`
SELECT * FROM page_owners_user WHERE "pageId" = ${pageId} AND "userId" = ${userId};
`)
if (ownerProof.length == 0) {
throw new Error('You are not an owner of this page.')
}
const result = await s3.listObjectsV2({ Bucket: process.env.S3_BUCKET, Prefix: `pages/${pageId}/photos/avatars` }).promise()
let keys = result?.Contents?.map(item => item.Key as string)
if (keys == undefined || keys.length == 0) {
return []
}
else {
keys = keys.filter(k => k != `pages/${pageId}/photos/avatars`)
return Promise.all(keys.map(async key => {
return {
key,
url: await s3.getSignedUrlPromise('getObject', { Bucket: process.env.S3_BUCKET, Key: key })
} as PhotoResponse
}))
}
}
@Query(() => [PhotoResponse])
@UseMiddleware(isAuth)
async pageCoverPhotos(
@Arg('pageId', () => Int) pageId: number,
@Ctx() { req, orm }: Context
): Promise<PhotoResponse[]> {
const { userId } = req.session
const ownerProof = await orm.query(`
SELECT * FROM page_owners_user WHERE "pageId" = ${pageId} AND "userId" = ${userId};
`)
if (ownerProof.length == 0) {
throw new Error('You are not an owner of this page.')
}
const result = await s3.listObjectsV2({ Bucket: process.env.S3_BUCKET, Prefix: `pages/${pageId}/photos/coverPhotos` }).promise()
let keys = result?.Contents?.map(item => item.Key as string)
if (keys == undefined || keys.length == 0) {
return []
}
else {
keys = keys.filter(k => k != `pages/${pageId}/photos/coverPhotos`)
return Promise.all(keys.map(async key => {
return {
key,
url: await s3.getSignedUrlPromise('getObject', { Bucket: process.env.S3_BUCKET, Key: key })
} as PhotoResponse
}))
}
}
@Mutation(() => Boolean)
@UseMiddleware(isAuth)
async updatePageInfo(
@Arg('id', () => Int) id: number,
@Arg('input', () => PageInfo) input: PageInfo,
@Ctx() { orm, req }: Context
): Promise<boolean> {
const { userId } = req.session
const ownerProof = await orm.query(`
SELECT * FROM page_owners_user WHERE "pageId" = ${id} AND "userId" = ${userId};
`)
if (ownerProof.length == 0) {
throw new Error('You are not an owner of this page.')
}
const page = await Page.findOneBy({ id })
saveAttributes<Page, PageInfo>(page, input)
await page!.save()
return true
}
}