TODOList
/
main.js
323 строки · 10.6 Кб
1// Description: Main file for start server
2// This file is part of the "Todo app" project
3// Author: ivanvit100 @ GitHub
4// Licence: MIT
5
6const { app, BrowserWindow } = require('electron');7const fs = require('fs');8const os = require('os');9const path = require('path');10const session = require('express-session');11const express = require('express');12const bodyParser = require('body-parser');13
14// TODO: logging
15
16let user;17let programFilesPath;18let lang;19let clientLang;20
21// Prepare directories and files
22// If the directories and files are not found, they will be created
23// Input: none
24// Output: none
25if (os.platform() === 'win32')26programFilesPath = 'C:\\Program Files\\.todo\\';27else if (os.platform() === 'linux')28programFilesPath = path.join(os.homedir(), '.todo/');29
30const filePath = path.join(programFilesPath, 'taskManager/default.json');31const fileDir = path.dirname(filePath);32if (!fs.existsSync(programFilesPath))33fs.mkdirSync(programFilesPath, { recursive: true });34if (!fs.existsSync(fileDir))35fs.mkdirSync(fileDir, { recursive: true });36
37if (!fs.existsSync(filePath))38fs.writeFileSync(filePath, '{"data":[{"name":"GitHub","done":false,"description":"https://github.com/ivanvit100/TODOList","date":null,"lvl":9}]}', 'utf8');39
40// Load config or create new one
41// Input: config.json
42// Output: user = {
43// 'color-date-alert': bool,
44// 'lang': string,
45// 'rewrite-config': bool,
46// 'login': string,
47// 'password': string,
48// 'key': string
49// }
50try {51const data = JSON.parse(fs.readFileSync(path.join(programFilesPath, 'config.json')));52user = data['todo'];53if (!['color-date-alert', 'lang', 'rewrite-config', 'login', 'password', 'key'].every(key => key in user)) {54throw new Error();55}56} catch (error) {57user = {58'color-date-alert': true,59'lang': 'ru',60'rewrite-config': true,61'login': 'admin',62'password': 'admin',63'key': 'secret'64};65fs.writeFileSync(path.join(programFilesPath, 'config.json'), JSON.stringify({todo: user}));66console.error('Error reading config file, default settings are used');67}
68
69// Load language file
70// If the file is not found, the default language will be used
71// Default language is "ru"
72// Input: /server/langs/lang.{user["lang"]}.json
73// Output: lang = {...}
74try {75lang = JSON.parse(fs.readFileSync(`${__dirname}/server/langs/lang.${user["lang"]}.json`));76clientLang = JSON.parse(fs.readFileSync(`${__dirname}/server/langs/client.${user["lang"]}.json`));77} catch (error) {78lang = JSON.parse(fs.readFileSync(`${__dirname}/server/langs/lang.ru.json`));79clientLang = JSON.parse(fs.readFileSync(`${__dirname}/server/langs/client.ru.json`));80console.error('Error reading lang file, default settings are used');81}
82
83// Create an express application
84// Use the express.json() middleware to parse JSON data
85var encoder = bodyParser.json();86const expressApp = express();87expressApp.use(express.json());88expressApp.use(session({89secret: user['key'],90resave: false,91saveUninitialized: true92}));93
94// Path to static files
95// Input: /public/{filename} (string)
96// Output: {filename} (file))
97expressApp.get('/public/:filename', encoder, (req, res) => {98res.sendFile(path.join(__dirname, 'public', req.params.filename), err => {99if (err) {100console.error(`[path]: Error reading file "${req.params.filename}"`);101res.sendFile(path.join(__dirname, 'public', '404.html'));102}103});104});105
106// Function to send config to the client
107// Automatically excludes login and password from issuance
108// Input: none
109// Output: status: string, message: object
110expressApp.post('/api/config', encoder, (req, res) => {111const data = {112'color-date-alert': user['color-date-alert'],113'lang': clientLang114};115const response = {116'status': 'success',117'login': req.session && req.session.login ? req.session.login : false,118'message': data119};120res.json(response);121});122
123// Main user authentification
124// Called by the client for the purpose of initial verification
125// and further request of taskList's list
126// Input: login: string, password: string
127// Output: status: string, message: string
128// "status" is an additional class of the notification window on the client side
129// It can be "success", "error" or "info"
130// "message" is the text of the notification window on the client side
131expressApp.post('/api/auth', encoder, (req, res) => {132const data = req.body;133let response;134if ((data['login'] == user['login'] && data['password'] == user['password']) || (user['login'].length == 0) || getCookie(req, 'login')){135res.setHeader('Set-Cookie', `login=${data['login']}; Max-Age=900000; HttpOnly; SameSite=None; Secure`);136response = {137'status': 'success',138'message': lang['auth-success']139};140} else if(data['login'].length != 0) {141response = {142'status': 'error',143'message': lang['auth-error']144};145}146res.send(response);147});148
149// Get data of taskList (array of tasks)
150// Input: login: string, password: string, taskList: string
151// Output: status: string, data: array | message: string
152// login and password are used for additional verification
153expressApp.post('/api/getTaskList', (req, res) => {154const requestData = req.body;155let response;156if (getCookie(req, 'login')) {157try {158const data = JSON.parse(fs.readFileSync(path.join(programFilesPath, `taskManager/${requestData["taskList"]}.json`)));159response = {160'status': 'success',161'message': data162};163} catch (error) {164console.error(`[getTaskList]: Error reading file "${requestData["taskList"]}.json"`);165response = {166'status': 'error',167'message': lang['file-error']168};169}170} else {171response = {172'status': 'error',173'message': lang['auth-error']174};175console.warn('[getTaskList]: Error authentication');176}177res.json(response);178});179
180// Get list of taskLists (array of taskLists)
181// Input: login: string, password: string
182// Output: status: string, data: array | message: string
183// login and password are used for additional verification
184expressApp.post('/api/getTaskListList', (req, res) => {185let response;186if(getCookie(req, 'login')){187try {188const files = fs.readdirSync(path.join(programFilesPath, 'taskManager'));189const data = files.filter(file => file.endsWith('.json')).map(file => path.basename(file, '.json'));190response = {191'status': 'success',192'message': data193};194} catch (error) {195console.error('[getTaskListList]: Error reading files');196response = {197'status': 'error',198'message': lang['file-error']199};200}201} else {202response = {203'status': 'error',204'message': lang['auth-error']205};206console.warn('[getTaskListList]: Error authentication');207}208res.json(response);209});210
211// Save data of taskList (array of tasks)
212// Can be used for updating information about one task
213// or for creating a new taskList
214// Input: login: string, password: string, taskList: string, data: object
215// Output: status: string, message: string
216expressApp.post('/api/saveTaskList', (req, res) => {217const requestData = req.body;218let response;219console.log(requestData["data"]);220if (getCookie(req, 'login')) {221try {222fs.writeFileSync(`${programFilesPath}/taskManager/${requestData["taskList"]}.json`, JSON.stringify(requestData["data"]));223response = {224'status': 'success',225'message': lang['save-success']226};227} catch (error) {228console.error(`[saveTaskList]: Error saving file "${requestData["taskList"]}.json because of "${error}"`);229response = {230'status': 'error',231'message': lang['save-error']232};233}234} else {235response = {236'status': 'error',237'message': lang['auth-error']238};239console.warn('[saveTaskList]: Error authentication');240}241res.json(response);242});243
244// Function to delete taskList
245// Its will delete /taskManager/{taskList}.json
246// Input: login: string, password: string, taskList: string
247// Output: status: string, message: string
248expressApp.post('/api/deleteList', (req, res) => {249const data = req.body;250let response;251if (req.session && req.session.login === user['login']) {252const taskList = data['taskList'];253try {254fs.unlinkSync(path.join(programFilesPath,`taskManager/${taskList}.json`));255response = {256'status': 'success',257'message': lang['delete-success']258};259} catch (error) {260console.error(`[deleteList]: Error deleting file "${taskList}.json"`);261response = {262'status': 'error',263'message': lang['delete-error']264};265}266} else {267response = {268'status': 'error',269'message': lang['auth-error']270};271console.warn('[deleteList]: Error authentication');272}273res.json(response);274});275
276// Path responsible for non-existent routes
277// Input: none
278// Output: 404.html
279expressApp.get('*', (req, res) => {280console.warn(`[path]: Unknown path "${req.path}" catched`);281res.sendFile(path.join(__dirname, 'public', '404.html'));282});283
284// Creating an application and launching a viewer window
285// The server is started on port 3000
286// Input: none
287// Output: none
288const server = expressApp.listen(3000, () => console.log('Server started on port 3000'));289app.whenReady().then(() => {290const window = new BrowserWindow({291width: 1280,292height: 720,293webPreferences: {294nodeIntegration: true,295contextIsolation: false,296nativeWindowOpen: true,297webviewTag: true,298enableRemoteModule: true299}300});301
302window.loadFile("./public/index.html");303});304
305// Closing the application
306// Input: none
307// Output: none
308app.on('window-all-closed', () => {309server.close();310app.quit();311});312
313// Function to get cookie and validate it
314// Input: req: object, name: string
315// Output: bool
316getCookie = (req, name) => {317const cookies = req.headers.cookie ? req.headers.cookie.split('; ').reduce((prev, current) => {318const [name, value] = current.split('=');319prev[name] = value;320return prev;321}, {}) : {};322return cookies[name] == user[name];323}