Compare commits
No commits in common. "30622c611a2850ed1146592ec1a04e30f9655824" and "0950ec772ee90535df9d77a81a48442468b5db56" have entirely different histories.
30622c611a
...
0950ec772e
9 changed files with 22 additions and 135 deletions
Binary file not shown.
|
@ -65,11 +65,11 @@ docker build -t calmerger-app .
|
|||
To start the container, use:
|
||||
|
||||
```bash
|
||||
docker run -d --name calmerger -p 3012:3012 calmerger-app
|
||||
docker run -d --name calmerger -p 3000:3000 calmerger-app
|
||||
|
||||
```
|
||||
|
||||
This maps the container's port `3012` to the host system's port `3012`. The application will be accessible at [http://localhost:3012](http://localhost:3012).
|
||||
This maps the container's port `3000` to the host system's port `3000`. The application will be accessible at [http://localhost:3000](http://localhost:3000).
|
||||
|
||||
### 3. Using Docker Compose (Optional)
|
||||
|
||||
|
@ -125,7 +125,7 @@ This generates a `coverage` report, showing how much of the codebase is tested.
|
|||
|
||||
## Usage
|
||||
|
||||
1. Open a web browser and navigate to `http://localhost:3012`.
|
||||
1. Open a web browser and navigate to `http://localhost:3000`.
|
||||
2. Click the **Add Calendar** button to add a new calendar.
|
||||
3. Enter the Link Group Name, calendar URL, prefix, and override options (if needed).
|
||||
4. Click the **Merge Calendars** button to generate the merged calendar.
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
version: "3.3"
|
||||
version: '3.3'
|
||||
|
||||
services:
|
||||
calmerge:
|
||||
build: .
|
||||
ports:
|
||||
- "3012:3012"
|
||||
- "3000:3000"
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- NODE_PORT=3012
|
||||
- NODE_PORT=3000
|
||||
volumes:
|
||||
- ./calendar:/app/calendar
|
||||
- ./logs:/app/logs
|
||||
restart: unless-stopped
|
||||
restart: unless-stopped
|
|
@ -14,7 +14,7 @@ RUN npm install --omit=dev
|
|||
COPY . .
|
||||
|
||||
# Expose the port your application runs on (if applicable)
|
||||
EXPOSE 3012
|
||||
EXPOSE 3000
|
||||
|
||||
# Command to run the application
|
||||
CMD ["node", "src/app.js"]
|
||||
|
|
39
package-lock.json
generated
39
package-lock.json
generated
|
@ -22,7 +22,6 @@
|
|||
"@babel/plugin-transform-modules-commonjs": "^7.25.9",
|
||||
"@babel/preset-env": "^7.26.0",
|
||||
"@babel/register": "^7.25.9",
|
||||
"axios-mock-adapter": "^2.1.0",
|
||||
"babel-jest": "^29.7.0",
|
||||
"jest": "^29.7.0",
|
||||
"rewire": "^7.0.0"
|
||||
|
@ -2680,20 +2679,6 @@
|
|||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/axios-mock-adapter": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/axios-mock-adapter/-/axios-mock-adapter-2.1.0.tgz",
|
||||
"integrity": "sha512-AZUe4OjECGCNNssH8SOdtneiQELsqTsat3SQQCWLPjN436/H+L9AjWfV7bF+Zg/YL9cgbhrz5671hoh+Tbn98w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"is-buffer": "^2.0.5"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"axios": ">= 0.17.0"
|
||||
}
|
||||
},
|
||||
"node_modules/babel-jest": {
|
||||
"version": "29.7.0",
|
||||
"resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz",
|
||||
|
@ -4517,30 +4502,6 @@
|
|||
"integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/is-buffer": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz",
|
||||
"integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/is-core-module": {
|
||||
"version": "2.15.1",
|
||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz",
|
||||
|
|
|
@ -24,7 +24,6 @@
|
|||
"@babel/plugin-transform-modules-commonjs": "^7.25.9",
|
||||
"@babel/preset-env": "^7.26.0",
|
||||
"@babel/register": "^7.25.9",
|
||||
"axios-mock-adapter": "^2.1.0",
|
||||
"babel-jest": "^29.7.0",
|
||||
"jest": "^29.7.0",
|
||||
"rewire": "^7.0.0"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import server from './server.js';
|
||||
|
||||
const port = process.env.NODE_PORT || 3012;
|
||||
const port = process.env.NODE_PORT || 3000;
|
||||
server.listen(port, () => {
|
||||
console.log(`Server started on port ${port}`);
|
||||
});
|
||||
|
|
|
@ -15,33 +15,18 @@ export const sanitizeFilename = (filename) => filename.replace(/[<>:"/\\|?* ]/g,
|
|||
// Fetch calendar data from URL or file
|
||||
export async function fetchCalendarData(calendar) {
|
||||
const isFilePath = !calendar.url.startsWith('http');
|
||||
if (isFilePath) {
|
||||
// logger.debug(`Reading calendar from file: ${calendar.url}`);
|
||||
return { data: fs.readFileSync(path.resolve(calendar.url), 'utf-8'), ...calendar };
|
||||
}
|
||||
try {
|
||||
// First try the original URL
|
||||
const initialResponse = await axios.get(calendar.url);
|
||||
return { data: initialResponse.data, ...calendar };
|
||||
} catch (initialError) {
|
||||
logger.debug(`Initial fetch failed, trying extension adjustment for: ${calendar.url}`);
|
||||
|
||||
// Determine alternate URL version
|
||||
const altUrl = calendar.url.endsWith('.ics')
|
||||
? calendar.url.slice(0, -4) // Remove .ics
|
||||
: calendar.url + '.ics'; // Add .ics
|
||||
|
||||
try {
|
||||
// Try the alternate version
|
||||
const altResponse = await axios.get(altUrl);
|
||||
logger.debug(`Success with adjusted URL: ${altUrl}`);
|
||||
return { data: altResponse.data, ...calendar };
|
||||
} catch (altError) {
|
||||
logger.error(`Both URL versions failed:
|
||||
Original: ${calendar.url}
|
||||
Adjusted: ${altUrl}`);
|
||||
throw new Error(`Calendar fetch failed for both URL versions`);
|
||||
if (isFilePath) {
|
||||
// logger.debug(`Reading calendar from file: ${calendar.url}`);
|
||||
return { data: fs.readFileSync(path.resolve(calendar.url), 'utf-8'), ...calendar };
|
||||
} else {
|
||||
// logger.debug(`Fetching calendar from URL: ${calendar.url}`);
|
||||
const response = await axios.get(calendar.url);
|
||||
return { data: response.data, ...calendar };
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(`Error retrieving calendar from ${calendar.url}: ${error.message}`);
|
||||
throw new Error(`Error retrieving calendar from ${calendar.url}: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,15 +2,12 @@ import request from 'supertest';
|
|||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import axios from 'axios';
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
import { fetchCalendarData } from '../src/calendarUtil.js';
|
||||
|
||||
// ESM equivalent of __dirname
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
const CALENDARS_DIR = path.join(process.cwd(), 'calendar');
|
||||
const CALENDARS_DIR = path.join(__dirname, 'calendar');
|
||||
const TEST_CALENDARS_DIR = path.join(__dirname, 'test_calendars');
|
||||
const EXPECTED_OUTPUTS_DIR = path.join(__dirname, 'expected_outputs');
|
||||
|
||||
|
@ -29,8 +26,9 @@ describe('Calendar Merging API', () => {
|
|||
await new Promise(resolve => server.close(resolve));
|
||||
|
||||
// Clean up the merged calendars directory after tests
|
||||
fs.rmdirSync(CALENDARS_DIR, { recursive: true });
|
||||
if (fs.existsSync(CALENDARS_DIR)) {
|
||||
fs.rmSync(CALENDARS_DIR, { recursive: true, force: true });
|
||||
fs.rmdirSync(CALENDARS_DIR, { recursive: true });
|
||||
}
|
||||
|
||||
// Optional: Add a delay to ensure all handles are released
|
||||
|
@ -282,60 +280,4 @@ describe('Calendar Merging API', () => {
|
|||
expect(actualOutput).toBe(expectedOutput);
|
||||
});
|
||||
|
||||
describe('Smart URL Handling', () => {
|
||||
let mockAxios;
|
||||
|
||||
beforeAll(() => {
|
||||
mockAxios = new MockAdapter(axios);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
mockAxios.reset();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
mockAxios.restore();
|
||||
});
|
||||
|
||||
test('should use original URL when valid without .ics', async () => {
|
||||
const validUrl = 'https://cals.ftt.gmbh/calendar/germanholithunder';
|
||||
mockAxios.onGet(validUrl).reply(200, 'VALID_CALENDAR');
|
||||
|
||||
const result = await fetchCalendarData({ url: validUrl });
|
||||
expect(result.data).toBe('VALID_CALENDAR');
|
||||
});
|
||||
|
||||
test('should try .ics version when original fails', async () => {
|
||||
const invalidUrl = 'https://cals.ftt.gmbh/calendar/germanholithunder.ics';
|
||||
const validUrl = invalidUrl.slice(0, -4);
|
||||
|
||||
mockAxios
|
||||
.onGet(invalidUrl).reply(404)
|
||||
.onGet(validUrl).reply(200, 'VALID_CALENDAR');
|
||||
|
||||
const result = await fetchCalendarData({ url: invalidUrl });
|
||||
expect(result.data).toBe('VALID_CALENDAR');
|
||||
});
|
||||
|
||||
test('should preserve valid .ics URLs', async () => {
|
||||
const googleUrl = 'https://calendar.google.com/.../basic.ics';
|
||||
mockAxios.onGet(googleUrl).reply(200, 'GOOGLE_CALENDAR');
|
||||
|
||||
const result = await fetchCalendarData({ url: googleUrl });
|
||||
expect(result.data).toBe('GOOGLE_CALENDAR');
|
||||
});
|
||||
|
||||
test('should try both versions for ambiguous URLs', async () => {
|
||||
const baseUrl = 'https://example.com/calendar';
|
||||
const icsUrl = baseUrl + '.ics';
|
||||
|
||||
mockAxios
|
||||
.onGet(baseUrl).reply(404)
|
||||
.onGet(icsUrl).reply(200, 'ICS_CALENDAR');
|
||||
|
||||
const result = await fetchCalendarData({ url: baseUrl });
|
||||
expect(result.data).toBe('ICS_CALENDAR');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue