diff --git a/src/boardService.js b/src/boardService.js new file mode 100644 index 0000000..bdc6aa6 --- /dev/null +++ b/src/boardService.js @@ -0,0 +1,89 @@ +const { startAuthentication } = require('./auth'); +const { fetchClosedTickets } = require('./tickets'); + +function createBoardService({ clientId, clientSecret, technicians, statusSet, windowMinutes, checkMinutes }) { + const state = { + auth: 'disconnected', + verificationUrl: null, + userCode: null, + lastCheck: null, + lastError: null + }; + + const tickets = new Map(); + let accessToken = null; + let pollingTimer = null; + + async function refreshTickets() { + if (!accessToken) return; + + await fetchClosedTickets({ + accessToken, + state, + tickets, + technicians, + statusSet, + windowMinutes + }); + } + + function startPolling() { + if (pollingTimer) return; + + pollingTimer = setInterval(() => { + refreshTickets().catch(err => { + state.lastError = err.message; + state.lastCheck = Date.now(); + }); + }, checkMinutes * 60 * 1000); + } + + async function connect() { + const authResult = await startAuthentication({ + clientId, + clientSecret, + state + }); + + if (!authResult?.accessToken) return; + + accessToken = authResult.accessToken; + console.log('Authorization successful'); + + await refreshTickets(); + startPolling(); + } + + function failConnection(err) { + state.auth = 'error'; + state.lastError = err.message; + state.verificationUrl = null; + state.userCode = null; + } + + function getState() { + return { + ...state, + windowMinutes, + checkMinutes, + watching: technicians, + serverTime: Date.now(), + tickets: [...tickets.values()].sort((a, b) => b.closedAt - a.closedAt) + }; + } + + async function checkNow() { + await refreshTickets(); + } + + return { + connect, + checkNow, + failConnection, + getState + }; +} + +module.exports = { + createBoardService +}; \ No newline at end of file diff --git a/src/routes.js b/src/routes.js new file mode 100644 index 0000000..c79f9f9 --- /dev/null +++ b/src/routes.js @@ -0,0 +1,37 @@ +const express = require('express'); + +function createRoutes(boardService) { + const router = express.Router(); + + router.get('/state', (req, res) => { + res.json(boardService.getState()); + }); + + router.post('/connect', async (req, res) => { + const currentState = boardService.getState(); + if (currentState.auth === 'pending' || currentState.auth === 'connected') { + return res.json({ ok: true }); + } + + try { + boardService.connect().catch(err => { + boardService.failConnection(err); + }); + res.json({ ok: true }); + } catch (err) { + boardService.failConnection(err); + res.status(500).json({ ok: false, error: err.message }); + } + }); + + router.post('/check', async (req, res) => { + await boardService.checkNow(); + res.json({ ok: true }); + }); + + return router; +} + +module.exports = { + createRoutes +}; \ No newline at end of file diff --git a/src/server.js b/src/server.js index 3ff533e..925bc2c 100644 --- a/src/server.js +++ b/src/server.js @@ -3,8 +3,8 @@ require('dotenv').config({ quiet: true }); const path = require('path'); const express = require('express'); -const { startAuthentication } = require('./auth'); -const { fetchClosedTickets } = require('./tickets'); +const { createBoardService } = require('./boardService'); +const { createRoutes } = require('./routes'); const { CLIENT_ID, CLIENT_SECRET, TECHNICIAN_NAMES, STATUS_NAMES } = process.env; const PORT = process.env.PORT || 3000; // change to 3000 after done testing using 3001 as I have a second server running to make sure it functions correctly @@ -21,95 +21,19 @@ const statusSet = new Set(statuses.map(s => s.toLowerCase())); const WINDOW_MINUTES = 10; const CHECK_MINUTES = 1; -let accessToken = null; - -const state = { - auth: 'disconnected', - verificationUrl: null, - userCode: null, - lastCheck: null, - lastError: null -}; - -const tickets = new Map(); - -async function connect() { - try { - const authResult = await startAuthentication({ - clientId: CLIENT_ID, - clientSecret: CLIENT_SECRET, - state - }); - - if (!authResult?.accessToken) return; - - accessToken = authResult.accessToken; - console.log('Authorization successful'); - - await fetchClosedTickets({ - accessToken, - state, - tickets, - technicians, - statusSet, - windowMinutes: WINDOW_MINUTES - }); - - setInterval(() => { - fetchClosedTickets({ - accessToken, - state, - tickets, - technicians, - statusSet, - windowMinutes: WINDOW_MINUTES - }); - }, CHECK_MINUTES * 60 * 1000); - } catch (err) { - state.auth = 'error'; - state.lastError = err.message; - state.verificationUrl = null; - state.userCode = null; - } -} - const app = express(); +const boardService = createBoardService({ + clientId: CLIENT_ID, + clientSecret: CLIENT_SECRET, + technicians, + statusSet, + windowMinutes: WINDOW_MINUTES, + checkMinutes: CHECK_MINUTES +}); + app.use(express.static(path.join(__dirname, 'public'))); -app.get('/api/state', (req, res) => { - res.json({ - ...state, - windowMinutes: WINDOW_MINUTES, - checkMinutes: CHECK_MINUTES, - watching: technicians, - serverTime: Date.now(), - tickets: [...tickets.values()].sort((a, b) => b.closedAt - a.closedAt) - }); -}); - -app.post('/api/connect', async (req, res) => { - if (state.auth === 'pending' || state.auth === 'connected') { - return res.json({ ok: true }); - } - try { - connect().catch(err => { - state.auth = 'error'; - state.lastError = err.message; - state.verificationUrl = null; - state.userCode = null; - }); - res.json({ ok: true }); - } catch (err) { - state.auth = 'error'; - state.lastError = err.message; - res.status(500).json({ ok: false, error: err.message }); - } -}); - -app.post('/api/check', async (req, res) => { - await fetchClosedTickets(); - res.json({ ok: true }); -}); +app.use('/api', createRoutes(boardService)); app.listen(PORT, () => { console.log(`Open http://localhost:${PORT}`);