Created
October 26, 2022 21:06
-
-
Save shawnco/cab58b64323de88fd1602fb144330dfa to your computer and use it in GitHub Desktop.
Time Management Web App section 6
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#nav { | |
list-style-type: none; | |
margin: 0; | |
padding: 0 0 10px 0; | |
} | |
#nav li { | |
display: inline; | |
margin-right: 10px | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {Outlet} from 'react-router-dom'; | |
function App() { | |
return ( | |
<div className="App"> | |
<Outlet /> | |
</div> | |
); | |
} | |
export default App; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {useState, useEffect} from 'react'; | |
import Datepicker from 'react-datepicker'; | |
import 'react-datepicker/dist/react-datepicker.css'; | |
const Timepicker = props => { | |
const {time} = props; | |
return <Datepicker | |
selected={time} | |
showTimeSelect | |
showTimeSelectOnly | |
timeIntervals={15} | |
timeCaption='Time' | |
dateFormat='h:mm aa' | |
onChange={props.handleChange} | |
/> | |
} | |
const FrequencyDropdown = props => { | |
const {frequency} = props; | |
const handleChange = e => { | |
props.handleFrequency(e.target.value); | |
} | |
return <select value={frequency} onChange={handleChange}> | |
<option value='daily'>Daily</option> | |
<option value='weekly'>Weekly</option> | |
<option value='monthly'>Monthly</option> | |
</select> | |
} | |
const Days = props => { | |
const weekdays = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'].map((d, i) => ({ | |
id: i, | |
day: d | |
})); | |
const monthdays = [...Array(31)].map((u, i) => ({ | |
id: i + 1, | |
day: (i + 1).toString() | |
})); | |
const {frequency, days} = props; | |
const handleChange = e => { | |
props.handleDayChange(+e.target.value); | |
} | |
if (frequency === 'daily') { | |
return null; | |
} else if (frequency === 'weekly') { | |
return weekdays.map(w => <div key={w.id}> | |
<input | |
type='checkbox' | |
value={w.id} | |
checked={days.includes(w.id)} | |
onChange={handleChange} | |
/> <label>{w.day}</label> | |
</div>); | |
} else if (frequency === 'monthly') { | |
return monthdays.map(m => <div key={m.id}> | |
<input | |
type='checkbox' | |
value={m.id} | |
checked={days.includes(m.id)} | |
onChange={handleChange} | |
/> <label>{m.day}</label> | |
</div>); | |
} | |
} | |
const CronDate = props => { | |
const {cron_string} = props; | |
const [frequency, setFrequency] = useState('daily'); | |
const [time, setTime] = useState(null); | |
const [days, setDays] = useState([]); | |
const handleFrequency = freq => setFrequency(freq); | |
const handleDayChange = day => { | |
const oldDays = [...days]; | |
const index = oldDays.findIndex(d => day === d); | |
if (index > -1) { | |
oldDays.splice(index, 1); | |
} else { | |
oldDays.push(day); | |
} | |
setDays(oldDays); | |
} | |
const handleChange = date => setTime(date); | |
const allAsterisk = chars => { | |
const asterisks = chars.filter(c => c === '*'); | |
return asterisks.length === chars.length; | |
} | |
const noneAsterisk = chars => { | |
const nonAsterisks = chars.filter(c => c !== '*'); | |
return nonAsterisks.length === chars.length; | |
} | |
useEffect(() => { | |
if (cron_string === '') { | |
const now = new Date(); | |
now.setHours(18); | |
now.setMinutes(0); | |
now.setSeconds(0); | |
now.setMilliseconds(0); | |
setTime(now); | |
} else { | |
const [minute, hour, ...rest] = cron_string.split(' '); | |
const now = new Date(); | |
now.setMinutes(minute); | |
now.setHours(hour); | |
setTime(now); | |
} | |
}, []); | |
useEffect(() => { | |
const {cron_string} = props; | |
if (cron_string === '') { | |
return; | |
} | |
const [a, b, c, d, e] = cron_string.split(' '); | |
let freq; | |
if (noneAsterisk([a, b]) && allAsterisk([c, d, e])) { | |
freq = 'daily'; | |
} else if (noneAsterisk([a, b, e]) && allAsterisk([c, d])) { | |
freq = 'weekly'; | |
} else if (noneAsterisk([a, b, c]) && allAsterisk([d, e])) { | |
freq = 'monthly'; | |
} else { | |
console.error(`Invalid format for cron string: ${cron_string}`); | |
} | |
if (freq) { | |
setFrequency(freq); | |
} | |
}, []); | |
useEffect(() => { | |
if (frequency === 'weekly') { | |
const weeklyDays = cron_string.split(' ')[4]; | |
if (weeklyDays === '*') { | |
setDays([0, 1, 2, 3, 4, 5, 6]); | |
} else if (weeklyDays.indexOf(',') > -1) { | |
setDays(weeklyDays.split(',').map(d => +d)); | |
} else { | |
setDays([+weeklyDays]); | |
} | |
} else if (frequency === 'monthly') { | |
const monthlyDays = cron_string.split(' ')[2]; | |
if (monthlyDays === '*') { | |
setDays([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, | |
11, 12, 13, 14, 15, 16, 17, 18, 19, 20, | |
21, 22, 23, 24, 25, 26, 27, 28, 29, 30, | |
31]); | |
} else if (monthlyDays.indexOf(',') > -1) { | |
setDays(monthlyDays.split(',').map(d => +d)); | |
} else { | |
setDays([+monthlyDays]); | |
} | |
} | |
}, [frequency]); | |
useEffect(() => { | |
if (time) { | |
const minute = time.getMinutes(); | |
const hour = time.getHours(); | |
let output = `${minute} ${hour} `; | |
if (frequency === 'daily') { | |
output += '* * *'; | |
} else if (frequency === 'weekly') { | |
output += `* * ${days.join(',')}`; | |
} else if (frequency === 'monthly') { | |
output += `${days.join(',')} * *`; | |
} | |
props.saveNewCronString(output); | |
} | |
}, [frequency, days, time]); | |
return <> | |
<td><FrequencyDropdown frequency={frequency} handleFrequency={handleFrequency} /></td> | |
<td><Days frequency={frequency} days={days} handleDayChange={handleDayChange} /></td> | |
<td><Timepicker time={time} handleChange={handleChange} /></td> | |
</> | |
} | |
export default CronDate; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {useEffect, useState} from 'react'; | |
const url = 'http://localhost:3001/api/task/today'; | |
const headers = { | |
'Content-Type': 'applicaton/json' | |
}; | |
const API = { | |
async getToday() { | |
const res = await fetch(`${url}`, { | |
method: 'GET', | |
headers | |
}); | |
const {result} = await res.json(); | |
return result; | |
} | |
} | |
const TaskRow = props => { | |
const {task} = props; | |
return <tr> | |
<td>{task.name}</td> | |
<td>{task.cron_string}</td> | |
</tr> | |
} | |
const Home = props => { | |
const [tasks, setTasks] = useState([]); | |
useEffect(() => { | |
async function getTasks() { | |
const data = await API.getToday(); | |
setTasks(data); | |
} | |
getTasks(); | |
}, []); | |
return <> | |
<h1>Today's Incomplete Tasks</h1> | |
<table border='1'> | |
<thead> | |
<tr> | |
<th>Name</th> | |
<th>Cron String</th> | |
</tr> | |
</thead> | |
<tbody> | |
{tasks.map(t => <TaskRow key={t.id} task={t} />)} | |
</tbody> | |
</table> | |
</> | |
} | |
export default Home; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from 'react'; | |
import ReactDOM from 'react-dom/client'; | |
import {BrowserRouter, Routes, Route} from 'react-router-dom'; | |
import App from './App'; | |
import TaskTable from './tasks'; | |
import Workspace from './workspace'; | |
import Home from './home'; | |
import Nav from './nav'; | |
const root = ReactDOM.createRoot(document.getElementById('root')); | |
root.render( | |
<React.StrictMode> | |
<BrowserRouter> | |
<Nav /> | |
<Routes> | |
<Route path='/' element={<App />}> | |
<Route path='' element={<Home />} /> | |
<Route path='tasks' element={<TaskTable />} /> | |
<Route path='workspace' element={<Workspace />} /> | |
</Route> | |
</Routes> | |
</BrowserRouter> | |
</React.StrictMode> | |
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {useState} from 'react'; | |
const url = 'http://localhost:3001/api/task'; | |
const headers = { | |
'Content-Type': 'application/json' | |
}; | |
const API = { | |
async create(task, content) { | |
const res = await fetch(`${url}/${task}/notes`, { | |
method: 'POST', | |
headers, | |
body: JSON.stringify({ | |
task, | |
content, | |
time: new Date() | |
}) | |
}); | |
const {result} = await res.json(); | |
return result; | |
}, | |
async update(task, body) { | |
const res = await fetch(`${url}/${task}/notes`, { | |
method: 'PUT', | |
headers, | |
body: JSON.stringify(body) | |
}); | |
const {result} = await res.json(); | |
return result; | |
}, | |
async del(task, id) { | |
const res = await fetch(`${url}/${task}/notes`, { | |
method: 'DELETE', | |
headers, | |
body: JSON.stringify({id}) | |
}); | |
const {result} = await res.json(); | |
return result; | |
} | |
}; | |
const NewNote = props => { | |
const [content, setContent] = useState(''); | |
const handleChange = e => setContent(e.target.value); | |
const save = async e => { | |
const result = await API.create(props.task, content); | |
props.handleSave(result); | |
setContent(''); | |
} | |
return <tr> | |
<td></td> | |
<td><textarea cols={40} rows={10} value={content} onChange={handleChange} /></td> | |
<td><button onClick={save}>Save</button></td> | |
<td></td> | |
</tr> | |
} | |
const NoteRow = props => { | |
const [content, setContent] = useState(props.note.content); | |
const handleChange = e => setContent(e.target.value); | |
const update = async e => { | |
const {task} = props; | |
const result = await API.update(task, {id: props.note.id, content}); | |
props.handleUpdate(result); | |
} | |
const del = async e => { | |
if (window.confirm('Delete note?')) { | |
const {task} = props; | |
const result = await API.del(task, props.note.id); | |
props.handleDelete(props.note.id); | |
} | |
} | |
return <tr> | |
<td>{(new Date(props.note.time)).toLocaleString()}</td> | |
<td><textarea cols={40} rows={10} value={content} onChange={handleChange} /></td> | |
<td><button onClick={update}>Update</button></td> | |
<td><button onClick={del}>Delete</button></td> | |
</tr> | |
} | |
const TaskNotes = props => { | |
const [notes, setNotes] = useState(props.notes); | |
const handleSave = note => { | |
setNotes([...notes, note]) | |
} | |
const handleUpdate = note => { | |
console.log('nooooote',note) | |
const oldNotes = [...notes]; | |
const find = oldNotes.find(n => n.id == note.id); | |
if (find) { | |
find.content = note.content; | |
} | |
setNotes(oldNotes); | |
} | |
const handleDelete = id => { | |
const oldNotes = [...notes]; | |
const index = oldNotes.findIndex(n => n.id == id); | |
if (index > -1) { | |
oldNotes.splice(index, 1); | |
} | |
setNotes(oldNotes); | |
} | |
return <table border='1'> | |
<thead> | |
<tr> | |
<th>Date/Time</th> | |
<th>Content</th> | |
<th>Update</th> | |
<th>Delete</th> | |
</tr> | |
</thead> | |
<tbody> | |
{notes.map(n => <NoteRow | |
task={props.task} | |
key={n.id} | |
note={n} | |
handleUpdate={handleUpdate} | |
handleDelete={handleDelete} | |
/>)} | |
<NewNote task={props.task} handleSave={handleSave} /> | |
</tbody> | |
</table> | |
} | |
export default TaskNotes; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {useState, useEffect} from 'react'; | |
import {isValidCron} from 'cron-validator'; | |
import CronDate from './cron_date'; | |
const url = 'http://localhost:3001/api/task'; | |
const headers = { | |
'Content-Type': 'application/json' | |
}; | |
const API = { | |
async getAll() { | |
const res = await fetch(`${url}/all`, {method: 'GET', headers}); | |
const {result} = await res.json(); | |
return result; | |
}, | |
async get(id) { | |
const res = await fetch(`${url}/${id}`, {method: 'GET',headers}); | |
const {result} = await res.json(); | |
return result; | |
}, | |
async create(body) { | |
const res = await fetch(url, {method: 'POST', headers, body: JSON.stringify(body)}); | |
const {result} = await res.json(); | |
return result; | |
}, | |
async update(id, body) { | |
const res = await fetch(`${url}/${id}`, {method: 'PUT',headers, body:JSON.stringify(body)}); | |
const {result} = await res.json(); | |
return result; | |
}, | |
async del(id) { | |
const res = await fetch(`${url}/${id}`, {method: 'DELETE', headers}); | |
const {result} = await res.json(); | |
return result; | |
} | |
} | |
const NewTask = props => { | |
const [task, setTask] = useState({name: '', cron_string: '', completed: false}); | |
const handleChange = e => setTask({...task, [e.target.name]:e.target.value}); | |
const saveNewCronString = cron => setTask({...task, cron_string: cron}); | |
const save = async e => { | |
const result = await API.create(task); | |
props.handleSave(result); | |
setTask({name: '', cron_string: '', completed: false}); | |
} | |
return <tr> | |
<td></td> | |
<td><input type='text' name='name' value={task.name} onChange={handleChange} /></td> | |
<CronDate cron_string={task.cron_string} saveNewCronString={saveNewCronString} /> | |
<td><button onClick={save} disabled={!task.name || !isValidCron(task.cron_string)}>Save</button></td> | |
<td></td> | |
</tr> | |
} | |
const TaskRow = props => { | |
const [task, setTask] = useState(props.task); | |
const [newCronString, setNewCronString] = useState(props.task.cron_string); | |
const handleClick = e => setTask({...task, completed: !task.completed}); | |
const handleChange = e => setTask({...task, [e.target.name]: e.target.value}); | |
const saveNewCronString = cron => setNewCronString(cron); | |
const update = async e => { | |
const {id, ...rest} = task; | |
rest.cron_string = newCronString; | |
const result = await API.update(id, rest); | |
props.handleUpdate(id, result); | |
} | |
const del = async e => { | |
if(window.confirm(`Delete task: ${task.name}?`)) { | |
const result = await API.del(task.id); | |
props.handleDelete(task.id); | |
} | |
} | |
return <tr> | |
<td><input type='checkbox' name='completed' checked={task.completed} onChange={handleClick} /></td> | |
<td><input type='text' name='name' value={task.name} onChange={handleChange} /></td> | |
<CronDate cron_string={task.cron_string} saveNewCronString={saveNewCronString} /> | |
<td><button onClick={update} disabled={!task.name || !isValidCron(task.cron_string)}>Update</button></td> | |
<td><button onClick={del}>Delete</button></td> | |
</tr> | |
} | |
const TaskTable = props => { | |
const [tasks, setTasks] = useState([]); | |
const handleSave = task => { | |
setTasks([...tasks, task]); | |
} | |
const handleUpdate = (id, task) => { | |
const oldTasks = [...tasks]; | |
const find = oldTasks.find(t => t.id == id); | |
if (find) { | |
find.completed = task.completed; | |
find.name = task.name; | |
find.cron_string = task.cron_string; | |
} | |
setTasks(oldTasks); | |
} | |
const handleDelete = id => { | |
const oldTasks = [...tasks]; | |
const index = oldTasks.findIndex(t => t.id == id); | |
if (index > -1) { | |
oldTasks.splice(index, 1); | |
} | |
setTasks(oldTasks); | |
} | |
useEffect(() => { | |
async function getTasks() { | |
const data = await API.getAll(); | |
setTasks(data); | |
} | |
getTasks(); | |
}, []); | |
return <table border='1'> | |
<thead> | |
<tr> | |
<th>Completed</th> | |
<th>Name</th> | |
<th>Frequency</th> | |
<th>Days</th> | |
<th>Time</th> | |
<th>Edit</th> | |
<th>Delete</th> | |
</tr> | |
</thead> | |
<tbody> | |
{tasks.map(t => <TaskRow key={t.id} task={t} handleUpdate={handleUpdate} handleDelete={handleDelete} />)} | |
<NewTask handleSave={handleSave} /> | |
</tbody> | |
</table> | |
} | |
export default TaskTable; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {useState, useEffect} from 'react'; | |
import TaskNotes from './task_notes'; | |
const url = 'http://localhost:3001/api/task'; | |
const headers = { | |
'Content-Type': 'application/json' | |
}; | |
const API = { | |
async update(id, body) { | |
const res = await fetch(`${url}/${id}`, {method: 'PUT', headers, body: JSON.stringify(body)}); | |
const {result} = await res.json(); | |
return result; | |
} | |
} | |
const Workspace = props => { | |
const [task, setTask] = useState({}); | |
const [notes, setNotes] = useState([]); | |
useEffect(() => { | |
const socket = new WebSocket('ws://localhost:8080'); | |
socket.onmessage = e => { | |
try { | |
const data = JSON.parse(e.data); | |
setTask(data.task); | |
setNotes(data.taskNotes); | |
} catch (e) { | |
console.log(e); | |
} | |
} | |
}, []); | |
const handleClick = async e => { | |
const {id, completed} = task; | |
const result = await API.update(id, {completed: !completed}); | |
setTask({...task, completed: !completed}); | |
}; | |
if (task.id) { | |
return <> | |
<table border='1'> | |
<thead> | |
<tr> | |
<th>Completed</th> | |
<th>Name</th> | |
</tr> | |
</thead> | |
<tbody> | |
<tr> | |
<td><input type='checkbox' name='completed' checked={task.completed} onChange={handleClick} /></td> | |
<td>{task.name}</td> | |
</tr> | |
</tbody> | |
</table> | |
<TaskNotes task={task.id} notes={notes} /> | |
</> | |
} else { | |
return <b>Waiting for a task to start</b> | |
} | |
} | |
export default Workspace; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const express = require('express'); | |
const cors = require('cors'); | |
const bodyParser = require('body-parser'); | |
const {Sequelize} = require('sequelize'); | |
const path = require('path'); | |
const TaskSchedule = require('./task_schedule'); | |
const Socket = require('./socket'); | |
const db = new Sequelize('task_schedule', 'username', 'password', { | |
host: 'localhost', | |
dialect: 'mysql', | |
operatorAliases: false, | |
pool: { max: 5, min: 0, acquire: 30000, idle: 10000 } | |
}); | |
const Task = db.define('task', { | |
id: { | |
type: Sequelize.INTEGER, | |
primaryKey: true, | |
autoIncrement: true | |
}, | |
name: Sequelize.STRING, | |
cron_string: Sequelize.STRING, | |
completed: Sequelize.BOOLEAN | |
}, { | |
freezeTableName: true, | |
timestamps: false | |
}); | |
const TaskNote = db.define('task_note', { | |
id: { | |
type: Sequelize.INTEGER, | |
primaryKey: true, | |
autoIncrement: true | |
}, | |
task: Sequelize.INTEGER, | |
time: Sequelize.DATE, | |
content: Sequelize.TEXT | |
}, { | |
freezeTableName: true, | |
timestamps: false | |
}); | |
const TaskFunctions = { | |
async getAll() { | |
return Task.findAll(); | |
}, | |
async get(id) { | |
return Task.findByPk(id); | |
}, | |
async create(data) { | |
const task = Task.create({ | |
name: data.name, | |
cron_string: data.cron_string | |
}); | |
return task; | |
}, | |
async update(id, data) { | |
const task = await Task.findByPk(id); | |
task.name = data.name || task.name; | |
task.cron_string = data.cron_string || task.cron_string; | |
task.completed = [true, false].includes(data.completed) ? data.completed : task.completed; | |
await task.save(); | |
return task; | |
}, | |
async del(id) { | |
const task = await Task.destroy({where: {id}}); | |
return id; | |
}, | |
async getIncomplete() { | |
return Task.findAll({where: {completed: false}}); | |
}, | |
async getByIds(ids) { | |
return Task.findAll({where:{id: ids}}); | |
}, | |
} | |
const TaskNoteFunctions = { | |
async getByTask(task) { | |
return TaskNote.findAll({where:{task}}); | |
}, | |
async create(data) { | |
return TaskNote.create({ | |
task: data.task, | |
time: data.time, | |
content: data.content | |
}); | |
}, | |
async update(id, data) { | |
const note = await TaskNote.findByPk(id); | |
note.content = data.content; | |
return note.save(); | |
}, | |
async del(id) { | |
const note = await TaskNote.destroy({where:{id}}); | |
return id; | |
} | |
} | |
const socket = new Socket(TaskNoteFunctions); | |
const taskSchedule = new TaskSchedule(TaskFunctions, socket); | |
taskSchedule.rebuildSchedule(); | |
const PORT = 3001; | |
const app = express(); | |
app.use(cors()); | |
app.use(bodyParser.json()); | |
app.use(express.static(path.join(__dirname + '/../client'))); | |
app.get('/api/test', (req, res) => { | |
res.send('Hello world!'); | |
}); | |
app.get('/api/task/today', async (req,res)=> { | |
const start = new Date(); | |
start.setHours(0); | |
start.setMinutes(0); | |
start.setSeconds(0); | |
start.setMilliseconds(0); | |
const end = new Date(); | |
end.setHours(23); | |
end.setMinutes(59); | |
end.setSeconds(59); | |
end.setMilliseconds(999); | |
const ids = taskSchedule.getTasksForRange(start.getTime(), end.getTime()); | |
const result = await TaskFunctions.getByIds(ids); | |
res.send({result}); | |
}); | |
app.get('/api/task/all', async (req, res) => { | |
const result = await TaskFunctions.getAll(); | |
res.send({result}); | |
}); | |
app.get('/api/task/:id', async (req, res) => { | |
const result = await TaskFunctions.get(req.params.id); | |
res.send({result}); | |
}); | |
app.post('/api/task', async (req,res) => { | |
const result = await TaskFunctions.create(req.body); | |
await taskSchedule.rebuildSchedule(); | |
res.send({result}); | |
}); | |
app.put('/api/task/:id', async (req, res) => { | |
const result = await TaskFunctions.update(req.params.id, req.body); | |
await taskSchedule.rebuildSchedule(); | |
res.send({result}); | |
}); | |
app.delete('/api/task/:id', async (req,res) => { | |
const result = await TaskFunctions.del(+req.params.id); | |
await taskSchedule.rebuildSchedule(); | |
res.send({result}); | |
}); | |
app.get('/api/task/:id/notes', async (req, res) => { | |
const result = await TaskNoteFunctions.getByTask(+req.params.id); | |
res.send({result}); | |
}); | |
app.post('/api/task/:id/notes', async (req, res) => { | |
const result = await TaskNoteFunctions.create({ | |
id: req.params.id, | |
...req.body | |
}); | |
res.send({result}); | |
}); | |
app.put('/api/task/:id/notes', async (req, res) => { | |
const {id, ...data} = req.body; | |
const result = await TaskNoteFunctions.update(id, data); | |
res.send({result}) | |
}); | |
app.delete('/api/task/:id/notes', async (req, res) => { | |
const {id} = req.body; | |
const result = await TaskNoteFunctions.del(id); | |
res.send({result}) | |
}); | |
app.listen(PORT, () => { | |
console.log(`App is live on port ${PORT}`); | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const {WebSocketServer, OPEN} = require('ws'); | |
class Socket { | |
constructor(taskNoteFns) { | |
this.taskNoteFns = taskNoteFns; | |
this.wss = new WebSocketServer({port: 8080}); | |
this.wss.on('connection', ws => console.log('Connected!')); | |
} | |
async sendTask(task) { | |
const taskNotes = await this.taskNoteFns.getByTask(task.id); | |
this.wss.clients.forEach(client => { | |
if (client.readyState === OPEN) { | |
const data = { | |
type: 'task_start', | |
task, | |
taskNotes | |
}; | |
client.send(JSON.stringify(data)); | |
} | |
}); | |
} | |
} | |
module.exports = Socket; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const schedule = require('node-schedule'); | |
class TaskSchedule { | |
constructor(taskFns, socket) { | |
this.taskFns = taskFns; | |
this.socket = socket; | |
process.on('SIGINT', async () => { | |
await schedule.gracefulShutdown(); | |
process.exit(0); | |
}); | |
} | |
addTask(task) { | |
const {id, cron_string} = task; | |
const job = schedule.scheduleJob(`job:${id}`, cron_string, () => { | |
this.socket.sendTask(task); | |
}); | |
return job; | |
} | |
async populateTasks() { | |
const tasks = await this.taskFns.getIncomplete(); | |
return tasks.map(t => this.addTask(t)); | |
} | |
clearTasks() { | |
Object.keys(schedule.scheduledJobs).map(k => { | |
const job = schedule.scheduledJobs[k]; | |
job.cancel(); | |
}); | |
} | |
async rebuildSchedule() { | |
this.clearTasks(); | |
await this.populateTasks(); | |
} | |
getTasksForRange(start, end) { | |
const todayTasks = []; | |
Object.keys(schedule.scheduledJobs).map(k => { | |
const job = schedule.scheduledJobs[k]; | |
const next = job.nextInvocation().toDate().getTime(); | |
if (next >= start && next <= end) { | |
const id = +job.name.split(':')[1]; | |
todayTasks.push(id); | |
} | |
}); | |
return todayTasks; | |
} | |
} | |
module.exports = TaskSchedule; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment