Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(WIP) Founders club feature preview #240

Open
wants to merge 13 commits into
base: dev
Choose a base branch
from
29 changes: 29 additions & 0 deletions api/functions/graphql/nexus-typegen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,9 +147,19 @@ export interface NexusGenInputs {
key: string; // String!
name: string; // String!
}
acceptOrRejectClubInvitationInput: { // input type
code: string; // String!
email?: string | null; // String
isAccepted: boolean; // Boolean!
}
applyToFoundersClubInput: { // input type
project_id: number; // Int!
reason: string; // String!
}
}

export interface NexusGenEnums {
CLUB_INVITATION_STATUS: "ACCEPTED" | "DECLINED" | "INVALID" | "UNUSED"
POST_TYPE: "Bounty" | "Question" | "Story"
ProjectLaunchStatusEnum: "Launched" | "WIP"
ProjectPermissionEnum: "DeleteProject" | "UpdateAdmins" | "UpdateInfo" | "UpdateMembers"
Expand Down Expand Up @@ -553,7 +563,9 @@ export interface NexusGenFieldTypes {
title: string; // String!
}
Mutation: { // field return type
acceptOrRejectClubInvitation: NexusGenRootTypes['User']; // User!
addProjectToTournament: NexusGenRootTypes['ParticipationInfo'] | null; // ParticipationInfo
applyToFoundersClub: string; // String!
confirmDonation: NexusGenRootTypes['Donation']; // Donation!
confirmVote: NexusGenRootTypes['Vote']; // Vote!
createProject: NexusGenRootTypes['CreateProjectResponse'] | null; // CreateProjectResponse
Expand Down Expand Up @@ -658,6 +670,7 @@ export interface NexusGenFieldTypes {
getTournamentToRegister: NexusGenRootTypes['Tournament'][]; // [Tournament!]!
getTrendingPosts: NexusGenRootTypes['Post'][]; // [Post!]!
hottestProjects: NexusGenRootTypes['Project'][]; // [Project!]!
isClubInvitationValid: NexusGenEnums['CLUB_INVITATION_STATUS'] | null; // CLUB_INVITATION_STATUS
me: NexusGenRootTypes['User'] | null; // User
newProjects: NexusGenRootTypes['Project'][]; // [Project!]!
officialTags: NexusGenRootTypes['Tag'][]; // [Tag!]!
Expand Down Expand Up @@ -794,6 +807,7 @@ export interface NexusGenFieldTypes {
github: string | null; // String
id: number; // Int!
in_tournament: boolean; // Boolean!
is_in_founders_club: boolean; // Boolean!
jobTitle: string | null; // String
join_date: NexusGenScalars['Date']; // Date!
lightning_address: string | null; // String
Expand Down Expand Up @@ -841,6 +855,7 @@ export interface NexusGenFieldTypes {
github: string | null; // String
id: number; // Int!
in_tournament: boolean; // Boolean!
is_in_founders_club: boolean; // Boolean!
jobTitle: string | null; // String
join_date: NexusGenScalars['Date']; // Date!
lightning_address: string | null; // String
Expand Down Expand Up @@ -974,7 +989,9 @@ export interface NexusGenFieldTypeNames {
title: 'String'
}
Mutation: { // field return type name
acceptOrRejectClubInvitation: 'User'
addProjectToTournament: 'ParticipationInfo'
applyToFoundersClub: 'String'
confirmDonation: 'Donation'
confirmVote: 'Vote'
createProject: 'CreateProjectResponse'
Expand Down Expand Up @@ -1079,6 +1096,7 @@ export interface NexusGenFieldTypeNames {
getTournamentToRegister: 'Tournament'
getTrendingPosts: 'Post'
hottestProjects: 'Project'
isClubInvitationValid: 'CLUB_INVITATION_STATUS'
me: 'User'
newProjects: 'Project'
officialTags: 'Tag'
Expand Down Expand Up @@ -1215,6 +1233,7 @@ export interface NexusGenFieldTypeNames {
github: 'String'
id: 'Int'
in_tournament: 'Boolean'
is_in_founders_club: 'Boolean'
jobTitle: 'String'
join_date: 'Date'
lightning_address: 'String'
Expand Down Expand Up @@ -1262,6 +1281,7 @@ export interface NexusGenFieldTypeNames {
github: 'String'
id: 'Int'
in_tournament: 'Boolean'
is_in_founders_club: 'Boolean'
jobTitle: 'String'
join_date: 'Date'
lightning_address: 'String'
Expand Down Expand Up @@ -1293,9 +1313,15 @@ export interface NexusGenFieldTypeNames {

export interface NexusGenArgTypes {
Mutation: {
acceptOrRejectClubInvitation: { // args
data?: NexusGenInputs['acceptOrRejectClubInvitationInput'] | null; // acceptOrRejectClubInvitationInput
}
addProjectToTournament: { // args
input?: NexusGenInputs['AddProjectToTournamentInput'] | null; // AddProjectToTournamentInput
}
applyToFoundersClub: { // args
data?: NexusGenInputs['applyToFoundersClubInput'] | null; // applyToFoundersClubInput
}
confirmDonation: { // args
payment_request: string; // String!
preimage: string; // String!
Expand Down Expand Up @@ -1427,6 +1453,9 @@ export interface NexusGenArgTypes {
skip?: number | null; // Int
take: number | null; // Int
}
isClubInvitationValid: { // args
invitationCode?: string | null; // String
}
newProjects: { // args
skip?: number | null; // Int
take: number | null; // Int
Expand Down
23 changes: 23 additions & 0 deletions api/functions/graphql/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ interface BaseUser {
github: String
id: Int!
in_tournament(id: Int!): Boolean!
is_in_founders_club: Boolean!
jobTitle: String
join_date: Date!
lightning_address: String
Expand Down Expand Up @@ -75,6 +76,13 @@ type BountyApplication {
workplan: String!
}

enum CLUB_INVITATION_STATUS {
ACCEPTED
DECLINED
INVALID
UNUSED
}

type Capability {
icon: String!
id: Int!
Expand Down Expand Up @@ -194,7 +202,9 @@ input MakerSkillInput {
}

type Mutation {
acceptOrRejectClubInvitation(data: acceptOrRejectClubInvitationInput): User!
addProjectToTournament(input: AddProjectToTournamentInput): ParticipationInfo
applyToFoundersClub(data: applyToFoundersClubInput): String!
confirmDonation(payment_request: String!, preimage: String!): Donation!
confirmVote(payment_request: String!, preimage: String!): Vote!
createProject(input: CreateProjectInput): CreateProjectResponse
Expand Down Expand Up @@ -368,6 +378,7 @@ type Query {
getTournamentToRegister: [Tournament!]!
getTrendingPosts: [Post!]!
hottestProjects(skip: Int = 0, take: Int = 50): [Project!]!
isClubInvitationValid(invitationCode: String): CLUB_INVITATION_STATUS
me: User
newProjects(skip: Int = 0, take: Int = 20): [Project!]!
officialTags: [Tag!]!
Expand Down Expand Up @@ -595,6 +606,7 @@ type User implements BaseUser {
github: String
id: Int!
in_tournament(id: Int!): Boolean!
is_in_founders_club: Boolean!
jobTitle: String
join_date: Date!
lightning_address: String
Expand Down Expand Up @@ -651,4 +663,15 @@ type WalletKey {
is_current: Boolean!
key: String!
name: String!
}

input acceptOrRejectClubInvitationInput {
code: String!
email: String
isAccepted: Boolean!
}

input applyToFoundersClubInput {
project_id: Int!
reason: String!
}
175 changes: 175 additions & 0 deletions api/functions/graphql/types/users.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const {
validateEvent,
} = require("../../../utils/nostr-tools");
const { queueService } = require("../../../services/queue.service");
const { ValidationError, AuthenticationError } = require("apollo-server");

const BaseUser = interfaceType({
name: "BaseUser",
Expand Down Expand Up @@ -76,6 +77,15 @@ const BaseUser = interfaceType({
return prisma.user.findUnique({ where: { id: parent.id } }).skills();
},
});

t.nonNull.boolean("is_in_founders_club", {
resolve: (parent) => {
return prisma.user
.findUnique({ where: { id: parent.id } })
.founders_club_membership()
.then((res) => !!res);
},
});
t.nonNull.list.nonNull.field("tournaments", {
type: Tournament,
resolve: async (parent) => {
Expand Down Expand Up @@ -765,6 +775,168 @@ const updateProfileRoles = extendType({
},
});

const ClubInvitationStatus = enumType({
name: "CLUB_INVITATION_STATUS",
members: {
INVALID: "INVALID",
UNUSED: "UNUSED",
ACCEPTED: "ACCEPTED",
DECLINED: "DECLINED",
},
});

const isClubInvitationValid = extendType({
type: "Query",
definition(t) {
t.field("isClubInvitationValid", {
type: ClubInvitationStatus,
args: {
invitationCode: stringArg(),
},
async resolve(parent, { invitationCode }, context, info) {
if (!invitationCode) return null;
return prisma.foundersClubInvitation
.findUnique({
where: {
code: invitationCode,
},
})
.then((res) =>
res ? res.status : ClubInvitationStatus.value.members["INVALID"]
);
},
});
},
});

const acceptOrRejectClubInvitationInput = inputObjectType({
name: "acceptOrRejectClubInvitationInput",
definition(t) {
t.nonNull.string("code");
t.nonNull.boolean("isAccepted");
t.string("email");
},
});

const acceptOrRejectClubInvitation = extendType({
type: "Mutation",
definition(t) {
t.nonNull.field("acceptOrRejectClubInvitation", {
type: "User",
args: { data: acceptOrRejectClubInvitationInput },
async resolve(_root, { data: { code, isAccepted, email } }, ctx, info) {
const user = await getUserById(ctx.user?.id);

if (!user?.id) throw new AuthenticationError("You have to login");

const invitation = await prisma.foundersClubInvitation.findUnique({
where: {
code,
},
select: {
status: true,
},
});

if (!invitation) throw new ValidationError("Invalid invitation code");
if (invitation.status !== "UNUSED")
throw new ValidationError("Invitation has already been used");

const isAlreadyMember = await prisma.foundersClubMember.findUnique({
where: {
user_id: user.id,
},
});

if (isAlreadyMember)
throw new ValidationError(
"You are already a member of the Founders Club!"
);

await prisma.$transaction([
prisma.foundersClubInvitation.update({
where: {
code,
},
data: {
status: isAccepted ? "ACCEPTED" : "DECLINED",
},
}),
prisma.foundersClubMember.create({
data: {
user_id: user.id,
email,
},
}),
]);

const select = new PrismaSelect(info, {
defaultFields: defaultPrismaSelectFields,
}).valueWithFilter("User");

return prisma.user.findUnique({
where: {
id: user.id,
},
...select,
});
},
});
},
});

const applyToFoundersClubInput = inputObjectType({
name: "applyToFoundersClubInput",
definition(t) {
t.nonNull.int("project_id");
t.nonNull.string("reason");
},
});

const applyToFoundersClub = extendType({
type: "Mutation",
definition(t) {
t.nonNull.field("applyToFoundersClub", {
type: "String",
args: { data: applyToFoundersClubInput },
async resolve(_root, { data: { project_id, reason } }, ctx) {
const user = await getUserById(ctx.user?.id);

// Do some validation
if (!user?.id) throw new AuthenticationError("You have to login");
const project = await prisma.project.findUnique({
where: {
id: project_id,
},
select: {
id: true,
title: true,
hashtag: true,
members: {
select: {
user: {
select: {
id: true,
},
},
},
},
},
});

if (!project) throw new ValidationError("Invalid project");

if (!project.members.find((m) => m.user.id === user.id))
throw new ValidationError("You are not a member of this project");

// sendInvitationEmail(user.email, project.title, project.hashtag, reason);

return "Success";
},
});
},
});

module.exports = {
// Types

Expand All @@ -781,10 +953,13 @@ module.exports = {
getAllMakersRoles,
getAllMakersSkills,
usersByNostrKeys,
isClubInvitationValid,
// Mutations
updateProfileDetails,
updateUserPreferences,
updateProfileRoles,
linkNostrKey,
unlinkNostrKey,
acceptOrRejectClubInvitation,
applyToFoundersClub,
};
Loading