Skip to content

Commit

Permalink
Merge pull request #1163 from swissgeol/GSNGM-815-delete-project
Browse files Browse the repository at this point in the history
Allow user to delete a project
  • Loading branch information
pedroslvieira authored Dec 11, 2023
2 parents 2d79a97 + 7f2021e commit 90fa68e
Show file tree
Hide file tree
Showing 11 changed files with 158 additions and 4 deletions.
54 changes: 54 additions & 0 deletions api/src/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,34 @@ pub async fn update_project(
Ok(StatusCode::NO_CONTENT)
}

#[axum_macros::debug_handler]
pub async fn delete_project(
Path(id): Path<Uuid>,
Extension(pool): Extension<PgPool>,
Extension(client): Extension<Client>,
) -> Result<StatusCode> {
// Delete assets from bucket
let saved_project: Project = sqlx::query_scalar!(
r#"SELECT project as "project: sqlx::types::Json<Project>" FROM projects WHERE id = $1"#,
id
)
.fetch_one(&pool)
.await?
.0;

if !saved_project.assets.is_empty() {
delete_assets(client, &saved_project.assets).await
}

// Delete project from database
sqlx::query(r#"DELETE FROM projects WHERE id = $1"#)
.bind(id)
.execute(&pool)
.await?;

Ok(StatusCode::NO_CONTENT)
}

#[axum_macros::debug_handler]
pub async fn update_project_geometries(
Path(id): Path<Uuid>,
Expand Down Expand Up @@ -449,3 +477,29 @@ async fn save_assets(client: Client, project_assets: &Vec<Asset>) {
}
}
}

async fn delete_assets(client: Client, project_assets: &Vec<Asset>) {
let bucket = std::env::var("PROJECTS_S3_BUCKET").unwrap();
for asset in project_assets {
let permanent_key = format!("assets/saved/{}", asset.key);

// Check if the file exists in the destination directory
let destination_exists = client
.head_object()
.bucket(&bucket)
.key(&permanent_key)
.send()
.await
.is_ok();

if destination_exists {
client
.delete_object()
.bucket(&bucket)
.key(&permanent_key)
.send()
.await
.unwrap();
}
}
}
5 changes: 4 additions & 1 deletion api/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use axum::{
extract::Extension,
http::{HeaderValue, Method},
routing::delete,
routing::get,
routing::post,
routing::put,
Expand Down Expand Up @@ -48,7 +49,9 @@ pub async fn app(pool: PgPool) -> Router {
.route("/api/projects/duplicate", post(handlers::duplicate_project))
.route(
"/api/projects/:id",
get(handlers::get_project).put(handlers::update_project),
get(handlers::get_project)
.put(handlers::update_project)
.delete(handlers::delete_project),
)
.route(
"/api/projects/:id/geometries",
Expand Down
2 changes: 2 additions & 0 deletions ui/locales/app.de.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
"created_on": "Erstellt am",
"dashboard_back_to_topics": "Zurück zu den Themen",
"dashboard_by_swisstopo_title": "von swisstopo",
"dashboard_delete_warning_description": "",
"dashboard_delete_warning_title": "",
"dashboard_description": "Kurzbeschrieb",
"dashboard_modified_title": "Geändert am",
"dashboard_my_projects": "Meine Projekte",
Expand Down
2 changes: 2 additions & 0 deletions ui/locales/app.en.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
"created_on": "Created on",
"dashboard_back_to_topics": "Back to overview topics",
"dashboard_by_swisstopo_title": "by swisstopo",
"dashboard_delete_warning_description": "The project and its assests will be permanently deleted. This action cannot be undone.",
"dashboard_delete_warning_title": "Are you sure you want to delete?",
"dashboard_description": "Description",
"dashboard_modified_title": "Modified on",
"dashboard_my_projects": "My projects",
Expand Down
2 changes: 2 additions & 0 deletions ui/locales/app.fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
"created_on": "Créé le",
"dashboard_back_to_topics": "Retour aux thèmes",
"dashboard_by_swisstopo_title": "par swisstopo",
"dashboard_delete_warning_description": "",
"dashboard_delete_warning_title": "",
"dashboard_description": "Description",
"dashboard_modified_title": "Modifié le",
"dashboard_my_projects": "Mes projets",
Expand Down
2 changes: 2 additions & 0 deletions ui/locales/app.it.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
"created_on": "Creato lo",
"dashboard_back_to_topics": "Tornare a temi",
"dashboard_by_swisstopo_title": "da swisstopo",
"dashboard_delete_warning_description": "",
"dashboard_delete_warning_title": "",
"dashboard_description": "Descrizione",
"dashboard_modified_title": "Modificato il",
"dashboard_my_projects": "I miei progetti",
Expand Down
14 changes: 14 additions & 0 deletions ui/src/api-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,20 @@ class ApiClient {
});
}

deleteProject(id: string): Promise<Response> {
const headers = {};
addAuthorization(headers, this.token);

return fetch(`${this.apiUrl}/projects/${id}`, {
method: 'DELETE',
headers: headers,
})
.then(response => {
this.refreshProjects();
return response;
});
}

updateProjectGeometries(id: string, geometries: NgmGeometry[]): Promise<Response> {
const headers = {
'Content-Type': 'application/json'
Expand Down
1 change: 1 addition & 0 deletions ui/src/elements/dashboard/ngm-dashboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -513,6 +513,7 @@ export class NgmDashboard extends LitElementI18n {
@onDeselect="${this.deselectTopicOrProject}"
@onEdit="${this.onProjectEdit}"
@onProjectDuplicated="${(evt: {detail: {project: Project}}) => this.onProjectDuplicated(evt.detail.project)}"
@onProjectDeleted="${() => this.deselectTopicOrProject()}"
></ngm-project-topic-overview>`}
</div>
</div>
Expand Down
57 changes: 57 additions & 0 deletions ui/src/elements/dashboard/ngm-delete-warning-modal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import {html} from 'lit';
import {customElement, property} from 'lit/decorators.js';
import $ from '../../jquery.js';
import i18next from 'i18next';
import {LitElementI18n} from '../../i18n.js';

import 'fomantic-ui-css/components/dimmer.js';
import 'fomantic-ui-css/components/modal.js';

@customElement('ngm-delete-warning-modal')
export class NgmDeleteWarningModal extends LitElementI18n {
@property({type: Boolean})
accessor show = false;
element;

firstUpdated(_changedProperties) {
this.element = $('.ngm-delete-warning-modal.ui.modal').modal({
centered: true,
onHidden: () => this.show = false,
onApprove: () => this.dispatchEvent(new CustomEvent('onProjectDeleted', {bubbles: true}))
});
super.firstUpdated(_changedProperties);
}

updated(changedProperties) {
if (changedProperties.has('show') && this.show) {
this.element.modal('show');
} else if (!this.show) {
this.element.modal('hide');
}
super.updated(changedProperties);
}

render() {
return html`
<div class="ngm-delete-warning-modal ui small modal">
<div class="content">
<h3>${i18next.t('dashboard_delete_warning_title')}</h3>
<p>${i18next.t('dashboard_delete_warning_description')}</p>
</div>
<div class="actions">
<div class="ui cancel button ngm-cancel-btn">
${i18next.t('cancel')}
</div>
<div class="ui ok button ngm-action-btn">
${i18next.t('delete')}
</div>
</div>
</div>
`;
}

createRenderRoot() {
// no shadow dom
return this;
}
}
17 changes: 16 additions & 1 deletion ui/src/elements/dashboard/ngm-project-topic-overview.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {customElement, property} from 'lit/decorators.js';
import {customElement, property, query} from 'lit/decorators.js';
import {LitElementI18n, toLocaleDateString, translated} from '../../i18n';
import {html, PropertyValues} from 'lit';
import i18next from 'i18next';
Expand All @@ -12,6 +12,7 @@ import $ from '../../jquery';
import {DEFAULT_PROJECT_COLOR} from '../../constants';
import './ngm-project-geoms-section';
import './ngm-project-assets-section';
import './ngm-delete-warning-modal';

@customElement('ngm-project-topic-overview')
export class NgmProjectTopicOverview extends LitElementI18n {
Expand All @@ -25,6 +26,8 @@ export class NgmProjectTopicOverview extends LitElementI18n {
accessor userEmail: string = '';
@property({type: Number})
accessor selectedViewIndx: number | undefined;
@query('ngm-delete-warning-modal')
accessor deleteWarningModal;

shouldUpdate(_changedProperties: PropertyValues): boolean {
return this.topicOrProject !== undefined;
Expand All @@ -42,6 +45,9 @@ export class NgmProjectTopicOverview extends LitElementI18n {
const backgroundImage = this.topicOrProject.image?.length ? `url('${this.topicOrProject.image}')` : 'none';

return html`
<ngm-delete-warning-modal
@onProjectDeleted="${() => this.deleteProject()}"
> </ngm-delete-warning-modal>
<div>
<div class="ngm-proj-title">
${translated(this.topicOrProject.title)}
Expand Down Expand Up @@ -125,6 +131,11 @@ export class NgmProjectTopicOverview extends LitElementI18n {
<a class="item" target="_blank" href="mailto:?body=${encodeURIComponent(this.getLink() || '')}">
${i18next.t('dashboard_share_topic_email')}
</a>
<div class="item"
?hidden=${this.activeTab === 'topics'}
@click=${() => this.deleteWarningModal.show = true}>
${i18next.t('delete')}
</div>
</div>
`;
}
Expand All @@ -138,6 +149,10 @@ export class NgmProjectTopicOverview extends LitElementI18n {
this.dispatchEvent(new CustomEvent('onProjectDuplicated', {detail: {project}}));
}

async deleteProject() {
await apiClient.deleteProject(this.topicOrProject!.id);
}

async copyLink(viewId?: string) {
try {
const link = this.getLink(viewId);
Expand Down
6 changes: 4 additions & 2 deletions ui/src/style/ngm-gst-modal.css
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
.ngm-gst-modal.ui.modal embed {
.ngm-gst-modal.ui.modal embed,
.ngm-delete-warning-modal embed {
width: 100%;
height: 500px;
}

.ngm-gst-modal .actions {
.ngm-gst-modal .actions,
.ngm-delete-warning-modal .actions {
display: flex;
justify-content: end;
}

0 comments on commit 90fa68e

Please sign in to comment.