builds working & http -> https redirect

This commit is contained in:
Sendou
2019-06-09 09:11:34 +03:00
parent 4efdb387f9
commit c213d769b3
20 changed files with 179 additions and 83 deletions

View File

@@ -1,13 +1,13 @@
{
"files": {
"main.js": "/static/js/main.4c1af679.chunk.js",
"main.js.map": "/static/js/main.4c1af679.chunk.js.map",
"main.js": "/static/js/main.1141a902.chunk.js",
"main.js.map": "/static/js/main.1141a902.chunk.js.map",
"runtime~main.js": "/static/js/runtime~main.a8a9905a.js",
"runtime~main.js.map": "/static/js/runtime~main.a8a9905a.js.map",
"static/js/2.55ce7a59.chunk.js": "/static/js/2.55ce7a59.chunk.js",
"static/js/2.55ce7a59.chunk.js.map": "/static/js/2.55ce7a59.chunk.js.map",
"static/js/2.15aafc23.chunk.js": "/static/js/2.15aafc23.chunk.js",
"static/js/2.15aafc23.chunk.js.map": "/static/js/2.15aafc23.chunk.js.map",
"index.html": "/index.html",
"precache-manifest.98616b2e0db57de939893fc6d6fa403d.js": "/precache-manifest.98616b2e0db57de939893fc6d6fa403d.js",
"precache-manifest.d801c050748c669d50167e0efc29dc61.js": "/precache-manifest.d801c050748c669d50167e0efc29dc61.js",
"service-worker.js": "/service-worker.js",
"static/media/ancho-v_games.png": "/static/media/ancho-v_games.07d5d0f6.png",
"static/media/arowana_mall.png": "/static/media/arowana_mall.26cf0823.png",

View File

@@ -1 +1 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="shortcut icon" href="/favicon.ico"/><link rel="stylesheet" href="//cdn.jsdelivr.net/npm/semantic-ui@2.4.2/dist/semantic.min.css"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><link rel="manifest" href="/manifest.json"/><title>sendou.ink</title></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div><script>!function(l){function e(e){for(var r,t,n=e[0],o=e[1],u=e[2],f=0,i=[];f<n.length;f++)t=n[f],p[t]&&i.push(p[t][0]),p[t]=0;for(r in o)Object.prototype.hasOwnProperty.call(o,r)&&(l[r]=o[r]);for(s&&s(e);i.length;)i.shift()();return c.push.apply(c,u||[]),a()}function a(){for(var e,r=0;r<c.length;r++){for(var t=c[r],n=!0,o=1;o<t.length;o++){var u=t[o];0!==p[u]&&(n=!1)}n&&(c.splice(r--,1),e=f(f.s=t[0]))}return e}var t={},p={1:0},c=[];function f(e){if(t[e])return t[e].exports;var r=t[e]={i:e,l:!1,exports:{}};return l[e].call(r.exports,r,r.exports,f),r.l=!0,r.exports}f.m=l,f.c=t,f.d=function(e,r,t){f.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},f.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},f.t=function(r,e){if(1&e&&(r=f(r)),8&e)return r;if(4&e&&"object"==typeof r&&r&&r.__esModule)return r;var t=Object.create(null);if(f.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:r}),2&e&&"string"!=typeof r)for(var n in r)f.d(t,n,function(e){return r[e]}.bind(null,n));return t},f.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return f.d(r,"a",r),r},f.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},f.p="/";var r=window.webpackJsonp=window.webpackJsonp||[],n=r.push.bind(r);r.push=e,r=r.slice();for(var o=0;o<r.length;o++)e(r[o]);var s=n;a()}([])</script><script src="/static/js/2.55ce7a59.chunk.js"></script><script src="/static/js/main.4c1af679.chunk.js"></script></body></html>
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="shortcut icon" href="/favicon.ico"/><link rel="stylesheet" href="//cdn.jsdelivr.net/npm/semantic-ui@2.4.2/dist/semantic.min.css"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><link rel="manifest" href="/manifest.json"/><title>sendou.ink</title></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div><script>!function(l){function e(e){for(var r,t,n=e[0],o=e[1],u=e[2],f=0,i=[];f<n.length;f++)t=n[f],p[t]&&i.push(p[t][0]),p[t]=0;for(r in o)Object.prototype.hasOwnProperty.call(o,r)&&(l[r]=o[r]);for(s&&s(e);i.length;)i.shift()();return c.push.apply(c,u||[]),a()}function a(){for(var e,r=0;r<c.length;r++){for(var t=c[r],n=!0,o=1;o<t.length;o++){var u=t[o];0!==p[u]&&(n=!1)}n&&(c.splice(r--,1),e=f(f.s=t[0]))}return e}var t={},p={1:0},c=[];function f(e){if(t[e])return t[e].exports;var r=t[e]={i:e,l:!1,exports:{}};return l[e].call(r.exports,r,r.exports,f),r.l=!0,r.exports}f.m=l,f.c=t,f.d=function(e,r,t){f.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},f.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},f.t=function(r,e){if(1&e&&(r=f(r)),8&e)return r;if(4&e&&"object"==typeof r&&r&&r.__esModule)return r;var t=Object.create(null);if(f.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:r}),2&e&&"string"!=typeof r)for(var n in r)f.d(t,n,function(e){return r[e]}.bind(null,n));return t},f.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return f.d(r,"a",r),r},f.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},f.p="/";var r=window.webpackJsonp=window.webpackJsonp||[],n=r.push.bind(r);r.push=e,r=r.slice();for(var o=0;o<r.length;o++)e(r[o]);var s=n;a()}([])</script><script src="/static/js/2.15aafc23.chunk.js"></script><script src="/static/js/main.1141a902.chunk.js"></script></body></html>

View File

@@ -1,15 +1,15 @@
self.__precacheManifest = (self.__precacheManifest || []).concat([
{
"revision": "263c0c76db10fbf668b70280425ae71c",
"revision": "0d2f115e143635d48d2d2ec0524efa03",
"url": "/index.html"
},
{
"revision": "ba394d26abe9b15f2753",
"url": "/static/js/2.55ce7a59.chunk.js"
"revision": "88368028096f05972b1b",
"url": "/static/js/2.15aafc23.chunk.js"
},
{
"revision": "5813558134488f70b3eb",
"url": "/static/js/main.4c1af679.chunk.js"
"revision": "0e0627ec3efbe9a79bd4",
"url": "/static/js/main.1141a902.chunk.js"
},
{
"revision": "42ac5946195a7306e2a5",

View File

@@ -14,7 +14,7 @@
importScripts("https://storage.googleapis.com/workbox-cdn/releases/4.3.0/workbox-sw.js");
importScripts(
"/precache-manifest.98616b2e0db57de939893fc6d6fa403d.js"
"/precache-manifest.d801c050748c669d50167e0efc29dc61.js"
);
self.addEventListener('message', (event) => {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -147,8 +147,8 @@ const typeDefs = gql`
discriminator: String!
avatar: String
discord_id: String!
twitch_name: String!
twitter_name: String!
twitch_name: String
twitter_name: String
custom_url: String
}
@@ -217,6 +217,7 @@ const typeDefs = gql`
user: User
searchForUser(discord_id: String!): User
searchForBuilds(discord_id: String!): [Build]!
deleteBuild(id: ID!): Boolean
}
type Mutation {
@@ -235,6 +236,9 @@ const typeDefs = gql`
clothing: [Ability!]!
shoes: [Ability!]!
): Build
deleteBuild(
id: ID!
): Boolean
}
`
@@ -435,7 +439,10 @@ const resolvers = {
playerInfo: async (root, args) => {
let searchCriteria = {}
if (args.uid) searchCriteria = { "unique_id": args.uid }
if (args.twitter) searchCriteria = { "twitter": args.twitter.toLowerCase()}
else if (args.twitter) searchCriteria = { "twitter": args.twitter.toLowerCase()}
else throw new UserInputError('no id or twitter provided', {
invalidArgs: args,
})
const player = await Player
.findOne(searchCriteria)
.catch(e => {
@@ -575,13 +582,29 @@ const resolvers = {
})
const build = new Build({ ...args, discord_id: ctx.user.discord_id })
console.log('build', build)
return build.save()
.catch(e => {
throw new UserInputError, {
invalidArgs: args,
}
})
},
deleteBuild: async (root, args, ctx) => {
if (!ctx.user) throw new AuthenticationError('not authenticated')
const build = await Build.findOne({ _id: args.id})
if (ctx.user.discord_id !== build.discord_id) throw new AuthenticationError('no privileges')
try {
await Build.findByIdAndDelete({ _id: args.id })
} catch (error) {
throw new UserInputError(error.message, {
invalidArgs: args,
})
}
return true
}
}
}
@@ -599,6 +622,18 @@ const app = express()
app.use(cors())
app.use(express.static('build'))
//https://stackoverflow.com/questions/8605720/how-to-force-ssl-https-in-express-js/31144924#31144924
function requireHTTPS(req, res, next) {
// The 'x-forwarded-proto' check is for Heroku
if (!req.secure && req.get('x-forwarded-proto') !== 'https' && process.env.NODE_ENV !== "development") {
return res.redirect('https://' + req.get('host') + req.url)
}
next()
}
app.use(requireHTTPS)
app.use(session({
secret: process.env.SESSION_SECRET,
resave: false,

View File

@@ -1,5 +1,5 @@
import React, { useState } from 'react'
import { Menu, Segment, Button, Icon, Image, Popup } from 'semantic-ui-react'
import { Menu, Segment, Button, Image, Dropdown } from 'semantic-ui-react'
import { useQuery } from 'react-apollo-hooks'
import { userLean } from '../../graphql/queries/userLean'
import { withRouter } from 'react-router-dom'
@@ -31,23 +31,25 @@ const MainMenu = withRouter(({ history, menuSelection, setMenuSelection }) => {
const user = data.user
const userMenuOptions = [
{ key: 'user', text: 'User Page', icon: 'user', onClick: () => history.push(`/u/${user.discord_id}`) },
//{ key: 'settings', text: 'Settings', icon: 'settings', onClick: () => history.push('/u/settings') },
{ key: 'sign-out', text: 'Sign Out', icon: 'sign out', onClick: () => window.location.assign('/logout') },
]
const userMenuTrigger = (
<span>
<span style={{"paddingRight": "5px"}}>{user.username}</span>
<Image src={user.avatar ?
`https://cdn.discordapp.com/avatars/${user.discord_id}/${user.avatar}.png` :
'https://cdn.discordapp.com/avatars/455039198672453645/088ae3838cc3b08b73f79aab0fefec2f.png'} avatar /> {/* If User has no avatar defaults to N-Zap user's avatar*/}
</span>
)
return (
<div>
<Menu.Item>
<span style={{"paddingRight": "3px"}}>{user.username}</span>
<Image src={`https://cdn.discordapp.com/avatars/${user.discord_id}/${user.avatar}.png`} avatar />
<span style={{"paddingLeft": "10px"}}>
<Popup
position='bottom center'
flowing
hoverable
trigger={<Icon name='log out' circular />}
>
<a href='/logout'>
<Button secondary>Log Out</Button>
</a>
</Popup>
</span>
<Dropdown item icon={null} options={userMenuOptions} trigger={userMenuTrigger}/>
</Menu.Item>
</div>
)

View File

@@ -1,6 +1,6 @@
/* eslint-disable jsx-a11y/alt-text */
import React from 'react'
import { Header, Segment, List, Grid } from 'semantic-ui-react'
import { Header, Segment, List, Grid, Button, Popup } from 'semantic-ui-react'
import weaponDict from '../../utils/english_internal.json'
import BDU from '../img/abilityIcons/BDU.png'
@@ -30,7 +30,7 @@ import TI from '../img/abilityIcons/TI.png'
import OS from '../img/abilityIcons/OS.png'
import EMPTY from '../img/abilityIcons/EMPTY.png'
const Build = ({ build, existingAbilities, setAbilities }) => {
const Build = ({ build, existingAbilities, setAbilities, removeBuildFunction }) => {
const lineStyle = {
"display": "inline-block",
@@ -114,6 +114,8 @@ const Build = ({ build, existingAbilities, setAbilities }) => {
</span>
)
}
const buildTitle = !build.title || build.title === "" ? `${build.weapon} Build` : build.title
return (
<div>
@@ -122,30 +124,39 @@ const Build = ({ build, existingAbilities, setAbilities }) => {
<Header size='huge' inverted>
<img
src={imageSource}
/> {!build.title || build.title === "" ? `${build.weapon} Build` : build.title}
/> {buildTitle}
</Header>
</div>
<div style={{"paddingLeft": "13px"}}>
<Grid stackable columns={4}>
<Grid.Column>
<div>
<div style={{'float': 'none', 'whiteSpace': 'nowrap'}}>
{build.headgear.map(abilityMap, {gearIndex: 0})}
</div>
<div>
<div style={{'float': 'none', 'whiteSpace': 'nowrap'}}>
{build.clothing.map(abilityMap, {gearIndex: 1})}
</div>
<div>
<div style={{'float': 'none', 'whiteSpace': 'nowrap'}}>
{build.shoes.map(abilityMap, {gearIndex: 2})}
</div>
</Grid.Column>
<Grid.Column></Grid.Column>
<Grid.Column>
<List>
{arrayOfSortedAbilityPoints().map(a => <List.Item key={a.fullName}>{a.ap} {a.fullName}</List.Item>)}
</List>
</Grid.Column>
<Grid.Column></Grid.Column>
<Grid.Column verticalAlign='bottom'>
<i>Added {new Date(parseInt(build.createdAt)).toLocaleString('en-GB')}</i>
{setAbilities ? null : <i>Added {new Date(parseInt(build.createdAt)).toLocaleString('en-GB')}</i>}
{removeBuildFunction ?
<span style={{'paddingLeft': '10px'}}>
<Popup
trigger={<Button circular size='mini' icon='trash' />}
hoverable
position='bottom right'
><Button negative onClick={ () => removeBuildFunction(build) }>{'Delete'}</Button></Popup>
</span>
: null}
</Grid.Column>
</Grid>
</div>

View File

@@ -30,8 +30,8 @@ const BuildForm = ({ addBuild, setShowForm, setSuccessMsg }) => {
setWeaponForm('')
setTitle('')
setAbilities([["", "", "", ""], ["", "", "", ""], ["", "", "", ""]])
setSuccessMsg(true)
setTimeout(() => { setSuccessMsg(false) }, 5000)
setSuccessMsg('New build succesfully added!')
setTimeout(() => { setSuccessMsg(null) }, 10000)
setShowForm(false)
}

View File

@@ -1,17 +1,19 @@
import React, { useState } from 'react'
import { useQuery, useMutation } from 'react-apollo-hooks'
import { Button, Loader, Message } from 'semantic-ui-react'
import { Button, Loader, Message, Popup } from 'semantic-ui-react'
import { addBuild } from '../../graphql/mutations/addBuild'
import { searchForBuilds } from '../../graphql/queries/searchForBuilds'
import { deleteBuild } from '../../graphql/mutations/deleteBuild'
import BuildForm from './BuildForm'
import Build from './Build'
const BuildTab = ({ user, userViewed }) => {
const { data, error, loading } = useQuery(searchForBuilds, {variables: { discord_id: userViewed.discord_id }})
const [ errorMsg, setErrorMsg ] = useState(null)
const [ successMsg, setSuccessMsg ] = useState(false)
const [ successMsg, setSuccessMsg ] = useState(null)
const [ showForm, setShowForm ] = useState(false)
const [ deleteConfirm, setDeleteConfirm ] = useState(false)
const handleError = (error) => {
errorMsg(error.graphQLErrors[0].message)
@@ -25,6 +27,23 @@ const BuildTab = ({ user, userViewed }) => {
refetchQueries: [{ query: searchForBuilds, variables: { discord_id: userViewed.discord_id }} ]
})
const deleteBuildMutation = useMutation(deleteBuild, {
onError: handleError,
refetchQueries: [{ query: searchForBuilds, variables: { discord_id: userViewed.discord_id }} ]
})
const deleteBuildById = async ({ id, title, weapon }) => {
await deleteBuildMutation({
variables: { id }
})
const buildTitle = title ? title : `${weapon} build`
setDeleteConfirm(false)
setSuccessMsg(`Succesfully deleted ${buildTitle}`)
setTimeout(() => { setSuccessMsg(null) }, 10000)
}
if (loading) {
return <div style={{"paddingTop": "25px", "paddingBottom": "20000px"}} ><Loader active inline='centered' /></div>
}
@@ -39,19 +58,22 @@ const BuildTab = ({ user, userViewed }) => {
<div style={{"paddingBottom": "10px"}}>
<Message
success
content='Adding the new build was succesful'
content={successMsg}
/>
</div> : null}
<div>
{data.searchForBuilds.length >= 20 && user && user.discord_id === userViewed.discord_id ? 'Looks like you have 20 buids. Insane flex. Delete a build before adding a new one.' : null}
{!user || user.discord_id !== userViewed.discord_id || data.searchForBuilds.length >= 20 ?
null :
<Button circular size='tiny' icon={showForm ? 'minus' : 'plus'} onClick={ () => setShowForm(!showForm) }
/>}
<Button circular size='tiny' icon={showForm ? 'minus' : 'plus'} onClick={ () => setShowForm(!showForm) } />
}
{showForm ? <div style={{"paddingTop": "10px"}}><BuildForm addBuild={addBuildMutation} setShowForm={setShowForm} setSuccessMsg={setSuccessMsg}/></div> : null}
</div>
<div style={{"paddingTop": "10px"}}>
{data.searchForBuilds.map(b => <div key={b.id} style={{"paddingTop": "10px"}}><Build build={b}/></div>)}
{data.searchForBuilds.map(b =>
<div key={b.id} style={{"paddingTop": "10px"}}>
<Build build={b} removeBuildFunction={deleteBuildById} />
</div>)}
{data.searchForBuilds.length === 0 ? 'So far this user has no builds!' : null}
</div>
</div>

View File

@@ -63,7 +63,7 @@ const UserPage = ({ userIdOrName }) => {
<Grid stackable>
<Grid.Column width={3}>
<Image
src={`https://cdn.discordapp.com/avatars/${userData.discord_id}/${userData.avatar}.png`}
src={userData.avatar ? `https://cdn.discordapp.com/avatars/${userData.discord_id}/${userData.avatar}.png` : 'https://cdn.discordapp.com/avatars/455039198672453645/088ae3838cc3b08b73f79aab0fefec2f.png'}
rounded
/>
</Grid.Column>
@@ -75,7 +75,6 @@ const UserPage = ({ userIdOrName }) => {
</List.Item>
{twitchDiscord()}
{/* "Member of Team Olive // Captain of Team Olive" */}
{/* Weapons?" */}
</List>
</Grid.Column>
</Grid>

View File

@@ -1,6 +1,6 @@
import React, { useEffect, useState } from 'react'
import { useQuery } from 'react-apollo-hooks'
import { Loader, Header, Image, Icon } from 'semantic-ui-react'
import { Loader, Header, Image, Icon, List } from 'semantic-ui-react'
import { playerInfo } from '../../graphql/queries/playerInfo'
import TopPlacementTable from './TopPlacementsTable'
@@ -21,42 +21,62 @@ const InfoPlayer = ({ uid, setMenuSelection, twitter }) => {
if (setMenuSelection) { //if we're on /xsearch
setMenuSelection('search')
document.title = `${data.playerInfo.player.alias ? data.playerInfo.player.alias : data.playerInfo.player.name} Top 500 X Rank - sendou.ink`
}
const placements = data.playerInfo.placements
if (data.playerInfo) {
const placements = data.playerInfo.placements
//reducing placements to top sz, tc etc. rank and x power
const tops = ["", "szTop", "tcTop", "rmTop", "cbTop"]
const exs = ["", "szX", "tcX", "rmX", "cbX"]
setTop(placements.reduce((acc, cur) => {
const topKey = tops[cur.mode]
const xKey = exs[cur.mode]
if (!acc[xKey]) {
acc[xKey] = cur
acc[topKey] = cur
return acc
}
if (acc[xKey].x_power < cur.x_power) {
acc[xKey] = cur
}
if (acc[topKey].rank > cur.rank) {
acc[topKey] = cur
}
//reducing placements to top sz, tc etc. rank and x power
const tops = ["", "szTop", "tcTop", "rmTop", "cbTop"]
const exs = ["", "szX", "tcX", "rmX", "cbX"]
setTop(placements.reduce((acc, cur) => {
const topKey = tops[cur.mode]
const xKey = exs[cur.mode]
if (!acc[xKey]) {
acc[xKey] = cur
acc[topKey] = cur
return acc
}
if (acc[xKey].x_power < cur.x_power) {
acc[xKey] = cur
}
if (acc[topKey].rank > cur.rank) {
acc[topKey] = cur
}
return acc
}, {
szX: null, szTop: null,
tcX: null, tcTop: null,
rmX: null, rmTop: null,
cbX: null, cbTop: null
}))
}, {
szX: null, szTop: null,
tcX: null, tcTop: null,
rmX: null, rmTop: null,
cbX: null, cbTop: null
}))
}
}, [data, loading, setMenuSelection])
if (loading || top.length === 0) {
const addXRankHelp = (
<div>
This user has no X Rank profile set up. If you are the owner of this user page here is how you can set it up:
<div style={{'paddingTop': "5px"}}>
<List ordered>
<List.Item>Finish an X rank season in the Top 500 in at least one mode.</List.Item>
<List.Item>Send your Twitter handle to Sendou via DM.</List.Item>
<List.Item>Add your Twitter account to your profile on Discord, verify it and make sure it's set to appear publicly.</List.Item>
<List.Item>Log in to sendou.ink</List.Item>
</List>
</div>
</div>
)
if (!uid && !twitter) return addXRankHelp
if (loading) {
return <div style={{"paddingTop": "25px", "paddingBottom": "20000px"}} ><Loader active inline='centered' /></div>
}
if (error) {
if (error.message === 'GraphQL error: player not found') {
return addXRankHelp
}
return <div style={{"color": "red"}}>{error.message}</div>
}
const playerData = data.playerInfo.player

View File

@@ -0,0 +1,7 @@
import { gql } from 'apollo-boost'
export const deleteBuild = gql`
mutation deleteBuild ($id: ID!) {
deleteBuild(id: $id)
}
`