Skip to content

Commit

Permalink
feat: campaigns management (wip)
Browse files Browse the repository at this point in the history
  • Loading branch information
ymarcon committed Oct 18, 2024
1 parent b65f9f4 commit e830b52
Show file tree
Hide file tree
Showing 12 changed files with 298 additions and 140 deletions.
125 changes: 66 additions & 59 deletions backend/api/services/campaigns.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,62 +24,69 @@ async def list(self) -> list[Campaign]:

return campaigns

# async def createOrUpdate(self, study: Study) -> Study:
# if study.identifier is None or study.identifier == "" or study.identifier == "_draft":
# study.identifier = str(uuid.uuid4())

# # Destination folder in s3
# s3_folder = f"draft/{study.identifier}"

# # Move tmp files to their final location
# if study.datasets is not None:
# for dataset in study.datasets:
# if "children" in dataset.folder:
# for i, file in enumerate(dataset.folder["children"]):
# if "/tmp/" in file["path"]:
# dataset_file_path = f"{s3_folder}/files/{dataset.name}/{file['name']}"
# new_key = await s3_client.move_file(file["path"], dataset_file_path)
# file["path"] = urllib.parse.quote(new_key)
# dataset.folder["children"][i] = file
# dataset.folder["name"] = dataset.name
# dataset.folder["path"] = s3_client.to_s3_path(
# urllib.parse.quote(f"{s3_folder}/files/{dataset.name}"))

# # TODO Remove files that are not linked to a dataset

# # Create a temporary directory to dump JSON
# with tempfile.TemporaryDirectory() as temp_dir:
# print(f"Temporary directory created at: {temp_dir}")

# # Use the temporary directory for file operations
# temp_file_path = os.path.join(temp_dir, "study.json")

# # Convert SQLModel object to dictionary
# study_dict = study.model_dump()
# with open(temp_file_path, "w") as temp_file:
# json.dump(study_dict, temp_file, indent=2)
# await s3_client.upload_local_file(temp_dir, "study.json", s3_folder=s3_folder)

# return study

# async def delete(self, identifier: str):
# exists = await self.exists(identifier)
# if not exists:
# raise Exception(
# f"Study with identifier {identifier} does not exist.")

# await s3_client.delete_files(f"draft/{identifier}")

# async def exists(self, identifier: str) -> bool:
# return await s3_client.path_exists(f"draft/{identifier}/study.json")

# async def get(self, identifier: str) -> StudyDraft:
# exists = await self.exists(identifier)
# if not exists:
# raise Exception(
# f"Study with identifier {identifier} does not exist.")

# file_path = f"draft/{identifier}/study.json"
# content, mime_type = await s3_client.get_file(file_path)
# study_dict = json.loads(content)
# return StudyDraft(**study_dict)
async def delete(self, identifier: str):
exists = await self.exists(identifier)
if not exists:
raise Exception(
f"Campaign with identifier {identifier} does not exist.")

await s3_client.delete_files(f"campaigns/{identifier}")

async def exists(self, identifier: str) -> bool:
return await s3_client.path_exists(f"campaigns/{identifier}/campaign.json")

async def createOrUpdate(self, campaign: Campaign) -> Campaign:
if campaign.id is None or campaign.id == "" or campaign.id == "_draft":
campaign.id = str(uuid.uuid4())

# Destination folder in s3
s3_folder = f"campaigns/{campaign.id}"

# Move tmp files to their final location
# Images
if campaign.images is not None:
for i, file in enumerate(campaign.images):
if "/tmp/" in file.path:
new_name = f"{i}-{file.name}"
file_path = f"{s3_folder}/{new_name}"
new_key = await s3_client.move_file(file.path, file_path)
file.name = new_name
file.path = urllib.parse.quote(new_key)
if file.alt_path is not None:
new_name = f"{i}-{file.alt_name}"
new_key = await s3_client.move_file(file.alt_path, f"{s3_folder}/{new_name}")
file.alt_name = new_name
file.alt_path = urllib.parse.quote(new_key)
# Track
if campaign.track is not None and campaign.track.file is not None:
if "/tmp/" in campaign.track.file.path:
file_path = f"{s3_folder}/{campaign.track.file.name}"
new_key = await s3_client.move_file(campaign.track.file.path, file_path)
campaign.track.file.path = new_key

# Create a temporary directory to dump JSON
with tempfile.TemporaryDirectory() as temp_dir:
print(f"Temporary directory created at: {temp_dir}")

# Use the temporary directory for file operations
temp_file_path = os.path.join(temp_dir, "campaign.json")

# Convert SQLModel object to dictionary
campaign_dict = campaign.model_dump()
with open(temp_file_path, "w") as temp_file:
json.dump(campaign_dict, temp_file, indent=2)
await s3_client.upload_local_file(temp_dir, "campaign.json", s3_folder=s3_folder)

return campaign


async def get(self, identifier: str) -> Campaign:
exists = await self.exists(identifier)
if not exists:
raise Exception(
f"Campaign with identifier {identifier} does not exist.")

file_path = f"campaigns/{identifier}/campaign.json"
content, mime_type = await s3_client.get_file(file_path)
campaign_dict = json.loads(content)
return Campaign(**campaign_dict)
1 change: 1 addition & 0 deletions backend/api/services/s3.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ async def copy_file(self, file_path: str, destination_path: str):
response = await client.copy_object(
Bucket=config.S3_BUCKET,
CopySource={'Bucket': config.S3_BUCKET, 'Key': source_key},
ACL="public-read",
Key=destination_key)
if response["ResponseMetadata"]["HTTPStatusCode"] == 200:
logger.info(
Expand Down
40 changes: 33 additions & 7 deletions backend/api/views/campaigns.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,47 @@
from typing import List
from fastapi import APIRouter, Depends, Body
from fastapi.responses import FileResponse
from fastapi.datastructures import UploadFile
from fastapi.param_functions import File
from fastapi import APIRouter, Depends
from fastapi.responses import Response
from api.services.campaigns import CampaignsService
from api.models.campaigns import Campaign
from api.utils.file_size import size_checker
from api.auth import require_admin, User

router = APIRouter()


@router.get("/campaigns", response_model=List[Campaign])
async def get_study_drafts(
async def get_campaigns(
#user: User = Depends(require_admin),
) -> List[Campaign]:
"""Get all campaigns"""
service = CampaignsService()
return await service.list()


@router.post("/campaigns", status_code=201, response_model=List[Campaign])
async def create_or_update_campaigns(
campaigns: List[Campaign],
user: User = Depends(require_admin),
):
"""Create or update campaigns, move temporary files to there final location"""
service = CampaignsService()
for campaign in campaigns:
await service.createOrUpdate(campaign)
return Response(status_code=201)

@router.get("/campaign/{identifier}", response_model=Campaign)
async def get_campaign(
identifier: str,
#user: User = Depends(require_admin),
) -> Campaign:
"""Get a campaign"""
service = CampaignsService()
return await service.get(identifier)

@router.delete("/campaign/{identifier}", response_model=List[Campaign])
async def delete_campaign(
identifier: str,
user: User = Depends(require_admin),
) -> List[Campaign]:
"""Delete a campaign and associated files"""
service = CampaignsService()
return await service.delete(identifier)

2 changes: 1 addition & 1 deletion backend/api/views/files.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from fastapi.datastructures import UploadFile
from fastapi.param_functions import File
from api.services.s3 import s3_client
from fastapi import Depends, Security, Query, APIRouter, HTTPException
from fastapi import Depends, Query, APIRouter, HTTPException
from fastapi.responses import Response
from api.utils.file_size import size_checker
from api.auth import require_admin, User
Expand Down
1 change: 1 addition & 0 deletions frontend/src/components/AppToolbar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
icon="settings"
:title="$t('administration')"
:to="'/admin'"
class="on-left"
></q-btn>
</span>
<q-btn v-if="$q.screen.lt.md" flat round icon="more_vert">
Expand Down
20 changes: 9 additions & 11 deletions frontend/src/components/admin/CampaignForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@
<div class="row q-col-gutter-md q-mb-md">
<div class="col">
<q-input
v-model="campaign.start_date"
v-model="campaign.end_date"
:label="$t('end_date')"
filled >
<template v-slot:append>
Expand Down Expand Up @@ -139,7 +139,7 @@
</div>
</div>
<div class="row q-col-gutter-md q-mb-md">
<div class="col">
<div class="col-12 col-lg-6">
<div class="text-h6 q-mb-md">{{ $t('images') }}</div>
<q-list bordered separator v-if="campaign.images && campaign.images.length" class="q-mb-md">
<template v-for="image in campaign.images" :key="image.path">
Expand Down Expand Up @@ -176,7 +176,7 @@
@update:model-value="onImageFileSelected"
/>
</div>
<div class="col">
<div class="col-12 col-lg-6">
<div class="text-h6 q-mb-md">{{ $t('track') }}</div>
<div v-if="campaign.track?.file">
<q-list bordered class="q-mb-md">
Expand Down Expand Up @@ -262,7 +262,7 @@
</div>
</div>
<div class="row q-col-gutter-md q-mb-md">
<div class="col">
<div class="col-12 col-lg-6">
<div class="text-h6 q-mb-md">{{ $t('fundings') }}</div>
<q-list bordered separator v-if="campaign.fundings && campaign.fundings.length" class="q-mb-md">
<q-item v-for="(funding, i) in campaign.fundings" :key="funding.name">
Expand Down Expand Up @@ -337,7 +337,7 @@
@click="onShowFunding(undefined)"
/>
</div>
<div class="col">
<div class="col-12 col-lg-6">
<div class="text-h6 q-mb-md">{{ $t('references') }}</div>
<q-list bordered separator v-if="campaign.references && campaign.references.length" class="q-mb-md">
<q-item v-for="(reference, i) in campaign.references" :key="reference.doi">
Expand Down Expand Up @@ -697,9 +697,8 @@ function onImageFileSelected() {
function onDeleteImage(image: FileRef) {
// TODO delete image from server
adminStore.deleteFile(image).then(() => {
campaign.value.images = campaign.value.images.filter((i) => i.path !== image.path);
});
adminStore.addFileToDelete(image);
campaign.value.images = campaign.value.images.filter((i) => i.path !== image.path);
}
function onTrackFileSelected() {
Expand Down Expand Up @@ -728,9 +727,8 @@ function onTrackFileSelected() {
function onDeleteTrack() {
if (!campaign.value.track?.file) return;
adminStore.deleteFile(campaign.value.track.file).then(() => {
campaign.value.track = undefined;
});
adminStore.addFileToDelete(campaign.value.track.file);
campaign.value.track = undefined;
}
function truncateString(str: string, num: number) {
Expand Down
10 changes: 6 additions & 4 deletions frontend/src/components/map/AzimuthView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ function initCampaign(campaign: Campaign) {
const trackFeature = new Feature({
geometry: new LineString(trackLine),
});
addFeatureLayer(campaign, pointStart, pointEnd, trackFeature);
addFeatureLayer(campaign, [pointStart, pointEnd, trackFeature]);
});
} else {
const trackLine = [
Expand All @@ -133,16 +133,18 @@ function initCampaign(campaign: Campaign) {
const trackFeature = new Feature({
geometry: new LineString(trackLine),
});
addFeatureLayer(campaign, pointStart, pointEnd, trackFeature);
addFeatureLayer(campaign, [pointStart, pointEnd, trackFeature]);
}
} else {
addFeatureLayer(campaign, [pointStart]);
}
}
function addFeatureLayer(campaign: Campaign, pointStart: Feature, pointEnd: Feature, trackFeature: Feature) {
function addFeatureLayer(campaign: Campaign, features: [Feature]) {
if (!map.value) return;
const vectorSource = new VectorSource({
features: [pointStart, pointEnd, trackFeature],
features,
});
const vectorLayer = new VectorLayer({
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/i18n/en/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export default {
administration: 'Administration',
profile: 'Profile',
campaigns: 'Campaigns',
campaigns_info: 'Campaigns information',
campaigns_info: 'Campaigns and expeditions information management.',
app_title: 'IceBreaker',
app_subtitle: 'Polar campaigns tracks',
error_not_found: 'Oops. Nothing here...',
Expand Down Expand Up @@ -58,4 +58,5 @@ export default {
citation: 'Citation',
doi: 'DOI',
description: 'Description',
reload_confirmation: 'You have pending changes. Are you sure you want to reload the page? All unsaved changes will be lost.',
};
Loading

0 comments on commit e830b52

Please sign in to comment.