diff --git a/CHANGELOG.md b/CHANGELOG.md index 54d5274..5b91cc2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ### v1.7.1 (TBA) - Fixed search action not being triggered by Numpad Enter - Fixed search bar not redirecting to valid URL if it starts with capital letter ([#118](https://github.com/pawelmalak/flame/issues/118)) +- Performance improvements ### v1.7.0 (2021-10-11) - Search bar will now redirect if valid URL or IP is provided ([#67](https://github.com/pawelmalak/flame/issues/67)) diff --git a/client/src/store/actions/app.ts b/client/src/store/actions/app.ts index 3a8e7d5..b33a78b 100644 --- a/client/src/store/actions/app.ts +++ b/client/src/store/actions/app.ts @@ -5,14 +5,17 @@ import { App, ApiResponse, NewApp, Config } from '../../interfaces'; import { CreateNotificationAction } from './notification'; export interface GetAppsAction { - type: ActionTypes.getApps | ActionTypes.getAppsSuccess | ActionTypes.getAppsError; + type: + | ActionTypes.getApps + | ActionTypes.getAppsSuccess + | ActionTypes.getAppsError; payload: T; } export const getApps = () => async (dispatch: Dispatch) => { dispatch>({ type: ActionTypes.getApps, - payload: undefined + payload: undefined, }); try { @@ -20,12 +23,12 @@ export const getApps = () => async (dispatch: Dispatch) => { dispatch>({ type: ActionTypes.getAppsSuccess, - payload: res.data.data - }) + payload: res.data.data, + }); } catch (err) { console.log(err); } -} +}; export interface PinAppAction { type: ActionTypes.pinApp; @@ -35,59 +38,64 @@ export interface PinAppAction { export const pinApp = (app: App) => async (dispatch: Dispatch) => { try { const { id, isPinned, name } = app; - const res = await axios.put>(`/api/apps/${id}`, { isPinned: !isPinned }); + const res = await axios.put>(`/api/apps/${id}`, { + isPinned: !isPinned, + }); - const status = isPinned ? 'unpinned from Homescreen' : 'pinned to Homescreen'; + const status = isPinned + ? 'unpinned from Homescreen' + : 'pinned to Homescreen'; dispatch({ type: ActionTypes.createNotification, payload: { title: 'Success', - message: `App ${name} ${status}` - } - }) + message: `App ${name} ${status}`, + }, + }); dispatch({ type: ActionTypes.pinApp, - payload: res.data.data - }) + payload: res.data.data, + }); } catch (err) { console.log(err); } -} +}; export interface AddAppAction { type: ActionTypes.addAppSuccess; payload: App; } -export const addApp = (formData: NewApp | FormData) => async (dispatch: Dispatch) => { - try { - const res = await axios.post>('/api/apps', formData); +export const addApp = + (formData: NewApp | FormData) => async (dispatch: Dispatch) => { + try { + const res = await axios.post>('/api/apps', formData); - dispatch({ - type: ActionTypes.createNotification, - payload: { - title: 'Success', - message: `App added` - } - }) + dispatch({ + type: ActionTypes.createNotification, + payload: { + title: 'Success', + message: `App added`, + }, + }); - await dispatch({ - type: ActionTypes.addAppSuccess, - payload: res.data.data - }) + await dispatch({ + type: ActionTypes.addAppSuccess, + payload: res.data.data, + }); - // Sort apps - dispatch(sortApps()) - } catch (err) { - console.log(err); - } -} + // Sort apps + dispatch(sortApps()); + } catch (err) { + console.log(err); + } + }; export interface DeleteAppAction { - type: ActionTypes.deleteApp, - payload: number + type: ActionTypes.deleteApp; + payload: number; } export const deleteApp = (id: number) => async (dispatch: Dispatch) => { @@ -98,79 +106,85 @@ export const deleteApp = (id: number) => async (dispatch: Dispatch) => { type: ActionTypes.createNotification, payload: { title: 'Success', - message: 'App deleted' - } - }) + message: 'App deleted', + }, + }); dispatch({ type: ActionTypes.deleteApp, - payload: id - }) + payload: id, + }); } catch (err) { console.log(err); } -} +}; export interface UpdateAppAction { type: ActionTypes.updateApp; payload: App; } -export const updateApp = (id: number, formData: NewApp | FormData) => async (dispatch: Dispatch) => { - try { - const res = await axios.put>(`/api/apps/${id}`, formData); +export const updateApp = + (id: number, formData: NewApp | FormData) => async (dispatch: Dispatch) => { + try { + const res = await axios.put>( + `/api/apps/${id}`, + formData + ); - dispatch({ - type: ActionTypes.createNotification, - payload: { - title: 'Success', - message: `App updated` - } - }) + dispatch({ + type: ActionTypes.createNotification, + payload: { + title: 'Success', + message: `App updated`, + }, + }); - await dispatch({ - type: ActionTypes.updateApp, - payload: res.data.data - }) + await dispatch({ + type: ActionTypes.updateApp, + payload: res.data.data, + }); - // Sort apps - dispatch(sortApps()) - } catch (err) { - console.log(err); - } -} + // Sort apps + dispatch(sortApps()); + } catch (err) { + console.log(err); + } + }; export interface ReorderAppsAction { type: ActionTypes.reorderApps; - payload: App[] + payload: App[]; } interface ReorderQuery { apps: { id: number; orderId: number; - }[] + }[]; } export const reorderApps = (apps: App[]) => async (dispatch: Dispatch) => { try { - const updateQuery: ReorderQuery = { apps: [] } + const updateQuery: ReorderQuery = { apps: [] }; - apps.forEach((app, index) => updateQuery.apps.push({ - id: app.id, - orderId: index + 1 - })) + apps.forEach((app, index) => + updateQuery.apps.push({ + id: app.id, + orderId: index + 1, + }) + ); await axios.put>('/api/apps/0/reorder', updateQuery); dispatch({ type: ActionTypes.reorderApps, - payload: apps - }) + payload: apps, + }); } catch (err) { console.log(err); } -} +}; export interface SortAppsAction { type: ActionTypes.sortApps; @@ -179,13 +193,13 @@ export interface SortAppsAction { export const sortApps = () => async (dispatch: Dispatch) => { try { - const res = await axios.get>('/api/config/useOrdering'); + const res = await axios.get>('/api/config'); dispatch({ type: ActionTypes.sortApps, - payload: res.data.data.value - }) + payload: res.data.data.useOrdering, + }); } catch (err) { console.log(err); } -} \ No newline at end of file +}; diff --git a/client/src/store/actions/bookmark.ts b/client/src/store/actions/bookmark.ts index b4b5831..6d6fdf5 100644 --- a/client/src/store/actions/bookmark.ts +++ b/client/src/store/actions/bookmark.ts @@ -1,133 +1,157 @@ import axios from 'axios'; import { Dispatch } from 'redux'; import { ActionTypes } from './actionTypes'; -import { Category, ApiResponse, NewCategory, Bookmark, NewBookmark, Config } from '../../interfaces'; +import { + Category, + ApiResponse, + NewCategory, + Bookmark, + NewBookmark, + Config, +} from '../../interfaces'; import { CreateNotificationAction } from './notification'; /** * GET CATEGORIES */ export interface GetCategoriesAction { - type: ActionTypes.getCategories | ActionTypes.getCategoriesSuccess | ActionTypes.getCategoriesError; + type: + | ActionTypes.getCategories + | ActionTypes.getCategoriesSuccess + | ActionTypes.getCategoriesError; payload: T; } export const getCategories = () => async (dispatch: Dispatch) => { dispatch>({ type: ActionTypes.getCategories, - payload: undefined - }) + payload: undefined, + }); try { const res = await axios.get>('/api/categories'); dispatch>({ type: ActionTypes.getCategoriesSuccess, - payload: res.data.data - }) + payload: res.data.data, + }); } catch (err) { console.log(err); } -} +}; /** * ADD CATEGORY */ export interface AddCategoryAction { - type: ActionTypes.addCategory, - payload: Category + type: ActionTypes.addCategory; + payload: Category; } -export const addCategory = (formData: NewCategory) => async (dispatch: Dispatch) => { - try { - const res = await axios.post>('/api/categories', formData); +export const addCategory = + (formData: NewCategory) => async (dispatch: Dispatch) => { + try { + const res = await axios.post>( + '/api/categories', + formData + ); - dispatch({ - type: ActionTypes.createNotification, - payload: { - title: 'Success', - message: `Category ${formData.name} created` - } - }) + dispatch({ + type: ActionTypes.createNotification, + payload: { + title: 'Success', + message: `Category ${formData.name} created`, + }, + }); - dispatch({ - type: ActionTypes.addCategory, - payload: res.data.data - }) + dispatch({ + type: ActionTypes.addCategory, + payload: res.data.data, + }); - dispatch(sortCategories()); - } catch (err) { - console.log(err); - } -} + dispatch(sortCategories()); + } catch (err) { + console.log(err); + } + }; /** * ADD BOOKMARK */ export interface AddBookmarkAction { - type: ActionTypes.addBookmark, - payload: Bookmark + type: ActionTypes.addBookmark; + payload: Bookmark; } -export const addBookmark = (formData: NewBookmark | FormData) => async (dispatch: Dispatch) => { - try { - const res = await axios.post>('/api/bookmarks', formData); +export const addBookmark = + (formData: NewBookmark | FormData) => async (dispatch: Dispatch) => { + try { + const res = await axios.post>( + '/api/bookmarks', + formData + ); - dispatch({ - type: ActionTypes.createNotification, - payload: { - title: 'Success', - message: `Bookmark created` - } - }) + dispatch({ + type: ActionTypes.createNotification, + payload: { + title: 'Success', + message: `Bookmark created`, + }, + }); - dispatch({ - type: ActionTypes.addBookmark, - payload: res.data.data - }) - } catch (err) { - console.log(err); - } -} + dispatch({ + type: ActionTypes.addBookmark, + payload: res.data.data, + }); + } catch (err) { + console.log(err); + } + }; /** * PIN CATEGORY */ export interface PinCategoryAction { - type: ActionTypes.pinCategory, - payload: Category + type: ActionTypes.pinCategory; + payload: Category; } -export const pinCategory = (category: Category) => async (dispatch: Dispatch) => { - try { - const { id, isPinned, name } = category; - const res = await axios.put>(`/api/categories/${id}`, { isPinned: !isPinned }); +export const pinCategory = + (category: Category) => async (dispatch: Dispatch) => { + try { + const { id, isPinned, name } = category; + const res = await axios.put>( + `/api/categories/${id}`, + { isPinned: !isPinned } + ); - const status = isPinned ? 'unpinned from Homescreen' : 'pinned to Homescreen'; + const status = isPinned + ? 'unpinned from Homescreen' + : 'pinned to Homescreen'; - dispatch({ - type: ActionTypes.createNotification, - payload: { - title: 'Success', - message: `Category ${name} ${status}` - } - }) + dispatch({ + type: ActionTypes.createNotification, + payload: { + title: 'Success', + message: `Category ${name} ${status}`, + }, + }); - dispatch({ - type: ActionTypes.pinCategory, - payload: res.data.data - }) - } catch (err) { - console.log(err); - } -} + dispatch({ + type: ActionTypes.pinCategory, + payload: res.data.data, + }); + } catch (err) { + console.log(err); + } + }; /** * DELETE CATEGORY */ export interface DeleteCategoryAction { - type: ActionTypes.deleteCategory, - payload: number + type: ActionTypes.deleteCategory; + payload: number; } export const deleteCategory = (id: number) => async (dispatch: Dispatch) => { @@ -138,141 +162,151 @@ export const deleteCategory = (id: number) => async (dispatch: Dispatch) => { type: ActionTypes.createNotification, payload: { title: 'Success', - message: `Category deleted` - } - }) + message: `Category deleted`, + }, + }); dispatch({ type: ActionTypes.deleteCategory, - payload: id - }) + payload: id, + }); } catch (err) { console.log(err); } -} +}; /** * UPDATE CATEGORY */ export interface UpdateCategoryAction { - type: ActionTypes.updateCategory, - payload: Category + type: ActionTypes.updateCategory; + payload: Category; } -export const updateCategory = (id: number, formData: NewCategory) => async (dispatch: Dispatch) => { - try { - const res = await axios.put>(`/api/categories/${id}`, formData); +export const updateCategory = + (id: number, formData: NewCategory) => async (dispatch: Dispatch) => { + try { + const res = await axios.put>( + `/api/categories/${id}`, + formData + ); - dispatch({ - type: ActionTypes.createNotification, - payload: { - title: 'Success', - message: `Category ${formData.name} updated` - } - }) + dispatch({ + type: ActionTypes.createNotification, + payload: { + title: 'Success', + message: `Category ${formData.name} updated`, + }, + }); - dispatch({ - type: ActionTypes.updateCategory, - payload: res.data.data - }) + dispatch({ + type: ActionTypes.updateCategory, + payload: res.data.data, + }); - dispatch(sortCategories()); - } catch (err) { - console.log(err); - } -} + dispatch(sortCategories()); + } catch (err) { + console.log(err); + } + }; /** * DELETE BOOKMARK */ export interface DeleteBookmarkAction { - type: ActionTypes.deleteBookmark, + type: ActionTypes.deleteBookmark; payload: { - bookmarkId: number, - categoryId: number - } + bookmarkId: number; + categoryId: number; + }; } -export const deleteBookmark = (bookmarkId: number, categoryId: number) => async (dispatch: Dispatch) => { - try { - await axios.delete>(`/api/bookmarks/${bookmarkId}`); +export const deleteBookmark = + (bookmarkId: number, categoryId: number) => async (dispatch: Dispatch) => { + try { + await axios.delete>(`/api/bookmarks/${bookmarkId}`); - dispatch({ - type: ActionTypes.createNotification, - payload: { - title: 'Success', - message: 'Bookmark deleted' - } - }) + dispatch({ + type: ActionTypes.createNotification, + payload: { + title: 'Success', + message: 'Bookmark deleted', + }, + }); - dispatch({ - type: ActionTypes.deleteBookmark, - payload: { - bookmarkId, - categoryId - } - }) - } catch (err) { - console.log(err); - } -} + dispatch({ + type: ActionTypes.deleteBookmark, + payload: { + bookmarkId, + categoryId, + }, + }); + } catch (err) { + console.log(err); + } + }; /** * UPDATE BOOKMARK */ export interface UpdateBookmarkAction { - type: ActionTypes.updateBookmark, - payload: Bookmark + type: ActionTypes.updateBookmark; + payload: Bookmark; } -export const updateBookmark = ( - bookmarkId: number, - formData: NewBookmark | FormData, - category: { - prev: number, - curr: number - } -) => async (dispatch: Dispatch) => { - try { - const res = await axios.put>(`/api/bookmarks/${bookmarkId}`, formData); - - dispatch({ - type: ActionTypes.createNotification, - payload: { - title: 'Success', - message: `Bookmark updated` - } - }) - - // Check if category was changed - const categoryWasChanged = category.curr !== category.prev; - - if (categoryWasChanged) { - // Delete bookmark from old category - dispatch({ - type: ActionTypes.deleteBookmark, - payload: { - bookmarkId, - categoryId: category.prev - } - }) - - // Add bookmark to the new category - dispatch({ - type: ActionTypes.addBookmark, - payload: res.data.data - }) - } else { - // Else update only name/url/icon - dispatch({ - type: ActionTypes.updateBookmark, - payload: res.data.data - }) +export const updateBookmark = + ( + bookmarkId: number, + formData: NewBookmark | FormData, + category: { + prev: number; + curr: number; } - } catch (err) { - console.log(err); - } -} + ) => + async (dispatch: Dispatch) => { + try { + const res = await axios.put>( + `/api/bookmarks/${bookmarkId}`, + formData + ); + + dispatch({ + type: ActionTypes.createNotification, + payload: { + title: 'Success', + message: `Bookmark updated`, + }, + }); + + // Check if category was changed + const categoryWasChanged = category.curr !== category.prev; + + if (categoryWasChanged) { + // Delete bookmark from old category + dispatch({ + type: ActionTypes.deleteBookmark, + payload: { + bookmarkId, + categoryId: category.prev, + }, + }); + + // Add bookmark to the new category + dispatch({ + type: ActionTypes.addBookmark, + payload: res.data.data, + }); + } else { + // Else update only name/url/icon + dispatch({ + type: ActionTypes.updateBookmark, + payload: res.data.data, + }); + } + } catch (err) { + console.log(err); + } + }; /** * SORT CATEGORIES @@ -284,16 +318,16 @@ export interface SortCategoriesAction { export const sortCategories = () => async (dispatch: Dispatch) => { try { - const res = await axios.get>('/api/config/useOrdering'); + const res = await axios.get>('/api/config'); dispatch({ type: ActionTypes.sortCategories, - payload: res.data.data.value - }) + payload: res.data.data.useOrdering, + }); } catch (err) { console.log(err); } -} +}; /** * REORDER CATEGORIES @@ -307,25 +341,31 @@ interface ReorderQuery { categories: { id: number; orderId: number; - }[] + }[]; } -export const reorderCategories = (categories: Category[]) => async (dispatch: Dispatch) => { - try { - const updateQuery: ReorderQuery = { categories: [] } +export const reorderCategories = + (categories: Category[]) => async (dispatch: Dispatch) => { + try { + const updateQuery: ReorderQuery = { categories: [] }; - categories.forEach((category, index) => updateQuery.categories.push({ - id: category.id, - orderId: index + 1 - })) + categories.forEach((category, index) => + updateQuery.categories.push({ + id: category.id, + orderId: index + 1, + }) + ); - await axios.put>('/api/categories/0/reorder', updateQuery); + await axios.put>( + '/api/categories/0/reorder', + updateQuery + ); - dispatch({ - type: ActionTypes.reorderCategories, - payload: categories - }) - } catch (err) { - console.log(err); - } -} \ No newline at end of file + dispatch({ + type: ActionTypes.reorderCategories, + payload: categories, + }); + } catch (err) { + console.log(err); + } + }; diff --git a/client/src/store/actions/config.ts b/client/src/store/actions/config.ts index 29c5186..8b1ef5a 100644 --- a/client/src/store/actions/config.ts +++ b/client/src/store/actions/config.ts @@ -3,16 +3,15 @@ import { Dispatch } from 'redux'; import { ActionTypes } from './actionTypes'; import { Config, ApiResponse, Query } from '../../interfaces'; import { CreateNotificationAction } from './notification'; -import { searchConfig } from '../../utility'; export interface GetConfigAction { type: ActionTypes.getConfig; - payload: Config[]; + payload: Config; } export const getConfig = () => async (dispatch: Dispatch) => { try { - const res = await axios.get>('/api/config'); + const res = await axios.get>('/api/config'); dispatch({ type: ActionTypes.getConfig, @@ -20,7 +19,7 @@ export const getConfig = () => async (dispatch: Dispatch) => { }); // Set custom page title if set - document.title = searchConfig('customTitle', 'Flame'); + document.title = res.data.data.customTitle; } catch (err) { console.log(err); } @@ -28,12 +27,12 @@ export const getConfig = () => async (dispatch: Dispatch) => { export interface UpdateConfigAction { type: ActionTypes.updateConfig; - payload: Config[]; + payload: Config; } export const updateConfig = (formData: any) => async (dispatch: Dispatch) => { try { - const res = await axios.put>('/api/config', formData); + const res = await axios.put>('/api/config', formData); dispatch({ type: ActionTypes.createNotification, diff --git a/client/src/store/reducers/config.ts b/client/src/store/reducers/config.ts index ae2699e..c0ece13 100644 --- a/client/src/store/reducers/config.ts +++ b/client/src/store/reducers/config.ts @@ -1,15 +1,16 @@ import { ActionTypes, Action } from '../actions'; import { Config, Query } from '../../interfaces'; +import { configTemplate } from '../../utility'; export interface State { loading: boolean; - config: Config[]; + config: Config; customQueries: Query[]; } const initialState: State = { loading: true, - config: [], + config: configTemplate, customQueries: [], }; diff --git a/controllers/apps/docker/useDocker.js b/controllers/apps/docker/useDocker.js index fcc4379..88ecb3e 100644 --- a/controllers/apps/docker/useDocker.js +++ b/controllers/apps/docker/useDocker.js @@ -1,8 +1,8 @@ -const App = require('../../models/App'); +const App = require('../../../models/App'); const axios = require('axios'); -const Logger = require('../../utils/Logger'); +const Logger = require('../../../utils/Logger'); const logger = new Logger(); -const loadConfig = require('../../utils/loadConfig'); +const loadConfig = require('../../../utils/loadConfig'); const useDocker = async (apps) => { const { @@ -50,7 +50,7 @@ const useDocker = async (apps) => { for (const container of containers) { let labels = container.Labels; - // todo + // Traefik labels for URL configuration if (!('flame.url' in labels)) { for (const label of Object.keys(labels)) { if (/^traefik.*.frontend.rule/.test(label)) { diff --git a/controllers/bookmark.js b/controllers/bookmark.js deleted file mode 100644 index e745d4d..0000000 --- a/controllers/bookmark.js +++ /dev/null @@ -1,112 +0,0 @@ -const asyncWrapper = require('../middleware/asyncWrapper'); -const ErrorResponse = require('../utils/ErrorResponse'); -const Bookmark = require('../models/Bookmark'); -const { Sequelize } = require('sequelize'); - -// @desc Create new bookmark -// @route POST /api/bookmarks -// @access Public -exports.createBookmark = asyncWrapper(async (req, res, next) => { - let bookmark; - - let _body = { - ...req.body, - categoryId: parseInt(req.body.categoryId), - }; - - if (req.file) { - _body.icon = req.file.filename; - } - - bookmark = await Bookmark.create(_body); - - res.status(201).json({ - success: true, - data: bookmark, - }); -}); - -// @desc Get all bookmarks -// @route GET /api/bookmarks -// @access Public -exports.getBookmarks = asyncWrapper(async (req, res, next) => { - const bookmarks = await Bookmark.findAll({ - order: [[Sequelize.fn('lower', Sequelize.col('name')), 'ASC']], - }); - - res.status(200).json({ - success: true, - data: bookmarks, - }); -}); - -// @desc Get single bookmark -// @route GET /api/bookmarks/:id -// @access Public -exports.getBookmark = asyncWrapper(async (req, res, next) => { - const bookmark = await Bookmark.findOne({ - where: { id: req.params.id }, - }); - - if (!bookmark) { - return next( - new ErrorResponse( - `Bookmark with id of ${req.params.id} was not found`, - 404 - ) - ); - } - - res.status(200).json({ - success: true, - data: bookmark, - }); -}); - -// @desc Update bookmark -// @route PUT /api/bookmarks/:id -// @access Public -exports.updateBookmark = asyncWrapper(async (req, res, next) => { - let bookmark = await Bookmark.findOne({ - where: { id: req.params.id }, - }); - - if (!bookmark) { - return next( - new ErrorResponse( - `Bookmark with id of ${req.params.id} was not found`, - 404 - ) - ); - } - - let _body = { - ...req.body, - categoryId: parseInt(req.body.categoryId), - }; - - if (req.file) { - _body.icon = req.file.filename; - } - - bookmark = await bookmark.update(_body); - - res.status(200).json({ - success: true, - data: bookmark, - }); -}); - -// @desc Delete bookmark -// @route DELETE /api/bookmarks/:id -// @access Public -exports.deleteBookmark = asyncWrapper(async (req, res, next) => { - await Bookmark.destroy({ - where: { id: req.params.id }, - }); - - res.status(200).json({ - success: true, - data: {}, - }); -}); diff --git a/controllers/bookmarks/createBookmark.js b/controllers/bookmarks/createBookmark.js new file mode 100644 index 0000000..2292c50 --- /dev/null +++ b/controllers/bookmarks/createBookmark.js @@ -0,0 +1,27 @@ +const asyncWrapper = require('../../middleware/asyncWrapper'); +const Bookmark = require('../../models/Bookmark'); + +// @desc Create new bookmark +// @route POST /api/bookmarks +// @access Public +const createBookmark = asyncWrapper(async (req, res, next) => { + let bookmark; + + let _body = { + ...req.body, + categoryId: parseInt(req.body.categoryId), + }; + + if (req.file) { + _body.icon = req.file.filename; + } + + bookmark = await Bookmark.create(_body); + + res.status(201).json({ + success: true, + data: bookmark, + }); +}); + +module.exports = createBookmark; diff --git a/controllers/bookmarks/deleteBookmark.js b/controllers/bookmarks/deleteBookmark.js new file mode 100644 index 0000000..c511a30 --- /dev/null +++ b/controllers/bookmarks/deleteBookmark.js @@ -0,0 +1,18 @@ +const asyncWrapper = require('../../middleware/asyncWrapper'); +const Bookmark = require('../../models/Bookmark'); + +// @desc Delete bookmark +// @route DELETE /api/bookmarks/:id +// @access Public +const deleteBookmark = asyncWrapper(async (req, res, next) => { + await Bookmark.destroy({ + where: { id: req.params.id }, + }); + + res.status(200).json({ + success: true, + data: {}, + }); +}); + +module.exports = deleteBookmark; diff --git a/controllers/bookmarks/getAllBookmarks.js b/controllers/bookmarks/getAllBookmarks.js new file mode 100644 index 0000000..c4d8dde --- /dev/null +++ b/controllers/bookmarks/getAllBookmarks.js @@ -0,0 +1,19 @@ +const asyncWrapper = require('../../middleware/asyncWrapper'); +const Bookmark = require('../../models/Bookmark'); +const { Sequelize } = require('sequelize'); + +// @desc Get all bookmarks +// @route GET /api/bookmarks +// @access Public +const getAllBookmarks = asyncWrapper(async (req, res, next) => { + const bookmarks = await Bookmark.findAll({ + order: [[Sequelize.fn('lower', Sequelize.col('name')), 'ASC']], + }); + + res.status(200).json({ + success: true, + data: bookmarks, + }); +}); + +module.exports = getAllBookmarks; diff --git a/controllers/bookmarks/getSingleBookmark.js b/controllers/bookmarks/getSingleBookmark.js new file mode 100644 index 0000000..18c0cbf --- /dev/null +++ b/controllers/bookmarks/getSingleBookmark.js @@ -0,0 +1,28 @@ +const asyncWrapper = require('../../middleware/asyncWrapper'); +const ErrorResponse = require('../../utils/ErrorResponse'); +const Bookmark = require('../../models/Bookmark'); + +// @desc Get single bookmark +// @route GET /api/bookmarks/:id +// @access Public +const getSingleBookmark = asyncWrapper(async (req, res, next) => { + const bookmark = await Bookmark.findOne({ + where: { id: req.params.id }, + }); + + if (!bookmark) { + return next( + new ErrorResponse( + `Bookmark with the id of ${req.params.id} was not found`, + 404 + ) + ); + } + + res.status(200).json({ + success: true, + data: bookmark, + }); +}); + +module.exports = getSingleBookmark; diff --git a/controllers/bookmarks/index.js b/controllers/bookmarks/index.js new file mode 100644 index 0000000..f1ef588 --- /dev/null +++ b/controllers/bookmarks/index.js @@ -0,0 +1,7 @@ +module.exports = { + createBookmark: require('./createBookmark'), + getAllBookmarks: require('./getAllBookmarks'), + getSingleBookmark: require('./getSingleBookmark'), + updateBookmark: require('./updateBookmark'), + deleteBookmark: require('./deleteBookmark'), +}; diff --git a/controllers/bookmarks/updateBookmark.js b/controllers/bookmarks/updateBookmark.js new file mode 100644 index 0000000..778d2eb --- /dev/null +++ b/controllers/bookmarks/updateBookmark.js @@ -0,0 +1,39 @@ +const asyncWrapper = require('../../middleware/asyncWrapper'); +const ErrorResponse = require('../../utils/ErrorResponse'); +const Bookmark = require('../../models/Bookmark'); + +// @desc Update bookmark +// @route PUT /api/bookmarks/:id +// @access Public +const updateBookmark = asyncWrapper(async (req, res, next) => { + let bookmark = await Bookmark.findOne({ + where: { id: req.params.id }, + }); + + if (!bookmark) { + return next( + new ErrorResponse( + `Bookmark with id of ${req.params.id} was not found`, + 404 + ) + ); + } + + let _body = { + ...req.body, + categoryId: parseInt(req.body.categoryId), + }; + + if (req.file) { + _body.icon = req.file.filename; + } + + bookmark = await bookmark.update(_body); + + res.status(200).json({ + success: true, + data: bookmark, + }); +}); + +module.exports = updateBookmark; diff --git a/controllers/category.js b/controllers/category.js index 0f1af58..557c1a1 100644 --- a/controllers/category.js +++ b/controllers/category.js @@ -4,15 +4,13 @@ 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) => { - // Get config from database - const pinCategories = await Config.findOne({ - where: { key: 'pinCategoriesByDefault' }, - }); + const { pinCategoriesByDefault: pinCategories } = await loadConfig(); let category; @@ -37,12 +35,8 @@ exports.createCategory = asyncWrapper(async (req, res, next) => { // @route GET /api/categories // @access Public exports.getCategories = asyncWrapper(async (req, res, next) => { - // Get config from database - const useOrdering = await Config.findOne({ - where: { key: 'useOrdering' }, - }); + const { useOrdering: orderType } = await loadConfig(); - const orderType = useOrdering ? useOrdering.value : 'createdAt'; let categories; if (orderType == 'name') { diff --git a/controllers/config.js b/controllers/config.js deleted file mode 100644 index e5290aa..0000000 --- a/controllers/config.js +++ /dev/null @@ -1,177 +0,0 @@ -const asyncWrapper = require('../middleware/asyncWrapper'); -const ErrorResponse = require('../utils/ErrorResponse'); -const Config = require('../models/Config'); -const { Op } = require('sequelize'); -const File = require('../utils/File'); -const { join } = require('path'); -const fs = require('fs'); - -// @desc Insert new key:value pair -// @route POST /api/config -// @access Public -exports.createPair = asyncWrapper(async (req, res, next) => { - const pair = await Config.create(req.body); - - res.status(201).json({ - success: true, - data: pair, - }); -}); - -// @desc Get all key:value pairs -// @route GET /api/config -// @route GET /api/config?keys=foo,bar,baz -// @access Public -exports.getAllPairs = asyncWrapper(async (req, res, next) => { - let pairs; - - if (req.query.keys) { - // Check for specific keys to get in a single query - const keys = req.query.keys.split(',').map((key) => { - return { key }; - }); - - pairs = await Config.findAll({ - where: { - [Op.or]: keys, - }, - }); - } else { - // Else get all - pairs = await Config.findAll(); - } - - res.status(200).json({ - success: true, - data: pairs, - }); -}); - -// @desc Get single key:value pair -// @route GET /api/config/:key -// @access Public -exports.getSinglePair = asyncWrapper(async (req, res, next) => { - const pair = await Config.findOne({ - where: { key: req.params.key }, - }); - - if (!pair) { - return next(new ErrorResponse(`Key ${req.params.key} was not found`, 404)); - } - - res.status(200).json({ - success: true, - data: pair, - }); -}); - -// @desc Update value -// @route PUT /api/config/:key -// @access Public -exports.updateValue = asyncWrapper(async (req, res, next) => { - let pair = await Config.findOne({ - where: { key: req.params.key }, - }); - - if (!pair) { - return next(new ErrorResponse(`Key ${req.params.key} was not found`, 404)); - } - - if (pair.isLocked) { - return next( - new ErrorResponse( - `Value of key ${req.params.key} is locked and can not be changed`, - 400 - ) - ); - } - - pair = await pair.update({ ...req.body }); - - res.status(200).json({ - success: true, - data: pair, - }); -}); - -// @desc Update multiple values -// @route PUT /api/config/ -// @access Public -exports.updateValues = asyncWrapper(async (req, res, next) => { - Object.entries(req.body).forEach(async ([key, value]) => { - await Config.update( - { value }, - { - where: { key }, - } - ); - }); - - const config = await Config.findAll(); - - res.status(200).send({ - success: true, - data: config, - }); -}); - -// @desc Delete key:value pair -// @route DELETE /api/config/:key -// @access Public -exports.deletePair = asyncWrapper(async (req, res, next) => { - const pair = await Config.findOne({ - where: { key: req.params.key }, - }); - - if (!pair) { - return next(new ErrorResponse(`Key ${req.params.key} was not found`, 404)); - } - - if (pair.isLocked) { - return next( - new ErrorResponse( - `Value of key ${req.params.key} is locked and can not be deleted`, - 400 - ) - ); - } - - await pair.destroy(); - - res.status(200).json({ - success: true, - data: {}, - }); -}); - -// @desc Get custom CSS file -// @route GET /api/config/0/css -// @access Public -exports.getCss = asyncWrapper(async (req, res, next) => { - const file = new File(join(__dirname, '../public/flame.css')); - const content = file.read(); - - res.status(200).json({ - success: true, - data: content, - }); -}); - -// @desc Update custom CSS file -// @route PUT /api/config/0/css -// @access Public -exports.updateCss = asyncWrapper(async (req, res, next) => { - const file = new File(join(__dirname, '../public/flame.css')); - file.write(req.body.styles, false); - - // Copy file to docker volume - fs.copyFileSync( - join(__dirname, '../public/flame.css'), - join(__dirname, '../data/flame.css') - ); - - res.status(200).json({ - success: true, - data: {}, - }); -}); diff --git a/controllers/config/getCSS.js b/controllers/config/getCSS.js new file mode 100644 index 0000000..db6b783 --- /dev/null +++ b/controllers/config/getCSS.js @@ -0,0 +1,18 @@ +const asyncWrapper = require('../../middleware/asyncWrapper'); +const File = require('../../utils/File'); +const { join } = require('path'); + +// @desc Get custom CSS file +// @route GET /api/config/0/css +// @access Public +const getCSS = asyncWrapper(async (req, res, next) => { + const file = new File(join(__dirname, '../../public/flame.css')); + const content = file.read(); + + res.status(200).json({ + success: true, + data: content, + }); +}); + +module.exports = getCSS; diff --git a/controllers/config/getConfig.js b/controllers/config/getConfig.js new file mode 100644 index 0000000..cb196f7 --- /dev/null +++ b/controllers/config/getConfig.js @@ -0,0 +1,16 @@ +const asyncWrapper = require('../../middleware/asyncWrapper'); +const loadConfig = require('../../utils/loadConfig'); + +// @desc Get config +// @route GET /api/config +// @access Public +const getConfig = asyncWrapper(async (req, res, next) => { + const config = await loadConfig(); + + res.status(200).json({ + success: true, + data: config, + }); +}); + +module.exports = getConfig; diff --git a/controllers/config/index.js b/controllers/config/index.js new file mode 100644 index 0000000..ae3c828 --- /dev/null +++ b/controllers/config/index.js @@ -0,0 +1,6 @@ +module.exports = { + getCSS: require('./getCSS'), + updateCSS: require('./updateCSS'), + getConfig: require('./getConfig'), + updateConfig: require('./updateConfig'), +}; diff --git a/controllers/config/updateCSS.js b/controllers/config/updateCSS.js new file mode 100644 index 0000000..4deea76 --- /dev/null +++ b/controllers/config/updateCSS.js @@ -0,0 +1,24 @@ +const asyncWrapper = require('../../middleware/asyncWrapper'); +const File = require('../../utils/File'); +const { join } = require('path'); + +// @desc Update custom CSS file +// @route PUT /api/config/0/css +// @access Public +const updateCSS = asyncWrapper(async (req, res, next) => { + const file = new File(join(__dirname, '../../public/flame.css')); + file.write(req.body.styles, false); + + // Copy file to docker volume + fs.copyFileSync( + join(__dirname, '../../public/flame.css'), + join(__dirname, '../../data/flame.css') + ); + + res.status(200).json({ + success: true, + data: {}, + }); +}); + +module.exports = updateCSS; diff --git a/controllers/config/updateConfig.js b/controllers/config/updateConfig.js new file mode 100644 index 0000000..722f334 --- /dev/null +++ b/controllers/config/updateConfig.js @@ -0,0 +1,24 @@ +const asyncWrapper = require('../../middleware/asyncWrapper'); +const loadConfig = require('../../utils/loadConfig'); +const { writeFile } = require('fs/promises'); + +// @desc Update config +// @route PUT /api/config/ +// @access Public +const updateConfig = asyncWrapper(async (req, res, next) => { + const existingConfig = await loadConfig(); + + const newConfig = { + ...existingConfig, + ...req.body, + }; + + await writeFile('data/config.json', JSON.stringify(newConfig)); + + res.status(200).send({ + success: true, + data: newConfig, + }); +}); + +module.exports = updateConfig; diff --git a/middleware/multer.js b/middleware/multer.js index bd493f5..806e5b4 100644 --- a/middleware/multer.js +++ b/middleware/multer.js @@ -11,7 +11,7 @@ const storage = multer.diskStorage({ }, filename: (req, file, cb) => { cb(null, Date.now() + '--' + file.originalname); - } + }, }); const supportedTypes = ['jpg', 'jpeg', 'png', 'svg', 'svg+xml']; diff --git a/routes/bookmark.js b/routes/bookmark.js index c594738..f7e541b 100644 --- a/routes/bookmark.js +++ b/routes/bookmark.js @@ -4,21 +4,18 @@ const upload = require('../middleware/multer'); const { createBookmark, - getBookmarks, - getBookmark, + getAllBookmarks, + getSingleBookmark, updateBookmark, - deleteBookmark -} = require('../controllers/bookmark'); + deleteBookmark, +} = require('../controllers/bookmarks'); -router - .route('/') - .post(upload, createBookmark) - .get(getBookmarks); +router.route('/').post(upload, createBookmark).get(getAllBookmarks); router .route('/:id') - .get(getBookmark) + .get(getSingleBookmark) .put(upload, updateBookmark) .delete(deleteBookmark); -module.exports = router; \ No newline at end of file +module.exports = router; diff --git a/routes/config.js b/routes/config.js index 8c9ac15..fbb632f 100644 --- a/routes/config.js +++ b/routes/config.js @@ -2,20 +2,14 @@ const express = require('express'); const router = express.Router(); const { - createPair, - getAllPairs, - getSinglePair, - updateValue, - updateValues, - deletePair, - updateCss, - getCss, + getCSS, + updateCSS, + getConfig, + updateConfig, } = require('../controllers/config'); -router.route('/').post(createPair).get(getAllPairs).put(updateValues); +router.route('/').get(getConfig).put(updateConfig); -router.route('/:key').get(getSinglePair).put(updateValue).delete(deletePair); - -router.route('/0/css').get(getCss).put(updateCss); +router.route('/0/css').get(getCSS).put(updateCSS); module.exports = router;