Skip to content

Commit 1c74086

Browse files
authored
Merge pull request #13 from rsscloud/modernize-js-patterns
Modernize JavaScript patterns for Node.js 22
2 parents e221eb8 + 2cf97f6 commit 1c74086

39 files changed

+2026
-2083
lines changed

.node-version

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
16
1+
22

CLAUDE.md

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Project Overview
6+
7+
This is an rssCloud Server v2 implementation in Node.js - a notification protocol server that allows RSS feeds to notify subscribers when they are updated. The server handles subscription management and real-time notifications for RSS/feed updates.
8+
9+
## Development Commands
10+
11+
### Start Development
12+
- `npm start` - Start server with nodemon (auto-reload on changes)
13+
- `npm run client` - Start client with nodemon
14+
15+
### Testing & Quality
16+
- `npm test` - Run Mocha test suite
17+
- `npm run test-api` - Run full API tests using Docker containers (MacOS tested)
18+
- `npm run jshint` - Run JSHint linter
19+
- `npm run eslint` - Run ESLint with auto-fix on controllers/, services/, test/
20+
21+
### Data Management
22+
- `npm run import-data` - Import data using bin/import-data.js
23+
24+
## Architecture
25+
26+
### Core Application Structure
27+
- **app.js** - Main Express application entry point, sets up middleware, MongoDB connection, and starts server
28+
- **config.js** - Configuration management using nconf (env vars, CLI args, defaults)
29+
- **controllers/** - Express route handlers for API endpoints
30+
- **services/** - Business logic modules for core functionality
31+
- **views/** - Handlebars templates for web interface
32+
33+
### Key Services
34+
- **services/mongodb.js** - MongoDB connection management with graceful shutdown
35+
- **services/notify-*.js** - Notification system for subscribers
36+
- **services/ping.js** - RSS feed update detection and processing
37+
- **services/please-notify.js** - Subscription management
38+
39+
### API Endpoints (defined in controllers/index.js)
40+
- `/pleaseNotify` - Subscribe to RSS feed notifications
41+
- `/ping` - Notify server of RSS feed updates
42+
- `/viewLog` - Event log viewer for debugging
43+
- `/RPC2` - XML-RPC endpoint
44+
- Web forms available at `/pleaseNotifyForm` and `/pingForm`
45+
46+
### Configuration
47+
Environment variables (with defaults in config.js):
48+
- `MONGODB_URI` (default: mongodb://localhost:27017/rsscloud)
49+
- `DOMAIN` (default: localhost)
50+
- `PORT` (default: 5337)
51+
- Resource limits: MAX_RESOURCE_SIZE, REQUEST_TIMEOUT, etc.
52+
53+
### Database
54+
Uses MongoDB for storing subscriptions and resource state. Connection handled through services/mongodb.js with proper cleanup on shutdown.
55+
56+
### Testing
57+
- Unit tests in test/ directory using Mocha/Chai
58+
- Docker-based API testing with mock endpoints
59+
- Test fixtures and SSL certificates in test/keys/

app.js

Lines changed: 67 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,84 +1,80 @@
1-
(function () {
2-
"use strict";
1+
require('dotenv').config();
32

4-
require('dotenv').config();
3+
const config = require('./config'),
4+
cors = require('cors'),
5+
express = require('express'),
6+
exphbs = require('express-handlebars'),
7+
fs = require('fs'),
8+
moment = require('moment'),
9+
mongodb = require('./services/mongodb'),
10+
morgan = require('morgan'),
11+
removeExpiredSubscriptions = require('./services/remove-expired-subscriptions');
512

6-
const config = require('./config'),
7-
cors = require('cors'),
8-
express = require('express'),
9-
exphbs = require('express-handlebars'),
10-
fs = require('fs'),
11-
moment = require('moment'),
12-
mongodb = require('./services/mongodb'),
13-
morgan = require('morgan'),
14-
removeExpiredSubscriptions = require('./services/remove-expired-subscriptions');
13+
let app,
14+
expressWs,
15+
hbs,
16+
server;
1517

16-
let app,
17-
expressWs,
18-
hbs,
19-
server;
18+
require('console-stamp')(console, 'HH:MM:ss.l');
2019

21-
require('console-stamp')(console, 'HH:MM:ss.l');
20+
console.log(`${config.appName} ${config.appVersion}`);
2221

23-
console.log(`${config.appName} ${config.appVersion}`);
22+
// TODO: Every 24 hours run removeExpiredSubscriptions(data);
2423

25-
// TODO: Every 24 hours run removeExpiredSubscriptions(data);
24+
morgan.format('mydate', function() {
25+
var df = require('dateformat');
26+
return df(new Date(), 'HH:MM:ss.l');
27+
});
2628

27-
morgan.format('mydate', function() {
28-
var df = require('dateformat');
29-
return df(new Date(), 'HH:MM:ss.l');
30-
});
31-
32-
app = express();
33-
expressWs = require('express-ws')(app);
29+
app = express();
30+
expressWs = require('express-ws')(app);
3431

35-
app.use(morgan('[:mydate] :method :url :status :res[content-length] - :remote-addr - :response-time ms'));
32+
app.use(morgan('[:mydate] :method :url :status :res[content-length] - :remote-addr - :response-time ms'));
3633

37-
app.use(cors());
34+
app.use(cors());
3835

39-
// Configure handlebars template engine to work with moment
40-
hbs = exphbs.create({
41-
helpers: {
42-
formatDate: function (datetime, format) {
43-
return moment(datetime).format(format);
44-
}
36+
// Configure handlebars template engine to work with moment
37+
hbs = exphbs.create({
38+
helpers: {
39+
formatDate: function (datetime, format) {
40+
return moment(datetime).format(format);
4541
}
46-
});
47-
48-
// Configure express to use handlebars
49-
app.engine('handlebars', hbs.engine);
50-
app.set('view engine', 'handlebars');
51-
52-
// Handle static files in public directory
53-
app.use(express.static('public', {
54-
dotfiles: 'ignore',
55-
maxAge: '1d'
56-
}));
57-
58-
// Load controllers
59-
app.use(require('./controllers'));
60-
61-
// Start server
62-
mongodb.connect('rsscloud', config.mongodbUri)
63-
.then(() => {
64-
server = app.listen(config.port, function () {
65-
app.locals.host = config.domain;
66-
app.locals.port = server.address().port;
42+
}
43+
});
44+
45+
// Configure express to use handlebars
46+
app.engine('handlebars', hbs.engine);
47+
app.set('view engine', 'handlebars');
48+
49+
// Handle static files in public directory
50+
app.use(express.static('public', {
51+
dotfiles: 'ignore',
52+
maxAge: '1d'
53+
}));
54+
55+
// Load controllers
56+
app.use(require('./controllers'));
57+
58+
// Start server
59+
mongodb.connect('rsscloud', config.mongodbUri)
60+
.then(() => {
61+
server = app.listen(config.port, function () {
62+
app.locals.host = config.domain;
63+
app.locals.port = server.address().port;
64+
65+
if (app.locals.host.indexOf(':') > -1) {
66+
app.locals.host = '[' + app.locals.host + ']';
67+
}
6768

68-
if (app.locals.host.indexOf(':') > -1) {
69-
app.locals.host = '[' + app.locals.host + ']';
69+
console.log('Listening at http://%s:%s', app.locals.host, app.locals.port);
70+
})
71+
.on('error', function (error) {
72+
switch (error.code) {
73+
case 'EADDRINUSE':
74+
console.log(`Error: Port ${config.port} is already in use.`);
75+
break;
76+
default:
77+
console.log(error.code);
7078
}
71-
72-
console.log('Listening at http://%s:%s', app.locals.host, app.locals.port);
73-
})
74-
.on('error', function (error) {
75-
switch (error.code) {
76-
case 'EADDRINUSE':
77-
console.log(`Error: Port ${config.port} is already in use.`);
78-
break;
79-
default:
80-
console.log(error.code);
81-
}
82-
});
83-
});
84-
}());
79+
});
80+
});

client.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
"use strict";
2-
31
var app,
42
bodyParser = require('body-parser'),
53
express = require('express'),

controllers/docs.js

Lines changed: 18 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,20 @@
1-
(function () {
2-
"use strict";
1+
const express = require('express'),
2+
router = new express.Router(),
3+
md = require('markdown-it')(),
4+
fs = require('fs');
35

4-
const express = require('express'),
5-
router = new express.Router(),
6-
md = require('markdown-it')(),
7-
fs = require('fs');
6+
router.get('/', function (req, res) {
7+
switch (req.accepts('html')) {
8+
case 'html':
9+
const vals = {
10+
htmltext: md.render(fs.readFileSync('README.md', { encoding: 'utf8' }))
11+
};
12+
res.render('docs', vals);
13+
break;
14+
default:
15+
res.status(406).send('Not Acceptable');
16+
break;
17+
}
18+
});
819

9-
router.get('/', function (req, res) {
10-
switch (req.accepts('html')) {
11-
case 'html':
12-
const vals = {
13-
htmltext: md.render(fs.readFileSync('README.md', { encoding: 'utf8' }))
14-
};
15-
res.render('docs', vals);
16-
break;
17-
default:
18-
res.status(406).send('Not Acceptable');
19-
break;
20-
}
21-
});
22-
23-
module.exports = router;
24-
}());
20+
module.exports = router;

controllers/home.js

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,15 @@
1-
(function () {
2-
"use strict";
1+
const express = require('express'),
2+
router = new express.Router();
33

4-
const express = require('express'),
5-
router = new express.Router();
4+
router.get('/', function (req, res) {
5+
switch (req.accepts('html')) {
6+
case 'html':
7+
res.render('home');
8+
break;
9+
default:
10+
res.status(406).send('Not Acceptable');
11+
break;
12+
}
13+
});
614

7-
router.get('/', function (req, res) {
8-
switch (req.accepts('html')) {
9-
case 'html':
10-
res.render('home');
11-
break;
12-
default:
13-
res.status(406).send('Not Acceptable');
14-
break;
15-
}
16-
});
17-
18-
module.exports = router;
19-
}());
15+
module.exports = router;

controllers/index.js

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,13 @@
1-
(function () {
2-
"use strict";
1+
const express = require('express'),
2+
router = new express.Router();
33

4-
const express = require('express'),
5-
router = new express.Router();
4+
router.use('/', require('./home'));
5+
router.use('/docs', require('./docs'));
6+
router.use('/pleaseNotify', require('./please-notify'));
7+
router.use('/pleaseNotifyForm', require('./please-notify-form'));
8+
router.use('/ping', require('./ping'));
9+
router.use('/pingForm', require('./ping-form'));
10+
router.use('/viewLog', require('./view-log'));
11+
router.use('/RPC2', require('./rpc2'));
612

7-
router.use('/', require('./home'));
8-
router.use('/docs', require('./docs'));
9-
router.use('/pleaseNotify', require('./please-notify'));
10-
router.use('/pleaseNotifyForm', require('./please-notify-form'));
11-
router.use('/ping', require('./ping'));
12-
router.use('/pingForm', require('./ping-form'));
13-
router.use('/viewLog', require('./view-log'));
14-
router.use('/RPC2', require('./rpc2'));
15-
16-
module.exports = router;
17-
}());
13+
module.exports = router;

controllers/ping-form.js

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,15 @@
1-
(function () {
2-
"use strict";
1+
const express = require('express'),
2+
router = new express.Router();
33

4-
const express = require('express'),
5-
router = new express.Router();
4+
router.get('/', function (req, res) {
5+
switch (req.accepts('html')) {
6+
case 'html':
7+
res.render('ping-form');
8+
break;
9+
default:
10+
res.status(406).send('Not Acceptable');
11+
break;
12+
}
13+
});
614

7-
router.get('/', function (req, res) {
8-
switch (req.accepts('html')) {
9-
case 'html':
10-
res.render('ping-form');
11-
break;
12-
default:
13-
res.status(406).send('Not Acceptable');
14-
break;
15-
}
16-
});
17-
18-
module.exports = router;
19-
}());
15+
module.exports = router;

0 commit comments

Comments
 (0)