mirror of
https://github.com/roleypoly/roleypoly-v1.git
synced 2025-04-25 04:09:12 +00:00
lerna: starting point
This commit is contained in:
parent
f7e2898633
commit
cb0b1d2410
17 changed files with 730 additions and 1067 deletions
|
@ -61,6 +61,7 @@ export default (R: Router, $: AppContext) => {
|
|||
const { r } = ctx.query
|
||||
// check if already authed
|
||||
if (await $.auth.isLoggedIn(ctx, { refresh: true })) {
|
||||
log.debug('already authed.', ctx.session)
|
||||
return ctx.redirect(r || '/')
|
||||
}
|
||||
|
||||
|
@ -79,6 +80,7 @@ export default (R: Router, $: AppContext) => {
|
|||
const { oauthRedirect: r } = ctx.session
|
||||
delete ctx.session.oauthRedirect
|
||||
if (await $.auth.isLoggedIn(ctx)) {
|
||||
log.debug('user was logged in')
|
||||
return ctx.redirect(r || '/')
|
||||
}
|
||||
|
||||
|
@ -108,6 +110,7 @@ export default (R: Router, $: AppContext) => {
|
|||
const tokens = await $.discord.initializeOAuth(code)
|
||||
const user = await $.discord.getUserFromToken(tokens.access_token)
|
||||
$.auth.injectSessionFromOAuth(ctx, tokens, user.id)
|
||||
log.debug('user logged in', { tokens, user, s: ctx.session })
|
||||
return ctx.redirect(r || '/')
|
||||
} catch (e) {
|
||||
log.error('token and auth fetch failure', e)
|
||||
|
|
|
@ -100,15 +100,6 @@ export default (R: Router, $: AppContext) => {
|
|||
ctx.body = { ok: true }
|
||||
})
|
||||
|
||||
R.get('/api/admin/servers', async (ctx: Context) => {
|
||||
const { userId } = (ctx.session: { userId: string })
|
||||
if (!$.discord.isRoot(userId)) {
|
||||
return
|
||||
}
|
||||
|
||||
ctx.body = $.discord.client.guilds.map(g => ({ url: `${$.config.appUrl}/s/${g.id}`, name: g.name, members: g.members.array().length, roles: g.roles.array().length }))
|
||||
})
|
||||
|
||||
R.patch('/api/servers/:server/roles', async (ctx: Context) => {
|
||||
const { userId } = (ctx.session: { userId: string })
|
||||
const { server } = (ctx.params: { server: string })
|
||||
|
@ -126,30 +117,20 @@ export default (R: Router, $: AppContext) => {
|
|||
}
|
||||
}
|
||||
|
||||
const { added, removed } = ((ctx.request.body: any): { added: string[], removed: string[] })
|
||||
const originalRoles = gm.roles
|
||||
let { added, removed } = ((ctx.request.body: any): { added: string[], removed: string[] })
|
||||
|
||||
const allowedRoles = await $.server.getAllowedRoles(server)
|
||||
const allowedRoles: string[] = await $.server.getAllowedRoles(server)
|
||||
|
||||
const pred = r => $.discord.safeRole(server, r) && allowedRoles.indexOf(r) !== -1
|
||||
const isSafe = (r: string) => $.discord.safeRole(server, r) && allowedRoles.includes(r)
|
||||
|
||||
if (added.length > 0) {
|
||||
gm = await gm.addRoles(added.filter(pred))
|
||||
}
|
||||
added = added.filter(isSafe)
|
||||
removed = removed.filter(isSafe)
|
||||
|
||||
setTimeout(() => {
|
||||
if (gm == null) {
|
||||
ctx.body = {
|
||||
err: 'guild member disappeared on remove, this should never happen.'
|
||||
}
|
||||
ctx.status = 500
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (removed.length > 0) {
|
||||
gm.removeRoles(removed.filter(pred))
|
||||
}
|
||||
}, 1000)
|
||||
const newRoles = originalRoles.concat(added).filter(x => !removed.includes(x))
|
||||
gm.edit({
|
||||
roles: newRoles
|
||||
})
|
||||
|
||||
ctx.body = { ok: true }
|
||||
})
|
||||
|
|
|
@ -3,5 +3,5 @@ module.exports = {
|
|||
'.',
|
||||
'./ui'
|
||||
],
|
||||
ignore: [ './node_modules', './flow-typed' ]
|
||||
ignore: [ './node_modules', './flow-typed', './ui' ]
|
||||
}
|
||||
|
|
|
@ -10,4 +10,6 @@ export default class Bot {
|
|||
this.svc = DS
|
||||
this.log = log
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
BIN
branding/Untitled.sketch
Normal file
BIN
branding/Untitled.sketch
Normal file
Binary file not shown.
|
@ -38,7 +38,7 @@
|
|||
"moment": "^2.24.0",
|
||||
"moniker": "^0.1.2",
|
||||
"nanoid": "^2.0.1",
|
||||
"next": "^8.0.3",
|
||||
"next": "8.0.3",
|
||||
"next-redux-wrapper": "^3.0.0-alpha.2",
|
||||
"pg": "^7.9.0",
|
||||
"pg-hstore": "^2.3.2",
|
||||
|
@ -75,9 +75,9 @@
|
|||
"enzyme-adapter-react-16": "^1.11.2",
|
||||
"enzyme-to-json": "^3.3.5",
|
||||
"eslint-plugin-flowtype": "^3.4.2",
|
||||
"flow-bin": "^0.95.1",
|
||||
"flow-bin": "^0.96.0",
|
||||
"flow-typed": "^2.5.1",
|
||||
"jest": "^24.5.0",
|
||||
"jest": "^24.6.0",
|
||||
"jest-styled-components": "^6.3.1",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"react-test-renderer": "^16.8.6",
|
||||
|
|
36
rpc/index.js
36
rpc/index.js
|
@ -109,25 +109,35 @@ export default class RPCServer {
|
|||
}
|
||||
|
||||
rpcError (ctx: Context & {body: any}, msg: ?string, err: ?Error = null, code: ?number = null) {
|
||||
log.error('rpc error', { msg, err })
|
||||
// log.error('rpc error', { msg, err })
|
||||
|
||||
ctx.body = {
|
||||
msg,
|
||||
msg: err?.message || msg,
|
||||
error: true
|
||||
}
|
||||
|
||||
if (err instanceof RPCError) {
|
||||
// this is one of our own errors, so we have a lot of data on this.
|
||||
ctx.status = err.code || code || 500
|
||||
ctx.body.msg = `${msg || 'RPC Error'}: ${err.message}`
|
||||
} else {
|
||||
if (msg == null && err != null) {
|
||||
ctx.body.msg = msg = err.message
|
||||
ctx.status = code || 500
|
||||
if (err != null) {
|
||||
if (err instanceof RPCError || err.constructor.name === 'RPCError') {
|
||||
// $FlowFixMe
|
||||
ctx.status = err.code
|
||||
console.log(err.code)
|
||||
}
|
||||
|
||||
// if not, just play cloudflare, say something was really weird, and move on.
|
||||
ctx.status = code || 520
|
||||
ctx.message = 'Unknown Error'
|
||||
}
|
||||
|
||||
// if (err != null && err.constructor.name === 'RPCError') {
|
||||
// console.log({ status: err.code })
|
||||
// // this is one of our own errors, so we have a lot of data on this.
|
||||
// ctx.status = err.code // || code || 500
|
||||
// ctx.body.msg = `${err.message || msg || 'RPC Error'}`
|
||||
// } else {
|
||||
// if (msg == null && err != null) {
|
||||
// ctx.body.msg = err.message
|
||||
// }
|
||||
|
||||
// // if not, just play cloudflare, say something was really weird, and move on.
|
||||
// ctx.status = code || 520
|
||||
// ctx.message = 'Unknown Error'
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,14 +5,12 @@ import * as secureAs from './_security'
|
|||
|
||||
export default ($: AppContext) => ({
|
||||
|
||||
getCurrentUser: secureAs.authed($, (ctx: Context) => {
|
||||
return $.discord.getUserPartial(ctx.session.userId)
|
||||
getCurrentUser: secureAs.authed($, async (ctx: Context) => {
|
||||
return await $.discord.getUserPartial(ctx.session.userId)
|
||||
}),
|
||||
|
||||
isRoot: secureAs.root($, () => {
|
||||
return true
|
||||
})
|
||||
|
||||
|
||||
|
||||
})
|
||||
|
|
|
@ -27,7 +27,8 @@ export type Permissions = {
|
|||
|
||||
type CachedRole = {
|
||||
id: string,
|
||||
position: number
|
||||
position: number,
|
||||
color?: number
|
||||
}
|
||||
|
||||
type OAuthRequestData = {
|
||||
|
@ -48,6 +49,11 @@ export type UserPartial = {
|
|||
avatar: string
|
||||
}
|
||||
|
||||
export type MemberExt = Member & {
|
||||
color?: number,
|
||||
__faked?: true
|
||||
}
|
||||
|
||||
export default class DiscordService extends Service {
|
||||
ctx: AppContext
|
||||
bot: Bot
|
||||
|
@ -57,6 +63,7 @@ export default class DiscordService extends Service {
|
|||
|
||||
// a small cache of role data for checking viability
|
||||
ownRoleCache: LRU<string, CachedRole>
|
||||
topRoleCache: LRU<string, CachedRole>
|
||||
|
||||
oauthCallback: string
|
||||
|
||||
|
@ -77,6 +84,7 @@ export default class DiscordService extends Service {
|
|||
this.oauthCallback = `${ctx.config.appUrl}/api/oauth/callback`
|
||||
|
||||
this.ownRoleCache = new LRU()
|
||||
this.topRoleCache = new LRU()
|
||||
|
||||
if (this.cfg.isBot) {
|
||||
this.client = new Eris(this.cfg.token, {
|
||||
|
@ -118,30 +126,42 @@ export default class DiscordService extends Service {
|
|||
return this.client.guilds.filter(guild => guild.members.has(user))
|
||||
}
|
||||
|
||||
gm (serverId: string, userId: string): ?Member {
|
||||
return this.client.guilds.get(serverId)?.members.get(userId)
|
||||
async gm (serverId: string, userId: string, { canFake = false }: { canFake: boolean } = {}): Promise<?MemberExt> {
|
||||
const gm: ?Member = await this.fetcher.getMember(serverId, userId)
|
||||
if (gm == null && this.isRoot(userId)) {
|
||||
return this.fakeGm({ id: userId })
|
||||
}
|
||||
|
||||
if (gm == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
const out: MemberExt = gm
|
||||
out.color = this.getHighestRole(gm).color
|
||||
return out
|
||||
}
|
||||
|
||||
ownGm (serverId: string) {
|
||||
return this.gm(serverId, this.client.user.id)
|
||||
}
|
||||
|
||||
fakeGm ({ id = '0', nickname = '[none]', displayHexColor = '#ffffff' }: $Shape<Member>): $Shape<Member> {
|
||||
return { id, nickname, displayHexColor, __faked: true, roles: { has () { return false } } }
|
||||
fakeGm ({ id = '0', nick = '[none]', color = 0 }: $Shape<MemberExt>): $Shape<MemberExt> {
|
||||
return { id, nick, color, __faked: true, roles: [] }
|
||||
}
|
||||
|
||||
getRoles (server: string) {
|
||||
return this.client.guilds.get(server)?.roles
|
||||
}
|
||||
|
||||
getOwnPermHeight (server: Guild): number {
|
||||
async getOwnPermHeight (server: Guild): Promise<number> {
|
||||
if (this.ownRoleCache.has(server)) {
|
||||
return this.ownRoleCache.get(server).position
|
||||
const r = this.ownRoleCache.get(server)
|
||||
return r.position
|
||||
}
|
||||
|
||||
const gm = this.ownGm(server.id)
|
||||
const gm = await this.ownGm(server.id)
|
||||
const g = gm?.guild
|
||||
const r: Role = OrderedSet(gm?.roles).map(id => g?.roles.get(id)).sortBy(r => r.position).last({ position: 0, id: '0' })
|
||||
const r: Role = OrderedSet(gm?.roles).map(id => g?.roles.get(id)).minBy(r => r.position)
|
||||
this.ownRoleCache.set(server, {
|
||||
id: r.id,
|
||||
position: r.position
|
||||
|
@ -150,6 +170,26 @@ export default class DiscordService extends Service {
|
|||
return r.position
|
||||
}
|
||||
|
||||
getHighestRole (gm: MemberExt): Role {
|
||||
const trk = `${gm.guild.id}:${gm.id}`
|
||||
if (this.topRoleCache.has(trk)) {
|
||||
const r = gm.guild.roles.get(this.topRoleCache.get(trk).id)
|
||||
if (r != null) {
|
||||
return r
|
||||
}
|
||||
}
|
||||
|
||||
const g = gm.guild
|
||||
const top = OrderedSet(gm.roles).map(id => g.roles.get(id)).minBy(r => r.position)
|
||||
this.topRoleCache.set(trk, {
|
||||
id: top.id,
|
||||
position: top.position,
|
||||
color: top.color
|
||||
})
|
||||
|
||||
return top
|
||||
}
|
||||
|
||||
calcPerms (permable: Role | Member): Permissions {
|
||||
const p: ErisPermission = (permable instanceof Role) ? permable.permissions : permable.permission
|
||||
return {
|
||||
|
@ -173,18 +213,19 @@ export default class DiscordService extends Service {
|
|||
return real
|
||||
}
|
||||
|
||||
safeRole (server: string, role: string) {
|
||||
async safeRole (server: string, role: string) {
|
||||
const r = this.getRoles(server)?.get(role)
|
||||
if (r == null) {
|
||||
throw new Error(`safeRole can't find ${role} in ${server}`)
|
||||
}
|
||||
|
||||
return this.roleIsEditable(r) && !this.calcPerms(r).canManageRoles
|
||||
return (await this.roleIsEditable(r)) && !this.calcPerms(r).canManageRoles
|
||||
}
|
||||
|
||||
roleIsEditable (role: Role): boolean {
|
||||
async roleIsEditable (role: Role): Promise<boolean> {
|
||||
// role will be LOWER than my own
|
||||
return this.getOwnPermHeight(role.guild) > role.position
|
||||
const ownPh = await this.getOwnPermHeight(role.guild)
|
||||
return ownPh > role.position
|
||||
}
|
||||
|
||||
async oauthRequest (path: string, data: OAuthRequestData) {
|
||||
|
@ -228,8 +269,8 @@ export default class DiscordService extends Service {
|
|||
})
|
||||
}
|
||||
|
||||
getUserPartial (userId: string): ?UserPartial {
|
||||
const u = this.client.users.get(userId)
|
||||
async getUserPartial (userId: string): Promise<?UserPartial> {
|
||||
const u = await this.fetcher.getUser(userId)
|
||||
if (u == null) {
|
||||
return null
|
||||
}
|
||||
|
@ -295,11 +336,16 @@ export default class DiscordService extends Service {
|
|||
].join('\n'))
|
||||
}
|
||||
|
||||
canManageRoles (server: string, user: string) {
|
||||
return this.getPermissions(this.gm(server, user)).canManageRoles
|
||||
async canManageRoles (server: string, user: string): Promise<boolean> {
|
||||
const gm = await this.gm(server, user)
|
||||
if (gm == null) {
|
||||
return false
|
||||
}
|
||||
|
||||
return this.getPermissions(gm).canManageRoles
|
||||
}
|
||||
|
||||
isMember (server: string, user: string) {
|
||||
isMember (server: string, user: string): boolean {
|
||||
return this.gm(server, user) != null
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// @flow
|
||||
import type { IFetcher } from './types'
|
||||
import type DiscordSvc from '../discord'
|
||||
import type ErisClient from 'eris'
|
||||
import type ErisClient, { User, Member, Guild } from 'eris'
|
||||
|
||||
export default class BotFetcher implements IFetcher {
|
||||
ctx: DiscordSvc
|
||||
|
@ -11,7 +11,12 @@ export default class BotFetcher implements IFetcher {
|
|||
this.client = ctx.client
|
||||
}
|
||||
|
||||
getUser = async (id: string) => this.client.users.get(id)
|
||||
getMember = async (server: string, user: string) => this.client.guilds.get(server)?.members.get(user)
|
||||
getGuild = async (server: string) => this.client.guilds.get(server)
|
||||
getUser = async (id: string): Promise<?User> =>
|
||||
this.client.users.get(id)
|
||||
|
||||
getMember = async (server: string, user: string): Promise<?Member> =>
|
||||
this.client.guilds.get(server)?.members.get(user)
|
||||
|
||||
getGuild = async (server: string): Promise<?Guild> =>
|
||||
this.client.guilds.get(server)
|
||||
}
|
||||
|
|
|
@ -4,8 +4,8 @@ import { colors } from '../global-colors'
|
|||
import Color from 'color'
|
||||
import RoleStyled from './role.styled'
|
||||
import Tooltip from '../tooltip'
|
||||
import logger from '../../../logger'
|
||||
const log = logger(__filename)
|
||||
// import logger from '../../../logger'
|
||||
// const log = logger(__filename)
|
||||
|
||||
const fromColors = (colors) => Object.entries(colors).reduce(
|
||||
(acc, [v, val]) => ({ ...acc, [`--role-color-${v}`]: val })
|
||||
|
@ -45,15 +45,15 @@ export default class Role extends React.Component<RoleProps, RoleState> {
|
|||
}
|
||||
|
||||
onMouseOver = () => {
|
||||
log.debug('caught hovering')
|
||||
// log.debug('caught hovering')
|
||||
if (this.props.disabled && this.state.hovering === false) {
|
||||
log.debug('set hovering')
|
||||
// log.debug('set hovering')
|
||||
this.setState({ hovering: true })
|
||||
}
|
||||
}
|
||||
|
||||
onMouseOut = () => {
|
||||
log.debug('out hovering')
|
||||
// log.debug('out hovering')
|
||||
this.setState({ hovering: false })
|
||||
}
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ const defaultProps: SocialCardProps = {
|
|||
imageSize: 200
|
||||
}
|
||||
|
||||
const SocialCards: React.StatelessFunctionalComponent<SocialCardProps> = (props) => {
|
||||
const SocialCards = (props: SocialCardProps) => {
|
||||
props = {
|
||||
...defaultProps,
|
||||
...props
|
||||
|
|
|
@ -10,7 +10,7 @@ type Props = {
|
|||
const HeaderBarAuth = dynamic(() => import('../components/header/auth'))
|
||||
const HeaderBarUnauth = dynamic(() => import('../components/header/unauth'))
|
||||
|
||||
const HeaderBar: React.StatelessFunctionalComponent<Props> = (props) => {
|
||||
const HeaderBar = (props: Props) => {
|
||||
if (props.user == null) {
|
||||
return <HeaderBarUnauth {...props} />
|
||||
} else {
|
||||
|
|
|
@ -8,6 +8,7 @@ import { Provider } from 'react-redux'
|
|||
import ErrorP, { Overlay } from './_error'
|
||||
import styled from 'styled-components'
|
||||
import { withRedux } from '../config/redux'
|
||||
import type { UserPartial } from '../../services/discord'
|
||||
|
||||
type NextPage = React.Component<any> & React.StatelessFunctionalComponent<any> & {
|
||||
getInitialProps: (ctx: any, ...args: any) => any
|
||||
|
@ -35,9 +36,18 @@ class RoleypolyApp extends App {
|
|||
|
||||
let pageProps = {}
|
||||
const rpc = withCookies(ctx)
|
||||
|
||||
const user = await rpc.getCurrentUser()
|
||||
ctx.user = user
|
||||
let user: ?UserPartial
|
||||
try {
|
||||
user = await rpc.getCurrentUser()
|
||||
ctx.user = user
|
||||
} catch (e) {
|
||||
if (e.code === 403) {
|
||||
ctx.user = null
|
||||
} else {
|
||||
console.error(e)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
ctx.robots = 'INDEX, FOLLOW'
|
||||
|
||||
ctx.layout = {
|
||||
|
|
|
@ -36,7 +36,7 @@ const RoleHolder = styled.div`
|
|||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
`
|
||||
class Server extends React.Component<ServerPageProps> {
|
||||
class Server extends React.PureComponent<ServerPageProps> {
|
||||
static async getInitialProps (ctx: *, rpc: *, router: *) {
|
||||
const isDiscordBot = ctx.req && ctx.req.headers['user-agent'].includes('Discordbot')
|
||||
if (ctx.user == null) {
|
||||
|
|
|
@ -25,7 +25,7 @@ export default class RPCClient {
|
|||
cookieHeader: string
|
||||
|
||||
rpc: {
|
||||
[fn: string]: (...args: any[]) => Promise<mixed> | string
|
||||
[fn: string]: (...args: any[]) => Promise<any>
|
||||
} = {}
|
||||
|
||||
__rpcAvailable: Array<{
|
||||
|
@ -42,11 +42,12 @@ export default class RPCClient {
|
|||
this.dev = process.env.NODE_ENV === 'development'
|
||||
}
|
||||
|
||||
this.rpc = new Proxy({
|
||||
toJSON () {
|
||||
return '{}'
|
||||
}
|
||||
}, { get: this.__rpcCall, has: this.__checkCall, ownKeys: this.__listCalls, delete: () => {} })
|
||||
this.rpc = new Proxy({}, {
|
||||
get: this.__rpcCall,
|
||||
has: this.__checkCall,
|
||||
ownKeys: this.__listCalls,
|
||||
delete: () => {}
|
||||
})
|
||||
|
||||
if (this.dev) {
|
||||
this.updateCalls()
|
||||
|
@ -90,7 +91,9 @@ export default class RPCClient {
|
|||
}
|
||||
const rsp = await rq.send(req).ok(() => true)
|
||||
const body: RPCResponse = rsp.body
|
||||
// console.log(body)
|
||||
if (body.error === true) {
|
||||
console.error(body)
|
||||
throw RPCError.fromResponse(body, rsp.status)
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue