diff --git a/.gitignore b/.gitignore index 98ec862..147804b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ node_modules data public +!client/public build.sh \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c870d1..afd7297 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +### v1.7.4 (TBA) +- Added iOS "Add to homescreen" icon ([#131](https://github.com/pawelmalak/flame/issues/131)) + ### v1.7.3 (2021-10-28) - Fixed bug with custom CSS not updating diff --git a/client/public/icons/apple-touch-icon-114x114.png b/client/public/icons/apple-touch-icon-114x114.png new file mode 100644 index 0000000..301cd25 Binary files /dev/null and b/client/public/icons/apple-touch-icon-114x114.png differ diff --git a/client/public/icons/apple-touch-icon-120x120.png b/client/public/icons/apple-touch-icon-120x120.png new file mode 100644 index 0000000..28ba56d Binary files /dev/null and b/client/public/icons/apple-touch-icon-120x120.png differ diff --git a/client/public/icons/apple-touch-icon-144x144.png b/client/public/icons/apple-touch-icon-144x144.png new file mode 100644 index 0000000..f13012b Binary files /dev/null and b/client/public/icons/apple-touch-icon-144x144.png differ diff --git a/client/public/icons/apple-touch-icon-152x152.png b/client/public/icons/apple-touch-icon-152x152.png new file mode 100644 index 0000000..e1a5b1d Binary files /dev/null and b/client/public/icons/apple-touch-icon-152x152.png differ diff --git a/client/public/icons/apple-touch-icon-180x180.png b/client/public/icons/apple-touch-icon-180x180.png new file mode 100644 index 0000000..33d6131 Binary files /dev/null and b/client/public/icons/apple-touch-icon-180x180.png differ diff --git a/client/public/icons/apple-touch-icon-57x57.png b/client/public/icons/apple-touch-icon-57x57.png new file mode 100644 index 0000000..b07d1da Binary files /dev/null and b/client/public/icons/apple-touch-icon-57x57.png differ diff --git a/client/public/icons/apple-touch-icon-72x72.png b/client/public/icons/apple-touch-icon-72x72.png new file mode 100644 index 0000000..0ebf2c8 Binary files /dev/null and b/client/public/icons/apple-touch-icon-72x72.png differ diff --git a/client/public/icons/apple-touch-icon-76x76.png b/client/public/icons/apple-touch-icon-76x76.png new file mode 100644 index 0000000..d636fe9 Binary files /dev/null and b/client/public/icons/apple-touch-icon-76x76.png differ diff --git a/client/public/icons/apple-touch-icon.png b/client/public/icons/apple-touch-icon.png new file mode 100644 index 0000000..b07d1da Binary files /dev/null and b/client/public/icons/apple-touch-icon.png differ diff --git a/client/public/favicon.ico b/client/public/icons/favicon.ico similarity index 100% rename from client/public/favicon.ico rename to client/public/icons/favicon.ico diff --git a/client/public/index.html b/client/public/index.html index c93d95e..32e17fe 100644 --- a/client/public/index.html +++ b/client/public/index.html @@ -2,7 +2,51 @@ - + + + + + + + + + + { + const { pinCategoriesByDefault: pinCategories } = await loadConfig(); + + let category; + + if (pinCategories) { + category = await Category.create({ + ...req.body, + isPinned: true, + }); + } else { + category = await Category.create(req.body); + } + + res.status(201).json({ + success: true, + data: category, + }); +}); + +module.exports = createCategory; diff --git a/controllers/categories/deleteCategory.js b/controllers/categories/deleteCategory.js new file mode 100644 index 0000000..e9b004b --- /dev/null +++ b/controllers/categories/deleteCategory.js @@ -0,0 +1,45 @@ +const asyncWrapper = require('../../middleware/asyncWrapper'); +const ErrorResponse = require('../../utils/ErrorResponse'); +const Category = require('../../models/Category'); +const Bookmark = require('../../models/Bookmark'); + +// @desc Delete category +// @route DELETE /api/categories/:id +// @access Public +const deleteCategory = asyncWrapper(async (req, res, next) => { + const category = await Category.findOne({ + where: { id: req.params.id }, + include: [ + { + model: Bookmark, + as: 'bookmarks', + }, + ], + }); + + if (!category) { + return next( + new ErrorResponse( + `Category with id of ${req.params.id} was not found`, + 404 + ) + ); + } + + category.bookmarks.forEach(async (bookmark) => { + await Bookmark.destroy({ + where: { id: bookmark.id }, + }); + }); + + await Category.destroy({ + where: { id: req.params.id }, + }); + + res.status(200).json({ + success: true, + data: {}, + }); +}); + +module.exports = deleteCategory; diff --git a/controllers/categories/getAllCategories.js b/controllers/categories/getAllCategories.js new file mode 100644 index 0000000..597bfcc --- /dev/null +++ b/controllers/categories/getAllCategories.js @@ -0,0 +1,43 @@ +const asyncWrapper = require('../../middleware/asyncWrapper'); +const Category = require('../../models/Category'); +const Bookmark = require('../../models/Bookmark'); +const { Sequelize } = require('sequelize'); +const loadConfig = require('../../utils/loadConfig'); + +// @desc Get all categories +// @route GET /api/categories +// @access Public +const getAllCategories = asyncWrapper(async (req, res, next) => { + const { useOrdering: orderType } = await loadConfig(); + + let categories; + + if (orderType == 'name') { + categories = await Category.findAll({ + include: [ + { + model: Bookmark, + as: 'bookmarks', + }, + ], + order: [[Sequelize.fn('lower', Sequelize.col('Category.name')), 'ASC']], + }); + } else { + categories = await Category.findAll({ + include: [ + { + model: Bookmark, + as: 'bookmarks', + }, + ], + order: [[orderType, 'ASC']], + }); + } + + res.status(200).json({ + success: true, + data: categories, + }); +}); + +module.exports = getAllCategories; diff --git a/controllers/categories/getSingleCategory.js b/controllers/categories/getSingleCategory.js new file mode 100644 index 0000000..084362b --- /dev/null +++ b/controllers/categories/getSingleCategory.js @@ -0,0 +1,35 @@ +const asyncWrapper = require('../../middleware/asyncWrapper'); +const ErrorResponse = require('../../utils/ErrorResponse'); +const Category = require('../../models/Category'); +const Bookmark = require('../../models/Bookmark'); + +// @desc Get single category +// @route GET /api/categories/:id +// @access Public +const getSingleCategory = asyncWrapper(async (req, res, next) => { + const category = await Category.findOne({ + where: { id: req.params.id }, + include: [ + { + model: Bookmark, + as: 'bookmarks', + }, + ], + }); + + if (!category) { + return next( + new ErrorResponse( + `Category with id of ${req.params.id} was not found`, + 404 + ) + ); + } + + res.status(200).json({ + success: true, + data: category, + }); +}); + +module.exports = getSingleCategory; diff --git a/controllers/categories/index.js b/controllers/categories/index.js new file mode 100644 index 0000000..8b3c179 --- /dev/null +++ b/controllers/categories/index.js @@ -0,0 +1,8 @@ +module.exports = { + createCategory: require('./createCategory'), + getAllCategories: require('./getAllCategories'), + getSingleCategory: require('./getSingleCategory'), + updateCategory: require('./updateCategory'), + deleteCategory: require('./deleteCategory'), + reorderCategories: require('./reorderCategories'), +}; diff --git a/controllers/categories/reorderCategories.js b/controllers/categories/reorderCategories.js new file mode 100644 index 0000000..492675b --- /dev/null +++ b/controllers/categories/reorderCategories.js @@ -0,0 +1,22 @@ +const asyncWrapper = require('../../middleware/asyncWrapper'); +const Category = require('../../models/Category'); +// @desc Reorder categories +// @route PUT /api/categories/0/reorder +// @access Public +const reorderCategories = asyncWrapper(async (req, res, next) => { + req.body.categories.forEach(async ({ id, orderId }) => { + await Category.update( + { orderId }, + { + where: { id }, + } + ); + }); + + res.status(200).json({ + success: true, + data: {}, + }); +}); + +module.exports = reorderCategories; diff --git a/controllers/categories/updateCategory.js b/controllers/categories/updateCategory.js new file mode 100644 index 0000000..cc43db6 --- /dev/null +++ b/controllers/categories/updateCategory.js @@ -0,0 +1,30 @@ +const asyncWrapper = require('../../middleware/asyncWrapper'); +const ErrorResponse = require('../../utils/ErrorResponse'); +const Category = require('../../models/Category'); + +// @desc Update category +// @route PUT /api/categories/:id +// @access Public +const updateCategory = asyncWrapper(async (req, res, next) => { + let category = await Category.findOne({ + where: { id: req.params.id }, + }); + + if (!category) { + return next( + new ErrorResponse( + `Category with id of ${req.params.id} was not found`, + 404 + ) + ); + } + + category = await category.update({ ...req.body }); + + res.status(200).json({ + success: true, + data: category, + }); +}); + +module.exports = updateCategory; diff --git a/controllers/category.js b/controllers/category.js deleted file mode 100644 index d10183f..0000000 --- a/controllers/category.js +++ /dev/null @@ -1,178 +0,0 @@ -const asyncWrapper = require('../middleware/asyncWrapper'); -const ErrorResponse = require('../utils/ErrorResponse'); -const Category = require('../models/Category'); -const Bookmark = require('../models/Bookmark'); -const Config = require('../models/Config'); -const { Sequelize } = require('sequelize'); -const loadConfig = require('../utils/loadConfig'); - -// @desc Create new category -// @route POST /api/categories -// @access Public -exports.createCategory = asyncWrapper(async (req, res, next) => { - const { pinCategoriesByDefault: pinCategories } = await loadConfig(); - - let category; - - if (pinCategories) { - category = await Category.create({ - ...req.body, - isPinned: true, - }); - } else { - category = await Category.create(req.body); - } - - res.status(201).json({ - success: true, - data: category, - }); -}); - -// @desc Get all categories -// @route GET /api/categories -// @access Public -exports.getCategories = asyncWrapper(async (req, res, next) => { - const { useOrdering: orderType } = await loadConfig(); - - let categories; - - if (orderType == 'name') { - categories = await Category.findAll({ - include: [ - { - model: Bookmark, - as: 'bookmarks', - }, - ], - order: [[Sequelize.fn('lower', Sequelize.col('Category.name')), 'ASC']], - }); - } else { - categories = await Category.findAll({ - include: [ - { - model: Bookmark, - as: 'bookmarks', - }, - ], - order: [[orderType, 'ASC']], - }); - } - - res.status(200).json({ - success: true, - data: categories, - }); -}); - -// @desc Get single category -// @route GET /api/categories/:id -// @access Public -exports.getCategory = asyncWrapper(async (req, res, next) => { - const category = await Category.findOne({ - where: { id: req.params.id }, - include: [ - { - model: Bookmark, - as: 'bookmarks', - }, - ], - }); - - if (!category) { - return next( - new ErrorResponse( - `Category with id of ${req.params.id} was not found`, - 404 - ) - ); - } - - res.status(200).json({ - success: true, - data: category, - }); -}); - -// @desc Update category -// @route PUT /api/categories/:id -// @access Public -exports.updateCategory = asyncWrapper(async (req, res, next) => { - let category = await Category.findOne({ - where: { id: req.params.id }, - }); - - if (!category) { - return next( - new ErrorResponse( - `Category with id of ${req.params.id} was not found`, - 404 - ) - ); - } - - category = await category.update({ ...req.body }); - - res.status(200).json({ - success: true, - data: category, - }); -}); - -// @desc Delete category -// @route DELETE /api/categories/:id -// @access Public -exports.deleteCategory = asyncWrapper(async (req, res, next) => { - const category = await Category.findOne({ - where: { id: req.params.id }, - include: [ - { - model: Bookmark, - as: 'bookmarks', - }, - ], - }); - - if (!category) { - return next( - new ErrorResponse( - `Category with id of ${req.params.id} was not found`, - 404 - ) - ); - } - - category.bookmarks.forEach(async (bookmark) => { - await Bookmark.destroy({ - where: { id: bookmark.id }, - }); - }); - - await Category.destroy({ - where: { id: req.params.id }, - }); - - res.status(200).json({ - success: true, - data: {}, - }); -}); - -// @desc Reorder categories -// @route PUT /api/categories/0/reorder -// @access Public -exports.reorderCategories = asyncWrapper(async (req, res, next) => { - req.body.categories.forEach(async ({ id, orderId }) => { - await Category.update( - { orderId }, - { - where: { id }, - } - ); - }); - - res.status(200).json({ - success: true, - data: {}, - }); -}); diff --git a/controllers/queries/addQuery.js b/controllers/queries/addQuery.js new file mode 100644 index 0000000..cd61c67 --- /dev/null +++ b/controllers/queries/addQuery.js @@ -0,0 +1,21 @@ +const asyncWrapper = require('../../middleware/asyncWrapper'); +const File = require('../../utils/File'); + +// @desc Add custom search query +// @route POST /api/queries +// @access Public +const addQuery = asyncWrapper(async (req, res, next) => { + const file = new File('data/customQueries.json'); + let content = JSON.parse(file.read()); + + // Add new query + content.queries.push(req.body); + file.write(content, true); + + res.status(201).json({ + success: true, + data: req.body, + }); +}); + +module.exports = addQuery; diff --git a/controllers/queries/deleteQuery.js b/controllers/queries/deleteQuery.js new file mode 100644 index 0000000..1a30041 --- /dev/null +++ b/controllers/queries/deleteQuery.js @@ -0,0 +1,22 @@ +const asyncWrapper = require('../../middleware/asyncWrapper'); +const File = require('../../utils/File'); + +// @desc Delete query +// @route DELETE /api/queries/:prefix +// @access Public +const deleteQuery = asyncWrapper(async (req, res, next) => { + const file = new File('data/customQueries.json'); + let content = JSON.parse(file.read()); + + content.queries = content.queries.filter( + (q) => q.prefix != req.params.prefix + ); + file.write(content, true); + + res.status(200).json({ + success: true, + data: content.queries, + }); +}); + +module.exports = deleteQuery; diff --git a/controllers/queries/getQueries.js b/controllers/queries/getQueries.js new file mode 100644 index 0000000..6299473 --- /dev/null +++ b/controllers/queries/getQueries.js @@ -0,0 +1,17 @@ +const asyncWrapper = require('../../middleware/asyncWrapper'); +const File = require('../../utils/File'); + +// @desc Get custom queries file +// @route GET /api/queries +// @access Public +const getQueries = asyncWrapper(async (req, res, next) => { + const file = new File('data/customQueries.json'); + const content = JSON.parse(file.read()); + + res.status(200).json({ + success: true, + data: content.queries, + }); +}); + +module.exports = getQueries; diff --git a/controllers/queries/index.js b/controllers/queries/index.js index ae1ccec..3d5d036 100644 --- a/controllers/queries/index.js +++ b/controllers/queries/index.js @@ -1,81 +1,6 @@ -const asyncWrapper = require('../../middleware/asyncWrapper'); -const File = require('../../utils/File'); -const { join } = require('path'); - -const QUERIES_PATH = join(__dirname, '../../data/customQueries.json'); - -// @desc Add custom search query -// @route POST /api/queries -// @access Public -exports.addQuery = asyncWrapper(async (req, res, next) => { - const file = new File(QUERIES_PATH); - let content = JSON.parse(file.read()); - - // Add new query - content.queries.push(req.body); - file.write(content, true); - - res.status(201).json({ - success: true, - data: req.body, - }); -}); - -// @desc Get custom queries file -// @route GET /api/queries -// @access Public -exports.getQueries = asyncWrapper(async (req, res, next) => { - const file = new File(QUERIES_PATH); - const content = JSON.parse(file.read()); - - res.status(200).json({ - success: true, - data: content.queries, - }); -}); - -// @desc Update query -// @route PUT /api/queries/:prefix -// @access Public -exports.updateQuery = asyncWrapper(async (req, res, next) => { - const file = new File(QUERIES_PATH); - let content = JSON.parse(file.read()); - - let queryIdx = content.queries.findIndex( - (q) => q.prefix == req.params.prefix - ); - - // query found - if (queryIdx > -1) { - content.queries = [ - ...content.queries.slice(0, queryIdx), - req.body, - ...content.queries.slice(queryIdx + 1), - ]; - } - - file.write(content, true); - - res.status(200).json({ - success: true, - data: content.queries, - }); -}); - -// @desc Delete query -// @route DELETE /api/queries/:prefix -// @access Public -exports.deleteQuery = asyncWrapper(async (req, res, next) => { - const file = new File(QUERIES_PATH); - let content = JSON.parse(file.read()); - - content.queries = content.queries.filter( - (q) => q.prefix != req.params.prefix - ); - file.write(content, true); - - res.status(200).json({ - success: true, - data: content.queries, - }); -}); +module.exports = { + addQuery: require('./addQuery'), + getQueries: require('./getQueries'), + updateQuery: require('./updateQuery'), + deleteQuery: require('./deleteQuery'), +}; diff --git a/controllers/queries/updateQuery.js b/controllers/queries/updateQuery.js new file mode 100644 index 0000000..a95b71a --- /dev/null +++ b/controllers/queries/updateQuery.js @@ -0,0 +1,32 @@ +const asyncWrapper = require('../../middleware/asyncWrapper'); +const File = require('../../utils/File'); + +// @desc Update query +// @route PUT /api/queries/:prefix +// @access Public +const updateQuery = asyncWrapper(async (req, res, next) => { + const file = new File('data/customQueries.json'); + let content = JSON.parse(file.read()); + + let queryIdx = content.queries.findIndex( + (q) => q.prefix == req.params.prefix + ); + + // query found + if (queryIdx > -1) { + content.queries = [ + ...content.queries.slice(0, queryIdx), + req.body, + ...content.queries.slice(queryIdx + 1), + ]; + } + + file.write(content, true); + + res.status(200).json({ + success: true, + data: content.queries, + }); +}); + +module.exports = updateQuery; diff --git a/controllers/weather.js b/controllers/weather.js deleted file mode 100644 index 3acd1ad..0000000 --- a/controllers/weather.js +++ /dev/null @@ -1,31 +0,0 @@ -const asyncWrapper = require('../middleware/asyncWrapper'); -const ErrorResponse = require('../utils/ErrorResponse'); -const Weather = require('../models/Weather'); -const getExternalWeather = require('../utils/getExternalWeather'); - -// @desc Get latest weather status -// @route GET /api/weather -// @access Public -exports.getWeather = asyncWrapper(async (req, res, next) => { - const weather = await Weather.findAll({ - order: [['createdAt', 'DESC']], - limit: 1, - }); - - res.status(200).json({ - success: true, - data: weather, - }); -}); - -// @desc Update weather -// @route GET /api/weather/update -// @access Public -exports.updateWeather = asyncWrapper(async (req, res, next) => { - const weather = await getExternalWeather(); - - res.status(200).json({ - success: true, - data: weather, - }); -}); diff --git a/controllers/weather/getWather.js b/controllers/weather/getWather.js new file mode 100644 index 0000000..44e6e3f --- /dev/null +++ b/controllers/weather/getWather.js @@ -0,0 +1,19 @@ +const asyncWrapper = require('../../middleware/asyncWrapper'); +const Weather = require('../../models/Weather'); + +// @desc Get latest weather status +// @route GET /api/weather +// @access Public +const getWeather = asyncWrapper(async (req, res, next) => { + const weather = await Weather.findAll({ + order: [['createdAt', 'DESC']], + limit: 1, + }); + + res.status(200).json({ + success: true, + data: weather, + }); +}); + +module.exports = getWeather; diff --git a/controllers/weather/index.js b/controllers/weather/index.js new file mode 100644 index 0000000..8c7231d --- /dev/null +++ b/controllers/weather/index.js @@ -0,0 +1,4 @@ +module.exports = { + getWeather: require('./getWather'), + updateWeather: require('./updateWeather'), +}; diff --git a/controllers/weather/updateWeather.js b/controllers/weather/updateWeather.js new file mode 100644 index 0000000..c66417e --- /dev/null +++ b/controllers/weather/updateWeather.js @@ -0,0 +1,16 @@ +const asyncWrapper = require('../../middleware/asyncWrapper'); +const getExternalWeather = require('../../utils/getExternalWeather'); + +// @desc Update weather +// @route GET /api/weather/update +// @access Public +const updateWeather = asyncWrapper(async (req, res, next) => { + const weather = await getExternalWeather(); + + res.status(200).json({ + success: true, + data: weather, + }); +}); + +module.exports = updateWeather; diff --git a/routes/category.js b/routes/category.js index 64067d7..b7527c8 100644 --- a/routes/category.js +++ b/routes/category.js @@ -3,26 +3,21 @@ const router = express.Router(); const { createCategory, - getCategories, - getCategory, + getAllCategories, + getSingleCategory, updateCategory, deleteCategory, - reorderCategories -} = require('../controllers/category'); + reorderCategories, +} = require('../controllers/categories'); -router - .route('/') - .post(createCategory) - .get(getCategories); +router.route('/').post(createCategory).get(getAllCategories); router .route('/:id') - .get(getCategory) + .get(getSingleCategory) .put(updateCategory) .delete(deleteCategory); -router - .route('/0/reorder') - .put(reorderCategories); +router.route('/0/reorder').put(reorderCategories); -module.exports = router; \ No newline at end of file +module.exports = router; diff --git a/utils/clearWeatherData.js b/utils/clearWeatherData.js index 07be15b..5e4972a 100644 --- a/utils/clearWeatherData.js +++ b/utils/clearWeatherData.js @@ -2,23 +2,28 @@ const { Op } = require('sequelize'); const Weather = require('../models/Weather'); const Logger = require('./Logger'); const logger = new Logger(); +const loadConfig = require('./loadConfig'); const clearWeatherData = async () => { + const { WEATHER_API_KEY: secret } = await loadConfig(); + const weather = await Weather.findOne({ - order: [[ 'createdAt', 'DESC' ]] + order: [['createdAt', 'DESC']], }); if (weather) { await Weather.destroy({ where: { id: { - [Op.lt]: weather.id - } - } - }) + [Op.lt]: weather.id, + }, + }, + }); } - logger.log('Old weather data was deleted'); -} + if (secret) { + logger.log('Old weather data was deleted'); + } +}; -module.exports = clearWeatherData; \ No newline at end of file +module.exports = clearWeatherData;