forked from ryanmwangi/CalMerger
Compare commits
No commits in common. "30622c611a2850ed1146592ec1a04e30f9655824" and "869beeb3d46715bca6686835b173b8f3bb57ef87" have entirely different histories.
30622c611a
...
869beeb3d4
12 changed files with 93 additions and 367 deletions
Binary file not shown.
17
README.md
17
README.md
|
@ -18,17 +18,6 @@ The application also generates a unique URL for the merged calendar and updates
|
||||||
- Generate a unique URL for the merged calendar
|
- Generate a unique URL for the merged calendar
|
||||||
- Automatically update the merged calendar every hour
|
- Automatically update the merged calendar every hour
|
||||||
|
|
||||||
## Calender Directory
|
|
||||||
|
|
||||||
#### `calendar/` (MERGED_CALENDARS_DIR)
|
|
||||||
- Location: Created in application's current working directory
|
|
||||||
- Purpose: Stores all generated calendar files
|
|
||||||
- Contains:
|
|
||||||
- `.ics` files - Final merged calendars in iCalendar format
|
|
||||||
- `.json` files - Configuration preserving original merge parameters
|
|
||||||
- Maintains both formats for each merged calendar group
|
|
||||||
|
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
- [Node.js](https://nodejs.org/) (version 14 or higher)
|
- [Node.js](https://nodejs.org/) (version 14 or higher)
|
||||||
|
@ -65,11 +54,11 @@ docker build -t calmerger-app .
|
||||||
To start the container, use:
|
To start the container, use:
|
||||||
|
|
||||||
```bash
|
```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)
|
### 3. Using Docker Compose (Optional)
|
||||||
|
|
||||||
|
@ -125,7 +114,7 @@ This generates a `coverage` report, showing how much of the codebase is tested.
|
||||||
|
|
||||||
## Usage
|
## 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.
|
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).
|
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.
|
4. Click the **Merge Calendars** button to generate the merged calendar.
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
version: "3.3"
|
|
||||||
|
|
||||||
services:
|
|
||||||
calmerge:
|
|
||||||
build: .
|
|
||||||
ports:
|
|
||||||
- "3012:3012"
|
|
||||||
environment:
|
|
||||||
- NODE_ENV=production
|
|
||||||
- NODE_PORT=3012
|
|
||||||
volumes:
|
|
||||||
- ./calendar:/app/calendar
|
|
||||||
- ./logs:/app/logs
|
|
||||||
restart: unless-stopped
|
|
|
@ -8,13 +8,13 @@ WORKDIR /usr/src/app
|
||||||
COPY package*.json ./
|
COPY package*.json ./
|
||||||
|
|
||||||
# Install dependencies
|
# Install dependencies
|
||||||
RUN npm install --omit=dev
|
RUN npm install --production
|
||||||
|
|
||||||
# Copy the rest of the project files
|
# Copy the rest of the project files
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
# Expose the port your application runs on (if applicable)
|
# Expose the port your application runs on (if applicable)
|
||||||
EXPOSE 3012
|
EXPOSE 3000
|
||||||
|
|
||||||
# Command to run the application
|
# Command to run the application
|
||||||
CMD ["node", "src/app.js"]
|
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/plugin-transform-modules-commonjs": "^7.25.9",
|
||||||
"@babel/preset-env": "^7.26.0",
|
"@babel/preset-env": "^7.26.0",
|
||||||
"@babel/register": "^7.25.9",
|
"@babel/register": "^7.25.9",
|
||||||
"axios-mock-adapter": "^2.1.0",
|
|
||||||
"babel-jest": "^29.7.0",
|
"babel-jest": "^29.7.0",
|
||||||
"jest": "^29.7.0",
|
"jest": "^29.7.0",
|
||||||
"rewire": "^7.0.0"
|
"rewire": "^7.0.0"
|
||||||
|
@ -2680,20 +2679,6 @@
|
||||||
"proxy-from-env": "^1.1.0"
|
"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": {
|
"node_modules/babel-jest": {
|
||||||
"version": "29.7.0",
|
"version": "29.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz",
|
||||||
|
@ -4517,30 +4502,6 @@
|
||||||
"integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
|
"integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
|
||||||
"dev": true
|
"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": {
|
"node_modules/is-core-module": {
|
||||||
"version": "2.15.1",
|
"version": "2.15.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz",
|
"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/plugin-transform-modules-commonjs": "^7.25.9",
|
||||||
"@babel/preset-env": "^7.26.0",
|
"@babel/preset-env": "^7.26.0",
|
||||||
"@babel/register": "^7.25.9",
|
"@babel/register": "^7.25.9",
|
||||||
"axios-mock-adapter": "^2.1.0",
|
|
||||||
"babel-jest": "^29.7.0",
|
"babel-jest": "^29.7.0",
|
||||||
"jest": "^29.7.0",
|
"jest": "^29.7.0",
|
||||||
"rewire": "^7.0.0"
|
"rewire": "^7.0.0"
|
||||||
|
|
|
@ -4,47 +4,45 @@
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Calendar Merger</title>
|
<title>Calendar Merger</title>
|
||||||
<link rel="stylesheet" href="styles.css">
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
}
|
||||||
|
#calendars {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
#calendars .calendar {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
#calendars .calendar input[type="text"] {
|
||||||
|
width: 50%;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
#calendars .calendar input[type="url"] {
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
#add-calendar {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<h1>Calendar Merger</h1>
|
||||||
<h1>📅 Calendar Merger</h1>
|
<form id="merge-form">
|
||||||
|
<input type="text" id="link-group-name" placeholder="Link Group Name">
|
||||||
<div class="form-card">
|
<div id="calendars">
|
||||||
<form id="merge-form">
|
<div class="calendar">
|
||||||
<div class="input-group">
|
<input type="text" id="prefix-0" placeholder="Prefix">
|
||||||
<input type="text"
|
|
||||||
id="link-group-name"
|
|
||||||
placeholder="Enter collection name"
|
|
||||||
class="input-field">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="calendars">
|
|
||||||
<div class="calendar-entry">
|
|
||||||
<input type="url" id="url-0" placeholder="https://example.com/calendar.ics">
|
|
||||||
<input type="text" id="prefix-0" placeholder="Event prefix">
|
|
||||||
<div class="checkbox-group">
|
|
||||||
<input type="checkbox" id="override-0">
|
<input type="checkbox" id="override-0">
|
||||||
<label for="override-0">Override</label>
|
<label for="override-0">Override</label>
|
||||||
</div>
|
<input type="url" id="url-0" placeholder="Calendar URL">
|
||||||
<button type="button" class="remove-btn" title="Remove calendar"></button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<button id="add-calendar" type="button">Add Calendar</button>
|
||||||
<div class="button-group">
|
<button type="submit">Merge Calendars</button>
|
||||||
<button type="button" id="add-calendar" class="button secondary-btn">
|
</form>
|
||||||
➕ Add Another Calendar
|
<div id="result"></div>
|
||||||
</button>
|
|
||||||
<button type="submit" class="button primary-btn">
|
|
||||||
🔗 Merge Calendars
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="result"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script src="script.js"></script>
|
<script src="script.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
|
@ -14,15 +14,12 @@ function isValidUrl(url) {
|
||||||
|
|
||||||
addCalendarButton.addEventListener('click', () => {
|
addCalendarButton.addEventListener('click', () => {
|
||||||
const newCalendar = document.createElement('div');
|
const newCalendar = document.createElement('div');
|
||||||
newCalendar.className = 'calendar-entry';
|
newCalendar.className = 'calendar';
|
||||||
newCalendar.innerHTML = `
|
newCalendar.innerHTML = `
|
||||||
<input type="url" id="url-${calendarIndex}" placeholder="https://example.com/calendar.ics">
|
<input type="text" id="prefix-${calendarIndex}" placeholder="Prefix">
|
||||||
<input type="text" id="prefix-${calendarIndex}" placeholder="Event prefix">
|
<input type="checkbox" id="override-${calendarIndex}">
|
||||||
<div class="checkbox-group">
|
<label for="override-${calendarIndex}">Override</label>
|
||||||
<input type="checkbox" id="override-${calendarIndex}">
|
<input type="url" id="url-${calendarIndex}" placeholder="Calendar URL">
|
||||||
<label for="override-${calendarIndex}">Override</label>
|
|
||||||
</div>
|
|
||||||
<button type="button" class="remove-btn" title="Remove calendar"></button>
|
|
||||||
`;
|
`;
|
||||||
calendars.appendChild(newCalendar);
|
calendars.appendChild(newCalendar);
|
||||||
calendarIndex++;
|
calendarIndex++;
|
||||||
|
@ -78,12 +75,3 @@ function isValidUrl(url) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
document.addEventListener('click', (event) => {
|
|
||||||
if (event.target.classList.contains('remove-btn')) {
|
|
||||||
const calendarEntry = event.target.closest('.calendar-entry');
|
|
||||||
if (calendarEntry) {
|
|
||||||
calendarEntry.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
|
@ -1,159 +1,37 @@
|
||||||
:root {
|
body {
|
||||||
--primary: #2563eb;
|
font-family: Arial, sans-serif;
|
||||||
--primary-hover: #1d4ed8;
|
}
|
||||||
--background: #f8fafc;
|
|
||||||
--surface: #ffffff;
|
|
||||||
--border: #e2e8f0;
|
|
||||||
--text: #1e293b;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
font-family: 'Segoe UI', system-ui, sans-serif;
|
|
||||||
background: var(--background);
|
|
||||||
color: var(--text);
|
|
||||||
line-height: 1.6;
|
|
||||||
margin: 0;
|
|
||||||
padding: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
max-width: 800px;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Form Styling */
|
|
||||||
.form-card {
|
|
||||||
background: var(--surface);
|
|
||||||
border-radius: 12px;
|
|
||||||
padding: 2rem;
|
|
||||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-group {
|
|
||||||
display: grid;
|
|
||||||
gap: 1rem;
|
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Calendar Entry Styling */
|
|
||||||
.calendar-entry {
|
|
||||||
display: flex;
|
|
||||||
gap: 1rem;
|
|
||||||
align-items: center;
|
|
||||||
padding: 1rem;
|
|
||||||
background: var(--background);
|
|
||||||
border-radius: 8px;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.calendar-entry:hover {
|
|
||||||
transform: translateY(-2px);
|
|
||||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Input Elements */
|
|
||||||
input[type="text"],
|
|
||||||
input[type="url"] {
|
|
||||||
padding: 0.75rem;
|
|
||||||
border: 2px solid var(--border);
|
|
||||||
border-radius: 6px;
|
|
||||||
width: 100%;
|
|
||||||
transition: border-color 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
input:focus {
|
|
||||||
outline: none;
|
|
||||||
border-color: var(--primary);
|
|
||||||
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Checkbox Styling */
|
|
||||||
.checkbox-group {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type="checkbox"] {
|
|
||||||
width: 1.2em;
|
|
||||||
height: 1.2em;
|
|
||||||
accent-color: var(--primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Button Styling */
|
|
||||||
.button {
|
|
||||||
padding: 0.75rem 1.5rem;
|
|
||||||
border: none;
|
|
||||||
border-radius: 6px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-weight: 600;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.primary-btn {
|
|
||||||
background: var(--primary);
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.primary-btn:hover {
|
|
||||||
background: var(--primary-hover);
|
|
||||||
}
|
|
||||||
|
|
||||||
.secondary-btn {
|
|
||||||
background: var(--background);
|
|
||||||
border: 2px solid var(--border);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Result Display */
|
|
||||||
#result {
|
|
||||||
padding: 1.5rem;
|
|
||||||
background: var(--surface);
|
|
||||||
border-radius: 8px;
|
|
||||||
margin-top: 1rem;
|
|
||||||
border: 2px dashed var(--border);
|
|
||||||
}
|
|
||||||
|
|
||||||
#result a {
|
|
||||||
color: var(--primary);
|
|
||||||
text-decoration: none;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Responsive Design */
|
|
||||||
@media (max-width: 640px) {
|
|
||||||
.calendar-entry {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: stretch;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type="text"] {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.remove-btn {
|
#merge-form {
|
||||||
background: #fef2f2;
|
max-width: 400px;
|
||||||
color: #dc2626;
|
margin: 40px auto;
|
||||||
|
padding: 20px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="url"], input[type="text"] {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
button[type="submit"] {
|
||||||
|
background-color: #4CAF50;
|
||||||
|
color: #fff;
|
||||||
|
padding: 10px 20px;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 50%;
|
border-radius: 5px;
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.2s ease;
|
}
|
||||||
}
|
|
||||||
|
button[type="submit"]:hover {
|
||||||
.remove-btn:hover {
|
background-color: #3e8e41;
|
||||||
background: #fee2e2;
|
}
|
||||||
transform: scale(1.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.remove-btn::before {
|
|
||||||
content: '×';
|
|
||||||
font-size: 1.4rem;
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
|
@ -1,6 +1,6 @@
|
||||||
import server from './server.js';
|
import server from './server.js';
|
||||||
|
|
||||||
const port = process.env.NODE_PORT || 3012;
|
const port = process.env.NODE_PORT || 3000;
|
||||||
server.listen(port, () => {
|
server.listen(port, () => {
|
||||||
console.log(`Server started on port ${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
|
// Fetch calendar data from URL or file
|
||||||
export async function fetchCalendarData(calendar) {
|
export async function fetchCalendarData(calendar) {
|
||||||
const isFilePath = !calendar.url.startsWith('http');
|
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 {
|
try {
|
||||||
// First try the original URL
|
if (isFilePath) {
|
||||||
const initialResponse = await axios.get(calendar.url);
|
// logger.debug(`Reading calendar from file: ${calendar.url}`);
|
||||||
return { data: initialResponse.data, ...calendar };
|
return { data: fs.readFileSync(path.resolve(calendar.url), 'utf-8'), ...calendar };
|
||||||
} catch (initialError) {
|
} else {
|
||||||
logger.debug(`Initial fetch failed, trying extension adjustment for: ${calendar.url}`);
|
// logger.debug(`Fetching calendar from URL: ${calendar.url}`);
|
||||||
|
const response = await axios.get(calendar.url);
|
||||||
// Determine alternate URL version
|
return { data: response.data, ...calendar };
|
||||||
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`);
|
|
||||||
}
|
}
|
||||||
|
} 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 fs from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from 'url';
|
||||||
import axios from 'axios';
|
|
||||||
import MockAdapter from 'axios-mock-adapter';
|
|
||||||
import { fetchCalendarData } from '../src/calendarUtil.js';
|
|
||||||
|
|
||||||
// ESM equivalent of __dirname
|
// ESM equivalent of __dirname
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
const __dirname = path.dirname(__filename);
|
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 TEST_CALENDARS_DIR = path.join(__dirname, 'test_calendars');
|
||||||
const EXPECTED_OUTPUTS_DIR = path.join(__dirname, 'expected_outputs');
|
const EXPECTED_OUTPUTS_DIR = path.join(__dirname, 'expected_outputs');
|
||||||
|
|
||||||
|
@ -29,8 +26,9 @@ describe('Calendar Merging API', () => {
|
||||||
await new Promise(resolve => server.close(resolve));
|
await new Promise(resolve => server.close(resolve));
|
||||||
|
|
||||||
// Clean up the merged calendars directory after tests
|
// Clean up the merged calendars directory after tests
|
||||||
|
fs.rmdirSync(CALENDARS_DIR, { recursive: true });
|
||||||
if (fs.existsSync(CALENDARS_DIR)) {
|
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
|
// Optional: Add a delay to ensure all handles are released
|
||||||
|
@ -282,60 +280,4 @@ describe('Calendar Merging API', () => {
|
||||||
expect(actualOutput).toBe(expectedOutput);
|
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