Working version of whole form
This commit is contained in:
parent
3741b37ff4
commit
eb2752c4e0
20 changed files with 625 additions and 42 deletions
|
@ -6,6 +6,7 @@
|
|||
"@emotion/react": "^11.11.1",
|
||||
"@emotion/styled": "^11.11.0",
|
||||
"@mui/base": "^5.0.0-beta.8",
|
||||
"@mui/icons-material": "^5.14.1",
|
||||
"@mui/material": "^5.14.2",
|
||||
"@types/node": "^16.18.39",
|
||||
"@types/react": "^18.2.16",
|
||||
|
|
29
client/src/api/bridge.ts
Normal file
29
client/src/api/bridge.ts
Normal file
|
@ -0,0 +1,29 @@
|
|||
|
||||
export const getBridgeByStateToken = (callback: CallableFunction) => {
|
||||
fetch('api/bridge')
|
||||
.then(res => res.json())
|
||||
.then((res) => {
|
||||
console.log('bridge result', res)
|
||||
if (res.status === 'success'){
|
||||
console.log('successful get bridge')
|
||||
callback(res.data)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export const setBridgeLabel = (label:string, callback: CallableFunction) => {
|
||||
fetch('api/bridge',{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify({
|
||||
Label: label
|
||||
})
|
||||
}).then(res => res.json())
|
||||
.then((res) => {
|
||||
if (res.status === "success") {
|
||||
callback()
|
||||
}
|
||||
})
|
||||
}
|
22
client/src/api/slack.ts
Normal file
22
client/src/api/slack.ts
Normal file
|
@ -0,0 +1,22 @@
|
|||
export const getSlackInstallURL = (callback: CallableFunction) => {
|
||||
fetch('api/slack/install')
|
||||
.then(res => res.json())
|
||||
.then(res => {
|
||||
console.log('Got slack url', res);
|
||||
callback(res.data.url, res.data.state_token)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
|
||||
export const getSlackChannels = (callback: CallableFunction) => {
|
||||
fetch('api/slack/channels')
|
||||
.then(res => res.json())
|
||||
.then(res => {
|
||||
console.log('Got slack channels', res);
|
||||
if (res.status === "success"){
|
||||
callback(res.data.channels.sort())
|
||||
}
|
||||
})
|
||||
|
||||
}
|
26
client/src/components/join/joinBridge.tsx
Normal file
26
client/src/components/join/joinBridge.tsx
Normal file
|
@ -0,0 +1,26 @@
|
|||
import {Grid} from "@mui/material";
|
||||
import {TextField} from "@mui/material";
|
||||
|
||||
|
||||
export const JoinBridge = ({
|
||||
bridge, setBridge
|
||||
}) => {
|
||||
const onSetLabel = (label) => {
|
||||
setBridge({...bridge, Label:label})
|
||||
}
|
||||
|
||||
return (
|
||||
<Grid container spacing={2} columns={2}>
|
||||
<Grid item xs={1}>
|
||||
|
||||
</Grid>
|
||||
<Grid item xs={1}>
|
||||
<TextField
|
||||
onChange={(event) => {onSetLabel(event.target.value)}}>
|
||||
|
||||
</TextField>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)
|
||||
|
||||
}
|
|
@ -1,25 +1,166 @@
|
|||
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 {JoinStep} from './joinStep';
|
||||
import {JoinPlatform} from "./joinPlatform";
|
||||
import {useState} 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 {setBridgeLabel} from "../../api/bridge";
|
||||
import {getSlackChannels} from "../../api/slack";
|
||||
import FormControl from "@mui/material/FormControl";
|
||||
import Select from "@mui/material/Select"
|
||||
import InputLabel from '@mui/material/InputLabel';
|
||||
import MenuItem from '@mui/material/MenuItem';
|
||||
|
||||
export interface JoinFormProps {
|
||||
group: Group
|
||||
}
|
||||
|
||||
interface stepCompleteType {
|
||||
login: boolean;
|
||||
bridge: boolean;
|
||||
channel: boolean;
|
||||
}
|
||||
|
||||
export const JoinForm = ({group}: JoinFormProps) => {
|
||||
const [platform, setPlatform] = useState();
|
||||
const [platform, setPlatform] = useState<string>();
|
||||
const [channels, setChannels] = useState<string[]>();
|
||||
const [selectedChannel, setSelectedChannel] = useState<string>();
|
||||
const [bridge, setBridge] = useState<{
|
||||
Label: string;
|
||||
Protocol: string;
|
||||
team_name: string;
|
||||
}>();
|
||||
const [stepComplete, setStepComplete] = useState<stepCompleteType>({
|
||||
login: false,
|
||||
bridge: false,
|
||||
channel: false
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (bridge !== undefined){
|
||||
setStepComplete({...stepComplete, login:true})
|
||||
}
|
||||
if (channels === undefined){
|
||||
getSlackChannels(setChannels)
|
||||
}
|
||||
}, [bridge])
|
||||
|
||||
const onSetBridge = (evt) => {
|
||||
setBridge({...bridge, Label:evt.target.value})
|
||||
}
|
||||
|
||||
const updateBridgeLabel = () => {
|
||||
setBridgeLabel(bridge.Label, () => {setStepComplete({...stepComplete, bridge:true})})
|
||||
}
|
||||
|
||||
const handleSelectChannel = (evt) => {
|
||||
|
||||
}
|
||||
|
||||
const joinChannel = () => {
|
||||
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<header className={'section-header'}>
|
||||
Joining group: <code>{group.name}</code>
|
||||
</header>
|
||||
<JoinPlatform
|
||||
platformSetter = {setPlatform}
|
||||
></JoinPlatform>
|
||||
</>
|
||||
<JoinStep
|
||||
title={"1) Login"}
|
||||
details={"Select your chat platform"}
|
||||
id={'login'}
|
||||
completed={stepComplete.login}
|
||||
>
|
||||
<JoinPlatform
|
||||
platformSetter = {setPlatform}
|
||||
bridgeSetter = {setBridge}
|
||||
completeSetter= {setStepComplete}
|
||||
/>
|
||||
</JoinStep>
|
||||
<JoinStep
|
||||
title={"2) Set Bridge Label"}
|
||||
details={"A short identifier shown before your messages"}
|
||||
id={'bridge'}
|
||||
disabled={!stepComplete.login}
|
||||
completed={stepComplete.bridge}
|
||||
>
|
||||
<Grid container spacing={2} columns={4} alignItems="center">
|
||||
<Grid item xs={1}>
|
||||
<Typography>Label:</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={2}>
|
||||
<TextField
|
||||
value={bridge?.Label}
|
||||
onChange={onSetBridge}>
|
||||
</TextField>
|
||||
</Grid>
|
||||
<Grid item xs={1}>
|
||||
<Button
|
||||
variant={"outlined"}
|
||||
onClick={updateBridgeLabel}>
|
||||
Update Label!
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</JoinStep>
|
||||
<JoinStep
|
||||
title={"3) Select a channel!"}
|
||||
details={"The bot will join :)"}
|
||||
id={'bridge'}
|
||||
disabled={!stepComplete.login}
|
||||
completed={stepComplete.channel}
|
||||
>
|
||||
<div className={"list-row"}>
|
||||
<FormControl sx={{width: "50%"}}>
|
||||
<InputLabel>Select Platform</InputLabel>
|
||||
<Select
|
||||
// value={platform}
|
||||
onChange={(evt:any) => {setSelectedChannel(evt.target.value)}}
|
||||
label={"Select Channel"}
|
||||
>
|
||||
{
|
||||
channels ?
|
||||
channels.map(chan => {
|
||||
return(<MenuItem value={chan} key={chan}>{chan}</MenuItem>)
|
||||
})
|
||||
: undefined
|
||||
}
|
||||
|
||||
</Select>
|
||||
</FormControl>
|
||||
<Button
|
||||
variant={"outlined"}
|
||||
onClick={joinChannel}>
|
||||
Join Channel
|
||||
</Button>
|
||||
</div>
|
||||
<Grid container spacing={2} columns={2} alignItems="center">
|
||||
<Grid item xs={1}>
|
||||
<Typography>Label:</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={2}>
|
||||
<TextField
|
||||
value={bridge?.Label}
|
||||
onChange={onSetBridge}>
|
||||
</TextField>
|
||||
</Grid>
|
||||
<Grid item xs={1}>
|
||||
<Button
|
||||
variant={"outlined"}
|
||||
onClick={updateBridgeLabel}>
|
||||
Update Label!
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</JoinStep>
|
||||
</>
|
||||
)
|
||||
}
|
|
@ -2,42 +2,125 @@
|
|||
Select which platform you're joining from!
|
||||
*/
|
||||
|
||||
import React, {useState} from 'react';
|
||||
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";
|
||||
|
||||
const PLATFORMS = {
|
||||
'Slack': JoinSlack
|
||||
enum PLATFORMS {
|
||||
Slack = 'Slack'
|
||||
}
|
||||
|
||||
export const JoinPlatform = ({
|
||||
platformSetter
|
||||
platformSetter,
|
||||
bridgeSetter,
|
||||
completeSetter
|
||||
}) => {
|
||||
const [platform, setPlatform] = useState()
|
||||
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()
|
||||
}
|
||||
|
||||
// const pingForBridge = () =>{
|
||||
// if (bridge === undefined){
|
||||
// console.log('bridge is', bridge)
|
||||
// getBridgeByStateToken(setBridge);
|
||||
// setTimeout(pingForBridge, 1000);
|
||||
// }
|
||||
// }
|
||||
|
||||
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 fullWidth>
|
||||
<FormControl sx={{width: "50%"}}>
|
||||
<InputLabel>Select Platform</InputLabel>
|
||||
<Select
|
||||
value={platform}
|
||||
// value={platform}
|
||||
onChange={handleSelect}
|
||||
label={"Select Platform"}
|
||||
>
|
||||
<MenuItem value={'slack'}>Slack</MenuItem>
|
||||
<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>
|
||||
)
|
||||
|
|
60
client/src/components/join/joinStep.tsx
Normal file
60
client/src/components/join/joinStep.tsx
Normal file
|
@ -0,0 +1,60 @@
|
|||
import * as React from 'react';
|
||||
import Accordion from '@mui/material/Accordion';
|
||||
import AccordionDetails from '@mui/material/AccordionDetails';
|
||||
import AccordionSummary from '@mui/material/AccordionSummary';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
||||
import RadioButtonUncheckedIcon from '@mui/icons-material/RadioButtonUnchecked';
|
||||
import TaskAltIcon from '@mui/icons-material/TaskAlt';
|
||||
import {useState} from "react";
|
||||
|
||||
export interface JoinStepProps {
|
||||
children: any;
|
||||
id: string;
|
||||
title?: string;
|
||||
details?: string;
|
||||
completed?: boolean;
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
export function JoinStep(
|
||||
{
|
||||
children,
|
||||
id,
|
||||
title = '',
|
||||
details = '',
|
||||
completed = false,
|
||||
disabled = false
|
||||
}: JoinStepProps){
|
||||
|
||||
return(
|
||||
<Accordion
|
||||
disabled={disabled}
|
||||
>
|
||||
<AccordionSummary
|
||||
expandIcon={<ExpandMoreIcon />}
|
||||
aria-controls="panel1bh-content"
|
||||
id="panel1bh-header"
|
||||
>
|
||||
<Typography sx={{ width: '33%', flexShrink: 0 }}>
|
||||
{ title }
|
||||
</Typography>
|
||||
<Typography sx={{ color: 'text.secondary', flexGrow: 1 }}>
|
||||
{ details }
|
||||
</Typography>
|
||||
{
|
||||
completed ?
|
||||
<TaskAltIcon color={"success"}/>
|
||||
:
|
||||
<RadioButtonUncheckedIcon/>
|
||||
}
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<Typography>
|
||||
{ children }
|
||||
</Typography>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
)
|
||||
|
||||
}
|
|
@ -1,3 +1,19 @@
|
|||
.list-row {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
gap: 2em;
|
||||
|
||||
&>div {
|
||||
flex-basis: 50%;
|
||||
}
|
||||
.MuiButton-root {
|
||||
flex-basis: 50%;
|
||||
flex-grow: 0;
|
||||
height: 56px;
|
||||
}
|
||||
}
|
||||
|
||||
.InputSlot {
|
||||
min-width: 320px;
|
||||
width: 100%;
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
.section-header {
|
||||
color: $color-text;
|
||||
font-size: 2rem;
|
||||
font: {
|
||||
size: 1.5rem;
|
||||
weight: bold;
|
||||
}
|
||||
margin: {
|
||||
top: 1rem;
|
||||
top: 2rem;
|
||||
bottom: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ POSTGRES_DB=
|
|||
SLACK_CLIENT_ID=
|
||||
SLACK_CLIENT_SECRET=
|
||||
SLACK_SIGNING_SECRET=
|
||||
SLACK_STATE_SECRET=
|
||||
|
||||
ADMIN_TOKEN=
|
||||
|
||||
|
|
|
@ -10,7 +10,8 @@ export default {
|
|||
slackConfig: {
|
||||
client_id: 'SLACK_CLIENT_ID',
|
||||
client_secret: 'SLACK_CLIENT_SECRET',
|
||||
signing_secret: 'SLACK_SIGNING_SECRET'
|
||||
signing_secret: 'SLACK_SIGNING_SECRET',
|
||||
state_secret: 'SLACK_STATE_SECRET'
|
||||
},
|
||||
admin_token: 'ADMIN_TOKEN',
|
||||
cookies:{
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
|
||||
|
||||
require('dotenv').config();
|
||||
import express, { NextFunction, Request, Response } from 'express';
|
||||
import config from 'config';
|
||||
|
@ -13,6 +11,7 @@ import {cookieMiddleware} from "./middleware/cookies";
|
|||
import groupRoutes from "./routes/group.routes";
|
||||
import slackRoutes from "./routes/slack.routes";
|
||||
import authRoutes from "./routes/auth.routes";
|
||||
import bridgeRoutes from './routes/bridge.routes';
|
||||
|
||||
|
||||
|
||||
|
@ -40,6 +39,7 @@ AppDataSource.initialize()
|
|||
// });
|
||||
|
||||
app.use('/slack', slackRoutes);
|
||||
app.use('/bridge', bridgeRoutes)
|
||||
|
||||
app.all('*', (req: Request, res: Response, next: NextFunction) => {
|
||||
next(new AppError(404, `Route ${req.originalUrl} not found`));
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
import {AppDataSource} from "../db/data-source";
|
||||
import {Bridge} from "../entities/bridge.entity";
|
||||
import {Request, Response} from "express";
|
||||
import {UpdateBridgeInput} from "../schemas/bridge.schema";
|
||||
|
||||
const bridgeRepository = AppDataSource.getRepository(Bridge)
|
||||
|
||||
|
||||
export const getBridgeHandler = async(
|
||||
req: Request,
|
||||
res: Response
|
||||
) => {
|
||||
if (req.session.state_token){
|
||||
let bridge = await bridgeRepository.findOne({
|
||||
where: {state_token: req.session.state_token},
|
||||
select: {
|
||||
Protocol: true,
|
||||
Label: true,
|
||||
team_name: true
|
||||
}
|
||||
})
|
||||
if (!bridge){
|
||||
res.status(404).json({
|
||||
status: 'failure',
|
||||
message: 'No matching bridge found'
|
||||
})
|
||||
return
|
||||
}
|
||||
res.status(200).json({
|
||||
status: 'success',
|
||||
data: bridge
|
||||
})
|
||||
} else {
|
||||
res.status(403).json({
|
||||
status: 'failure',
|
||||
message: 'No state token found'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const setBridgeHandler = async(
|
||||
req: Request<{}, {}, UpdateBridgeInput>,
|
||||
res: Response
|
||||
) => {
|
||||
if (req.session.state_token) {
|
||||
let bridge = await bridgeRepository.findOneBy({state_token: req.session.state_token})
|
||||
if (!bridge){
|
||||
res.status(404).json({
|
||||
status: 'failure',
|
||||
message: 'No matching bridge found'
|
||||
})
|
||||
return
|
||||
}
|
||||
bridge.Label = req.body.Label;
|
||||
bridge.save()
|
||||
res.status(200).json({
|
||||
status:'success'
|
||||
})
|
||||
} else {
|
||||
res.status(403).json({
|
||||
status: 'failure',
|
||||
message: 'No state token found'
|
||||
})
|
||||
}
|
||||
}
|
|
@ -11,11 +11,13 @@ const scopes = ['bot', 'channels:write', 'chat:write:bot', 'chat:write:user', 'u
|
|||
const bridgeRepository = AppDataSource.getRepository(Bridge)
|
||||
const groupRepository = AppDataSource.getRepository(Group)
|
||||
|
||||
const SLACK_COOKIE_NAME = "slack-oauth-state";
|
||||
|
||||
const slackConfig = config.get<{
|
||||
client_id: string,
|
||||
client_secret: string,
|
||||
signing_secret: string
|
||||
signing_secret: string,
|
||||
state_secret: string
|
||||
}>('slackConfig');
|
||||
|
||||
const installer = new InstallProvider({
|
||||
|
@ -23,9 +25,11 @@ const installer = new InstallProvider({
|
|||
clientSecret: slackConfig.client_secret,
|
||||
authVersion: 'v1',
|
||||
scopes,
|
||||
stateSecret: randomUUID(),
|
||||
installationStore: new FileInstallationStore(),
|
||||
// stateSecret: slackConfig.state_secret,
|
||||
stateVerification: false,
|
||||
// installationStore: new FileInstallationStore(),
|
||||
logLevel: LogLevel.DEBUG,
|
||||
// stateCookieName: SLACK_COOKIE_NAME
|
||||
|
||||
})
|
||||
|
||||
|
@ -45,18 +49,24 @@ export const SlackInstallLinkHandler = async(
|
|||
req: Request,
|
||||
res: Response
|
||||
) => {
|
||||
let login_token = randomUUID()
|
||||
let state_token = randomUUID()
|
||||
req.session.state_token = state_token;
|
||||
|
||||
const url = await installer.generateInstallUrl({
|
||||
scopes,
|
||||
metadata: {token: login_token, group: req.query.group}
|
||||
});
|
||||
const url = await installer.generateInstallUrl(
|
||||
{
|
||||
scopes,
|
||||
metadata: {token: state_token, group: req.query.group}
|
||||
},
|
||||
true,
|
||||
state_token
|
||||
);
|
||||
|
||||
res.cookie(SLACK_COOKIE_NAME, state_token, { maxAge: 60*5 })
|
||||
res.status(200).json({
|
||||
status: 'success',
|
||||
data: {
|
||||
url,
|
||||
login_token
|
||||
state_token
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -72,27 +82,78 @@ export const SlackCallbackHandler = async(
|
|||
// using custom success and failure handlers
|
||||
const callbackOptions = {
|
||||
success: async (installation, installOptions, req, res) => {
|
||||
console.log(installation, installOptions, req.body, req.content, req.query, req.params)
|
||||
console.log(installation.team.id, installation.team.name, installation.bot.token);
|
||||
let bridge = await bridgeRepository.create({
|
||||
// console.log(installation, installOptions, req.body, req.content, req.query, req.params)
|
||||
// console.log(installation.team.id, installation.team.name, installation.bot.token);
|
||||
let bridge_data = {
|
||||
'Protocol': 'slack',
|
||||
'Label': installation.metadata.name,
|
||||
'Label': installation.team.name,
|
||||
'team_id': installation.team.id,
|
||||
'team_name': installation.team.name,
|
||||
'state_token': req.session.state_token,
|
||||
'Token': installation.bot.token
|
||||
});
|
||||
let result = await bridgeRepository.save(bridge);
|
||||
}
|
||||
|
||||
|
||||
res.send(result);
|
||||
// check if we have an entity
|
||||
let bridge = await bridgeRepository.findOneBy({Token: installation.bot.token})
|
||||
let result
|
||||
if (!bridge){
|
||||
bridge = await bridgeRepository.create(bridge_data);
|
||||
await bridgeRepository.save(bridge);
|
||||
console.log('created bridge')
|
||||
} else {
|
||||
await bridgeRepository.update(
|
||||
{Token: installation.bot.token},
|
||||
{state_token: req.session.state_token})
|
||||
console.log('updated bridge')
|
||||
}
|
||||
res.send('<html><body><h1>Success! Return to the chatbridge login window.</h1><h3>This tab will close in 3 seconds...</h3><script>window.setTimeout(window.close, 3000)</script></body>')
|
||||
|
||||
},
|
||||
failure: (error, installOptions , req, res) => {
|
||||
res.send('failure');
|
||||
console.log(error, installOptions, req.body, req.content, req.query, req.params)
|
||||
res.send('failure. Something is broken about chatbridge :(');
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
await installer.handleCallback(req, res, callbackOptions);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
export const getChannelsHandler = async(
|
||||
req: Request,
|
||||
res: Response
|
||||
) => {
|
||||
let bridge = await bridgeRepository.findOneBy({state_token: req.session.state_token})
|
||||
console.log('bridge data', bridge)
|
||||
|
||||
try {
|
||||
fetch('https://slack.com/api/conversations.list', {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": `Bearer ${bridge.Token}`
|
||||
}
|
||||
}).then(result => result.json())
|
||||
.then((result) => {
|
||||
console.log('channels',res)
|
||||
let channels = result.channels.map(
|
||||
(chan: { name: string; }) => {
|
||||
return chan.name
|
||||
}
|
||||
);
|
||||
res.status(200).json({
|
||||
status: 'success',
|
||||
data: {
|
||||
channels
|
||||
}
|
||||
})
|
||||
})
|
||||
} catch {
|
||||
return res.status(502).json({
|
||||
status: 'failure',
|
||||
message: 'couldnt get lists!'
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -27,6 +27,10 @@ export class Bridge extends Model {
|
|||
@Column({nullable:true})
|
||||
team_name: string;
|
||||
|
||||
// Used to fetch the bridge data from the client while installing
|
||||
@Column()
|
||||
state_token: string;
|
||||
|
||||
@Column({
|
||||
unique: true
|
||||
})
|
||||
|
|
|
@ -2,6 +2,11 @@ import cookieSession from 'cookie-session';
|
|||
import config from 'config';
|
||||
import {Request, Response, NextFunction} from "express";
|
||||
import { tokenHasher, hashed_token } from "../auth";
|
||||
import {AppDataSource} from "../db/data-source";
|
||||
import {Bridge} from "../entities/bridge.entity";
|
||||
|
||||
const bridgeRepository = AppDataSource.getRepository(Bridge)
|
||||
|
||||
|
||||
const cookieConfig = config.get<{
|
||||
'key1': string,
|
||||
|
@ -33,3 +38,21 @@ export const requireAdmin = (req: Request, res: Response, next: NextFunction) =>
|
|||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const requireStateToken = async(req: Request, res: Response, next: NextFunction) => {
|
||||
if (req.session.state_token) {
|
||||
let bridge = await bridgeRepository.findOneBy({state_token: req.session.state_token})
|
||||
if (!bridge){
|
||||
return res.status(404).json({
|
||||
status: 'failure',
|
||||
message: 'No matching bridge found'
|
||||
})
|
||||
}
|
||||
next()
|
||||
} else {
|
||||
return res.status(403).json({
|
||||
status: 'failure',
|
||||
message: 'No state token found'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
20
server/src/routes/bridge.routes.ts
Normal file
20
server/src/routes/bridge.routes.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
import express from 'express';
|
||||
|
||||
import {
|
||||
getBridgeHandler,
|
||||
setBridgeHandler
|
||||
} from "../controllers/bridge.controller";
|
||||
|
||||
import {
|
||||
updateBridgeSchema
|
||||
} from "../schemas/bridge.schema";
|
||||
|
||||
import { validate } from "../middleware/validate";
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.route('/')
|
||||
.get(getBridgeHandler)
|
||||
.post(validate(updateBridgeSchema), setBridgeHandler)
|
||||
|
||||
export default router
|
|
@ -1,16 +1,24 @@
|
|||
import express from 'express';
|
||||
|
||||
import {
|
||||
SlackInstallHandler,
|
||||
SlackCallbackHandler
|
||||
SlackInstallLinkHandler,
|
||||
SlackCallbackHandler,
|
||||
getChannelsHandler
|
||||
} from '../controllers/slack.controller'
|
||||
|
||||
import {
|
||||
requireStateToken
|
||||
} from "../middleware/cookies";
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.route('/install')
|
||||
.get(SlackInstallHandler)
|
||||
.get(SlackInstallLinkHandler)
|
||||
|
||||
router.route('/oauth_redirect')
|
||||
.get(SlackCallbackHandler)
|
||||
|
||||
router.route('/channels')
|
||||
.get(requireStateToken, getChannelsHandler)
|
||||
|
||||
export default router
|
11
server/src/schemas/bridge.schema.ts
Normal file
11
server/src/schemas/bridge.schema.ts
Normal file
|
@ -0,0 +1,11 @@
|
|||
import { object, string, TypeOf } from 'zod';
|
||||
|
||||
export const updateBridgeSchema = object({
|
||||
body: object({
|
||||
Label: string({
|
||||
required_error: "Label for bridge required to update bridge"
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
export type UpdateBridgeInput = TypeOf<typeof updateBridgeSchema>['body'];
|
|
@ -1942,6 +1942,13 @@
|
|||
resolved "https://registry.yarnpkg.com/@mui/core-downloads-tracker/-/core-downloads-tracker-5.14.2.tgz#d8fcacdb1d37e621fce33ea808180fa5a590f908"
|
||||
integrity sha512-x+c/MgDL1t/IIy5lDbMlrDouFG5DYZbl3DP4dbbuhlpPFBnE9glYwmJEee/orVHQpOPwLxCAIWQs+2DKSaBVWQ==
|
||||
|
||||
"@mui/icons-material@^5.14.1":
|
||||
version "5.14.1"
|
||||
resolved "https://registry.yarnpkg.com/@mui/icons-material/-/icons-material-5.14.1.tgz#2f145c15047a0c7f01353ce620cb88276dadba9e"
|
||||
integrity sha512-xV/f26muQqtWzerzOIdGPrXoxp/OKaE2G2Wp9gnmG47mHua5Slup/tMc3fA4ZYUreGGrK6+tT81TEvt1Wsng8Q==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.22.6"
|
||||
|
||||
"@mui/material@^5.14.2":
|
||||
version "5.14.2"
|
||||
resolved "https://registry.yarnpkg.com/@mui/material/-/material-5.14.2.tgz#13b113489a61021145d62e0383912ca487a46375"
|
||||
|
|
Loading…
Reference in a new issue