Skip to content

Commit 3b58974

Browse files
committed
add delete endpoint and add Dockerfile
1 parent 72c604d commit 3b58974

6 files changed

Lines changed: 156 additions & 8 deletions

File tree

.github/workflows/publish.yaml

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
name: Create and publish a Docker image
2+
3+
on:
4+
push:
5+
branches: ['main']
6+
schedule:
7+
- cron: "0 0 3 * *"
8+
workflow_dispatch:
9+
10+
env:
11+
REGISTRY: ghcr.io
12+
IMAGE_NAME: ${{ github.repository }}
13+
14+
jobs:
15+
build-and-push-image:
16+
runs-on: ubuntu-latest
17+
permissions:
18+
contents: write
19+
packages: write
20+
21+
steps:
22+
- name: Checkout repository
23+
uses: actions/checkout@v3
24+
25+
- name: Set up Docker Buildx
26+
uses: docker/setup-buildx-action@v3
27+
28+
- name: Docker Image Update Checker
29+
id: check
30+
uses: lucacome/docker-image-update-checker@v1
31+
if: github.event_name != 'push'
32+
with:
33+
base-image: alpine
34+
image: greencode-hackathon/${{ env.IMAGE_NAME }}
35+
36+
- name: Log in to the Container registry
37+
uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1
38+
if: github.event_name == 'push' || steps.check.outputs.needs-updating == 'true'
39+
with:
40+
registry: ${{ env.REGISTRY }}
41+
username: ${{ github.actor }}
42+
password: ${{ secrets.GITHUB_TOKEN }}
43+
44+
- name: Extract metadata (tags, labels) for Docker
45+
id: meta
46+
uses: docker/metadata-action@v4
47+
if: github.event_name == 'push' || steps.check.outputs.needs-updating == 'true'
48+
with:
49+
images: |
50+
ghcr.io/${{ env.IMAGE_NAME }}
51+
tags: |
52+
type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', 'master') }}
53+
type=ref,event=branch
54+
type=ref,event=pr
55+
type=semver,pattern={{version}}
56+
type=semver,pattern={{major}}.{{minor}}
57+
type=sha
58+
59+
- name: Build and push Docker image
60+
uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4
61+
if: github.event_name == 'push' || steps.check.outputs.needs-updating == 'true'
62+
with:
63+
context: .
64+
platforms: linux/amd64,linux/arm64
65+
push: true
66+
tags: ${{ steps.meta.outputs.tags }}
67+
labels: ${{ steps.meta.outputs.labels }}

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
/target
22
.vscode
33
.idea
4+
*.zst

Dockerfile

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
FROM rust:alpine as builder
2+
3+
WORKDIR /cdn-api
4+
5+
COPY . .
6+
7+
RUN apk add musl-dev
8+
9+
RUN cargo build --release
10+
11+
FROM alpine:latest
12+
13+
WORKDIR /cdn-api
14+
15+
RUN apk add ffmpeg
16+
17+
COPY --from=builder /cdn-api/target/release/cdn-api .
18+
19+
CMD ["./cdn-api"]

src/api/media.rs

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
use actix_multipart::Multipart;
22
use actix_web::{
3-
get, post,
3+
delete, get, post,
44
web::{scope, Data, Path},
55
HttpRequest, HttpResponse, Scope,
66
};
77
use futures::StreamExt;
8-
use log::error;
8+
use log::{error, info};
99
use uuid::Uuid;
1010

1111
use crate::storage::service::{Format, StorageService};
@@ -18,6 +18,7 @@ pub fn routes() -> Scope {
1818
.service(get_metadata)
1919
.service(get_media)
2020
.service(insert_media)
21+
.service(delete_media)
2122
}
2223

2324
#[get("/{uuid}/metadata")]
@@ -60,6 +61,8 @@ pub async fn insert_media(
6061
storage: Data<&'static StorageService>,
6162
mut payload: Multipart,
6263
) -> HttpResponse {
64+
let mut metadatas = Vec::new();
65+
6366
while let Some(item) = payload.next().await {
6467
let field = match item {
6568
Ok(f) => f,
@@ -84,10 +87,39 @@ pub async fn insert_media(
8487
continue;
8588
};
8689

87-
if let Err(e) = storage.insert(field, format).await {
88-
error!("{}", e);
90+
match storage.insert(field, format).await {
91+
Ok(m) => {
92+
metadatas.push(m);
93+
}
94+
Err(e) => {
95+
error!("{}", e);
96+
continue;
97+
}
8998
}
9099
}
91100

92-
HttpResponse::Ok().finish()
101+
for metadata in &metadatas {
102+
info!("Inserted media with uuid '{}'", metadata.uuid)
103+
}
104+
105+
HttpResponse::Ok().json(&metadatas)
106+
}
107+
108+
#[delete("/{uuid}")]
109+
pub async fn delete_media(
110+
storage: Data<&'static StorageService>,
111+
uuid: Path<Uuid>,
112+
) -> HttpResponse {
113+
let uuid = uuid.into_inner();
114+
115+
match storage.remove(uuid).await {
116+
Ok(m) => {
117+
info!("Deleted media with uuid '{}'", uuid);
118+
HttpResponse::Ok().json(&m)
119+
}
120+
Err(e) => {
121+
error!("Couldn't delete '{}'", e);
122+
HttpResponse::NotFound().finish()
123+
}
124+
}
93125
}

src/storage/media.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use std::path::{Path, PathBuf};
22

33
use chrono::{DateTime, Utc};
44
use serde::Serialize;
5+
use uuid::Uuid;
56

67
#[derive(Clone)]
78
pub struct Media {
@@ -27,6 +28,7 @@ pub enum MediaType {
2728

2829
#[derive(Clone, Serialize)]
2930
pub struct MediaMetadata {
31+
pub uuid: Uuid,
3032
pub media_type: MediaType,
3133
pub modified: DateTime<Utc>,
3234
}

src/storage/service.rs

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ impl StorageService {
6767
let media = match extension.as_str() {
6868
"webp" => {
6969
let metadata = MediaMetadata {
70+
uuid,
7071
media_type: MediaType::Image,
7172
modified,
7273
};
@@ -78,6 +79,7 @@ impl StorageService {
7879
}
7980
"webm" => {
8081
let metadata = MediaMetadata {
82+
uuid,
8183
media_type: MediaType::Video,
8284
modified,
8385
};
@@ -117,7 +119,28 @@ impl StorageService {
117119
self.media.read().unwrap().len()
118120
}
119121

120-
pub async fn insert(&self, mut field: Field, format: Format) -> anyhow::Result<()> {
122+
pub async fn remove(&self, uuid: Uuid) -> anyhow::Result<MediaMetadata> {
123+
let mut lock = self.media.write().unwrap();
124+
125+
let metadata;
126+
127+
match lock.get(&uuid) {
128+
Some(m) => {
129+
let path = m.path.clone();
130+
131+
tokio::fs::remove_file(path).await?;
132+
133+
metadata = lock.remove(&uuid).unwrap().metadata;
134+
}
135+
None => {
136+
bail!("Media with uuid '{}' doesn't exist", uuid);
137+
}
138+
}
139+
140+
Ok(metadata)
141+
}
142+
143+
pub async fn insert(&self, mut field: Field, format: Format) -> anyhow::Result<MediaMetadata> {
121144
let uuid = Uuid::new_v4();
122145

123146
// TEMPORARY
@@ -150,6 +173,9 @@ impl StorageService {
150173

151174
let output = command.output().unwrap();
152175

176+
// Remove the temporary file to save space
177+
tokio::fs::remove_file(&temporary).await?;
178+
153179
if !output.status.success() {
154180
bail!(
155181
"Couldn't convert '{}' to '{}'",
@@ -159,20 +185,21 @@ impl StorageService {
159185
}
160186

161187
let metadata = MediaMetadata {
188+
uuid,
162189
media_type: MediaType::Image,
163190
modified: Utc::now(),
164191
};
165192

166193
let media = Media {
167194
path: persistent,
168-
metadata,
195+
metadata: metadata.clone(),
169196
};
170197

171198
let mut lock = self.media.write().unwrap();
172199

173200
lock.insert(uuid, media);
174201

175-
Ok(())
202+
Ok(metadata)
176203
}
177204
}
178205

0 commit comments

Comments
 (0)