-
-
Notifications
You must be signed in to change notification settings - Fork 157
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat: add backend-api folder * feat: move app code to mobile-app folder * update backend-api package.json * change `backend-api` to `mobile-api` * install all dependencies in one go * add script for development * fix: missing types * order imports
- Loading branch information
1 parent
d00f44d
commit 52a39ef
Showing
174 changed files
with
479 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
{ | ||
"name": "@freecodecamp/mobile-api", | ||
"version": "1.0.0", | ||
"author": "freeCodeCamp <[email protected]>", | ||
"license": "BSD-3-Clause", | ||
"private": true, | ||
"engines": { | ||
"node": ">=16", | ||
"npm": ">=8" | ||
}, | ||
"scripts": { | ||
"dev": "nodemon src/index.ts" | ||
}, | ||
"dependencies": { | ||
"@types/express": "^4.17.13", | ||
"@types/node": "^16.11.11", | ||
"bree": "^7.1.0", | ||
"dotenv": "^10.0.0", | ||
"express": "^4.17.1", | ||
"mongoose": "^6.0.14", | ||
"nodemon": "^2.0.15", | ||
"rss-parser": "^3.12.0", | ||
"ts-node": "^10.4.0", | ||
"typescript": "^4.5.2" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
MONGODB_URL= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
require('dotenv').config(); | ||
import mongoose from 'mongoose'; | ||
|
||
async function dbConnect() { | ||
try { | ||
await mongoose.connect(process.env.MONGODB_URL!); | ||
return console.log('Database connected'); | ||
} catch (error) { | ||
console.log('Database connection error: ' + error); | ||
process.exit(1); | ||
} | ||
} | ||
|
||
export default dbConnect; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
require('dotenv').config(); | ||
import Bree from 'bree'; | ||
import express, { Request, Response } from 'express'; | ||
import path from 'path/posix'; | ||
import dbConnect from './db-connect'; | ||
import podcastRoutes from './routes'; | ||
|
||
const app = express(); | ||
const port = 3000; | ||
const bree = new Bree({ | ||
root: path.join(__dirname, 'jobs'), | ||
defaultExtension: 'ts', | ||
jobs: [ | ||
{ | ||
name: 'Update Podcasts', | ||
path: typescript_worker, | ||
timeout: 0, | ||
interval: '5m', | ||
worker: { | ||
workerData: { __filename: './src/jobs/update-podcasts.ts' }, | ||
}, | ||
}, | ||
], | ||
}); | ||
|
||
function typescript_worker() { | ||
const path = require('path'); | ||
require('ts-node').register(); | ||
const workerData = require('worker_threads').workerData; | ||
require(path.resolve(__dirname, workerData.__filename)); | ||
} | ||
|
||
app.get('/', async (req: Request, res: Response) => { | ||
res.json({ msg: 'Hello World!' }); | ||
}); | ||
|
||
app.use('/podcasts', podcastRoutes); | ||
|
||
app.listen(port, async () => { | ||
await dbConnect(); | ||
console.log(`API listening on port: ${port}`); | ||
console.log('Initialising jobs...'); | ||
bree.start(); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
import { UpdateQuery } from "mongoose"; | ||
import Parser from 'rss-parser'; | ||
import { parentPort } from 'worker_threads'; | ||
import dbConnect from '../db-connect'; | ||
import Episode from '../models/Episode'; | ||
import Podcast from '../models/Podcast'; | ||
import { feedUrls } from '../podcast-feed-urls.json'; | ||
|
||
console.log('Job running at', new Date().toISOString()); | ||
const parser = new Parser(); | ||
|
||
(async function () { | ||
await dbConnect(); | ||
for (const feedUrl of feedUrls) { | ||
const feed = await parser.parseURL(feedUrl); | ||
console.log('UPDATING PODCAST', feed.title); | ||
const podcast = await Podcast.findOneAndUpdate( | ||
{ feedUrl: feedUrl }, | ||
{ | ||
title: feed.title, | ||
description: feed.description, | ||
feedUrl: feedUrl, | ||
podcastLink: feed.link, | ||
imageLink: feed.image?.url || feed.itunes?.image, | ||
copyright: feed.copyright, | ||
numOfEps: feed.items.length, | ||
} as UpdateQuery<typeof Podcast>, | ||
{ | ||
new: true, | ||
upsert: true, | ||
} | ||
); | ||
for (const episode of feed.items) { | ||
await Episode.findOneAndUpdate( | ||
{ | ||
podcastId: podcast._id, | ||
guid: episode.guid, | ||
}, | ||
{ | ||
guid: episode.guid, | ||
podcastId: podcast._id, | ||
title: episode.title, | ||
description: episode.content, | ||
publicationDate: | ||
Date.parse(episode.isoDate!) || Date.parse(episode.pubDate!), | ||
audioUrl: episode.enclosure?.url, | ||
duration: episode.itunes?.duration, | ||
} as UpdateQuery<typeof Episode>, | ||
{ | ||
new: true, | ||
upsert: true, | ||
} | ||
); | ||
} | ||
} | ||
if (parentPort) { | ||
console.log('Job finished at', new Date().toISOString()); | ||
parentPort.postMessage('done'); | ||
} else { | ||
console.log('Job finished at', new Date().toISOString()); | ||
process.exit(0); | ||
} | ||
})(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import * as mongoose from 'mongoose'; | ||
|
||
const Schema = mongoose.Schema; | ||
|
||
const Episode = new Schema({ | ||
guid: { | ||
type: String, | ||
required: true, | ||
}, | ||
podcastId: { | ||
type: Schema.Types.ObjectId, | ||
ref: 'Podcast', | ||
required: true, | ||
}, | ||
title: { | ||
type: String, | ||
required: true, | ||
}, | ||
description: { | ||
type: String, | ||
default: '', | ||
}, | ||
publicationDate: { | ||
type: Date, | ||
}, | ||
audioUrl: { | ||
type: String, | ||
required: true, | ||
}, | ||
duration: { | ||
type: String, | ||
}, | ||
}); | ||
|
||
export default mongoose.models.Episode || | ||
mongoose.model('Episode', Episode, 'episodes'); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import * as mongoose from 'mongoose'; | ||
|
||
const Schema = mongoose.Schema; | ||
|
||
const Podcast = new Schema({ | ||
title: { | ||
type: String, | ||
required: true, | ||
}, | ||
description: { | ||
type: String, | ||
default: '', | ||
}, | ||
feedUrl: { | ||
type: String, | ||
required: true, | ||
}, | ||
podcastLink: { | ||
type: String, | ||
default: '', | ||
}, | ||
imageLink: { | ||
type: String, | ||
required: true, | ||
}, | ||
copyright: { | ||
type: String, | ||
default: '', | ||
}, | ||
numOfEps: { | ||
type: Number, | ||
required: true, | ||
default: 0, | ||
}, | ||
}); | ||
|
||
export default mongoose.models.Podcast || | ||
mongoose.model('Podcast', Podcast, 'podcasts'); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
{ | ||
"feedUrls": [ | ||
"https://feed.syntax.fm/rss", | ||
"https://changelog.com/podcast/feed", | ||
"https://pinecast.com/feed/ladybug-podcast", | ||
"https://freecodecamp.libsyn.com/rss" | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
import express, { Request, Response } from 'express'; | ||
import { UpdateQuery } from "mongoose"; | ||
import Parser from 'rss-parser'; | ||
import Episode from './models/Episode'; | ||
import Podcast from './models/Podcast'; | ||
import { feedUrls } from './podcast-feed-urls.json'; | ||
|
||
const router = express.Router(); | ||
const parser = new Parser(); | ||
|
||
router.get('/', async (req: Request, res: Response) => { | ||
const podcasts = await Podcast.find({}); | ||
if (feedUrls.length !== podcasts.length) { | ||
console.log('Fetching missing podcasts'); | ||
for (const url of feedUrls) { | ||
let feed = await parser.parseURL(url); | ||
console.log(`${feed.title} ${feed.items.length}`); | ||
await Podcast.findOneAndUpdate( | ||
{ feedUrl: url }, | ||
{ | ||
title: feed.title, | ||
description: feed.description, | ||
feedUrl: url, | ||
podcastLink: feed.link, | ||
imageLink: feed.image?.url || feed.itunes?.image, | ||
copyright: feed.copyright, | ||
numOfEps: feed.items.length, | ||
} as UpdateQuery<typeof Podcast>, | ||
{ | ||
new: true, | ||
upsert: true, | ||
} | ||
); | ||
} | ||
res.json(await Podcast.find({})); | ||
} else { | ||
console.log('No missing podcasts'); | ||
res.json(podcasts); | ||
} | ||
}); | ||
|
||
router.get('/:podcastId/episodes', async (req: Request, res: Response) => { | ||
console.log('PARAMS', req.params); | ||
console.log('QUERY', req.query); | ||
let podcast = await Podcast.findById(req.params.podcastId); | ||
let feed = await parser.parseURL(podcast.feedUrl); | ||
// For Debugging | ||
// console.log(feed.items[Math.floor(Math.random() * feed.items.length)]); | ||
if (podcast.numOfEps !== feed.items.length) { | ||
console.log('Fetching missing episodes'); | ||
podcast = await Podcast.findByIdAndUpdate( | ||
req.params.podcastId, | ||
{ | ||
numOfEps: feed.items.length, | ||
} as UpdateQuery<typeof Podcast>, | ||
{ | ||
new: true, | ||
} | ||
); | ||
for (const episode of feed.items) { | ||
await Episode.findOneAndUpdate( | ||
{ | ||
podcastId: podcast._id, | ||
guid: episode.guid, | ||
}, | ||
{ | ||
guid: episode.guid, | ||
podcastId: podcast._id, | ||
title: episode.title, | ||
description: episode.content, | ||
publicationDate: | ||
Date.parse(episode.isoDate!) || Date.parse(episode.pubDate!), | ||
audioUrl: episode.enclosure?.url, | ||
duration: episode.itunes?.duration, | ||
} as UpdateQuery<typeof Episode>, | ||
{ | ||
new: true, | ||
upsert: true, | ||
} | ||
); | ||
} | ||
} else { | ||
console.log('No missing episodes'); | ||
} | ||
let episodes = await Episode.find({ podcastId: podcast._id }) | ||
.sort({ publicationDate: -1 }) | ||
.skip(parseInt((req.query?.page as string) || '0') * 20) | ||
.limit(20); | ||
console.log(episodes.length); | ||
res.json({ podcast, episodes }); | ||
}); | ||
|
||
router.get( | ||
'/:podcastId/episodes/:episodeId', | ||
async (req: Request, res: Response) => { | ||
const episode = await Episode.findOne({ | ||
podcastId: req.params.podcastId, | ||
_id: req.params.episodeId, | ||
}); | ||
console.log('EPISODE', episode.title); | ||
res.json(episode); | ||
} | ||
); | ||
|
||
export default router; |
Oops, something went wrong.