Pārlūkot izejas kodu

Split apps controllers into separate files

Paweł Malak 3 gadi atpakaļ
vecāks
revīzija
34279c8b8c

+ 0 - 352
controllers/apps.js

@@ -1,352 +0,0 @@
-const asyncWrapper = require('../middleware/asyncWrapper');
-const ErrorResponse = require('../utils/ErrorResponse');
-const App = require('../models/App');
-const Config = require('../models/Config');
-const { Sequelize } = require('sequelize');
-const axios = require('axios');
-const Logger = require('../utils/Logger');
-const logger = new Logger();
-const k8s = require('@kubernetes/client-node');
-
-// @desc      Create new app
-// @route     POST /api/apps
-// @access    Public
-exports.createApp = asyncWrapper(async (req, res, next) => {
-  // Get config from database
-  const pinApps = await Config.findOne({
-    where: { key: 'pinAppsByDefault' },
-  });
-
-  let app;
-  let _body = { ...req.body };
-
-  if (req.file) {
-    _body.icon = req.file.filename;
-  }
-
-  if (pinApps) {
-    if (parseInt(pinApps.value)) {
-      app = await App.create({
-        ..._body,
-        isPinned: true,
-      });
-    } else {
-      app = await App.create(req.body);
-    }
-  }
-
-  res.status(201).json({
-    success: true,
-    data: app,
-  });
-});
-
-// @desc      Get all apps
-// @route     GET /api/apps
-// @access    Public
-exports.getApps = asyncWrapper(async (req, res, next) => {
-  // Get config from database
-  const useOrdering = await Config.findOne({
-    where: { key: 'useOrdering' },
-  });
-  const useDockerApi = await Config.findOne({
-    where: { key: 'dockerApps' },
-  });
-  const useKubernetesApi = await Config.findOne({
-    where: { key: 'kubernetesApps' },
-  });
-  const unpinStoppedApps = await Config.findOne({
-    where: { key: 'unpinStoppedApps' },
-  });
-
-  const orderType = useOrdering ? useOrdering.value : 'createdAt';
-  let apps;
-
-  if (useDockerApi && useDockerApi.value == 1) {
-    let containers = null;
-
-    const host = await Config.findOne({
-      where: { key: 'dockerHost' },
-    });
-
-    try {
-      if (host.value.includes('localhost')) {
-        let { data } = await axios.get(
-          `http://${host.value}/containers/json?{"status":["running"]}`,
-          {
-            socketPath: '/var/run/docker.sock',
-          }
-        );
-        containers = data;
-      } else {
-        let { data } = await axios.get(
-          `http://${host.value}/containers/json?{"status":["running"]}`
-        );
-        containers = data;
-      }
-    } catch {
-      logger.log(`Can't connect to the docker api on ${host.value}`, 'ERROR');
-    }
-
-    if (containers) {
-      apps = await App.findAll({
-        order: [[orderType, 'ASC']],
-      });
-
-      containers = containers.filter((e) => Object.keys(e.Labels).length !== 0);
-      const dockerApps = [];
-      for (const container of containers) {
-        let labels = container.Labels;
-
-        if (!('flame.url' in labels)) {
-          for (const label of Object.keys(labels)) {
-            if (/^traefik.*.frontend.rule/.test(label)) {
-              // Traefik 1.x
-              let value = labels[label];
-              if (value.indexOf('Host') !== -1) {
-                value = value.split('Host:')[1];
-                labels['flame.url'] = 'https://' + value.split(',').join(';https://');
-              }
-            } else if (/^traefik.*?\.rule/.test(label)) {
-              // Traefik 2.x
-              const value = labels[label];
-              if (value.indexOf('Host') !== -1) {
-                const regex = /\`([a-zA-Z0-9\.\-]+)\`/g;
-                const domains = []
-                while ((match = regex.exec(value)) != null) {
-                  domains.push('http://' + match[1]);
-                }
-                if (domains.length > 0) {
-                  labels['flame.url'] = domains.join(';');
-                }
-              }
-            }
-          }
-        }
-
-        if (
-          'flame.name' in labels &&
-          'flame.url' in labels &&
-          /^app/.test(labels['flame.type'])
-        ) {
-          for (let i = 0; i < labels['flame.name'].split(';').length; i++) {
-            const names = labels['flame.name'].split(';');
-            const urls = labels['flame.url'].split(';');
-            let icons = '';
-
-            if ('flame.icon' in labels) {
-              icons = labels['flame.icon'].split(';');
-            }
-
-            dockerApps.push({
-              name: names[i] || names[0],
-              url: urls[i] || urls[0],
-              icon: icons[i] || 'docker',
-            });
-          }
-        }
-      }
-
-      if (unpinStoppedApps && unpinStoppedApps.value == 1) {
-        for (const app of apps) {
-          await app.update({ isPinned: false });
-        }
-      }
-
-      for (const item of dockerApps) {
-        if (apps.some((app) => app.name === item.name)) {
-          const app = apps.filter((e) => e.name === item.name)[0];
-
-          if (
-            item.icon === 'custom' ||
-            (item.icon === 'docker' && app.icon != 'docker')
-          ) {
-            await app.update({
-              name: item.name,
-              url: item.url,
-              isPinned: true,
-            });
-          } else {
-            await app.update({
-              name: item.name,
-              url: item.url,
-              icon: item.icon,
-              isPinned: true,
-            });
-          }
-        } else {
-          await App.create({
-            name: item.name,
-            url: item.url,
-            icon: item.icon === 'custom' ? 'docker' : item.icon,
-            isPinned: true,
-          });
-        }
-      }
-    }
-  }
-
-  if (useKubernetesApi && useKubernetesApi.value == 1) {
-    let ingresses = null;
-
-    try {
-      const kc = new k8s.KubeConfig();
-      kc.loadFromCluster();
-      const k8sNetworkingV1Api = kc.makeApiClient(k8s.NetworkingV1Api);
-      await k8sNetworkingV1Api.listIngressForAllNamespaces().then((res) => {
-        ingresses = res.body.items;
-      });
-    } catch {
-      logger.log("Can't connect to the kubernetes api", 'ERROR');
-    }
-
-    if (ingresses) {
-      apps = await App.findAll({
-        order: [[orderType, 'ASC']],
-      });
-
-      ingresses = ingresses.filter(
-        (e) => Object.keys(e.metadata.annotations).length !== 0
-      );
-      const kubernetesApps = [];
-      for (const ingress of ingresses) {
-        const annotations = ingress.metadata.annotations;
-
-        if (
-          'flame.pawelmalak/name' in annotations &&
-          'flame.pawelmalak/url' in annotations &&
-          /^app/.test(annotations['flame.pawelmalak/type'])
-        ) {
-          kubernetesApps.push({
-            name: annotations['flame.pawelmalak/name'],
-            url: annotations['flame.pawelmalak/url'],
-            icon: annotations['flame.pawelmalak/icon'] || 'kubernetes',
-          });
-        }
-      }
-
-      if (unpinStoppedApps && unpinStoppedApps.value == 1) {
-        for (const app of apps) {
-          await app.update({ isPinned: false });
-        }
-      }
-
-      for (const item of kubernetesApps) {
-        if (apps.some((app) => app.name === item.name)) {
-          const app = apps.filter((e) => e.name === item.name)[0];
-          await app.update({ ...item, isPinned: true });
-        } else {
-          await App.create({
-            ...item,
-            isPinned: true,
-          });
-        }
-      }
-    }
-  }
-
-  if (orderType == 'name') {
-    apps = await App.findAll({
-      order: [[Sequelize.fn('lower', Sequelize.col('name')), 'ASC']],
-    });
-  } else {
-    apps = await App.findAll({
-      order: [[orderType, 'ASC']],
-    });
-  }
-
-  if (process.env.NODE_ENV === 'production') {
-    // Set header to fetch containers info every time
-    res.status(200).setHeader('Cache-Control', 'no-store').json({
-      success: true,
-      data: apps,
-    });
-    return;
-  }
-
-  res.status(200).json({
-    success: true,
-    data: apps,
-  });
-});
-
-// @desc      Get single app
-// @route     GET /api/apps/:id
-// @access    Public
-exports.getApp = asyncWrapper(async (req, res, next) => {
-  const app = await App.findOne({
-    where: { id: req.params.id },
-  });
-
-  if (!app) {
-    return next(
-      new ErrorResponse(`App with id of ${req.params.id} was not found`, 404)
-    );
-  }
-
-  res.status(200).json({
-    success: true,
-    data: app,
-  });
-});
-
-// @desc      Update app
-// @route     PUT /api/apps/:id
-// @access    Public
-exports.updateApp = asyncWrapper(async (req, res, next) => {
-  let app = await App.findOne({
-    where: { id: req.params.id },
-  });
-
-  if (!app) {
-    return next(
-      new ErrorResponse(`App with id of ${req.params.id} was not found`, 404)
-    );
-  }
-
-  let _body = { ...req.body };
-
-  if (req.file) {
-    _body.icon = req.file.filename;
-  }
-
-  app = await app.update(_body);
-
-  res.status(200).json({
-    success: true,
-    data: app,
-  });
-});
-
-// @desc      Delete app
-// @route     DELETE /api/apps/:id
-// @access    Public
-exports.deleteApp = asyncWrapper(async (req, res, next) => {
-  await App.destroy({
-    where: { id: req.params.id },
-  });
-
-  res.status(200).json({
-    success: true,
-    data: {},
-  });
-});
-
-// @desc      Reorder apps
-// @route     PUT /api/apps/0/reorder
-// @access    Public
-exports.reorderApps = asyncWrapper(async (req, res, next) => {
-  req.body.apps.forEach(async ({ id, orderId }) => {
-    await App.update(
-      { orderId },
-      {
-        where: { id },
-      }
-    );
-  });
-
-  res.status(200).json({
-    success: true,
-    data: {},
-  });
-});

+ 33 - 0
controllers/apps/createApp.js

@@ -0,0 +1,33 @@
+const asyncWrapper = require('../../middleware/asyncWrapper');
+const App = require('../../models/App');
+const loadConfig = require('../../utils/loadConfig');
+
+// @desc      Create new app
+// @route     POST /api/apps
+// @access    Public
+const createApp = asyncWrapper(async (req, res, next) => {
+  const { pinAppsByDefault } = await loadConfig();
+
+  let app;
+  let _body = { ...req.body };
+
+  if (req.file) {
+    _body.icon = req.file.filename;
+  }
+
+  if (pinAppsByDefault) {
+    app = await App.create({
+      ..._body,
+      isPinned: true,
+    });
+  } else {
+    app = await App.create(req.body);
+  }
+
+  res.status(201).json({
+    success: true,
+    data: app,
+  });
+});
+
+module.exports = createApp;

+ 18 - 0
controllers/apps/deleteApp.js

@@ -0,0 +1,18 @@
+const asyncWrapper = require('../../middleware/asyncWrapper');
+const App = require('../../models/App');
+
+// @desc      Delete app
+// @route     DELETE /api/apps/:id
+// @access    Public
+const deleteApp = asyncWrapper(async (req, res, next) => {
+  await App.destroy({
+    where: { id: req.params.id },
+  });
+
+  res.status(200).json({
+    success: true,
+    data: {},
+  });
+});
+
+module.exports = deleteApp;

+ 4 - 0
controllers/apps/docker/index.js

@@ -0,0 +1,4 @@
+module.exports = {
+  useKubernetes: require('./useKubernetes'),
+  useDocker: require('./useDocker'),
+};

+ 148 - 0
controllers/apps/docker/useDocker.js

@@ -0,0 +1,148 @@
+const App = require('../../models/App');
+const axios = require('axios');
+const Logger = require('../../utils/Logger');
+const logger = new Logger();
+const loadConfig = require('../../utils/loadConfig');
+
+const useDocker = async (apps) => {
+  const {
+    useOrdering: orderType,
+    unpinStoppedApps,
+    dockerHost: host,
+  } = await loadConfig();
+
+  let containers = null;
+
+  // Get list of containers
+  try {
+    if (host.includes('localhost')) {
+      // Use default host
+      let { data } = await axios.get(
+        `http://${host}/containers/json?{"status":["running"]}`,
+        {
+          socketPath: '/var/run/docker.sock',
+        }
+      );
+
+      containers = data;
+    } else {
+      // Use custom host
+      let { data } = await axios.get(
+        `http://${host}/containers/json?{"status":["running"]}`
+      );
+
+      containers = data;
+    }
+  } catch {
+    logger.log(`Can't connect to the Docker API on ${host}`, 'ERROR');
+  }
+
+  if (containers) {
+    apps = await App.findAll({
+      order: [[orderType, 'ASC']],
+    });
+
+    // Filter out containers without any annotations
+    containers = containers.filter((e) => Object.keys(e.Labels).length !== 0);
+
+    const dockerApps = [];
+
+    for (const container of containers) {
+      let labels = container.Labels;
+
+      // todo
+      if (!('flame.url' in labels)) {
+        for (const label of Object.keys(labels)) {
+          if (/^traefik.*.frontend.rule/.test(label)) {
+            // Traefik 1.x
+            let value = labels[label];
+
+            if (value.indexOf('Host') !== -1) {
+              value = value.split('Host:')[1];
+              labels['flame.url'] =
+                'https://' + value.split(',').join(';https://');
+            }
+          } else if (/^traefik.*?\.rule/.test(label)) {
+            // Traefik 2.x
+            const value = labels[label];
+
+            if (value.indexOf('Host') !== -1) {
+              const regex = /\`([a-zA-Z0-9\.\-]+)\`/g;
+              const domains = [];
+
+              while ((match = regex.exec(value)) != null) {
+                domains.push('http://' + match[1]);
+              }
+
+              if (domains.length > 0) {
+                labels['flame.url'] = domains.join(';');
+              }
+            }
+          }
+        }
+      }
+
+      // add each container as flame formatted app
+      if (
+        'flame.name' in labels &&
+        'flame.url' in labels &&
+        /^app/.test(labels['flame.type'])
+      ) {
+        for (let i = 0; i < labels['flame.name'].split(';').length; i++) {
+          const names = labels['flame.name'].split(';');
+          const urls = labels['flame.url'].split(';');
+          let icons = '';
+
+          if ('flame.icon' in labels) {
+            icons = labels['flame.icon'].split(';');
+          }
+
+          dockerApps.push({
+            name: names[i] || names[0],
+            url: urls[i] || urls[0],
+            icon: icons[i] || 'docker',
+          });
+        }
+      }
+    }
+
+    if (unpinStoppedApps) {
+      for (const app of apps) {
+        await app.update({ isPinned: false });
+      }
+    }
+
+    for (const item of dockerApps) {
+      // If app already exists, update it
+      if (apps.some((app) => app.name === item.name)) {
+        const app = apps.find((a) => a.name === item.name);
+
+        if (
+          item.icon === 'custom' ||
+          (item.icon === 'docker' && app.icon != 'docker')
+        ) {
+          // update without overriding icon
+          await app.update({
+            name: item.name,
+            url: item.url,
+            isPinned: true,
+          });
+        } else {
+          await app.update({
+            ...item,
+            isPinned: true,
+          });
+        }
+      } else {
+        // else create new app
+        await App.create({
+          ...item,
+          icon: item.icon === 'custom' ? 'docker' : item.icon,
+          isPinned: true,
+        });
+      }
+    }
+  }
+};
+
+module.exports = useDocker;

+ 70 - 0
controllers/apps/docker/useKubernetes.js

@@ -0,0 +1,70 @@
+const App = require('../../../models/App');
+const k8s = require('@kubernetes/client-node');
+const Logger = require('../../../utils/Logger');
+const logger = new Logger();
+const loadConfig = require('../../../utils/loadConfig');
+
+const useKubernetes = async (apps) => {
+  const { useOrdering: orderType, unpinStoppedApps } = await loadConfig();
+
+  let ingresses = null;
+
+  try {
+    const kc = new k8s.KubeConfig();
+    kc.loadFromCluster();
+    const k8sNetworkingV1Api = kc.makeApiClient(k8s.NetworkingV1Api);
+    await k8sNetworkingV1Api.listIngressForAllNamespaces().then((res) => {
+      ingresses = res.body.items;
+    });
+  } catch {
+    logger.log("Can't connect to the Kubernetes API", 'ERROR');
+  }
+
+  if (ingresses) {
+    apps = await App.findAll({
+      order: [[orderType, 'ASC']],
+    });
+
+    ingresses = ingresses.filter(
+      (e) => Object.keys(e.metadata.annotations).length !== 0
+    );
+
+    const kubernetesApps = [];
+
+    for (const ingress of ingresses) {
+      const annotations = ingress.metadata.annotations;
+
+      if (
+        'flame.pawelmalak/name' in annotations &&
+        'flame.pawelmalak/url' in annotations &&
+        /^app/.test(annotations['flame.pawelmalak/type'])
+      ) {
+        kubernetesApps.push({
+          name: annotations['flame.pawelmalak/name'],
+          url: annotations['flame.pawelmalak/url'],
+          icon: annotations['flame.pawelmalak/icon'] || 'kubernetes',
+        });
+      }
+    }
+
+    if (unpinStoppedApps) {
+      for (const app of apps) {
+        await app.update({ isPinned: false });
+      }
+    }
+
+    for (const item of kubernetesApps) {
+      if (apps.some((app) => app.name === item.name)) {
+        const app = apps.find((a) => a.name === item.name);
+        await app.update({ ...item, isPinned: true });
+      } else {
+        await App.create({
+          ...item,
+          isPinned: true,
+        });
+      }
+    }
+  }
+};
+
+module.exports = useKubernetes;

+ 52 - 0
controllers/apps/getAllApps.js

@@ -0,0 +1,52 @@
+const asyncWrapper = require('../../middleware/asyncWrapper');
+const App = require('../../models/App');
+const { Sequelize } = require('sequelize');
+const loadConfig = require('../../utils/loadConfig');
+
+const { useKubernetes, useDocker } = require('./docker');
+
+// @desc      Get all apps
+// @route     GET /api/apps
+// @access    Public
+const getAllApps = asyncWrapper(async (req, res, next) => {
+  const {
+    useOrdering: orderType,
+    dockerApps: useDockerAPI,
+    kubernetesApps: useKubernetesAPI,
+  } = await loadConfig();
+
+  let apps;
+
+  if (useDockerAPI) {
+    await useDocker(apps);
+  }
+
+  if (useKubernetesAPI) {
+    await useKubernetes(apps);
+  }
+
+  if (orderType == 'name') {
+    apps = await App.findAll({
+      order: [[Sequelize.fn('lower', Sequelize.col('name')), 'ASC']],
+    });
+  } else {
+    apps = await App.findAll({
+      order: [[orderType, 'ASC']],
+    });
+  }
+
+  if (process.env.NODE_ENV === 'production') {
+    // Set header to fetch containers info every time
+    return res.status(200).setHeader('Cache-Control', 'no-store').json({
+      success: true,
+      data: apps,
+    });
+  }
+
+  res.status(200).json({
+    success: true,
+    data: apps,
+  });
+});
+
+module.exports = getAllApps;

+ 27 - 0
controllers/apps/getSingleApp.js

@@ -0,0 +1,27 @@
+const asyncWrapper = require('../../middleware/asyncWrapper');
+const App = require('../../models/App');
+
+// @desc      Get single app
+// @route     GET /api/apps/:id
+// @access    Public
+const getSingleApp = asyncWrapper(async (req, res, next) => {
+  const app = await App.findOne({
+    where: { id: req.params.id },
+  });
+
+  if (!app) {
+    return next(
+      new ErrorResponse(
+        `App with the id of ${req.params.id} was not found`,
+        404
+      )
+    );
+  }
+
+  res.status(200).json({
+    success: true,
+    data: app,
+  });
+});
+
+module.exports = getSingleApp;

+ 8 - 0
controllers/apps/index.js

@@ -0,0 +1,8 @@
+module.exports = {
+  createApp: require('./createApp'),
+  getSingleApp: require('./getSingleApp'),
+  deleteApp: require('./deleteApp'),
+  updateApp: require('./updateApp'),
+  reorderApps: require('./reorderApps'),
+  getAllApps: require('./getAllApps'),
+};

+ 23 - 0
controllers/apps/reorderApps.js

@@ -0,0 +1,23 @@
+const asyncWrapper = require('../../middleware/asyncWrapper');
+const App = require('../../models/App');
+
+// @desc      Reorder apps
+// @route     PUT /api/apps/0/reorder
+// @access    Public
+const reorderApps = asyncWrapper(async (req, res, next) => {
+  req.body.apps.forEach(async ({ id, orderId }) => {
+    await App.update(
+      { orderId },
+      {
+        where: { id },
+      }
+    );
+  });
+
+  res.status(200).json({
+    success: true,
+    data: {},
+  });
+});
+
+module.exports = reorderApps;

+ 35 - 0
controllers/apps/updateApp.js

@@ -0,0 +1,35 @@
+const asyncWrapper = require('../../middleware/asyncWrapper');
+const App = require('../../models/App');
+
+// @desc      Update app
+// @route     PUT /api/apps/:id
+// @access    Public
+const updateApp = asyncWrapper(async (req, res, next) => {
+  let app = await App.findOne({
+    where: { id: req.params.id },
+  });
+
+  if (!app) {
+    return next(
+      new ErrorResponse(
+        `App with the id of ${req.params.id} was not found`,
+        404
+      )
+    );
+  }
+
+  let _body = { ...req.body };
+
+  if (req.file) {
+    _body.icon = req.file.filename;
+  }
+
+  app = await app.update(_body);
+
+  res.status(200).json({
+    success: true,
+    data: app,
+  });
+});
+
+module.exports = updateApp;

+ 0 - 1
db/index.js

@@ -1,6 +1,5 @@
 const { Sequelize } = require('sequelize');
 const { join } = require('path');
-const fs = require('fs');
 const Umzug = require('umzug');
 const backupDB = require('./utils/backupDb');
 

+ 3 - 13
middleware/asyncWrapper.js

@@ -1,17 +1,7 @@
-// const asyncWrapper = foo => (req, res, next) => {
-//   return Promise
-//     .resolve(foo(req, res, next))
-//     .catch(next);
-// }
-
-// module.exports = asyncWrapper;
-
 function asyncWrapper(foo) {
   return function (req, res, next) {
-    return Promise
-      .resolve(foo(req, res, next))
-      .catch(next);
-  }
+    return Promise.resolve(foo(req, res, next)).catch(next);
+  };
 }
 
-module.exports = asyncWrapper;
+module.exports = asyncWrapper;

+ 8 - 4
middleware/errorHandler.js

@@ -14,10 +14,14 @@ const errorHandler = (err, req, res, next) => {
 
   logger.log(error.message.split(',')[0], 'ERROR');
 
+  if (process.env.NODE_ENV == 'development') {
+    console.log(err);
+  }
+
   res.status(err.statusCode || 500).json({
     success: false,
-    error: error.message || 'Server Error'
-  })
-}
+    error: error.message || 'Server Error',
+  });
+};
 
-module.exports = errorHandler;
+module.exports = errorHandler;

+ 7 - 16
routes/apps.js

@@ -4,26 +4,17 @@ const upload = require('../middleware/multer');
 
 const {
   createApp,
-  getApps,
-  getApp,
+  getAllApps,
+  getSingleApp,
   updateApp,
   deleteApp,
-  reorderApps
+  reorderApps,
 } = require('../controllers/apps');
 
-router
-  .route('/')
-  .post(upload, createApp)
-  .get(getApps);
+router.route('/').post(upload, createApp).get(getAllApps);
 
-router
-  .route('/:id')
-  .get(getApp)
-  .put(upload, updateApp)
-  .delete(deleteApp);
+router.route('/:id').get(getSingleApp).put(upload, updateApp).delete(deleteApp);
 
-router
-  .route('/0/reorder')
-  .put(reorderApps);
+router.route('/0/reorder').put(reorderApps);
 
-module.exports = router;
+module.exports = router;

+ 8 - 12
utils/getExternalWeather.js

@@ -1,15 +1,9 @@
-const Config = require('../models/Config');
 const Weather = require('../models/Weather');
 const axios = require('axios');
+const loadConfig = require('./loadConfig');
 
 const getExternalWeather = async () => {
-  // Get config from database
-  const config = await Config.findAll();
-
-  // Find and check values
-  const secret = config.find(pair => pair.key === 'WEATHER_API_KEY');
-  const lat = config.find(pair => pair.key === 'lat');
-  const long = config.find(pair => pair.key === 'long');
+  const { WEATHER_API_KEY: secret, lat, long } = await loadConfig();
 
   if (!secret) {
     throw new Error('API key was not found. Weather updated failed');
@@ -21,7 +15,9 @@ const getExternalWeather = async () => {
 
   // Fetch data from external API
   try {
-    const res = await axios.get(`http://api.weatherapi.com/v1/current.json?key=${secret.value}&q=${lat.value},${long.value}`);
+    const res = await axios.get(
+      `http://api.weatherapi.com/v1/current.json?key=${secret}&q=${lat},${long}`
+    );
 
     // Save weather data
     const cursor = res.data.current;
@@ -32,12 +28,12 @@ const getExternalWeather = async () => {
       isDay: cursor.is_day,
       cloud: cursor.cloud,
       conditionText: cursor.condition.text,
-      conditionCode: cursor.condition.code
+      conditionCode: cursor.condition.code,
     });
     return weatherData;
   } catch (err) {
     throw new Error('External API request failed');
   }
-}
+};
 
-module.exports = getExternalWeather;
+module.exports = getExternalWeather;