cleaning up components, beginning to pull out slack logic

This commit is contained in:
sneakers-the-rat 2023-08-01 18:22:49 -07:00
parent b377997617
commit 1b7715d0c8
16 changed files with 311 additions and 288 deletions

View file

@ -3,7 +3,7 @@ export const getSlackInstallURL = (callback: CallableFunction) => {
.then(res => res.json()) .then(res => res.json())
.then(res => { .then(res => {
console.log('Got slack url', res); console.log('Got slack url', res);
callback(res.data.url, res.data.state_token) callback(res.data.url)
}) })
} }

View file

@ -1,9 +1,6 @@
import Grid from "@mui/material/Grid"; import Grid from "@mui/material/Grid";
import TextField from "@mui/material/TextField"; import TextField from "@mui/material/TextField";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
import Typography from "@mui/material/Typography";
import FormControl from "@mui/material/FormControl";
import InputLabel from "@mui/material/InputLabel";
import {setBridgeLabel} from "../../api/bridge"; import {setBridgeLabel} from "../../api/bridge";
import {useState} from "react"; import {useState} from "react";

View file

@ -5,16 +5,15 @@ import MenuItem from "@mui/material/MenuItem";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
import {stepCompleteType} from "./joinForm"; import {stepCompleteType} from "./joinForm";
import {joinSlackChannel} from "../../api/slack"; import {getSlackChannels, joinSlackChannel} from "../../api/slack";
import {useState} from "react"; import {useEffect, useState} from "react";
import {channelsType} from "../../types/channel";
import {bridgeType} from "../../types/bridge";
export interface JoinChannelProps { export interface JoinChannelProps {
channels: Array<{ platform: string;
name: string; bridge: bridgeType;
id: string;
is_member: boolean;
}>;
selectedChannel: string; selectedChannel: string;
setSelectedChannel: CallableFunction; setSelectedChannel: CallableFunction;
setStepComplete: CallableFunction; setStepComplete: CallableFunction;
@ -22,16 +21,26 @@ export interface JoinChannelProps {
} }
const JoinChannel = ({ const JoinChannel = ({
channels, platform,
bridge,
selectedChannel, selectedChannel,
setSelectedChannel, setSelectedChannel,
setStepComplete, setStepComplete,
stepComplete stepComplete
}: JoinChannelProps) => { }: JoinChannelProps) => {
console.log('joinchannel channels', channels)
const [errored, setErrored] = useState(false); const [errored, setErrored] = useState(false);
const [channels, setChannels] = useState<channelsType[]>();
useEffect(() => {
if (bridge){
switch(platform){
case "Slack":
getSlackChannels(setChannels)
}
}
}, [platform, bridge])
const onJoinChannel = (response) => { const onJoinChannel = (response) => {
if (response.status === 'success'){ if (response.status === 'success'){
setErrored(false) setErrored(false)
@ -47,11 +56,15 @@ const JoinChannel = ({
} }
const onJoinButtonClicked = () => { const onJoinButtonClicked = () => {
switch(platform){
case "Slack":
let channel_id = channels.filter(chan => chan.name === selectedChannel) let channel_id = channels.filter(chan => chan.name === selectedChannel)
.map(chan => chan.id)[0] .map(chan => chan.id)[0]
joinSlackChannel(channel_id, onJoinChannel) joinSlackChannel(channel_id, onJoinChannel)
} }
}
return ( return (
<div className={"list-row"}> <div className={"list-row"}>
<FormControl sx={{width: "50%"}}> <FormControl sx={{width: "50%"}}>

View file

@ -1,29 +1,16 @@
import Typography from "@mui/material/Typography";
import Accordion from '@mui/material/Accordion';
import AccordionDetails from '@mui/material/AccordionDetails';
import AccordionSummary from '@mui/material/AccordionSummary';
import {Group} from "../../types/group"; import {Group} from "../../types/group";
import {JoinStep} from './joinStep'; import {JoinStep} from './joinStep';
import {JoinPlatform} from "./joinPlatform"; import {JoinLogin} from "./joinLogin";
import {useState, useEffect} from "react"; import {useState, useEffect} from "react";
import Grid from '@mui/material/Grid';
import TextField from "@mui/material/TextField";
import Button from '@mui/material/Button'; import Button from '@mui/material/Button';
import {setBridgeLabel} from "../../api/bridge";
import {getSlackChannels} from "../../api/slack";
import {createChannel} from "../../api/channel"; import {createChannel} from "../../api/channel";
import FormControl from "@mui/material/FormControl";
import Select from "@mui/material/Select"
import InputLabel from '@mui/material/InputLabel';
import MenuItem from '@mui/material/MenuItem';
import {JoinBridge} from "./joinBridge"; import {JoinBridge} from "./joinBridge";
import JoinChannel from "./joinChannel"; import JoinChannel from "./joinChannel";
import {bridgeType} from "../../types/bridge";
export interface JoinFormProps { export interface JoinFormProps {
group?: Group group?: Group
invite_token: string
} }
export interface stepCompleteType { export interface stepCompleteType {
@ -32,31 +19,30 @@ export interface stepCompleteType {
channel: boolean; channel: boolean;
} }
export const JoinForm = ({group, invite_token}: JoinFormProps) => { export interface stepErrorType {
const [bridgeCreated, setBridgeCreated] = useState(false); login: string;
const [bridgeErrorMessage, setBridgeErrorMessage] = useState(''); bridge: string;
channel: string;
}
export const JoinForm = ({group}: JoinFormProps) => {
// The platform selected to log in to
const [platform, setPlatform] = useState<string>(); const [platform, setPlatform] = useState<string>();
const [channels, setChannels] = useState<Array<{ const [bridge, setBridge] = useState<bridgeType>();
name: string;
id: string;
is_member: boolean;
}>
>();
const [selectedChannel, setSelectedChannel] = useState<string>(''); const [selectedChannel, setSelectedChannel] = useState<string>('');
const [bridge, setBridge] = useState<{
Label: string;
Protocol: string;
team_name: string;
id: string;
}>();
const [stepComplete, setStepComplete] = useState<stepCompleteType>({ const [stepComplete, setStepComplete] = useState<stepCompleteType>({
login: false, login: false,
bridge: false, bridge: false,
channel: false channel: false
}); });
const [bridgeCreated, setBridgeCreated] = useState(false);
const [bridgeErrorMessage, setBridgeErrorMessage] = useState('');
const createBridgedChannel = () => { const createBridgedChannel = () => {
createChannel(selectedChannel, bridge.id, invite_token, onBridgedChannelCreated) createChannel(selectedChannel, bridge.id, group.invite_token, onBridgedChannelCreated)
} }
const onBridgedChannelCreated = (result) => { const onBridgedChannelCreated = (result) => {
@ -69,15 +55,6 @@ export const JoinForm = ({group, invite_token}: JoinFormProps) => {
} }
} }
useEffect(() => {
if (bridge !== undefined){
setStepComplete({...stepComplete, login:true})
}
if (bridge !== undefined && channels === undefined){
getSlackChannels(setChannels)
}
}, [bridge])
return ( return (
<> <>
<header className={'section-header'}> <header className={'section-header'}>
@ -89,10 +66,13 @@ export const JoinForm = ({group, invite_token}: JoinFormProps) => {
id={'login'} id={'login'}
completed={stepComplete.login} completed={stepComplete.login}
> >
<JoinPlatform <JoinLogin
platformSetter = {setPlatform} platform={platform}
bridgeSetter = {setBridge} setPlatform = {setPlatform}
completeSetter= {setStepComplete} bridge={bridge}
setBridge = {setBridge}
stepComplete = {stepComplete}
setStepComplete = {setStepComplete}
/> />
</JoinStep> </JoinStep>
<JoinStep <JoinStep
@ -117,7 +97,8 @@ export const JoinForm = ({group, invite_token}: JoinFormProps) => {
completed={stepComplete.channel} completed={stepComplete.channel}
> >
<JoinChannel <JoinChannel
channels={channels} platform={platform}
bridge={bridge}
selectedChannel={selectedChannel} selectedChannel={selectedChannel}
setSelectedChannel={setSelectedChannel} setSelectedChannel={setSelectedChannel}
setStepComplete={setStepComplete} setStepComplete={setStepComplete}

View file

@ -0,0 +1,60 @@
import React, {useState} from 'react';
import {Group} from '../../types/group'
import {groupInvite} from '../../api/groups'
import TextField from "@mui/material/TextField";
import Button from "@mui/material/Button";
export interface JoinGroupProps {
group: Group;
setGroup: React.Dispatch<React.SetStateAction<Group>>
}
export const JoinGroup = ({
group,
setGroup
}: JoinGroupProps) => {
const [text, setText] = useState('');
const [authError, setAuthError] = useState(false);
const [errorText, setErrorText] = useState('');
const onGroupLogin = (response) => {
if (response.status !== "success"){
setAuthError(true);
setErrorText(response.message);
setGroup(undefined);
} else if (response.status === "success"){
setAuthError(false);
setErrorText('');
setGroup(response.data)
console.log(response)
}
}
const handleClick = () => {
groupInvite(text, onGroupLogin)
}
const textChanged = (event: React.ChangeEvent<HTMLInputElement>) => {
setText(event.target.value)
setGroup(undefined)
}
return (
<div className={"InputGroup"}>
<TextField
error={authError}
helperText={errorText}
className={"Input"}
label={"Join with invite token"}
onChange={textChanged}
/>
<Button
variant="contained"
onClick={handleClick}
color={authError ? "error" : undefined}
>
Submit
</Button>
</div>
)
}

View file

@ -0,0 +1,72 @@
/*
Select which platform you're joining from!
*/
import React, {useEffect, useState} from 'react';
import InputLabel from '@mui/material/InputLabel';
import MenuItem from '@mui/material/MenuItem';
import FormControl from '@mui/material/FormControl';
import Select from '@mui/material/Select';
import {SlackLogin} from "../platforms/slackLogin";
enum PLATFORMS {
Slack = 'Slack'
}
export const JoinLogin = ({
platform,
setPlatform,
bridge,
setBridge,
stepComplete,
setStepComplete
}) => {
const [loginComponent, setLoginComponent] = useState(<></>);
const handleSelect = (event) => {
setBridge(undefined);
setPlatform(event.target.value);
}
useEffect(() => {
switch(platform){
case "Slack":
setLoginComponent(<SlackLogin
bridge={bridge}
setBridge={setBridge}
/>)
default:
setLoginComponent(<></>)
}
}, [platform])
useEffect(() => {
if (bridge !== undefined) {
setStepComplete({...stepComplete, login:true})
}
},[bridge])
return(
<div className={"list-row"}>
<FormControl sx={{width: "50%"}}>
<InputLabel>Select Platform</InputLabel>
<Select
// value={platform}
onChange={handleSelect}
label={"Select Platform"}
>
<MenuItem value={'Slack'}>Slack</MenuItem>
</Select>
</FormControl>
{
platform ? loginComponent : <div style={{width: "50%"}}></div>
}
</div>
)
}

View file

@ -1,120 +0,0 @@
/*
Select which platform you're joining from!
*/
import React, {useEffect, useRef, useState} from 'react';
import Box from '@mui/material/Box';
import InputLabel from '@mui/material/InputLabel';
import MenuItem from '@mui/material/MenuItem';
import FormControl from '@mui/material/FormControl';
import Select from '@mui/material/Select';
import Button from "@mui/material/Button";
import {getSlackInstallURL} from "../../api/slack";
import {getBridgeByStateToken} from "../../api/bridge";
import {JoinSlack} from "../panels/joinSlack";
enum PLATFORMS {
Slack = 'Slack'
}
export const JoinPlatform = ({
platformSetter,
bridgeSetter,
completeSetter
}) => {
const [platform, setPlatform] = useState<PLATFORMS>();
const [installLink, setInstallLink] = useState();
const [stateToken, setStateToken] = useState();
const [bridge, setBridge] = useState();
const pingTimeout = useRef(null);
const handleSelect = (event) => {
setPlatform(event.target.value);
platformSetter(event.target.value);
}
useEffect(() => {
if (platform === "Slack") {
console.log('Getting slack URL')
getSlackInstallURL(handleInstallLink)
}
}, [platform])
const handleInstallLink = (url, state_token) => {
setInstallLink(url);
setStateToken(state_token);
// pingForBridge()
}
useEffect(() => {
const pingForBridge = () => {
if (bridge === undefined) {
console.log('bridge is', bridge);
getBridgeByStateToken(setBridge);
pingTimeout.current = setTimeout(pingForBridge, 1000);
}
}
if (stateToken !== undefined) {
if (bridge === undefined){
pingForBridge()
// pingTimeout.current = setInterval(pingForBridge, 1000)
}
}
return () => {clearInterval(pingTimeout.current)}
// if (stateToken !== undefined){
// if (bridge === undefined){
// console.log('bridge is', bridge)
// pingTimeout.current = setTimeout(() => {
// getBridgeByStateToken(setBridge);
// }, 1000)
// // setTimeout(pingForBridge, 1000);
// }
// }
}, [stateToken, bridge])
useEffect(() => {
bridgeSetter(bridge)
}, [bridge])
// useEffect(() => {
//
// }, [installLink])
const openInstallTab = () => {
window.open(installLink, '_blank').focus();
}
return(
<div className={"list-row"}>
<FormControl sx={{width: "50%"}}>
<InputLabel>Select Platform</InputLabel>
<Select
// value={platform}
onChange={handleSelect}
label={"Select Platform"}
>
<MenuItem value={'Slack'}>Slack</MenuItem>
</Select>
</FormControl>
{
installLink && platform == "Slack" ?
<Button
variant={"outlined"}
onClick={openInstallTab}>
Add to Slack
</Button>
: <div style={{width: "50%"}}></div>
}
</div>
)
}

View file

@ -4,58 +4,23 @@ import TextField from '@mui/material/TextField';
import Button from '@mui/material/Button'; import Button from '@mui/material/Button';
import {JoinForm} from "../join/joinForm"; import {JoinForm} from "../join/joinForm";
import {JoinGroup} from '../join/joinGroup';
import {Group} from "../../types/group"; import {Group} from "../../types/group";
import {groupInvite} from "../../api/groups"; import {groupInvite} from "../../api/groups";
export default function JoinPanel(){ export default function JoinPanel(){
const [text, setText] = useState('');
const [authError, setAuthError] = useState(false);
const [errorText, setErrorText] = useState('');
const [group, setGroup] = useState<Group>(undefined); const [group, setGroup] = useState<Group>(undefined);
const onGroupLogin = (response) => {
if (response.status !== "success"){
setAuthError(true);
setErrorText(response.message);
setGroup(undefined);
} else if (response.status === "success"){
setAuthError(false);
setErrorText('');
setGroup(response.data)
console.log(response)
}
}
const handleClick = () => {
groupInvite(text, onGroupLogin)
}
const textChanged = (event: React.ChangeEvent<HTMLInputElement>) => {
setText(event.target.value)
setGroup(undefined)
}
return( return(
<div className={"JoinPanel"}> <div className={"JoinPanel"}>
<div className={"InputGroup"}> <JoinGroup
<TextField group={group}
error={authError} setGroup={setGroup}
helperText={errorText}
className={"Input"}
label={"Join with invite token"}
onChange={textChanged}
/> />
<Button
variant="contained"
onClick={handleClick}
color={authError ? "error" : undefined}
>
Submit
</Button>
</div>
{ group ? { group ?
<JoinForm <JoinForm
group = {group} group = {group}
invite_token = {text}
/> : undefined /> : undefined
} }
</div> </div>

View file

@ -1,7 +0,0 @@
export const JoinSlack = () => {
return(
<></>
)
}

View file

@ -0,0 +1,55 @@
import React, {useEffect, useRef, useState} from 'react'
import Button from "@mui/material/Button";
import {getSlackInstallURL} from "../../api/slack";
import {getBridgeByStateToken} from "../../api/bridge";
export const SlackLogin = ({
bridge,
setBridge
}) => {
const [installLink, setInstallLink] = useState();
const [installStarted, setInstallStarted] = useState(false)
const pingTimeout = useRef(null);
const openInstallTab = () => {
window.open(installLink, '_blank').focus();
setInstallStarted(true)
}
useEffect(() => {
getSlackInstallURL(handleInstallLink)
}, [])
const handleInstallLink = (url) => {
setInstallLink(url);
}
useEffect(() => {
const pingForBridge = () => {
if (bridge === undefined) {
getBridgeByStateToken(setBridge);
pingTimeout.current = setTimeout(pingForBridge, 1000);
}
}
if (installStarted) {
if (bridge === undefined){
pingForBridge()
}
}
return () => {clearInterval(pingTimeout.current)}
}, [installStarted, bridge])
return(
<Button
variant={"outlined"}
onClick={openInstallTab}
color={bridge !== undefined ? 'success': undefined}
disabled={installLink === undefined}
>
{installLink === undefined ? 'Waiting for Install Link...' : 'Add to Slack'}
</Button>
)
}

View file

@ -0,0 +1,6 @@
export interface bridgeType {
Label: string;
Protocol: string;
team_name: string;
id: string;
}

View file

@ -0,0 +1,5 @@
export interface channelsType {
name: string;
id: string;
is_member: boolean;
}

View file

View file

@ -1,14 +1,15 @@
import config from 'config'; import config from 'config';
const { InstallProvider, LogLevel, FileInstallationStore } = require('@slack/oauth'); import {Request, Response} from 'express';
import {NextFunction, Request, Response} from 'express';
import {AppDataSource} from "../db/data-source"; import {AppDataSource} from "../db/data-source";
import {Bridge} from "../entities/bridge.entity"; import {Bridge} from "../entities/bridge.entity";
import {Group} from "../entities/group.entity"; import {Group} from "../entities/group.entity";
import {JoinSlackChannelInput} from "../schemas/slack.schema"; import {JoinSlackChannelInput} from "../schemas/slack.schema";
import { randomUUID } from "crypto"; import {randomUUID} from "crypto";
import {log} from "util";
import {Join} from "typeorm";
import logger from "../logging"; import logger from "../logging";
import {authTest, slackChannel } from "../types/slack";
import { slackConfigType } from "../types/config";
const { InstallProvider, LogLevel, FileInstallationStore } = require('@slack/oauth');
const scopes = ['bot', 'channels:write', 'channels:write.invites', 'chat:write:bot', 'chat:write:user', 'users.profile:read']; const scopes = ['bot', 'channels:write', 'channels:write.invites', 'chat:write:bot', 'chat:write:user', 'users.profile:read'];
const bridgeRepository = AppDataSource.getRepository(Bridge) const bridgeRepository = AppDataSource.getRepository(Bridge)
@ -16,61 +17,15 @@ const groupRepository = AppDataSource.getRepository(Group)
const SLACK_COOKIE_NAME = "slack-oauth-state"; const SLACK_COOKIE_NAME = "slack-oauth-state";
const slackConfig = config.get<{ const slackConfig = config.get<slackConfigType>('slackConfig');
client_id: string,
client_secret: string,
signing_secret: string,
state_secret: string
}>('slackConfig');
export interface slackChannel {
id: string;
name: string;
is_channel: boolean;
is_group: boolean;
is_im: boolean;
is_mpim: boolean;
is_private: boolean;
created: number;
is_archived: boolean;
is_general: boolean;
unlinked: number;
name_normalized: string;
is_shared: boolean;
is_org_shared: boolean;
is_pending_ext_shared: boolean;
pending_shared: [];
context_team_id: string;
updated: number;
creator: string;
is_ext_shared: boolean;
shared_team_ids: string[];
is_member: boolean;
num_members: number;
}
export interface authTest {
ok: boolean;
url: string;
team: string;
user: string;
team_id: string;
user_id: string;
bot_id: string;
is_enterprise_install: boolean;
}
const installer = new InstallProvider({ const installer = new InstallProvider({
clientId: slackConfig.client_id, clientId: slackConfig.client_id,
clientSecret: slackConfig.client_secret, clientSecret: slackConfig.client_secret,
authVersion: 'v1', authVersion: 'v1',
scopes, scopes,
// stateSecret: slackConfig.state_secret,
stateVerification: false, stateVerification: false,
// installationStore: new FileInstallationStore(),
logLevel: LogLevel.DEBUG, logLevel: LogLevel.DEBUG,
// stateCookieName: SLACK_COOKIE_NAME
}) })
@ -94,8 +49,7 @@ export const SlackInstallLinkHandler = async(
res.status(200).json({ res.status(200).json({
status: 'success', status: 'success',
data: { data: {
url, url
state_token
} }
}) })

View file

@ -0,0 +1,6 @@
export interface slackConfigType {
client_id: string,
client_secret: string,
signing_secret: string,
state_secret: string
}

36
server/src/types/slack.ts Normal file
View file

@ -0,0 +1,36 @@
export interface slackChannel {
id: string;
name: string;
is_channel: boolean;
is_group: boolean;
is_im: boolean;
is_mpim: boolean;
is_private: boolean;
created: number;
is_archived: boolean;
is_general: boolean;
unlinked: number;
name_normalized: string;
is_shared: boolean;
is_org_shared: boolean;
is_pending_ext_shared: boolean;
pending_shared: [];
context_team_id: string;
updated: number;
creator: string;
is_ext_shared: boolean;
shared_team_ids: string[];
is_member: boolean;
num_members: number;
}
export interface authTest {
ok: boolean;
url: string;
team: string;
user: string;
team_id: string;
user_id: string;
bot_id: string;
is_enterprise_install: boolean;
}