From 30a2855243371257c71cf10c9c74730593eba783 Mon Sep 17 00:00:00 2001 From: Josue Date: Mon, 25 Oct 2021 23:21:35 -0400 Subject: [PATCH] feat: add a planet microservice --- config/env.development | 8 + config/env.production | 8 + config/env.staging | 8 + config/nginx.conf.development.template | 18 +- config/nginx.conf.template | 21 +- docker/development.yml | 8 +- docker/docker-compose.yml | 11 ++ docker/production.yml | 4 + package.json | 1 - src/api/planet/.dockerignore | 6 + src/api/planet/Dockerfile | 14 ++ src/{backend/web => api}/planet/README.md | 0 src/api/planet/env.local | 2 + src/api/planet/package.json | 28 +++ src/api/planet/src/index.js | 34 ++++ .../web/routes => api/planet/src}/planet.js | 52 ++--- src/api/planet/src/server.js | 5 + .../planet/static/feed-icon-10x10.png | Bin .../planet/static/planet-cdot-logo.png | Bin .../web => api}/planet/static/planet.css | 0 .../planet/views/compare-planets.handlebars | 0 .../web => api}/planet/views/layouts/LICENSE | 0 .../planet/views/layouts/main.handlebars | 0 .../planet/views/planet.handlebars | 0 src/api/posts/src/index.js | 2 + src/api/posts/src/routes/feeds.js | 186 ++++++++++++++++++ src/api/posts/src/validation.js | 32 ++- src/backend/web/app.js | 7 - src/backend/web/routes/index.js | 5 - 29 files changed, 419 insertions(+), 41 deletions(-) create mode 100644 src/api/planet/.dockerignore create mode 100644 src/api/planet/Dockerfile rename src/{backend/web => api}/planet/README.md (100%) create mode 100644 src/api/planet/env.local create mode 100644 src/api/planet/package.json create mode 100644 src/api/planet/src/index.js rename src/{backend/web/routes => api/planet/src}/planet.js (57%) create mode 100644 src/api/planet/src/server.js rename src/{backend/web => api}/planet/static/feed-icon-10x10.png (100%) rename src/{backend/web => api}/planet/static/planet-cdot-logo.png (100%) rename src/{backend/web => api}/planet/static/planet.css (100%) rename src/{backend/web => api}/planet/views/compare-planets.handlebars (100%) rename src/{backend/web => api}/planet/views/layouts/LICENSE (100%) rename src/{backend/web => api}/planet/views/layouts/main.handlebars (100%) rename src/{backend/web => api}/planet/views/planet.handlebars (100%) create mode 100644 src/api/posts/src/routes/feeds.js diff --git a/config/env.development b/config/env.development index 32a46320e3..0c42280c82 100644 --- a/config/env.development +++ b/config/env.development @@ -147,6 +147,14 @@ PARSER_PORT=10000 PARSER_URL=http://localhost/v1/parser +################################################################################ +# Planet Service +################################################################################ + +# Planet Service Port (default is 9876) +PLANET_PORT=9876 + + ################################################################################ # Telescope 1.0 Legacy Environment ################################################################################ diff --git a/config/env.production b/config/env.production index cd3936707d..584dc83fea 100644 --- a/config/env.production +++ b/config/env.production @@ -157,6 +157,14 @@ PARSER_PORT=10000 PARSER_URL=https://api.telescope.cdot.systems/v1/parser +################################################################################ +# Planet Service +################################################################################ + +# Planet Service Port (default is 9876) +PLANET_PORT=9876 + + ################################################################################ # Telescope 1.0 Legacy Environment ################################################################################ diff --git a/config/env.staging b/config/env.staging index 206de8700d..e72f816e05 100644 --- a/config/env.staging +++ b/config/env.staging @@ -159,6 +159,14 @@ PARSER_PORT=10000 PARSER_URL=https://dev.api.telescope.cdot.systems/v1/parser +################################################################################ +# Planet Service +################################################################################ + +# Planet Service Port (default is 9876) +PLANET_PORT=9876 + + ################################################################################ # Telescope 1.0 Legacy Environment ################################################################################ diff --git a/config/nginx.conf.development.template b/config/nginx.conf.development.template index 989bea847f..bef078cc51 100644 --- a/config/nginx.conf.development.template +++ b/config/nginx.conf.development.template @@ -134,8 +134,22 @@ http { # Front-end and Legacy Back-end node.js app are served here server { - listen 80 default_server; - server_name localhost:8000; + listen 8000 default_server; + server_name localhost; + + # Cache content served by /legacy forever + location /legacy { + proxy_cache_bypass 1; + proxy_set_header Connection ""; + proxy_pass http://planet:9876; + } + + # Cache content served by /planet forever + location /planet { + proxy_cache_bypass 1; + proxy_set_header Connection ""; + proxy_pass http://planet:9876; + } # Static next.js front-end location / { diff --git a/config/nginx.conf.template b/config/nginx.conf.template index d37a131542..f8269feba8 100644 --- a/config/nginx.conf.template +++ b/config/nginx.conf.template @@ -195,7 +195,26 @@ http { location /legacy { expires max; add_header Cache-Control "public, max-age=31536000, immutable"; - proxy_pass http://telescope:3000; + proxy_pass http://planet:9876; + } + + # Cache content served by /planet forever + location /planet { + # Defines conditions under which the response will not be taken from a cache + # In this case, cache will be ignored when requested from client + proxy_cache_bypass $http_cache_control; + + # Sets caching time for different response codes + proxy_cache_valid 200 307 10m; + proxy_cache_valid 404 1m; + + # Disables processing of certain response header fields from the proxied server + proxy_ignore_headers "Cache-Control" "Expires"; + + # Added 'X-Proxy-Cache' header to be able to monitor caching + add_header X-Proxy-Cache $upstream_cache_status; + + proxy_pass http://planet:9876; } # Non-cached /portainer route diff --git a/docker/development.yml b/docker/development.yml index a9e2b1a017..db6e1c84c1 100644 --- a/docker/development.yml +++ b/docker/development.yml @@ -15,7 +15,7 @@ services: volumes: - ../config/nginx.conf.development.template:/etc/nginx/nginx.conf ports: - - '8000:80' + - '8000:8000' traefik: command: @@ -94,3 +94,9 @@ services: # For development and testing, the Parser service needs to contact the users # service directly via Docker vs through the http://localhost domain. - USERS_URL=http://users:7000 + + planet: + ports: + - '9876:9876' + environment: + - POSTS_URL=http://posts:5555 diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 629efcdeee..942cfa7009 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -221,6 +221,17 @@ services: - 'traefik.http.middlewares.strip_parser_prefix.stripprefix.forceSlash=true' - 'traefik.http.routers.parser.middlewares=strip_parser_prefix' + # planet service + planet: + container_name: 'planet' + build: + context: ../src/api/planet + dockerfile: Dockerfile + environment: + - NODE_ENV + - PLANET_PORT + - POSTS_URL + ############################################################################## # Third-Party Dependencies and Support Services ############################################################################## diff --git a/docker/production.yml b/docker/production.yml index 726ee293e9..75d89b1901 100644 --- a/docker/production.yml +++ b/docker/production.yml @@ -134,6 +134,10 @@ services: parser: restart: unless-stopped + # planet service + planet: + restart: unless-stopped + # search service search: restart: unless-stopped diff --git a/package.json b/package.json index c952e97926..2c9a45132e 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,6 @@ "dotenv": "8.2.0", "entities": "2.2.0", "express": "4.17.1", - "express-handlebars": "5.3.0", "express-healthcheck": "0.1.0", "express-pino-logger": "6.0.0", "express-session": "1.17.1", diff --git a/src/api/planet/.dockerignore b/src/api/planet/.dockerignore new file mode 100644 index 0000000000..b5d1637224 --- /dev/null +++ b/src/api/planet/.dockerignore @@ -0,0 +1,6 @@ +.dockerignore +node_modules +npm-debug.log +Dockerfile +.git +.gitignore diff --git a/src/api/planet/Dockerfile b/src/api/planet/Dockerfile new file mode 100644 index 0000000000..87fb4b35df --- /dev/null +++ b/src/api/planet/Dockerfile @@ -0,0 +1,14 @@ +FROM node:lts-alpine as base + +# https://snyk.io/blog/10-best-practices-to-containerize-nodejs-web-applications-with-docker/ +RUN apk add dumb-init + +WORKDIR /app + +COPY --chown=node:node . . + +RUN npm install --only=production --no-package-lock + +USER node + +CMD ["dumb-init", "npm", "start"] diff --git a/src/backend/web/planet/README.md b/src/api/planet/README.md similarity index 100% rename from src/backend/web/planet/README.md rename to src/api/planet/README.md diff --git a/src/api/planet/env.local b/src/api/planet/env.local new file mode 100644 index 0000000000..941c0e7ac2 --- /dev/null +++ b/src/api/planet/env.local @@ -0,0 +1,2 @@ +PLANET_PORT=9876 +POSTS_URL=http://localhost/v1/posts diff --git a/src/api/planet/package.json b/src/api/planet/package.json new file mode 100644 index 0000000000..bd976873e8 --- /dev/null +++ b/src/api/planet/package.json @@ -0,0 +1,28 @@ +{ + "name": "@senecacdot/planet-service", + "private": true, + "version": "1.0.0", + "description": "A service the planet version of Telescope", + "scripts": { + "dev": "env-cmd -f env.local nodemon src/server.js", + "start": "node src/server.js" + }, + "repository": "Seneca-CDOT/telescope", + "license": "BSD-2-Clause", + "bugs": { + "url": "https://github.com/Seneca-CDOT/telescope/issues" + }, + "homepage": "https://github.com/Seneca-CDOT/telescope#readme", + "dependencies": { + "@senecacdot/satellite": "^1.x", + "express": "^4.17.1", + "express-handlebars": "^5.3.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "devDependencies": { + "nodemon": "^2.0.7", + "env-cmd": "^10.1.0" + } +} diff --git a/src/api/planet/src/index.js b/src/api/planet/src/index.js new file mode 100644 index 0000000000..d5fd42fd94 --- /dev/null +++ b/src/api/planet/src/index.js @@ -0,0 +1,34 @@ +const { static } = require('express'); +const expressHandlebars = require('express-handlebars'); +const { Satellite } = require('@senecacdot/satellite'); +const path = require('path'); +const planet = require('./planet'); + +const service = new Satellite({ + beforeRouter(app) { + app.engine('handlebars', expressHandlebars()); + app.set('views', path.join(__dirname, '../views')); + app.set('view engine', 'handlebars'); + }, + helmet: { + contentSecurityPolicy: { + directives: { + defaultSrc: ["'self'"], + fontSrc: ["'self'", 'https:', 'data:'], + frameSrc: ["'self'", '*.youtube.com', '*.vimeo.com'], + frameAncestors: ["'self'"], + imgSrc: ["'self'", 'data:', 'https:'], + scriptSrc: ["'self'"], + styleSrc: ["'self'", 'https:', "'unsafe-inline'"], + objectSrc: ["'none'"], + upgradeInsecureRequests: [], + }, + }, + }, +}); + +// Legacy CDOT Planet static assets +service.router.use('/legacy', static(path.join(__dirname, '../static'))); +service.router.use('/planet', planet); + +module.exports = service; diff --git a/src/backend/web/routes/planet.js b/src/api/planet/src/planet.js similarity index 57% rename from src/backend/web/routes/planet.js rename to src/api/planet/src/planet.js index d3f96d7fcc..70faf9e1f0 100644 --- a/src/backend/web/routes/planet.js +++ b/src/api/planet/src/planet.js @@ -1,34 +1,29 @@ -const express = require('express'); +const { Router, logger, fetch } = require('@senecacdot/satellite'); +const router = Router(); -const { getPosts, getFeeds } = require('../../utils/storage'); -const Post = require('../../data/post'); -const Feed = require('../../data/feed'); -const { logger } = require('../../utils/logger'); - -const router = express.Router(); +const { POSTS_URL } = process.env; +const FEEDS_URL = `${POSTS_URL}/feeds`; // Format a Date into a String like `Saturday, September 17, 2016` -function formatDate(date) { +const formatDate = (date) => { const options = { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' }; return date.toLocaleDateString('en-US', options); -} +}; /** * Get most recent 50 posts, and group them according to * day, channel, date. */ -async function getPostDataGrouped() { - const ids = await getPosts(0, 50); - const posts = await Promise.all(ids.map(Post.byId)); - +const getPostDataGrouped = (posts) => { // We group the posts into nested objects: days > authors > posts const grouped = { days: {} }; - posts.forEach((post) => { + posts?.forEach((post) => { if (!post) { return; } - // Top level is formated Day strings + // Top level is formatted Day strings + post.updated = new Date(post.updated); const formatted = formatDate(post.updated); if (!grouped.days[formatted]) { grouped.days[formatted] = {}; @@ -48,22 +43,33 @@ async function getPostDataGrouped() { }); return grouped; -} +}; -async function getFeedData() { - const feedIds = await getFeeds(); - const feeds = await Promise.all(feedIds.map(Feed.byId)); - return feeds.sort((a, b) => { +const sort = (feeds) => { + return feeds?.sort((a, b) => { if (a.author < b.author) return -1; if (a.author > b.author) return 1; return 0; }); -} +}; + +const fetchData = async (dataUrl) => { + try { + const response = await fetch(dataUrl); + const data = await response.json(); + return Promise.all(data.map((item) => fetch(`${dataUrl}/${item.id}`).then((r) => r.json()))); + } catch (e) { + logger.error(e); + } + return; +}; router.get('/', async (req, res, next) => { try { - const [grouped, feeds] = await Promise.all([getPostDataGrouped(), getFeedData()]); - res.render('planet', { feeds, ...grouped }); + const [feeds, posts] = await Promise.all([fetchData(FEEDS_URL), fetchData(POSTS_URL)]); + + const grouped = getPostDataGrouped(posts); + res.render('planet', { feeds: sort(feeds), ...grouped }); } catch (error) { logger.error({ error }, 'Error processing posts from Redis'); next(error); diff --git a/src/api/planet/src/server.js b/src/api/planet/src/server.js new file mode 100644 index 0000000000..92ccaade52 --- /dev/null +++ b/src/api/planet/src/server.js @@ -0,0 +1,5 @@ +const service = require('.'); + +const port = parseInt(process.env.PLANET_PORT || 9876, 10); + +service.start(port); diff --git a/src/backend/web/planet/static/feed-icon-10x10.png b/src/api/planet/static/feed-icon-10x10.png similarity index 100% rename from src/backend/web/planet/static/feed-icon-10x10.png rename to src/api/planet/static/feed-icon-10x10.png diff --git a/src/backend/web/planet/static/planet-cdot-logo.png b/src/api/planet/static/planet-cdot-logo.png similarity index 100% rename from src/backend/web/planet/static/planet-cdot-logo.png rename to src/api/planet/static/planet-cdot-logo.png diff --git a/src/backend/web/planet/static/planet.css b/src/api/planet/static/planet.css similarity index 100% rename from src/backend/web/planet/static/planet.css rename to src/api/planet/static/planet.css diff --git a/src/backend/web/planet/views/compare-planets.handlebars b/src/api/planet/views/compare-planets.handlebars similarity index 100% rename from src/backend/web/planet/views/compare-planets.handlebars rename to src/api/planet/views/compare-planets.handlebars diff --git a/src/backend/web/planet/views/layouts/LICENSE b/src/api/planet/views/layouts/LICENSE similarity index 100% rename from src/backend/web/planet/views/layouts/LICENSE rename to src/api/planet/views/layouts/LICENSE diff --git a/src/backend/web/planet/views/layouts/main.handlebars b/src/api/planet/views/layouts/main.handlebars similarity index 100% rename from src/backend/web/planet/views/layouts/main.handlebars rename to src/api/planet/views/layouts/main.handlebars diff --git a/src/backend/web/planet/views/planet.handlebars b/src/api/planet/views/planet.handlebars similarity index 100% rename from src/backend/web/planet/views/planet.handlebars rename to src/api/planet/views/planet.handlebars diff --git a/src/api/posts/src/index.js b/src/api/posts/src/index.js index a1c8956119..633a7017ba 100644 --- a/src/api/posts/src/index.js +++ b/src/api/posts/src/index.js @@ -2,11 +2,13 @@ const { Satellite } = require('@senecacdot/satellite'); const redis = require('./redis'); const posts = require('./routes/posts'); +const feeds = require('./routes/feeds'); const service = new Satellite({ healthCheck: () => redis.ping(), }); +service.router.use('/feeds', feeds); service.router.use('/', posts); module.exports = service; diff --git a/src/api/posts/src/routes/feeds.js b/src/api/posts/src/routes/feeds.js new file mode 100644 index 0000000000..59e41de032 --- /dev/null +++ b/src/api/posts/src/routes/feeds.js @@ -0,0 +1,186 @@ +const { + Router, + logger, + createError, + isAuthenticated, + isAuthorized, +} = require('@senecacdot/satellite'); +const Feed = require('../data/feed'); +const { getFeeds } = require('../storage'); +const { validateNewFeed, validateFeedsIdParam } = require('../validation'); + +const feeds = Router(); + +const feedURL = `${process.env.POSTS_URL}/feeds` || '/'; + +feeds.get('/', async (req, res, next) => { + let ids; + + try { + ids = await getFeeds(); + } catch (error) { + logger.error({ error }, 'Unable to get feeds from Redis'); + next(error); + } + + res.set('X-Total-Count', ids.length); + res.json( + ids + // Return id and url for a specific feed + .map((id) => ({ + id, + url: `${feedURL}/${id}`, + })) + ); +}); + +feeds.get('/:id', validateFeedsIdParam(), async (req, res, next) => { + const { id } = req.params; + + try { + // If the object we get back is empty, use 404 + const feed = await Feed.byId(id); + if (!feed) { + res.status(404).json({ + message: `Feed not found for id ${id}`, + }); + } else { + res.json(feed); + } + } catch (error) { + logger.error({ error }, 'Unable to get feeds from Redis'); + next(error); + } +}); + +feeds.put( + '/:id/flag', + isAuthenticated(), + isAuthorized((req, user) => { + // Or an admin, or another authorized microservice + return user.roles.includes('admin') || user.roles.includes('service'); + }), + validateFeedsIdParam(), + async (req, res, next) => { + const { id } = req.params; + try { + const feed = await Feed.byId(id); + if (!feed) { + return res.status(404).json({ + message: `Feed for Feed ID ${id} doesn't exist.`, + }); + } + await feed.flag(); + return res.status(200).json({ + message: `Feed successfully flagged`, + }); + } catch (error) { + logger.error({ error }, 'Unable to flag feed in Redis'); + next(error); + } + } +); + +feeds.post( + '/', + isAuthenticated(), + isAuthorized( + // A Seneca user can create a new Telescope user, but an existing Telescope + // user cannot, since they must already have one. + (req, user) => user.roles.includes('seneca') && !user.roles.includes('telescope') + ), + validateNewFeed(), + async (req, res, next) => { + const feedData = req.body; + const { user } = req; + feedData.user = user.id; + try { + if (!(feedData.url && feedData.author)) { + return res.status(400).json({ message: `URL and Author must be submitted` }); + } + if (await Feed.byUrl(feedData.url)) { + return res.status(409).json({ message: `Feed for url ${feedData.url} already exists.` }); + } + const feedId = await Feed.create(feedData); + return res + .status(201) + .json({ message: `Feed was successfully added.`, id: feedId, url: `/feeds/${feedId}` }); + } catch (error) { + logger.error({ error }, 'Unable to add feed to Redis'); + next(error); + } + } +); + +feeds.delete( + '/cache', + isAuthenticated(), + isAuthorized((req, user) => { + // Or an admin, or another authorized microservice + return user.roles.includes('admin') || user.roles.includes('service'); + }), + async (req, res, next) => { + try { + await Feed.clearCache(); + return res.status(204).send(); + } catch (error) { + logger.error({ error }, 'Unable to reset Feed data in Redis'); + next(error); + } + } +); + +feeds.delete( + '/:id', + validateFeedsIdParam(), + isAuthenticated(), + isAuthorized( + // A Seneca user can create a new Telescope user, but an existing Telescope + // user cannot, since they must already have one. + (req, user) => user.roles.includes('seneca') && !user.roles.includes('telescope') + ), + async (req, res, next) => { + const { user } = req; + const { id } = req.params; + try { + const feed = await Feed.byId(id); + if (!feed) { + return res.status(404).json({ message: `Feed for Feed ID ${id} doesn't exist.` }); + } + if (!user.owns(feed)) { + return res.status(403).json({ message: `User does not own this feed.` }); + } + await feed.delete(); + return res.status(204).json({ message: `Feed ${id} was successfully deleted.` }); + } catch (error) { + logger.error({ error }, 'Unable to delete feed to Redis'); + next(error); + } + } +); + +feeds.delete( + '/:id/flag', + isAuthenticated(), + isAuthorized((req, user) => { + // Or an admin, or another authorized microservice + return user.roles.includes('admin') || user.roles.includes('service'); + }), + validateFeedsIdParam(), + async (req, res, next) => { + const { id } = req.params; + try { + const feed = await Feed.byId(id); + if (!feed) { + return res.status(404).json({ message: `Feed for Feed ID ${id} doesn't exist.` }); + } + await feed.unflag(); + return res.status(204).json({ message: `Feed ${id} was successfully unflagged.` }); + } catch (error) { + logger.error({ error }, 'Unable to unflag feed in Redis'); + next(error); + } + } +); + +module.exports = feeds; diff --git a/src/api/posts/src/validation.js b/src/api/posts/src/validation.js index 99b673e34e..fbd2aba9c4 100644 --- a/src/api/posts/src/validation.js +++ b/src/api/posts/src/validation.js @@ -1,4 +1,4 @@ -const { param, validationResult, query } = require('express-validator'); +const { check, param, validationResult, query } = require('express-validator'); const validate = (rules) => { return async (req, res, next) => { @@ -14,6 +14,30 @@ const validate = (rules) => { }; }; +const feedValidationRules = [ + check('user') + .exists({ checkFalsy: true }) + .withMessage('username is required') + .bail() + .isString() + .withMessage('username must be string') + .bail(), + check('author') + .exists({ checkFalsy: true }) + .withMessage('author is required') + .bail() + .isString() + .withMessage('author must be string') + .bail(), + check('url') + .isString() + .withMessage('url must be string') + .bail() + .isURL({ protocols: ['http', 'https'], require_valid_protocol: true, require_protocol: true }) + .withMessage('url must be a valid url') + .bail(), +]; + const postsQueryValidationRules = [ query('per_page', 'per_page needs to be empty or a integer').custom( (value) => !value || Number.isInteger(+value) @@ -27,7 +51,13 @@ const postsIdParamValidationRules = [ param('id', 'Id Length is invalid').isLength({ min: 10, max: 10 }), ]; +const feedsIdParamValidationRules = [ + param('id', 'Feeds Id Length is invalid').isLength({ min: 10, max: 10 }), +]; + module.exports = { + validateNewFeed: () => validate(feedValidationRules), + validateFeedsIdParam: () => validate(feedsIdParamValidationRules), validatePostsQuery: () => validate(postsQueryValidationRules), validatePostsIdParam: () => validate(postsIdParamValidationRules), }; diff --git a/src/backend/web/app.js b/src/backend/web/app.js index 9a322e9c75..b51128ba78 100644 --- a/src/backend/web/app.js +++ b/src/backend/web/app.js @@ -1,6 +1,4 @@ -const path = require('path'); const express = require('express'); -const expressHandlebars = require('express-handlebars'); const session = require('express-session'); const bodyParser = require('body-parser'); const passport = require('passport'); @@ -65,11 +63,6 @@ authentication.init(); app.use(passport.initialize()); app.use(passport.session()); -// Template rendering for legacy "planet" view of posts -app.engine('handlebars', expressHandlebars()); -app.set('views', path.join(__dirname, 'planet/views')); -app.set('view engine', 'handlebars'); - // Add our logger to the app app.set('logger', logger); app.use(logger); diff --git a/src/backend/web/routes/index.js b/src/backend/web/routes/index.js index e05879aa14..78a4af2175 100644 --- a/src/backend/web/routes/index.js +++ b/src/backend/web/routes/index.js @@ -9,7 +9,6 @@ const feeds = require('./feeds'); // The /feed router allows access to generated feeds (RSS, ATOM, etc) const feed = require('./feed'); const health = require('./health'); -const planet = require('./planet'); const serviceProvider = require('./service-provider'); const stats = require('./stats'); const user = require('./user'); @@ -26,15 +25,11 @@ router.use('/auth', auth); router.use('/feeds', feeds); router.use('/feed', feed); router.use('/health', health); -router.use('/planet', planet); router.use('/sp', serviceProvider); router.use('/stats', stats); router.use('/user', user); router.use('/query', query); -// Legacy CDOT Planet static assets -router.use('/legacy', express.static(path.join(__dirname, '../planet/static'))); - /** * In staging and production, our reverse proxy takes care of serving the content in the public folder. * We're keeping this route for development.