Skip to content

Commit

Permalink
feat: migration to monorepo (#97)
Browse files Browse the repository at this point in the history
* 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
Nirajn2311 authored Dec 13, 2021
1 parent d00f44d commit 52a39ef
Show file tree
Hide file tree
Showing 174 changed files with 479 additions and 5 deletions.
15 changes: 10 additions & 5 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
### Backend API ###
node_modules/
package-lock.json

### Flutter ###
# Miscellaneous
*.class
*.log
Expand Down Expand Up @@ -29,10 +34,10 @@
.packages
.pub-cache/
.pub/
/build/
build/

# Web related
lib/generated_plugin_registrant.dart
**/lib/generated_plugin_registrant.dart

# Symbolication related
app.*.symbols
Expand All @@ -41,9 +46,9 @@ app.*.symbols
app.*.map.json

# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release
**/android/app/debug
**/android/app/profile
**/android/app/release

# VS Code
.vscode/
Expand Down
26 changes: 26 additions & 0 deletions mobile-api/package.json
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"
}
}
1 change: 1 addition & 0 deletions mobile-api/sample.env
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
MONGODB_URL=
14 changes: 14 additions & 0 deletions mobile-api/src/db-connect.ts
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;
44 changes: 44 additions & 0 deletions mobile-api/src/index.ts
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();
});
63 changes: 63 additions & 0 deletions mobile-api/src/jobs/update-podcasts.ts
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);
}
})();
36 changes: 36 additions & 0 deletions mobile-api/src/models/Episode.ts
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');
38 changes: 38 additions & 0 deletions mobile-api/src/models/Podcast.ts
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');
8 changes: 8 additions & 0 deletions mobile-api/src/podcast-feed-urls.json
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"
]
}
105 changes: 105 additions & 0 deletions mobile-api/src/routes.ts
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;
Loading

0 comments on commit 52a39ef

Please sign in to comment.