Trip Offer Page Deployment Plan¶
Overview¶
This document outlines the implementation plan for deploying generated Trip Offer HTML pages directly to production web servers without a full build/deploy cycle.
Current State¶
- HTML pages are generated via
tripoffer/page/generateAPI endpoint - Generated HTML is downloaded to the user's browser for manual deployment
- No automated mechanism to push pages to web servers
Proposed Solution¶
Implement a two-step publish workflow: 1. Preview & Download - Generate and download HTML for review (current behavior) 2. Deploy to Production - Write generated HTML to NFS staging directory for automatic distribution
Architecture¶
┌─────────────────────────────────────────────────────────────────────────┐
│ TQPro Admin UI │
│ │
│ [Preview & Download] [Deploy to Production] │
│ │ │ │
│ ▼ ▼ │
│ Downloads .html to Calls deploy API │
│ user's browser │
└─────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ TQPro API Server │
│ │
│ POST /tripoffer/page/deploy │
│ - Generates HTML (same as download) │
│ - Writes to: /mnt/deploy-staging/{pageName} │
│ - Logs deployment to audit table │
│ - Returns success/failure │
└─────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ NFS Staging Directory │
│ /mnt/deploy-staging/ │
│ │
│ ├── dubai-packages.html │
│ ├── turkiye.html │
│ └── maldives-packages.html │
└─────────────────────────────────────────────────────────────────────────┘
│
┌───────────────────┴───────────────────┐
│ rsync/lsyncd daemon │
│ (cron every 1-5 min or inotify) │
└───────────────────┬───────────────────┘
│
┌───────────────────────────┼───────────────────────────┐
▼ ▼ ▼
┌────────────────┐ ┌────────────────┐ ┌────────────────┐
│ Web Server 1 │ │ Web Server 2 │ │ Web Server N │
│ /var/www/html │ │ /var/www/html │ │ /var/www/html │
└────────────────┘ └────────────────┘ └────────────────┘
Implementation Tasks¶
1. Configuration Updates¶
File: config/tlinqapi.properties
Add new configuration entries:
# Deployment staging directory (NFS mounted)
deploy.staging.dir=/mnt/deploy-staging
# Optional: subdirectory for trip offer pages
deploy.tripoffer.subdir=offers
# Enable/disable deployment feature
deploy.enabled=true
2. Database Changes¶
File: config/db-changes/trip-page-deploy-log.sql
Create deployment audit table:
CREATE TABLE nts.trip_page_deploy_log (
deploy_id SERIAL PRIMARY KEY,
page_id INTEGER REFERENCES nts.trip_page(page_id),
page_name VARCHAR(200),
deployed_by VARCHAR(100),
deployed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
skeleton_id INTEGER,
snippet_file_id INTEGER,
snippet_count INTEGER,
file_path VARCHAR(500),
status VARCHAR(20) -- 'success', 'failed', 'rolled_back'
);
CREATE INDEX idx_deploy_log_page_id ON nts.trip_page_deploy_log(page_id);
CREATE INDEX idx_deploy_log_deployed_at ON nts.trip_page_deploy_log(deployed_at);
3. Backend Implementation¶
3.1 TripOfferFacade Changes¶
File: tqapp/src/main/java/com/perun/tlinq/entity/trip/TripOfferFacade.java
Add new method:
/**
* Deploy generated HTML page to staging directory
* @param pageId Page to deploy
* @param skeletonId Skeleton template to use
* @param snippetFileId Snippet template to use
* @param deployedBy Username performing deployment
* @return Deployment result with file path and timestamp
*/
public Map<String, Object> deployHtmlPage(Integer pageId, Integer skeletonId,
Integer snippetFileId, String deployedBy) throws TlinqClientException {
// 1. Generate HTML (reuse existing generateHtmlPage logic)
// 2. Get deployment directory from configuration
// 3. Write file to staging directory
// 4. Create backup of previous version if exists
// 5. Log deployment to audit table
// 6. Return result
}
3.2 TripOfferApi Changes¶
File: tqapi/src/main/java/com/perun/tlinq/api/TripOfferApi.java
Add new endpoint:
@POST
@Path("/page/deploy")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Response deployPage(Map reqData) {
// Validate admin permissions
// Call facade.deployHtmlPage()
// Return result
}
3.3 API Roles Configuration¶
File: config/api-roles.properties
Add permission entry:
4. Frontend Implementation¶
4.1 HTML Changes¶
File: tqweb-adm/offerman.html
Update the generation section buttons:
<!-- Current -->
<button id="gen_generate">Generate & Download</button>
<button id="manage_skeletons">Manage Skeletons</button>
<!-- Proposed -->
<button id="gen_download">Preview & Download</button>
<button id="gen_deploy">Deploy to Production</button>
<button id="manage_skeletons">Manage Skeletons</button>
Add confirmation dialog:
<div class="small reveal" id="deploy_confirm_dlg" data-reveal>
<h4>Deploy to Production</h4>
<p>Are you sure you want to deploy <strong id="deploy_page_name"></strong> to production?</p>
<p>This will make the page live on all web servers within 2 minutes.</p>
<button class="button success" id="deploy_confirm">Deploy Now</button>
<button class="button secondary" data-close>Cancel</button>
</div>
4.2 JavaScript Changes¶
File: tqweb-adm/js/modules/offerman.js
Add new functions:
/**
* Open deploy confirmation dialog
*/
export function confirmDeploy() {
if (!selectedPageId) {
$.notify('Please select a page first', 'warning');
return;
}
$('#deploy_page_name').text(selectedPageData.pageName);
$('#deploy_confirm_dlg').foundation('open');
}
/**
* Execute deployment
*/
export function executeDeploy() {
const reqData = {
session: "",
pageId: selectedPageId,
skeletonId: parseInt($('#gen_skeleton').val()),
snippetFileId: parseInt($('#gen_snippet_file').val())
};
tlinq('tripoffer/page/deploy', reqData).then(
(data) => {
$.notify(`Deployed successfully to ${data.deployedTo}`, 'success');
$('#deploy_confirm_dlg').foundation('close');
},
(err) => {
$.notify(err.errorMessage || 'Deployment failed', 'error');
}
);
}
5. Infrastructure Setup¶
5.1 NFS Mount Configuration¶
On TQPro API server:
5.2 Rsync Distribution Options¶
Option A: Cron-based (Simple)
# /etc/cron.d/deploy-sync
*/2 * * * * root rsync -avz --delete /mnt/deploy-staging/ webserver1:/var/www/html/
*/2 * * * * root rsync -avz --delete /mnt/deploy-staging/ webserver2:/var/www/html/
Option B: Lsyncd (Recommended for real-time)
-- /etc/lsyncd.conf
sync {
default.rsync,
source = "/mnt/deploy-staging/",
target = "webserver1:/var/www/html/",
rsync = { archive = true, compress = true }
}
sync {
default.rsync,
source = "/mnt/deploy-staging/",
target = "webserver2:/var/www/html/",
rsync = { archive = true, compress = true }
}
Option C: Inotify Script
#!/bin/bash
# /opt/scripts/deploy-watcher.sh
inotifywait -m -e close_write /mnt/deploy-staging/ | while read dir event file; do
rsync -avz "$dir$file" webserver1:/var/www/html/
rsync -avz "$dir$file" webserver2:/var/www/html/
logger "Deployed $file to web servers"
done
API Specification¶
Deploy Endpoint¶
Request:
POST /api/tripoffer/page/deploy
Content-Type: application/json
{
"session": "",
"pageId": 123,
"skeletonId": 1,
"snippetFileId": 1
}
Success Response:
{
"apiStatus": { "errorCode": "OK", "errorMessage": "Success" },
"apiData": {
"success": true,
"fileName": "turkiye.html",
"deployedTo": "/mnt/deploy-staging/offers/turkiye.html",
"deployedAt": "2024-12-23T14:30:00Z",
"snippetCount": 5,
"backupCreated": true
}
}
Error Response:
{
"apiStatus": { "errorCode": "GENERAL", "errorMessage": "Deployment directory not writable" },
"apiData": null
}
Safety Features¶
| Feature | Description |
|---|---|
| Backup before overwrite | Keep last 5 versions in /mnt/deploy-staging/.backup/{pageName}/ |
| Deployment log | All deployments logged to nts.trip_page_deploy_log table |
| Permission check | Only admin role can access deploy endpoint |
| Confirmation dialog | User must confirm before deployment |
| Validation | Verify skeleton and snippet template selected before deploy |
Future Enhancements¶
- Rollback UI - Button to restore previous version from backup
- Deployment history view - Table showing recent deployments with status
- Staging preview - Preview URL at
staging.bookmyholiday.aebefore production push - Scheduled deployment - Deploy at a specific date/time
- Batch deployment - Deploy multiple pages at once
- Webhook notifications - Notify Slack/Teams on deployment
Workflow Summary¶
| Step | Actor | Action |
|---|---|---|
| 1 | Content Creator | Edits page placeholders & snippets in offerman.html |
| 2 | Content Creator | Clicks Preview & Download |
| 3 | Content Creator | Reviews HTML locally in browser |
| 4 | Content Creator | Clicks Deploy to Production |
| 5 | System | Shows confirmation dialog |
| 6 | Content Creator | Confirms deployment |
| 7 | System | Writes file to NFS staging directory |
| 8 | System | Creates backup of previous version |
| 9 | System | Logs deployment to audit table |
| 10 | Rsync/Lsyncd | Syncs to all web servers (1-2 minutes) |
| 11 | System | Page live on production |
Files to Modify/Create¶
| File | Action | Description |
|---|---|---|
config/tlinqapi.properties |
Modify | Add deployment configuration |
config/db-changes/trip-page-deploy-log.sql |
Create | Audit table migration |
config/api-roles.properties |
Modify | Add deploy endpoint permission |
tqapp/.../TripOfferFacade.java |
Modify | Add deployHtmlPage method |
tqapi/.../TripOfferApi.java |
Modify | Add /page/deploy endpoint |
tqweb-adm/offerman.html |
Modify | Add deploy button and confirmation dialog |
tqweb-adm/js/modules/offerman.js |
Modify | Add deploy functions |
Dependencies¶
- NFS server configured and mounted on TQPro API server
- SSH key-based authentication from API server to web servers
- Rsync or lsyncd installed on API server
- Write permissions to staging directory for API process user