Skip to content

Commit

Permalink
feat: use api to access campaigns stored in s3
Browse files Browse the repository at this point in the history
  • Loading branch information
ymarcon committed Oct 17, 2024
1 parent 0132c79 commit e0d3c31
Show file tree
Hide file tree
Showing 17 changed files with 305 additions and 26 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/publish-ui-container-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
- uses: actions/checkout@v4

- name: Build image
run: docker build frontend --build-arg API_URL=https://icebreaker-dev.epfl.ch --build-arg API_PATH=/api --build-arg AUTH_CLIENT_ID=icebreaker-dev-ui --tag $IMAGE_NAME
run: docker build frontend --build-arg API_URL=https://icebreaker-dev.epfl.ch --build-arg API_PATH=/api --build-arg CDN_PATH=icebreaker-dev --build-arg AUTH_CLIENT_ID=icebreaker-dev-ui --tag $IMAGE_NAME

- name: Log into registry
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login docker.pkg.github.com -u ${{ github.actor }} --password-stdin
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/publish-ui-container.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
- uses: actions/checkout@v4

- name: Build image
run: docker build frontend --build-arg API_URL=https://icebreaker.epfl.ch --build-arg API_PATH=/api --build-arg AUTH_CLIENT_ID=icebreaker-ui --tag $IMAGE_NAME
run: docker build frontend --build-arg API_URL=https://icebreaker.epfl.ch --build-arg API_PATH=/api --build-arg CDN_PATH=icebreaker --build-arg AUTH_CLIENT_ID=icebreaker-ui --tag $IMAGE_NAME

- name: Log into registry
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login docker.pkg.github.com -u ${{ github.actor }} --password-stdin
Expand Down
7 changes: 7 additions & 0 deletions backend/api/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from logging import basicConfig, INFO
from pydantic import BaseModel
from api.views.files import router as files_router
from api.views.campaigns import router as campaigns_router

basicConfig(level=INFO)

Expand Down Expand Up @@ -43,3 +44,9 @@ async def get_health(
prefix="/files",
tags=["Files"],
)

app.include_router(
campaigns_router,
prefix="/catalog",
tags=["Catalog"],
)
54 changes: 54 additions & 0 deletions backend/api/models/campaigns.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
from typing import List, Optional, Tuple
from pydantic import BaseModel

class Reference(BaseModel):
citation: str
doi: str


class Measures(BaseModel):
name: str
description: Optional[str] = None
datasets: Optional[List[str]] = None


class Instrument(BaseModel):
name: str
description: Optional[str] = None
measures: Optional[List[Measures]]

class TrackColumns(BaseModel):
latitude: str
longitude: str
timestamp: Optional[str] = None

class Track(BaseModel):
file: str
columns: TrackColumns
color: Optional[str] = None
timestamp_format: Optional[str]


class Campaign(BaseModel):
id: Optional[int] = None
name: str
acronym: str
website: Optional[str] = None
type: Optional[str] = None
color: Optional[str] = None
objectives: Optional[str] = None
start_date: Optional[str] = None
end_date: Optional[str] = None
location: Optional[str] = None
platform: Optional[str] = None
start_location: Tuple[float, float]
end_location: Optional[Tuple[float, float]] = None
images: Optional[List[str]] = None
track: Optional[Track] = None
fundings: Optional[List[str]] = None
references: Optional[List[Reference]] = None
instruments: Optional[List[Instrument]] = None


class CampaignStore(BaseModel):
campaigns: List[str]
85 changes: 85 additions & 0 deletions backend/api/services/campaigns.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import os
import uuid
import json
import tempfile
import urllib.parse
from api.services.s3 import s3_client
from api.config import config
from api.models.campaigns import Campaign


class CampaignsService:

async def list(self) -> list[Campaign]:
"""List all campaigns"""
folder_path = "campaigns/"
files = [file for file in await s3_client.list_files(folder_path) if file.endswith("/campaign.json")]

campaigns = []
for file in files:
content, mime_type = await s3_client.get_file(file)
campaign_dict = json.loads(content)
campaign = Campaign(**campaign_dict)
campaigns.append(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)
21 changes: 21 additions & 0 deletions backend/api/views/campaigns.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
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 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(
#user: User = Depends(require_admin),
) -> List[Campaign]:
"""Get all campaigns"""
service = CampaignsService()
return await service.list()

1 change: 0 additions & 1 deletion backend/api/views/files.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
from fastapi.responses import Response

from api.utils.file_size import size_checker
from api.auth import get_api_key

from pydantic import BaseModel

Expand Down
11 changes: 8 additions & 3 deletions data/Makefile
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
version=$(shell date +%FT%H:%M)
bucket=10208-fcd9acb029f419e6493edf97f4592b96
path=icebreaker-dev

help:
@echo s3://${bucket}/${path}/${version}/
@echo s3://${bucket}/${path}/

cdn:
s3cmd put --recursive --acl-public --guess-mime-type campaigns s3://${bucket}/${path}/${version}/
s3cmd put --recursive --acl-public --guess-mime-type campaigns s3://${bucket}/${path}/

rm:
s3cmd rm --recursive s3://${bucket}/${path}/

sync:
s3cmd sync s3://${bucket}/${path}/ cdn-local/

2 changes: 2 additions & 0 deletions frontend/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ ARG API_URL
ENV API_URL $API_URL
ARG API_PATH
ENV API_PATH $API_PATH
ARG CDN_PATH
ENV CDN_PATH $CDN_PATH
ARG AUTH_CLIENT_ID
ENV AUTH_CLIENT_ID $AUTH_CLIENT_ID
RUN npm run build
Expand Down
1 change: 1 addition & 0 deletions frontend/quasar.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ module.exports = configure(function (ctx) {
env: {
API_URL: ctx.dev ? 'http://localhost:8000' : process.env.API_URL,
API_PATH: ctx.dev ? '' : process.env.API_PATH,
CDN_PATH: ctx.dev ? 'icebreaker-local-dev' : process.env.CDN_PATH,
AUTH_CLIENT_ID: ctx.dev ? 'local-ui' : process.env.AUTH_CLIENT_ID,
CESIUM_ACCESS_TOKEN: ctx.dev ? '' : process.env.CESIUM_ACCESS_TOKEN,
},
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/boot/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ declare module '@vue/runtime-core' {
}
}

const cdnUrl = 'https://enacit4r-cdn.epfl.ch/icebreaker-dev/2024-10-15T08:20/';
const cdnUrl = `https://enacit4r-cdn.epfl.ch/${process.env.CDN_PATH}`;

const baseUrl = `${process.env.API_URL}${process.env.API_PATH}`;

Expand Down
5 changes: 5 additions & 0 deletions frontend/src/components/AppToolbar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,8 @@ withDefaults(defineProps<Props>(), {
});
const emit = defineEmits(['toggle']);
const route = useRoute();
const router = useRouter();
const settingsStore = useSettingsStore();
const mapStore = useMapStore();
const authStore = useAuthStore();
Expand All @@ -160,5 +162,8 @@ function toggleLeftDrawer() {
function toggleShowGlobe() {
mapStore.showGlobe = !mapStore.showGlobe;
settingsStore.saveSettings({ show_globe: mapStore.showGlobe } as Settings);
if (route.path === '/admin') {
router.push('/');
}
}
</script>
4 changes: 2 additions & 2 deletions frontend/src/components/CampaignView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -161,11 +161,11 @@ const tab = ref('info');
const slide = ref(1);
const imageUrls = computed(() => {
return props.campaign.images ? props.campaign.images.map((image) => `${cdnUrl}campaigns/${props.campaign.acronym}/${image}`) : [];
return props.campaign.images ? props.campaign.images.map((image) => `${cdnUrl}/campaigns/${props.campaign.id}/${image}`) : [];
});
const trackUrl = computed(() => {
return props.campaign.track ? `${cdnUrl}campaigns/${props.campaign.acronym}/${props.campaign.track.file}` : '';
return props.campaign.track ? `${cdnUrl}/campaigns/${props.campaign.id}/${props.campaign.track.file}` : '';
});
function truncateString(str: string, num: number) {
Expand Down
27 changes: 27 additions & 0 deletions frontend/src/components/admin/CampaignForm.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<template>
<div>
<pre>{{ campaign }}</pre>
</div>
</template>


<script lang="ts">
export default defineComponent({
name: 'CampaignForm',
});
</script>
<script setup lang="ts">
import { Campaign } from 'src/models';
import { cdnUrl } from 'src/boot/api';
interface Props {
modelValue: Campaign;
}
const props = defineProps<Props>();
const campaign = ref(props.modelValue);
watch(() => props.modelValue, (val) => {
campaign.value = val;
});
</script>
2 changes: 2 additions & 0 deletions frontend/src/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,10 @@ export interface Track {
}

export interface Campaign {
id: string;
name: string;
acronym: string;
website?: string;
type?: string;
color?: string;
objectives?: string;
Expand Down
Loading

0 comments on commit e0d3c31

Please sign in to comment.