Skip to content

Commit

Permalink
Download layer file (#182)
Browse files Browse the repository at this point in the history
* WIP - download layer component, html

* WIP - remove layer.download html, btn placed top of layer/:id page

* WIP - layer download

* downloadLayer in AdminLayerController and layer.resource

* layer downloading

* Fixed layer download functionality to handle JSON error responses and ensured correct filename and download button visibility for file-based layers.

* WIP - still need file property on layer

* download layer file

* Download btn style

* remove unused code

* CHANGELOG update
  • Loading branch information
kimura-developer authored Nov 9, 2023
1 parent 2e2962c commit 771d34c
Show file tree
Hide file tree
Showing 7 changed files with 535 additions and 328 deletions.
685 changes: 405 additions & 280 deletions CHANGELOG.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion service/src/entities/authorization/entities.permissions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export enum LayerPermission {
READ_LAYER_EVENT = 'READ_LAYER_EVENT',
UPDATE_LAYER = 'UPDATE_LAYER',
CREATE_LAYER = 'CREATE_LAYER',
DELETE_LAYER = 'DELETE_LAYER'
DELETE_LAYER = 'DELETE_LAYER',
}

export enum ObservationPermission {
Expand Down
19 changes: 19 additions & 0 deletions service/src/routes/layers.js
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,25 @@ module.exports = function(app, security) {
}
);

// get layer file
app.get(
'/api/layers/:layerId/file',
access.authorize('READ_LAYER_ALL'),
function(req, res) {
if (!req.layer.file) {
return res.status(404).send('Layer does not have a file');
}
const stream = fs.createReadStream(
path.join(environment.layerBaseDirectory, req.layer.file.relativePath)
);
stream.on('open', () => {
res.type(req.layer.file.contentType);
res.header('Content-Length', req.layer.file.size);
stream.pipe(res);
});
}
);

// get layer
app.get('/api/layers/:layerId',
access.authorize('READ_LAYER_ALL'),
Expand Down
2 changes: 1 addition & 1 deletion web-app/src/ng1/admin/layers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@ angular.module('mage')
.component('adminLayerEdit', adminLayerEdit)
.component('adminLayerPreview', adminLayerPreview)
.component('adminLayerDelete', adminLayerDelete)
.component('adminLayerWmsEdit',adminLayerWmsEdit);
.component('adminLayerWmsEdit', adminLayerWmsEdit)
121 changes: 91 additions & 30 deletions web-app/src/ng1/admin/layers/layer.component.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,17 @@ import _ from 'underscore';
import { snackbar } from 'material-components-web';

class AdminLayerController {
constructor($uibModal, $state, $stateParams, $filter, $timeout, Layer, Event, LocalStorageService, UserService) {
constructor(
$uibModal,
$state,
$stateParams,
$filter,
$timeout,
Layer,
Event,
LocalStorageService,
UserService
) {
this.$uibModal = $uibModal;
this.$state = $state;
this.$stateParams = $stateParams;
Expand All @@ -19,12 +29,22 @@ class AdminLayerController {
this.eventsPerPage = 10;
this.status = {};

this.hasLayerEditPermission = _.contains(UserService.myself.role.permissions, 'UPDATE_LAYER');
this.hasLayerDeletePermission = _.contains(UserService.myself.role.permissions, 'DELETE_LAYER');
this.hasLayerEditPermission = _.contains(
UserService.myself.role.permissions,
'UPDATE_LAYER'
);
this.hasLayerDeletePermission = _.contains(
UserService.myself.role.permissions,
'DELETE_LAYER'
);

this.fileUploadOptions = {
acceptFileTypes: /(\.|\/)(kml)$/i,
url: '/api/layers/' + $stateParams.layerId + '/kml?access_token=' + LocalStorageService.getToken(),
url:
'/api/layers/' +
$stateParams.layerId +
'/kml?access_token=' +
LocalStorageService.getToken()
};

this.uploads = [{}];
Expand All @@ -35,7 +55,7 @@ class AdminLayerController {
}

$onInit() {
this.Layer.get({ id: this.$stateParams.layerId }, layer => {
this.Layer.get({ id: this.$stateParams.layerId }, (layer) => {
this.layer = layer;

if (this.layer.state !== 'available') {
Expand All @@ -44,27 +64,29 @@ class AdminLayerController {

this.updateUrlLayers();

this.Event.query(events => {
this.Event.query((events) => {
this.event = {};
this.layerEvents = _.filter(events, event => {
return _.some(event.layers, layer => {
this.layerEvents = _.filter(events, (event) => {
return _.some(event.layers, (layer) => {
return this.layer.id === layer.id;
});
});

let nonLayerEvents = _.chain(events);
if (!_.contains(this.UserService.myself.role.permissions, 'UPDATE_EVENT')) {
if (
!_.contains(this.UserService.myself.role.permissions, 'UPDATE_EVENT')
) {
// filter teams based on acl
nonLayerEvents = nonLayerEvents.filter(event => {
nonLayerEvents = nonLayerEvents.filter((event) => {
const permissions = event.acl[this.UserService.myself.id]
? event.acl[this.UserService.myself.id].permissions
: [];
return _.contains(permissions, 'update');
});
}

nonLayerEvents = nonLayerEvents.reject(event => {
return _.some(event.layers, layer => {
nonLayerEvents = nonLayerEvents.reject((event) => {
return _.some(event.layers, (layer) => {
return this.layer.id === layer.id;
});
});
Expand All @@ -75,7 +97,9 @@ class AdminLayerController {
}

$postLink() {
this.uploadSnackbar = new snackbar.MDCSnackbar(document.querySelector('#upload-snackbar'));
this.uploadSnackbar = new snackbar.MDCSnackbar(
document.querySelector('#upload-snackbar')
);
}

_filterEvents(event) {
Expand All @@ -86,22 +110,22 @@ class AdminLayerController {
updateUrlLayers() {
const mapping = [];
if (this.layer.tables) {
this.layer.tables.forEach(table => {
this.layer.tables.forEach((table) => {
mapping.push({
table: table.name,
url: `/api/layers/${this.layer.id}/${
table.name
}/{z}/{x}/{y}.png?access_token=${this.LocalStorageService.getToken()}`,
}/{z}/{x}/{y}.png?access_token=${this.LocalStorageService.getToken()}`
});
});
}
this.urlLayers = mapping;
}

addEventToLayer(event) {
this.Event.addLayer({ id: event.id }, this.layer, event => {
this.Event.addLayer({ id: event.id }, this.layer, (event) => {
this.layerEvents.push(event);
this.nonLayerEvents = _.reject(this.nonLayerEvents, e => {
this.nonLayerEvents = _.reject(this.nonLayerEvents, (e) => {
return e.id === event.id;
});

Expand All @@ -112,12 +136,15 @@ class AdminLayerController {
removeEventFromLayer($event, event) {
$event.stopPropagation();

this.Event.removeLayer({ id: event.id, layerId: this.layer.id }, event => {
this.layerEvents = _.reject(this.layerEvents, e => {
return e.id === event.id;
});
this.nonLayerEvents.push(event);
});
this.Event.removeLayer(
{ id: event.id, layerId: this.layer.id },
(event) => {
this.layerEvents = _.reject(this.layerEvents, (e) => {
return e.id === event.id;
});
this.nonLayerEvents.push(event);
}
);
}

editLayer(layer) {
Expand All @@ -133,24 +160,58 @@ class AdminLayerController {
resolve: {
layer: () => {
return this.layer;
},
}
},
component: 'adminLayerDelete',
component: 'adminLayerDelete'
});

modalInstance.result.then(() => {
this.$state.go('admin.layers');
});
}

isLayerFileBased() {
return this.layer && !!this.layer.file;
}

getAccessToken() {
return this.LocalStorageService.getToken();
}

constructDownloadURL() {
const accessToken = this.getAccessToken();
return `/api/layers/${this.layer.id}/file?access_token=${accessToken}`;
}

createDownloadLink() {
const downloadURL = this.constructDownloadURL();

const a = document.createElement('a');
a.href = downloadURL;
a.download = this.layer.file.name;
a.style.display = 'none';

return a;
}

downloadLayer() {
const a = this.createDownloadLink();

document.body.appendChild(a);
a.click();

// Clean up
document.body.removeChild(a);
}

addUploadFile() {
this.uploads.push({});
}

confirmUpload() {
this.$timeout(() => {
this.uploadConfirmed = true;
})
});
}

uploadComplete($event) {
Expand All @@ -160,9 +221,9 @@ class AdminLayerController {
this.status[$event.id] = $event.response.files[0];

const url = new URL(this.layer.url);
url.searchParams.set('_dc', new Date().getTime())
url.searchParams.set('_dc', new Date().getTime());
this.layer.url = url.toString();
})
});
}

uploadFailed($event) {
Expand All @@ -171,7 +232,7 @@ class AdminLayerController {
}

confirmCreateLayer() {
this.Layer.makeAvailable({ id: this.$stateParams.layerId }, layer => {
this.Layer.makeAvailable({ id: this.$stateParams.layerId }, (layer) => {
if (layer.processing) {
this.layer.processing = layer.processing;
this.$timeout(this.checkLayerProcessingStatus.bind(this), 1500);
Expand All @@ -180,7 +241,7 @@ class AdminLayerController {
}

checkLayerProcessingStatus() {
this.Layer.get({ id: this.$stateParams.layerId }, layer => {
this.Layer.get({ id: this.$stateParams.layerId }, (layer) => {
this.layer = layer;
this.updateUrlLayers();
if (this.layer.state !== 'available') {
Expand Down
2 changes: 2 additions & 0 deletions web-app/src/ng1/admin/layers/layer.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ <h2>Layer: {{$ctrl.layer.name}}</h2>
<h2>
<button class="btn btn-default pull-right" ng-click="$ctrl.editLayer($ctrl.layer)"><i class="fa fa-edit"></i> Edit</button>
</h2>
<h2>
<button ng-if="$ctrl.isLayerFileBased()" ng-click="$ctrl.downloadLayer()" class="btn btn-default pull-right" style="margin-right: 10px;"><i class="fa fa-download"></i> Download</button>
</div>
</div>
<hr>
Expand Down
32 changes: 16 additions & 16 deletions web-app/src/ng1/factories/layer.resource.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,57 +6,57 @@ function Layer($resource) {
const Layer = $resource(
'/api/layers/:id',
{
id: '@id',
id: '@id'
},
{
get: {
headers: {
Accept: 'application/json',
},
Accept: 'application/json'
}
},
create: {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
'Content-Type': 'application/json'
}
},
update: {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
'Content-Type': 'application/json'
}
},
makeAvailable: {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Content-Type': 'application/json'
},
url: '/api/layers/:id/available',
params: {
available: true,
},
available: true
}
},
queryByEvent: {
method: 'GET',
isArray: true,
url: '/api/events/:eventId/layers',
url: '/api/events/:eventId/layers'
},
closestFeatureByLayer: {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Type': 'application/json'
},
isArray: true,
url: '/api/events/:eventId/features',
url: '/api/events/:eventId/features'
},
count: {
method: 'GET',
url: '/api/layers/count',
headers: {
'Content-Type': 'application/json',
},
'Content-Type': 'application/json'
}
},
},
}
);

Layer.prototype.$save = function(params, success, error) {
Expand Down

0 comments on commit 771d34c

Please sign in to comment.