Merge pull request #168 from Dolu89/feature/project-mutation

Feature/project mutation
This commit is contained in:
Mohammed Taher Ghazal
2022-09-25 10:23:36 +03:00
committed by GitHub
5 changed files with 536 additions and 41 deletions

View File

@@ -132,7 +132,7 @@ export interface NexusGenEnums {
POST_TYPE: "Bounty" | "Question" | "Story"
ProjectLaunchStatusEnum: "Launched" | "WIP"
RoleLevelEnum: 3 | 0 | 1 | 2 | 4
TEAM_MEMBER_ROLE: "Admin" | "Maker"
TEAM_MEMBER_ROLE: "Admin" | "Maker" | "Owner"
TournamentEventTypeEnum: 2 | 3 | 0 | 1
TournamentMakerHackingStatusEnum: 1 | 0
VOTE_ITEM_TYPE: "Bounty" | "PostComment" | "Project" | "Question" | "Story" | "User"
@@ -283,6 +283,7 @@ export interface NexusGenObjects {
tagline: string; // String!
telegram?: string | null; // String
title: string; // String!
tournaments?: NexusGenRootTypes['TournamentProject'][] | null; // [TournamentProject!]
twitter?: string | null; // String
votes_count: number; // Int!
website: string; // String!
@@ -360,6 +361,10 @@ export interface NexusGenObjects {
amount: string; // String!
title: string; // String!
}
TournamentProject: { // root type
project: NexusGenRootTypes['Project']; // Project!
tournament: NexusGenRootTypes['Tournament']; // Tournament!
}
TournamentProjectsResponse: { // root type
hasNext?: boolean | null; // Boolean
hasPrev?: boolean | null; // Boolean
@@ -518,7 +523,7 @@ export interface NexusGenFieldTypes {
confirmVote: NexusGenRootTypes['Vote']; // Vote!
createProject: NexusGenRootTypes['CreateProjectResponse'] | null; // CreateProjectResponse
createStory: NexusGenRootTypes['Story'] | null; // Story
deleteProject: NexusGenRootTypes['CreateProjectResponse'] | null; // CreateProjectResponse
deleteProject: NexusGenRootTypes['Project'] | null; // Project
deleteStory: NexusGenRootTypes['Story'] | null; // Story
donate: NexusGenRootTypes['Donation']; // Donation!
registerInTournament: NexusGenRootTypes['User'] | null; // User
@@ -590,6 +595,7 @@ export interface NexusGenFieldTypes {
telegram: string | null; // String
thumbnail_image: string; // String!
title: string; // String!
tournaments: NexusGenRootTypes['TournamentProject'][] | null; // [TournamentProject!]
twitter: string | null; // String
votes_count: number; // Int!
website: string; // String!
@@ -720,6 +726,10 @@ export interface NexusGenFieldTypes {
image: string; // String!
title: string; // String!
}
TournamentProject: { // field return type
project: NexusGenRootTypes['Project']; // Project!
tournament: NexusGenRootTypes['Tournament']; // Tournament!
}
TournamentProjectsResponse: { // field return type
hasNext: boolean | null; // Boolean
hasPrev: boolean | null; // Boolean
@@ -904,7 +914,7 @@ export interface NexusGenFieldTypeNames {
confirmVote: 'Vote'
createProject: 'CreateProjectResponse'
createStory: 'Story'
deleteProject: 'CreateProjectResponse'
deleteProject: 'Project'
deleteStory: 'Story'
donate: 'Donation'
registerInTournament: 'User'
@@ -976,6 +986,7 @@ export interface NexusGenFieldTypeNames {
telegram: 'String'
thumbnail_image: 'String'
title: 'String'
tournaments: 'TournamentProject'
twitter: 'String'
votes_count: 'Int'
website: 'String'
@@ -1106,6 +1117,10 @@ export interface NexusGenFieldTypeNames {
image: 'String'
title: 'String'
}
TournamentProject: { // field return type name
project: 'Project'
tournament: 'Tournament'
}
TournamentProjectsResponse: { // field return type name
hasNext: 'Boolean'
hasPrev: 'Boolean'

View File

@@ -187,7 +187,7 @@ type Mutation {
confirmVote(payment_request: String!, preimage: String!): Vote!
createProject(input: CreateProjectInput): CreateProjectResponse
createStory(data: StoryInputType): Story
deleteProject(id: Int!): CreateProjectResponse
deleteProject(id: Int!): Project
deleteStory(id: Int!): Story
donate(amount_in_sat: Int!): Donation!
registerInTournament(data: RegisterInTournamentInput, tournament_id: Int!): User
@@ -302,6 +302,7 @@ type Project {
telegram: String
thumbnail_image: String!
title: String!
tournaments: [TournamentProject!]
twitter: String
votes_count: Int!
website: String!
@@ -406,6 +407,7 @@ input StoryInputType {
enum TEAM_MEMBER_ROLE {
Admin
Maker
Owner
}
type Tag {
@@ -494,6 +496,11 @@ type TournamentPrize {
title: String!
}
type TournamentProject {
project: Project!
tournament: Tournament!
}
type TournamentProjectsResponse {
hasNext: Boolean
hasPrev: Boolean

View File

@@ -10,10 +10,13 @@ const {
} = require('nexus');
const { getUserByPubKey } = require('../../../auth/utils/helperFuncs');
const { prisma } = require('../../../prisma');
const { deleteImage } = require('../../../services/imageUpload.service');
const { logError } = require('../../../utils/logger');
const { resolveImgObjectToUrl } = require('../../../utils/resolveImageUrl');
const { paginationArgs, getLnurlDetails, lightningAddressToLnurl } = require('./helpers');
const { ImageInput } = require('./misc');
const { TournamentProject } = require('./tournament');
const { MakerRole } = require('./users');
@@ -98,6 +101,9 @@ const Project = objectType({
}
})
t.list.nonNull.field('tournaments', {
type: TournamentProject
})
t.nonNull.list.nonNull.field('capabilities', {
type: Capability,
@@ -130,9 +136,13 @@ const Project = objectType({
}
})
const ROLE_OWNER = 'Owner'
const ROLE_ADMIN = 'Admin'
const ROLE_MAKER = 'Maker'
const TEAM_MEMBER_ROLE = enumType({
name: 'TEAM_MEMBER_ROLE',
members: ['Admin', 'Maker'],
members: [ROLE_OWNER, ROLE_ADMIN, ROLE_MAKER],
});
const ProjectMember = objectType({
@@ -450,6 +460,8 @@ const CreateProjectResponse = objectType({
}
})
const createProject = extendType({
type: 'Mutation',
definition(t) {
@@ -457,7 +469,7 @@ const createProject = extendType({
type: CreateProjectResponse,
args: { input: CreateProjectInput },
async resolve(_root, args, ctx) {
const {
let {
title,
tagline,
hashtag,
@@ -467,6 +479,8 @@ const createProject = extendType({
cover_image,
discord,
github,
slack,
telegram,
twitter,
website,
launch_status,
@@ -476,54 +490,194 @@ const createProject = extendType({
thumbnail_image,
tournaments,
} = args.input
console.log(launch_status);
const user = await getUserByPubKey(ctx.userPubKey);
const user = await getUserByPubKey(ctx.userPubKey)
// Do some validation
if (!user)
throw new ApolloError("Not Authenticated");
if (!user) throw new ApolloError('Not Authenticated')
// TODO Create project
}
// Many Owners found. Throw an error
if (members.filter((m) => m.role === ROLE_OWNER).length > 1) {
throw new ApolloError('Only 1 owner can be defined.')
}
// No owner found. Set the current user as Owner
if (!members.find((m) => m.role === ROLE_OWNER)) {
const currentUser = members.find((m) => m.id === user.id)
if (currentUser) {
currentUser.role = ROLE_OWNER
} else {
members = [{ id: user.id, role: ROLE_OWNER }, ...members]
}
}
const coverImage = await prisma.hostedImage.findFirst({
where: {
provider_image_id: cover_image.id,
},
})
const coverImageRel = coverImage
? {
cover_image_rel: {
connect: {
id: coverImage ? coverImage.id : null,
},
},
}
: {}
const thumbnailImage = await prisma.hostedImage.findFirst({
where: {
provider_image_id: thumbnail_image.id,
},
})
const thumbnailImageRel = thumbnailImage
? {
thumbnail_image_rel: {
connect: {
id: thumbnailImage ? thumbnailImage.id : null,
},
},
}
: {}
const screenshots_ids = await prisma.hostedImage.findMany({
where: {
provider_image_id: {
in: screenshots.map((s) => s.id),
},
},
select: {
id: true,
},
})
const project = await prisma.project.create({
data: {
title,
description,
tagline,
hashtag,
website,
discord,
github,
twitter,
slack,
telegram,
launch_status,
...coverImageRel,
...thumbnailImageRel,
screenshots_ids: screenshots_ids.map((s) => s.id),
category: {
connect: {
id: category_id,
},
},
members: {
create: members.map((member) => {
return {
role: member.role,
user: {
connect: {
id: member.id,
},
},
}
}),
},
recruit_roles: {
create: recruit_roles.map((role) => {
return {
level: 0,
role: {
connect: {
id: role,
},
},
}
}),
},
tournaments: {
create: tournaments.map((tournament) => {
return {
tournament: {
connect: {
id: tournament,
},
},
}
}),
},
capabilities: {
connect: capabilities.map((c) => {
return {
id: c,
}
}),
},
},
})
await prisma.hostedImage
.updateMany({
where: {
id: {
in: [coverImage.id, thumbnailImage.id, ...screenshots_ids.map((s) => s.id)],
},
},
data: {
is_used: true,
},
})
.catch((error) => {
logError(error)
throw new ApolloError('Unexpected error happened...')
})
return { project }
},
})
},
})
const UpdateProjectInput = inputObjectType({
name: 'UpdateProjectInput',
definition(t) {
t.int('id')
t.nonNull.string('title');
t.nonNull.string('hashtag');
t.nonNull.string('website');
t.nonNull.string('tagline');
t.nonNull.string('description');
t.nonNull.string('title')
t.nonNull.string('hashtag')
t.nonNull.string('website')
t.nonNull.string('tagline')
t.nonNull.string('description')
t.nonNull.field('thumbnail_image', {
type: ImageInput
type: ImageInput,
})
t.nonNull.field('cover_image', {
type: ImageInput
type: ImageInput,
})
t.string('twitter');
t.string('discord');
t.string('github');
t.string('slack');
t.string('telegram');
t.nonNull.int('category_id');
t.nonNull.list.nonNull.int('capabilities');
t.string('twitter')
t.string('discord')
t.string('github')
t.string('slack')
t.string('telegram')
t.nonNull.int('category_id')
t.nonNull.list.nonNull.int('capabilities')
t.nonNull.list.nonNull.field('screenshots', {
type: ImageInput
});
type: ImageInput,
})
t.nonNull.list.nonNull.field('members', {
type: TeamMemberInput
});
t.nonNull.list.nonNull.int('recruit_roles'); // ids
type: TeamMemberInput,
})
t.nonNull.list.nonNull.int('recruit_roles') // ids
t.nonNull.field('launch_status', {
type: ProjectLaunchStatusEnum
});
t.nonNull.list.nonNull.int('tournaments'); // ids
}
type: ProjectLaunchStatusEnum,
})
t.nonNull.list.nonNull.int('tournaments') // ids
},
})
const updateProject = extendType({
@@ -533,8 +687,252 @@ const updateProject = extendType({
type: CreateProjectResponse,
args: { input: UpdateProjectInput },
async resolve(_root, args, ctx) {
const {
id,
title,
tagline,
hashtag,
description,
capabilities,
category_id,
cover_image,
discord,
github,
slack,
telegram,
twitter,
website,
launch_status,
members,
recruit_roles,
screenshots,
thumbnail_image,
tournaments,
} = args.input
}
const user = await getUserByPubKey(ctx.userPubKey)
// Do some validation
if (!user) throw new ApolloError('Not Authenticated')
const project = await prisma.project.findFirst({
where: {
id,
},
include: {
members: true,
},
})
// Maker can't project info
if (project.members.find((m) => m.userId === user.id)?.role === ROLE_MAKER) {
throw new ApolloError("Makers can't change project info")
}
let newMembers = []
// Admin can only change makers
if (project.members.find((m) => m.userId === user.id)?.role === ROLE_ADMIN) {
// Changing Makers
const newMakers = members.filter((m) => m.role === ROLE_MAKER)
// Set old Admins and Owner using current project.memebers because Admin can't change these Roles
const currentAdminsOwner = project.members
.filter((m) => m.role === ROLE_ADMIN || m.role === ROLE_OWNER)
.map((m) => ({ id: m.userId, role: m.role }))
newMembers = [...newMakers, ...currentAdminsOwner]
} else {
// Curent user is Owner. Can change all users roles
newMembers = members
}
let imagesToDelete = []
let imagesToAdd = []
let coverImageRel = {}
if (cover_image.id) {
const coverImage = await prisma.hostedImage.findFirst({
where: {
provider_image_id: cover_image.id,
},
})
coverImageRel = coverImage
? {
cover_image_rel: {
connect: {
id: coverImage ? coverImage.id : null,
},
},
}
: {}
if (coverImage) {
imagesToAdd.push(coverImage.id)
}
imagesToDelete.push(project.cover_image_id)
}
let thumbnailImageRel = {}
if (thumbnail_image.id) {
const thumbnailImage = await prisma.hostedImage.findFirst({
where: {
provider_image_id: thumbnail_image.id,
},
})
thumbnailImageRel = thumbnailImage
? {
thumbnail_image_rel: {
connect: {
id: thumbnailImage ? thumbnailImage.id : null,
},
},
}
: {}
if (thumbnailImage) {
imagesToAdd.push(thumbnailImage.id)
}
imagesToDelete.push(project.thumbnail_image_id)
}
let screenshots_ids = []
for (const screenshot of screenshots) {
if (screenshot.id) {
const newScreenshot = await prisma.hostedImage.findFirst({
where: {
provider_image_id: screenshot.id,
},
select: {
id: true,
},
})
if (newScreenshot) {
screenshots_ids.push(newScreenshot.id)
imagesToAdd.push(newScreenshot.id)
}
} else {
const newScreenshot = await prisma.hostedImage.findFirst({
where: {
url: screenshot.url,
},
select: {
id: true,
},
})
if (newScreenshot) {
screenshots_ids.push(newScreenshot.id)
}
}
}
const screenshotsIdsToDelete = project.screenshots_ids.filter((x) => !screenshots_ids.includes(x))
imagesToDelete = [...imagesToDelete, ...screenshotsIdsToDelete]
const updatedProject = await prisma.project
.update({
where: {
id,
},
data: {
title,
description,
tagline,
hashtag,
website,
discord,
github,
twitter,
slack,
telegram,
launch_status,
...coverImageRel,
...thumbnailImageRel,
screenshots_ids,
category: {
connect: {
id: category_id,
},
},
members: {
deleteMany: {},
create: newMembers.map((member) => {
return {
role: member.role,
user: {
connect: {
id: member.id,
},
},
}
}),
},
recruit_roles: {
deleteMany: {},
create: recruit_roles.map((role) => {
return {
level: 0,
role: {
connect: {
id: role,
},
},
}
}),
},
tournaments: {
deleteMany: {},
create: tournaments.map((tournament) => {
return {
tournament: {
connect: {
id: tournament,
},
},
}
}),
},
capabilities: {
set: capabilities.map((c) => {
return {
id: c,
}
}),
},
},
})
.catch((error) => {
logError(error)
throw new ApolloError('Unexpected error happened...')
})
if (imagesToAdd.length > 0) {
await prisma.hostedImage
.updateMany({
where: {
id: {
in: imagesToAdd,
},
},
data: {
is_used: true,
},
})
.catch((error) => {
logError(error)
throw new ApolloError('Unexpected error happened...')
})
}
imagesToDelete.map(async (i) => await deleteImage(i))
return { project: updatedProject }
},
})
},
})
@@ -543,11 +941,78 @@ const deleteProject = extendType({
type: 'Mutation',
definition(t) {
t.field('deleteProject', {
type: CreateProjectResponse,
type: 'Project',
args: { id: nonNull(intArg()) },
async resolve(_root, args, ctx) {
// ...
}
const { id } = args
const user = await getUserByPubKey(ctx.userPubKey)
// Do some validation
if (!user) throw new ApolloError('Not Authenticated')
const project = await prisma.project.findFirst({
where: { id },
include: {
members: true,
},
})
if (!project) throw new ApolloError('Project not found')
if (project.members.find((m) => m.userId === user.id)?.role !== ROLE_OWNER)
throw new ApolloError("You don't have the right to delete this project")
// Award is not implemented yet
// await prisma.award.deleteMany({
// where: {
// projectId: project.id
// }
// })
await prisma.projectRecruitRoles.deleteMany({
where: {
projectId: project.id,
},
})
await prisma.projectMember.deleteMany({
where: {
projectId: project.id,
},
})
await prisma.tournamentProject.deleteMany({
where: {
project_id: project.id,
},
})
const deletedProject = await prisma.project.delete({
where: {
id,
},
})
const imagesToDelete = await prisma.hostedImage.findMany({
where: {
OR: [
{ id: project.cover_image_id },
{ id: project.thumbnail_image_id },
{
id: {
in: project.screenshots_ids,
},
},
],
},
select: {
id: true,
},
})
imagesToDelete.map(async (i) => await deleteImage(i.id))
return deletedProject
},
})
},
})

View File

@@ -76,7 +76,13 @@ const TournamentMakerHackingStatusEnum = enumType({
},
});
const TournamentProject = objectType({
name: "TournamentProject",
definition(t) {
t.nonNull.field('project', { type: "Project" });
t.nonNull.field('tournament', { type: "Tournament" })
}
});
const TournamentEvent = objectType({
name: 'TournamentEvent',
@@ -551,6 +557,7 @@ const updateTournamentRegistration = extendType({
module.exports = {
// Types
Tournament,
TournamentProject,
// Enums
TournamentEventTypeEnum,

View File

@@ -647,6 +647,7 @@ export type StoryInputType = {
};
export enum Team_Member_Role {
Owner = 'Owner',
Admin = 'Admin',
Maker = 'Maker'
}