]> Untitled Git - lemmy.git/blob - src/routes/images.rs
routes.api: fix get_captcha endpoint (#1135)
[lemmy.git] / src / routes / images.rs
1 use actix::clock::Duration;
2 use actix_web::{body::BodyStream, http::StatusCode, *};
3 use awc::Client;
4 use lemmy_rate_limit::RateLimit;
5 use lemmy_utils::settings::Settings;
6 use serde::{Deserialize, Serialize};
7
8 pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimit) {
9   let client = Client::builder()
10     .header("User-Agent", "pict-rs-frontend, v0.1.0")
11     .timeout(Duration::from_secs(30))
12     .finish();
13
14   cfg
15     .data(client)
16     .service(
17       web::resource("/pictrs/image")
18         .wrap(rate_limit.image())
19         .route(web::post().to(upload)),
20     )
21     .service(web::resource("/pictrs/image/{filename}").route(web::get().to(full_res)))
22     .service(
23       web::resource("/pictrs/image/thumbnail{size}/{filename}").route(web::get().to(thumbnail)),
24     )
25     .service(web::resource("/pictrs/image/delete/{token}/{filename}").route(web::get().to(delete)));
26 }
27
28 #[derive(Debug, Serialize, Deserialize)]
29 pub struct Image {
30   file: String,
31   delete_token: String,
32 }
33
34 #[derive(Debug, Serialize, Deserialize)]
35 pub struct Images {
36   msg: String,
37   files: Option<Vec<Image>>,
38 }
39
40 async fn upload(
41   req: HttpRequest,
42   body: web::Payload,
43   client: web::Data<Client>,
44 ) -> Result<HttpResponse, Error> {
45   // TODO: check auth and rate limit here
46
47   let mut res = client
48     .request_from(format!("{}/image", Settings::get().pictrs_url), req.head())
49     .if_some(req.head().peer_addr, |addr, req| {
50       req.header("X-Forwarded-For", addr.to_string())
51     })
52     .send_stream(body)
53     .await?;
54
55   let images = res.json::<Images>().await?;
56
57   Ok(HttpResponse::build(res.status()).json(images))
58 }
59
60 async fn full_res(
61   filename: web::Path<String>,
62   req: HttpRequest,
63   client: web::Data<Client>,
64 ) -> Result<HttpResponse, Error> {
65   let url = format!(
66     "{}/image/{}",
67     Settings::get().pictrs_url,
68     &filename.into_inner()
69   );
70   image(url, req, client).await
71 }
72
73 async fn thumbnail(
74   parts: web::Path<(u64, String)>,
75   req: HttpRequest,
76   client: web::Data<Client>,
77 ) -> Result<HttpResponse, Error> {
78   let (size, file) = parts.into_inner();
79
80   let url = format!(
81     "{}/image/thumbnail{}/{}",
82     Settings::get().pictrs_url,
83     size,
84     &file
85   );
86
87   image(url, req, client).await
88 }
89
90 async fn image(
91   url: String,
92   req: HttpRequest,
93   client: web::Data<Client>,
94 ) -> Result<HttpResponse, Error> {
95   let res = client
96     .request_from(url, req.head())
97     .if_some(req.head().peer_addr, |addr, req| {
98       req.header("X-Forwarded-For", addr.to_string())
99     })
100     .no_decompress()
101     .send()
102     .await?;
103
104   if res.status() == StatusCode::NOT_FOUND {
105     return Ok(HttpResponse::NotFound().finish());
106   }
107
108   let mut client_res = HttpResponse::build(res.status());
109
110   for (name, value) in res.headers().iter().filter(|(h, _)| *h != "connection") {
111     client_res.header(name.clone(), value.clone());
112   }
113
114   Ok(client_res.body(BodyStream::new(res)))
115 }
116
117 async fn delete(
118   components: web::Path<(String, String)>,
119   req: HttpRequest,
120   client: web::Data<Client>,
121 ) -> Result<HttpResponse, Error> {
122   let (token, file) = components.into_inner();
123
124   let url = format!(
125     "{}/image/delete/{}/{}",
126     Settings::get().pictrs_url,
127     &token,
128     &file
129   );
130   let res = client
131     .request_from(url, req.head())
132     .if_some(req.head().peer_addr, |addr, req| {
133       req.header("X-Forwarded-For", addr.to_string())
134     })
135     .no_decompress()
136     .send()
137     .await?;
138
139   Ok(HttpResponse::build(res.status()).body(BodyStream::new(res)))
140 }