mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-05-14 15:00:54 -05:00
459 lines
12 KiB
JavaScript
459 lines
12 KiB
JavaScript
const {
|
|
UserInputError,
|
|
AuthenticationError,
|
|
gql,
|
|
} = require("apollo-server-express")
|
|
const shuffle = require("../utils/shuffleArray")
|
|
const User = require("../mongoose-models/user")
|
|
const Suggested = require("../mongoose-models/suggested")
|
|
const Summary = require("../mongoose-models/summary")
|
|
const VotedPerson = require("../mongoose-models/votedperson")
|
|
const State = require("../mongoose-models/state")
|
|
|
|
const typeDef = gql`
|
|
extend type Query {
|
|
plusInfo: PlusGeneralInfo
|
|
hasAccess(discord_id: String!, server: String!): Boolean!
|
|
suggestions: [Suggested!]!
|
|
usersForVoting: UsersForVoting!
|
|
}
|
|
|
|
extend type Mutation {
|
|
addSuggestion(
|
|
discord_id: String!
|
|
server: String!
|
|
region: String!
|
|
description: String!
|
|
): Boolean!
|
|
addVotes(votes: [VoteInput!]!): Boolean!
|
|
startVoting(ends: String!): Boolean!
|
|
endVoting: Boolean!
|
|
}
|
|
|
|
"+1 or +2 LFG server on Discord"
|
|
enum PlusServer {
|
|
ONE
|
|
TWO
|
|
}
|
|
|
|
"Region used for voting"
|
|
enum PlusRegion {
|
|
EU
|
|
NA
|
|
}
|
|
|
|
type PlusGeneralInfo {
|
|
plus_one_invite_link: String
|
|
plus_two_invite_link: String!
|
|
voting_ends: String
|
|
voter_count: Int!
|
|
eligible_voters: Int!
|
|
}
|
|
|
|
type Suggested {
|
|
discord_id: String!
|
|
discord_user: User!
|
|
suggester_discord_id: String!
|
|
suggester_discord_user: User!
|
|
plus_region: PlusRegion!
|
|
plus_server: PlusServer!
|
|
description: String!
|
|
createdAt: String!
|
|
}
|
|
|
|
"Status with +1 and +2 related things"
|
|
type PlusStatus {
|
|
membership_status: PlusServer
|
|
vouch_status: PlusServer
|
|
voucher_discord_id: String
|
|
plus_region: PlusRegion
|
|
can_vouch: Boolean
|
|
last_vouched: String
|
|
}
|
|
|
|
extend type User {
|
|
plus: PlusStatus
|
|
}
|
|
|
|
input VoteInput {
|
|
discord_id: String!
|
|
score: Int!
|
|
}
|
|
|
|
type VotedPerson {
|
|
discord_id: String!
|
|
voter_discord_id: String!
|
|
plus_server: String!
|
|
month: Int!
|
|
year: Int!
|
|
"Voting result -2 to +2 (-1 to +1 cross-region)"
|
|
score: Int!
|
|
}
|
|
|
|
"Voting result of a player"
|
|
type Summary {
|
|
discord_id: String!
|
|
suggester_discord_id: String
|
|
voucher_discord_id: String
|
|
plus_server: PlusServer!
|
|
month: Int!
|
|
year: Int!
|
|
"Average of all scores of the voters for the month 0% to 100%"
|
|
score: Float!
|
|
new: Boolean!
|
|
}
|
|
|
|
type UsersForVoting {
|
|
users: [User!]!
|
|
suggested: [Suggested!]!
|
|
votes: [VotedPerson!]!
|
|
}
|
|
`
|
|
|
|
const validateVotes = (votes, users, suggested, user) => {
|
|
const region = user.plus.plus_region
|
|
|
|
votes.forEach(vote => {
|
|
const { discord_id, score } = vote
|
|
|
|
let votedUser = users.find(
|
|
userInServer => userInServer.discord_id === discord_id
|
|
)
|
|
|
|
if (!votedUser) {
|
|
votedUser = suggested.find(
|
|
suggested => suggested.discord_user.discord_id === discord_id
|
|
)
|
|
if (!votedUser)
|
|
throw new UserInputError(
|
|
`Invalid user voted on with the id ${discord_id}`
|
|
)
|
|
|
|
const plus_region_of_suggested = votedUser.plus_region
|
|
votedUser = votedUser.discord_user
|
|
votedUser.plus = {}
|
|
votedUser.plus.plus_region = plus_region_of_suggested
|
|
}
|
|
|
|
if (score !== -2 && score !== -1 && score !== 1 && score !== 2)
|
|
throw new Error(`Invalid score provided: ${score}`)
|
|
|
|
if ((score === -2 || score === 2) && region !== votedUser.plus.plus_region)
|
|
throw new Error("Score of -2 or 2 given cross region")
|
|
})
|
|
}
|
|
|
|
const resolvers = {
|
|
Query: {
|
|
hasAccess: async (root, args) => {
|
|
const user = await User.findOne({ discord_id: args.discord_id }).catch(
|
|
e => {
|
|
throw (new Error(),
|
|
{
|
|
invalidArgs: args,
|
|
})
|
|
}
|
|
)
|
|
|
|
if (!user || !user.plus) return false
|
|
|
|
const { membership_status, vouch_status } = user.plus
|
|
let membership_code =
|
|
membership_status === "TWO" || vouch_status === "TWO" ? "TWO" : null
|
|
membership_code =
|
|
membership_status === "ONE" || vouch_status === "ONE"
|
|
? "ONE"
|
|
: membership_code
|
|
|
|
if (membership_code === "ONE") return true
|
|
if (membership_code === "TWO" && args.server === "TWO") return true
|
|
|
|
return false
|
|
},
|
|
plusInfo: async (root, args, ctx) => {
|
|
if (!ctx.user) return null
|
|
if (
|
|
!ctx.user.plus ||
|
|
(!ctx.user.plus.membership_status && !ctx.user.plus.vouch_status)
|
|
) {
|
|
return null
|
|
}
|
|
|
|
const plus_server =
|
|
ctx.user.plus.membership_status === "ONE" ||
|
|
ctx.user.plus.vouch_status === "ONE"
|
|
? "ONE"
|
|
: "TWO"
|
|
|
|
const state = await State.findOne({})
|
|
|
|
const date = new Date()
|
|
const year = date.getFullYear()
|
|
const month = date.getMonth() + 1
|
|
const votedPeople = await VotedPerson.find({
|
|
month,
|
|
year,
|
|
plus_server: ctx.user.plus.membership_status,
|
|
})
|
|
|
|
const votedIds = new Set()
|
|
|
|
votedPeople.forEach(vote => {
|
|
votedIds.add(vote.voter_discord_id)
|
|
})
|
|
|
|
const eligible_voters = await User.countDocuments({
|
|
"plus.membership_status": ctx.user.plus.membership_status,
|
|
})
|
|
|
|
return {
|
|
plus_one_invite_link:
|
|
plus_server === "ONE" ? process.env.PLUS_ONE_LINK : null,
|
|
plus_two_invite_link: process.env.PLUS_TWO_LINK,
|
|
voting_ends: state.voting_ends,
|
|
voter_count: votedIds.size,
|
|
eligible_voters,
|
|
}
|
|
},
|
|
usersForVoting: async (root, args, ctx) => {
|
|
if (!ctx.user) throw new UserInputError("Not logged in")
|
|
if (!ctx.user.plus || !ctx.user.plus.membership_status)
|
|
throw new UserInputError("Not plus server member")
|
|
const plus_server = ctx.user.plus.membership_status
|
|
|
|
const users = await User.find({
|
|
$or: [
|
|
{
|
|
"plus.membership_status": plus_server,
|
|
},
|
|
|
|
{ "plus.vouch_status": plus_server },
|
|
],
|
|
}).catch(e => {
|
|
throw (new Error(),
|
|
{
|
|
error: e,
|
|
})
|
|
})
|
|
|
|
const suggested = await Suggested.find({ plus_server })
|
|
.populate("discord_user")
|
|
.populate("suggester_discord_user")
|
|
.catch(e => {
|
|
throw (new Error(),
|
|
{
|
|
error: e,
|
|
})
|
|
})
|
|
|
|
const date = new Date()
|
|
const year = date.getFullYear()
|
|
const month = date.getMonth() + 1
|
|
const votes = await VotedPerson.find({
|
|
plus_server,
|
|
month,
|
|
year,
|
|
voter_discord_id: ctx.user.discord_id,
|
|
})
|
|
|
|
shuffle(users)
|
|
shuffle(suggested)
|
|
|
|
return { users, suggested, votes }
|
|
},
|
|
suggestions: (root, args, ctx) => {
|
|
if (!ctx.user || !ctx.user.plus) return null
|
|
const searchCriteria =
|
|
ctx.user.plus.membership_status === "ONE" ? {} : { plus_server: "TWO" }
|
|
return Suggested.find(searchCriteria)
|
|
.populate("discord_user")
|
|
.populate("suggester_discord_user")
|
|
.sort({ createdAt: "desc" })
|
|
.catch(e => {
|
|
throw new UserInputError(e.message, {
|
|
invalidArgs: args,
|
|
})
|
|
})
|
|
},
|
|
},
|
|
Mutation: {
|
|
addSuggestion: async (root, args, ctx) => {
|
|
if (!ctx.user) throw new AuthenticationError("Not logged in.")
|
|
if (
|
|
!ctx.user.plus ||
|
|
(!ctx.user.plus.membership_status && !ctx.user.plus.vouch_status)
|
|
) {
|
|
throw new AuthenticationError("Not plus member.")
|
|
}
|
|
|
|
const user = await User.findOne({ discord_id: args.discord_id }).catch(
|
|
e => {
|
|
throw (new Error(),
|
|
{
|
|
invalidArgs: args,
|
|
error: e,
|
|
})
|
|
}
|
|
)
|
|
|
|
const suggestion = await Suggested.findOne({
|
|
suggester_discord_id: ctx.user.discord_id,
|
|
}).catch(e => {
|
|
throw (new Error(),
|
|
{
|
|
invalidArgs: args,
|
|
error: e,
|
|
})
|
|
})
|
|
|
|
if (suggestion) throw new UserInputError("Already suggested this month.")
|
|
|
|
const duplicateSuggestion = await Suggested.findOne({
|
|
discord_id: args.discord_id,
|
|
plus_server: args.server,
|
|
}).catch(e => {
|
|
throw (new Error(),
|
|
{
|
|
invalidArgs: args,
|
|
error: e,
|
|
})
|
|
})
|
|
|
|
if (duplicateSuggestion)
|
|
throw new UserInputError(
|
|
"This user has already been suggested this month."
|
|
)
|
|
|
|
if (!user)
|
|
throw new UserInputError("Suggested user not sendou.ink member.")
|
|
|
|
if (args.server !== "ONE" && args.server !== "TWO")
|
|
throw new UserInputError("Server arg has to be 'ONE' or 'TWO'.")
|
|
|
|
if (user.plus.membership_status === args.server)
|
|
throw new UserInputError(
|
|
"Suggested user is already a member of the server."
|
|
)
|
|
|
|
if (ctx.user.plus.membership_status !== "ONE" && args.server === "ONE")
|
|
throw new UserInputError("Can't suggest to +1 without being +1 member.")
|
|
|
|
if (args.region !== "EU" && args.region !== "NA")
|
|
throw new UserInputError("Region arg has to be 'NA' or 'EU'.")
|
|
|
|
if (args.description.length > 1000)
|
|
throw new UserInputError("Description has to be below 1000 characters.")
|
|
|
|
const newSuggestion = new Suggested({
|
|
discord_id: args.discord_id,
|
|
suggester_discord_id: ctx.user.discord_id,
|
|
plus_region: args.region,
|
|
plus_server: args.server,
|
|
description: args.description,
|
|
})
|
|
|
|
await newSuggestion.save().catch(e => {
|
|
throw (new Error(),
|
|
{
|
|
invalidArgs: args,
|
|
})
|
|
})
|
|
|
|
return true
|
|
},
|
|
addVotes: async (root, args, ctx) => {
|
|
if (!ctx.user) throw new AuthenticationError("Not logged in.")
|
|
if (
|
|
!ctx.user.plus ||
|
|
(!ctx.user.plus.membership_status && !ctx.user.plus.vouch_status)
|
|
) {
|
|
throw new AuthenticationError("Not plus member.")
|
|
}
|
|
|
|
const state = await State.findOne({})
|
|
|
|
const date = new Date()
|
|
if (!state.voting_ends || state.voting_ends < date.getTime())
|
|
throw new Error("Voting now open right now")
|
|
|
|
const votedUsers = {}
|
|
|
|
args.votes.forEach(vote => {
|
|
if (votedUsers[vote.discord_id])
|
|
throw new UserInputVote(
|
|
`Duplicate vote with the id ${vote.discord_id}`
|
|
)
|
|
votedUsers[vote.discord_id] = true
|
|
})
|
|
|
|
const plus_server = ctx.user.plus.membership_status
|
|
|
|
const users = await User.find({
|
|
$or: [
|
|
{
|
|
"plus.membership_status": plus_server,
|
|
},
|
|
|
|
{ "plus.vouch_status": plus_server },
|
|
],
|
|
})
|
|
|
|
const suggested = await Suggested.find({ plus_server })
|
|
.populate("discord_user")
|
|
.populate("suggester_discord_user")
|
|
|
|
if (users.length + suggested.length !== args.votes.length)
|
|
throw new UserInputError("Invalid number of votes provided")
|
|
|
|
validateVotes(args.votes, users, suggested, ctx.user)
|
|
|
|
const year = date.getFullYear()
|
|
const month = date.getMonth() + 1
|
|
await VotedPerson.deleteMany({
|
|
voter_discord_id: ctx.user.discord_id,
|
|
month,
|
|
year,
|
|
})
|
|
|
|
const toInsert = args.votes.map(vote => ({
|
|
discord_id: vote.discord_id,
|
|
voter_discord_id: ctx.user.discord_id,
|
|
month,
|
|
year,
|
|
plus_server,
|
|
score: vote.score,
|
|
}))
|
|
await VotedPerson.insertMany(toInsert)
|
|
|
|
return true
|
|
},
|
|
startVoting: async (root, args, ctx) => {
|
|
if (!ctx.user) throw new AuthenticationError("Not logged in.")
|
|
if (ctx.user.discord_id !== process.env.ADMIN_ID)
|
|
throw new AuthenticationError("Not admin.")
|
|
|
|
await State.findOneAndUpdate({}, { voting_ends: args.ends })
|
|
|
|
return true
|
|
},
|
|
endVoting: async (root, args, ctx) => {
|
|
if (!ctx.user) throw new AuthenticationError("Not logged in.")
|
|
if (ctx.user.discord_id !== process.env.ADMIN_ID)
|
|
throw new AuthenticationError("Not admin.")
|
|
|
|
const date = new Date()
|
|
const year = date.getFullYear()
|
|
const month = date.getMonth() + 1
|
|
const votes = await VotedPerson.find({
|
|
month,
|
|
year,
|
|
})
|
|
},
|
|
},
|
|
}
|
|
|
|
module.exports = {
|
|
Plus: typeDef,
|
|
plusResolvers: resolvers,
|
|
}
|