diff --git a/Cargo.lock b/Cargo.lock
index fca6522..0d245bf 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -887,6 +887,7 @@ dependencies = [
"common",
"sqlx",
"testdir",
+ "time 0.3.27",
"tokio",
"tracing",
"uuid",
diff --git a/crates/accounts/src/api/routes/get_user_id_profile.rs b/crates/accounts/src/api/routes/get_user_id_profile.rs
index 82c8d9e..6ff98bd 100644
--- a/crates/accounts/src/api/routes/get_user_id_profile.rs
+++ b/crates/accounts/src/api/routes/get_user_id_profile.rs
@@ -1,3 +1,20 @@
+/* Photos.network · A privacy first photo storage and sharing service for fediverse.
+ * Copyright (C) 2020 Photos network developers
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
use axum::http::StatusCode;
pub(crate) async fn get_user_id_profile() -> std::result::Result {
diff --git a/crates/common/src/config/configuration.rs b/crates/common/src/config/configuration.rs
index 2008fe5..45d5dbb 100644
--- a/crates/common/src/config/configuration.rs
+++ b/crates/common/src/config/configuration.rs
@@ -16,7 +16,7 @@
*/
//! This defines the app configuration
-use std::{fmt, fs, path::PathBuf};
+use std::{fmt, fs};
use serde::Deserialize;
use tracing::info;
diff --git a/crates/common/src/database/details.rs b/crates/common/src/database/details.rs
new file mode 100644
index 0000000..a7b713e
--- /dev/null
+++ b/crates/common/src/database/details.rs
@@ -0,0 +1,45 @@
+/* Photos.network · A privacy first photo storage and sharing service for fediverse.
+ * Copyright (C) 2020 Photos network developers
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+use uuid::Uuid;
+
+pub struct Details {
+ pub uuid: &'static Uuid,
+ pub camera_manufacturer: &'static str,
+ pub camera_model: &'static str,
+ pub camera_serial: &'static str,
+ pub lens_model: &'static str,
+ pub lens_serial: &'static str,
+ pub orientation: &'static str,
+ pub compression: &'static str,
+ pub resolution_x: &'static str,
+ pub resolution_y: &'static str,
+ pub resolution_unit: &'static str,
+ pub exposure_time: &'static str,
+ pub exposure_mode: &'static str,
+ pub exposure_program: &'static str,
+ pub exposure_bias: &'static str,
+ pub aperture: &'static f32,
+ pub iso: &'static i32,
+ pub color_space: &'static str,
+ pub pixel_x: &'static i64,
+ pub pixel_y: &'static i64,
+ pub user_comment: &'static str,
+ pub white_balance: &'static str,
+ pub flash: bool,
+ pub exif_version: &'static f32,
+}
diff --git a/crates/common/src/database/location.rs b/crates/common/src/database/location.rs
new file mode 100644
index 0000000..bb928cc
--- /dev/null
+++ b/crates/common/src/database/location.rs
@@ -0,0 +1,25 @@
+/* Photos.network · A privacy first photo storage and sharing service for fediverse.
+ * Copyright (C) 2020 Photos network developers
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+use uuid::Uuid;
+
+pub struct Location {
+ pub uuid: &'static Uuid,
+ pub latitude: &'static f64,
+ pub longitude: &'static f64,
+ pub altitude: &'static Option,
+}
diff --git a/crates/common/src/database/media_item.rs b/crates/common/src/database/media_item.rs
new file mode 100644
index 0000000..8c2db07
--- /dev/null
+++ b/crates/common/src/database/media_item.rs
@@ -0,0 +1,31 @@
+/* Photos.network · A privacy first photo storage and sharing service for fediverse.
+ * Copyright (C) 2020 Photos network developers
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+use std::time::Instant;
+
+use super::{details::Details, location::Location, reference::Reference, tag::Tag};
+
+pub struct MediaItem {
+ pub uuid: &'static str,
+ pub name: &'static str,
+ pub added_at: Instant,
+ pub taken_at: Option,
+ pub details: Option,
+ pub tags: Option>,
+ pub location: Option,
+ pub references: Option>,
+}
diff --git a/crates/common/src/database/mod.rs b/crates/common/src/database/mod.rs
index f09198b..a29110d 100644
--- a/crates/common/src/database/mod.rs
+++ b/crates/common/src/database/mod.rs
@@ -1,12 +1,85 @@
+/* Photos.network · A privacy first photo storage and sharing service for fediverse.
+ * Copyright (C) 2020 Photos network developers
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
use async_trait::async_trait;
use std::error::Error;
+use time::OffsetDateTime;
use crate::auth::user::User;
+use self::{media_item::MediaItem, reference::Reference};
+
+pub mod details;
+pub mod location;
+pub mod media_item;
+pub mod reference;
+pub mod tag;
+
#[async_trait]
pub trait Database {
+ /// Initialize the database and run required migrations
async fn setup(&mut self) -> Result<(), Box>;
+
+ /// List registered user accounts
+ async fn get_users(&self) -> Result, Box>;
+
+ /// Create a new user account
async fn create_user(&self, user: &User) -> Result<(), Box>;
+
+ /// Get user by user_id
+ async fn get_user(&self, user_id: &str) -> Result>;
+
+ /// Partial update a single user account
async fn update_email(&self, email: &str, user_id: &str) -> Result<(), Box>;
- async fn get_users(&self) -> Result, Box>;
+ async fn update_nickname(&self, nickname: &str) -> Result<(), Box>;
+ async fn update_names(
+ &self,
+ firstname: &str,
+ lastname: &str,
+ user_id: &str,
+ ) -> Result<(), Box>;
+
+ async fn disable_user(&self, user_id: &str) -> Result<(), Box>;
+ async fn enable_user(&self, user_id: &str) -> Result<(), Box>;
+
+ async fn get_media_items(&self, user_id: &str) -> Result, Box>;
+ async fn create_media_item(
+ &self,
+ user_id: &str,
+ name: &str,
+ date_taken: OffsetDateTime,
+ ) -> Result>;
+ async fn get_media_item(&self, media_id: &str) -> Result>;
+ async fn add_reference(
+ &self,
+ user_id: &str,
+ media_id: &str,
+ reference: &Reference,
+ ) -> Result>;
+
+ async fn update_reference(
+ &self,
+ reference_id: &str,
+ reference: &Reference,
+ ) -> Result<(), Box>;
+
+ async fn remove_reference(
+ &self,
+ media_id: &str,
+ reference_id: &str,
+ ) -> Result<(), Box>;
}
diff --git a/crates/common/src/database/reference.rs b/crates/common/src/database/reference.rs
new file mode 100644
index 0000000..fb2e8f4
--- /dev/null
+++ b/crates/common/src/database/reference.rs
@@ -0,0 +1,28 @@
+/* Photos.network · A privacy first photo storage and sharing service for fediverse.
+ * Copyright (C) 2020 Photos network developers
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+use time::OffsetDateTime;
+
+pub struct Reference {
+ pub uuid: &'static str,
+ pub filepath: String,
+ pub filename: String,
+ pub size: u64,
+ pub description: &'static str,
+ pub last_modified: OffsetDateTime,
+ pub is_missing: bool,
+}
diff --git a/crates/common/src/database/tag.rs b/crates/common/src/database/tag.rs
new file mode 100644
index 0000000..6f9605b
--- /dev/null
+++ b/crates/common/src/database/tag.rs
@@ -0,0 +1,24 @@
+/* Photos.network · A privacy first photo storage and sharing service for fediverse.
+ * Copyright (C) 2020 Photos network developers
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+use uuid::Uuid;
+
+pub struct Tag {
+ pub uuid: &'static Uuid,
+ pub tag: &'static str,
+ pub origin: &'static str,
+}
diff --git a/crates/common/src/database/user.rs b/crates/common/src/database/user.rs
new file mode 100644
index 0000000..8c2db07
--- /dev/null
+++ b/crates/common/src/database/user.rs
@@ -0,0 +1,31 @@
+/* Photos.network · A privacy first photo storage and sharing service for fediverse.
+ * Copyright (C) 2020 Photos network developers
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+use std::time::Instant;
+
+use super::{details::Details, location::Location, reference::Reference, tag::Tag};
+
+pub struct MediaItem {
+ pub uuid: &'static str,
+ pub name: &'static str,
+ pub added_at: Instant,
+ pub taken_at: Option,
+ pub details: Option,
+ pub tags: Option>,
+ pub location: Option,
+ pub references: Option>,
+}
diff --git a/crates/database/Cargo.toml b/crates/database/Cargo.toml
index 80ae5d9..522023d 100644
--- a/crates/database/Cargo.toml
+++ b/crates/database/Cargo.toml
@@ -25,3 +25,4 @@ sqlx = { workspace = true, features = ["runtime-tokio", "tls-native-tls", "postg
[dev-dependencies]
testdir.workspace = true
+time.workspace = true
diff --git a/crates/database/data/init.sql b/crates/database/data/init.sql
deleted file mode 100644
index d00697b..0000000
--- a/crates/database/data/init.sql
+++ /dev/null
@@ -1,45 +0,0 @@
-CREATE TABLE IF NOT EXISTS users (
- --auto generated
- uuid VARCHAR NOT NULL,
- email VARCHAR,
- password VARCHAR,
- lastname VARCHAR,
- firstname VARCHAR,
- --indicates if user is not able to login
- is_locked BOOLEAN DEFAULT FALSE,
- create_at TIMESTAMPTZ DEFAULT NULL,
- updated_at TIMESTAMPTZ DEFAULT NULL,
- last_login TIMESTAMPTZ DEFAULT NULL,
- PRIMARY KEY (uuid)
-);
-
-CREATE TABLE IF NOT EXISTS media (
- uuid VARCHAR NOT NULL,
- name VARCHAR,
- owner VARCHAR NOT NULL,
- -- show/hide by default regarding user settings
- is_sensitive BOOLEAN DEFAULT FALSE,
- -- added to photos.network
- date_added TIMESTAMPTZ DEFAULT NULL,
- -- captured timestamp
- date_taken TIMESTAMPTZ DEFAULT NULL,
- PRIMARY KEY (uuid),
- FOREIGN KEY(owner) REFERENCES users (uuid)
-);
-
-CREATE TABLE IF NOT EXISTS reference (
- uuid VARCHAR NOT NULL,
- media VARCHAR NOT NULL,
- -- ./data/files/[media.owner.uuid]/[media.date_taken.year]/[filename]
- filepath VARCHAR NOT NULL,
- -- original filename e.g. DSC1234.NEF
- filename VARCHAR NOT NULL,
- -- file size in bytes
- size INTEGER NOT NULL,
- -- xmp, metadata
- last_modified TIMESTAMPTZ DEFAULT NULL,
- -- reference got deleted from 3rd party
- is_missing BOOLEAN DEFAULT TRUE,
- PRIMARY KEY (uuid),
- FOREIGN KEY(media) REFERENCES media (uuid)
-);
diff --git a/crates/database/migrations/0001_initial.sql b/crates/database/migrations/0001_initial.sql
index 21177f5..e489500 100644
--- a/crates/database/migrations/0001_initial.sql
+++ b/crates/database/migrations/0001_initial.sql
@@ -5,31 +5,36 @@ CREATE TABLE IF NOT EXISTS users (
password VARCHAR,
lastname VARCHAR,
firstname VARCHAR,
+ displayname VARCHAR,
--indicates if user is not able to login
is_locked BOOLEAN DEFAULT FALSE,
- create_at TIMESTAMPTZ DEFAULT NULL,
+ created_at TIMESTAMPTZ DEFAULT NULL,
updated_at TIMESTAMPTZ DEFAULT NULL,
- last_login TIMESTAMPTZ DEFAULT NULL,
+ last_login_at TIMESTAMPTZ DEFAULT NULL,
PRIMARY KEY (uuid)
);
CREATE TABLE IF NOT EXISTS media (
uuid VARCHAR NOT NULL,
- name VARCHAR,
+ -- reference to `users`
owner VARCHAR NOT NULL,
+ name VARCHAR,
-- show/hide by default regarding user settings
is_sensitive BOOLEAN DEFAULT FALSE,
-- added to photos.network
- date_added TIMESTAMPTZ DEFAULT NULL,
+ added_at TIMESTAMPTZ DEFAULT NULL,
-- captured timestamp
- date_taken TIMESTAMPTZ DEFAULT NULL,
+ taken_at TIMESTAMPTZ DEFAULT NULL,
PRIMARY KEY (uuid),
FOREIGN KEY(owner) REFERENCES users (uuid)
);
CREATE TABLE IF NOT EXISTS reference (
uuid VARCHAR NOT NULL,
+ -- reference to `media`
media VARCHAR NOT NULL,
+ -- reference to `users`
+ owner VARCHAR NOT NULL,
-- ./data/files/[media.owner.uuid]/[media.date_taken.year]/[filename]
filepath VARCHAR NOT NULL,
-- original filename e.g. DSC1234.NEF
@@ -39,8 +44,87 @@ CREATE TABLE IF NOT EXISTS reference (
description VARCHAR DEFAULT NULL,
-- xmp, metadata
last_modified TIMESTAMPTZ DEFAULT NULL,
- -- reference got deleted from 3rd party
- is_missing BOOLEAN DEFAULT TRUE,
+ PRIMARY KEY (uuid),
+ FOREIGN KEY(media) REFERENCES media (uuid)
+ FOREIGN KEY(owner) REFERENCES users (uuid)
+);
+CREATE TABLE IF NOT EXISTS details (
+ uuid VARCHAR NOT NULL,
+ -- reference to `reference`
+ reference VARCHAR DEFAULT NULL,
+ -- NIKON
+ camera_manufacturer VARCHAR DEFAULT NULL,
+ -- Z7
+ camera_model VARCHAR DEFAULT NULL,
+ -- 6014533
+ camera_serial VARCHAR DEFAULT NULL,
+ -- NIKKOR Z 35mm f/1.8 S
+ lens_model VARCHAR DEFAULT NULL,
+ -- 20028476
+ lens_serial VARCHAR DEFAULT NULL,
+ -- https://jdhao.github.io/2019/07/31/image_rotation_exif_info/
+ orientation VARCHAR DEFAULT NULL,
+ -- JPEG compression
+ compression VARCHAR DEFAULT NULL,
+ -- 72.0
+ resolution_x FLOAT DEFAULT NULL,
+ -- 72.0
+ resolution_y FLOAT DEFAULT NULL,
+ -- Inch
+ resolution_unit VARCHAR DEFAULT NULL,
+ -- 1/400 s
+ exposure_time FLOAT DEFAULT NULL,
+ -- Auto exposure
+ exposure_mode VARCHAR DEFAULT NULL,
+ -- Aperture priority
+ exposure_program VARCHAR DEFAULT NULL,
+ -- 0 EV
+ exposure_bias VARCHAR DEFAULT NULL,
+ -- 1.8
+ aperture FLOAT DEFAULT NULL,
+ focal_length VARCHAR DEFAULT NULL,
+ iso INTEGER NOT NULL,
+ -- sRGB
+ color_space VARCHAR DEFAULT NULL,
+ -- 8.256 pixel
+ pixel_x INTEGER NOT NULL,
+ -- 5.504 pixel
+ pixel_y INTEGER NOT NULL,
+ -- copyright info
+ user_comment VARCHAR DEFAULT NULL,
+ -- Auto white balance
+ white_balance VARCHAR DEFAULT NULL,
+ -- 0 = no flash
+ flash BOOL DEFAULT NULL,
+ -- Exif version 2.1
+ exif_version FLOAT DEFAULT NULL,
+ FOREIGN KEY(reference) REFERENCES reference (uuid)
+);
+
+CREATE TABLE IF NOT EXISTS tags (
+ uuid VARCHAR NOT NULL,
+ -- language unaware string like "landscape"
+ tag VARCHAR NOT NULL,
+ -- reference to `media`
+ media VARCHAR NOT NULL,
+ -- plugin name or `USER` where the tag comes from
+ origin VARCHAR DEFAULT NULL,
+ PRIMARY KEY (uuid),
+ FOREIGN KEY(media) REFERENCES media (uuid)
+);
+
+CREATE TABLE IF NOT EXISTS locations (
+ uuid VARCHAR NOT NULL,
+ -- reference to `media`
+ media VARCHAR NOT NULL,
+ -- float gives a precision of ~1,7m
+ -- see https://stackoverflow.com/questions/159255/what-is-the-ideal-data-type-to-use-when-storing-latitude-longitude-in-a-mysql
+ -- 48.13750
+ latitude FLOAT NOT NULL,
+ -- 11.57586
+ longitude FLOAT NOT NULL,
+ -- 520 m
+ altitude FLOAT DEFAULT NULL,
PRIMARY KEY (uuid),
FOREIGN KEY(media) REFERENCES media (uuid)
);
diff --git a/crates/database/src/postgres.rs b/crates/database/src/postgres.rs
index 43f0055..befd2d8 100644
--- a/crates/database/src/postgres.rs
+++ b/crates/database/src/postgres.rs
@@ -19,6 +19,8 @@
//!
use async_trait::async_trait;
use common::auth::user::User;
+use common::database::media_item::MediaItem;
+use common::database::reference::Reference;
use common::database::Database;
use sqlx::types::time::OffsetDateTime;
use sqlx::PgPool;
@@ -34,7 +36,7 @@ pub struct PostgresDatabase {
impl PostgresDatabase {
pub async fn new(db_url: &str) -> Self {
- let pool = sqlx::postgres::PgPool::connect(db_url).await.unwrap();
+ let pool = PgPool::connect(db_url).await.unwrap();
PostgresDatabase { pool }
}
@@ -49,6 +51,31 @@ impl Database for PostgresDatabase {
Ok(())
}
+ async fn get_users(&self) -> Result, Box> {
+ let query = "SELECT uuid, email, password, lastname, firstname FROM users";
+
+ let res = sqlx::query(query);
+
+ let rows = res.fetch_all(&self.pool).await?;
+
+ let users = rows
+ .iter()
+ .map(|row| User {
+ uuid: row.get("uuid"),
+ email: row.get("email"),
+ password: row.get("password"),
+ lastname: row.get("lastname"),
+ firstname: row.get("firstname"),
+ is_locked: false,
+ created_at: OffsetDateTime::now_utc(),
+ updated_at: None,
+ last_login: None,
+ })
+ .collect();
+
+ Ok(users)
+ }
+
async fn create_user(&self, user: &User) -> Result<(), Box> {
let query = "INSERT INTO users (uuid, email, password, lastname, firstname) VALUES ($1, $2, $3, $4, $5)";
let id = Uuid::new_v4().hyphenated().to_string();
@@ -65,6 +92,10 @@ impl Database for PostgresDatabase {
Ok(())
}
+ async fn get_user(&self, _user_id: &str) -> Result> {
+ Err("Not implemented".into())
+ }
+
async fn update_email(&self, email: &str, user_id: &str) -> Result<(), Box> {
let query = "UPDATE users SET email = $1 WHERE uuid = $2";
@@ -77,28 +108,84 @@ impl Database for PostgresDatabase {
Ok(())
}
- async fn get_users(&self) -> Result, Box> {
- let query = "SELECT uuid, email, password, lastname, firstname FROM users";
+ async fn update_nickname(&self, _nickname: &str) -> Result<(), Box> {
+ Err("Not implemented".into())
+ }
- let res = sqlx::query(query);
+ async fn update_names(
+ &self,
+ _firstname: &str,
+ _lastname: &str,
+ _user_id: &str,
+ ) -> Result<(), Box> {
+ Err("Not implemented".into())
+ }
+
+ async fn disable_user(&self, _user_id: &str) -> Result<(), Box> {
+ Err("Not implemented".into())
+ }
+ async fn enable_user(&self, _user_id: &str) -> Result<(), Box> {
+ Err("Not implemented".into())
+ }
+
+ async fn get_media_items(&self, _user_id: &str) -> Result, Box> {
+ Err("Not implemented".into())
+ }
+ /// Creates a new media item if it doesn't exist and returns the media_id
+ async fn create_media_item(
+ &self,
+ user_id: &str,
+ name: &str,
+ date_taken: OffsetDateTime,
+ ) -> Result> {
+ let query = "SELECT COUNT(*) FROM media WHERE owner is $1 and taken_at like $2";
+ let res = sqlx::query(query).bind(user_id).bind(date_taken);
let rows = res.fetch_all(&self.pool).await?;
- let users = rows
- .iter()
- .map(|row| User {
- uuid: row.get("uuid"),
- email: row.get("email"),
- password: row.get("password"),
- lastname: row.get("lastname"),
- firstname: row.get("firstname"),
- is_locked: false,
- created_at: OffsetDateTime::now_utc(),
- updated_at: None,
- last_login: None,
- })
- .collect();
+ if rows.len() > 1 {
+ // TODO: return media item id for existing item
+ // rows.first()
+ } else {
+ // TODO: create a new media item and return id for new item
+ let query = "INSERT INTO media (uuid, name) VALUES ($1, $2)";
+ let id = Uuid::new_v4().hyphenated().to_string();
+ info!("create new media item with id `{}`.", id);
+
+ sqlx::query(query)
+ .bind(id)
+ .bind(name)
+ .execute(&self.pool)
+ .await?;
+ }
+
+ Ok("NOT IMPLEMENTED".to_string())
+ }
+ async fn get_media_item(&self, _media_id: &str) -> Result> {
+ Err("Not implemented".into())
+ }
+ async fn add_reference(
+ &self,
+ _user_id: &str,
+ _media_id: &str,
+ _reference: &Reference,
+ ) -> Result> {
+ Err("Not implemented".into())
+ }
- Ok(users)
+ async fn update_reference(
+ &self,
+ _reference_id: &str,
+ _reference: &Reference,
+ ) -> Result<(), Box> {
+ Err("Not implemented".into())
+ }
+
+ async fn remove_reference(
+ &self,
+ _media_id: &str,
+ _reference_id: &str,
+ ) -> Result<(), Box> {
+ Err("Not implemented".into())
}
}
diff --git a/crates/database/src/sqlite.rs b/crates/database/src/sqlite.rs
index fbce878..be91beb 100644
--- a/crates/database/src/sqlite.rs
+++ b/crates/database/src/sqlite.rs
@@ -1,10 +1,32 @@
+/* Photos.network · A privacy first photo storage and sharing service for fediverse.
+ * Copyright (C) 2020 Photos network developers
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+//! This crate offers a database abstraction for [Photos.network](https://photos.network) core application.
+//!
use async_trait::async_trait;
use common::auth::user::User;
+use common::database::media_item::MediaItem;
+use common::database::reference::Reference;
use common::database::Database;
use sqlx::types::time::OffsetDateTime;
use sqlx::Row;
use sqlx::SqlitePool;
use std::error::Error;
+use sqlx::sqlite::SqliteQueryResult;
use tracing::info;
use uuid::Uuid;
@@ -24,11 +46,37 @@ impl SqliteDatabase {
#[async_trait]
impl Database for SqliteDatabase {
async fn setup(&mut self) -> Result<(), Box> {
+ // run migrations from `migrations` directory
sqlx::migrate!("./migrations").run(&self.pool).await?;
Ok(())
}
+ async fn get_users(&self) -> Result, Box> {
+ let query = "SELECT uuid, email, password, lastname, firstname FROM users";
+
+ let res = sqlx::query(query);
+
+ let rows = res.fetch_all(&self.pool).await?;
+
+ let users = rows
+ .iter()
+ .map(|row| User {
+ uuid: row.get("uuid"),
+ email: row.get("email"),
+ password: row.get("password"),
+ lastname: row.get("lastname"),
+ firstname: row.get("firstname"),
+ is_locked: false,
+ created_at: OffsetDateTime::now_utc(),
+ updated_at: None,
+ last_login: None,
+ })
+ .collect();
+
+ Ok(users)
+ }
+
async fn create_user(&self, user: &User) -> Result<(), Box> {
let query = "INSERT INTO users (uuid, email, password, lastname, firstname) VALUES ($1, $2, $3, $4, $5)";
let id = Uuid::new_v4().hyphenated().to_string();
@@ -45,6 +93,10 @@ impl Database for SqliteDatabase {
Ok(())
}
+ async fn get_user(&self, _user_id: &str) -> Result> {
+ Err("Not implemented".into())
+ }
+
async fn update_email(&self, email: &str, user_id: &str) -> Result<(), Box> {
let query = "UPDATE users SET email = $1 WHERE uuid = $2";
@@ -57,35 +109,98 @@ impl Database for SqliteDatabase {
Ok(())
}
- async fn get_users(&self) -> Result, Box> {
- let query = "SELECT uuid, email, password, lastname, firstname FROM users";
+ async fn update_nickname(&self, _nickname: &str) -> Result<(), Box> {
+ Err("Not implemented".into())
+ }
- let res = sqlx::query(query);
+ async fn update_names(
+ &self,
+ _firstname: &str,
+ _lastname: &str,
+ _user_id: &str,
+ ) -> Result<(), Box> {
+ Err("Not implemented".into())
+ }
- let rows = res.fetch_all(&self.pool).await?;
+ async fn disable_user(&self, _user_id: &str) -> Result<(), Box> {
+ Err("Not implemented".into())
+ }
+ async fn enable_user(&self, _user_id: &str) -> Result<(), Box> {
+ Err("Not implemented".into())
+ }
- let users = rows
- .iter()
- .map(|row| User {
- uuid: row.get("uuid"),
- email: row.get("email"),
- password: row.get("password"),
- lastname: row.get("lastname"),
- firstname: row.get("firstname"),
- is_locked: false,
- created_at: OffsetDateTime::now_utc(),
- updated_at: None,
- last_login: None,
- })
- .collect();
+ async fn get_media_items(&self, _user_id: &str) -> Result, Box> {
+ Err("Not implemented".into())
+ }
+ async fn create_media_item(
+ &self,
+ user_id: &str,
+ name: &str,
+ date_taken: OffsetDateTime,
+ ) -> Result> {
+ // TODO: check if item with same `name` and `date_taken` already exists
+ let query = "INSERT INTO media (uuid, owner, name, is_sensitive, added_at, taken_at) VALUES ($1, $2, $3, $4, $5, $6)";
+ let id = Uuid::new_v4().hyphenated().to_string();
+ sqlx::query(query)
+ .bind(id.clone())
+ .bind(&user_id)
+ .bind(&name)
+ .bind(false)
+ .bind(OffsetDateTime::now_utc())
+ .bind(date_taken)
+ .execute(&self.pool)
+ .await?;
- Ok(users)
+ Ok(id)
+ }
+ async fn get_media_item(&self, _media_id: &str) -> Result> {
+ Err("Not implemented".into())
+ }
+ async fn add_reference(
+ &self,
+ user_id: &str,
+ media_id: &str,
+ reference: &Reference,
+ ) -> Result> {
+ let query = "INSERT INTO reference (uuid, media, owner, filepath, filename, size) VALUES ($1, $2, $3, $4, $5, $6)";
+ let id = Uuid::new_v4().hyphenated().to_string();
+ let res: SqliteQueryResult = sqlx::query(query)
+ .bind(id.clone())
+ .bind(&media_id)
+ .bind(&user_id)
+ .bind(&reference.filepath)
+ .bind(&reference.filename)
+ .bind(&reference.size)
+ .execute(&self.pool)
+ .await?;
+
+ Ok(id)
+ }
+
+ async fn update_reference(
+ &self,
+ _reference_id: &str,
+ _reference: &Reference,
+ ) -> Result<(), Box> {
+ Err("Not implemented".into())
+ }
+
+ async fn remove_reference(
+ &self,
+ _media_id: &str,
+ _reference_id: &str,
+ ) -> Result<(), Box> {
+ Err("Not implemented".into())
}
}
#[allow(unused_imports)]
mod tests {
+ use std::path::PathBuf;
+ use std::time::Instant;
+ use testdir::testdir;
use super::*;
+ use time::format_description::well_known::Rfc3339;
#[sqlx::test]
async fn create_user_should_succeed(pool: SqlitePool) -> sqlx::Result<()> {
@@ -98,7 +213,7 @@ mod tests {
// when
for i in 0..3 {
let user = User {
- uuid: uuid::Uuid::new_v4().hyphenated().to_string(),
+ uuid: Uuid::new_v4().hyphenated().to_string(),
email: format!("test_{}@photos.network", i),
password: Some("unsecure".into()),
lastname: Some("Stuermer".into()),
@@ -263,4 +378,87 @@ mod tests {
Ok(())
}
+
+ #[sqlx::test]
+ async fn create_media_item_should_succeed(pool: SqlitePool) -> sqlx::Result<()> {
+ // given
+ let user_id = "570DC079-664A-4496-BAA3-668C445A447";
+ // create fake user - used as FOREIGN KEY in media
+ sqlx::query("INSERT INTO users (uuid, email, password, lastname, firstname) VALUES ($1, $2, $3, $4, $5)")
+ .bind(user_id.clone())
+ .bind("info@photos.network")
+ .bind("unsecure")
+ .bind("Stuermer")
+ .bind("Benjamin")
+ .execute(&pool).await?;
+ let db = SqliteDatabase::new(
+ "target/sqlx/test-dbs/database/sqlite/tests/create_media_item_should_succeed.sqlite",
+ ).await;
+
+ let name = "DSC_1234";
+ let date_taken = OffsetDateTime::now_utc();
+
+ // when
+ let media_item_result = db.create_media_item(user_id.clone(), name, date_taken).await;
+
+ // then
+ assert!(media_item_result.is_ok());
+
+ Ok(())
+ }
+
+ #[sqlx::test]
+ async fn add_reference_should_succeed(pool: SqlitePool) -> sqlx::Result<()> {
+ // given
+ let user_id = "570DC079-664A-4496-BAA3-668C445A447";
+ let media_id = "ef9ac799-02f3-4b3f-9d96-7576be0434e6";
+ let reference_id = "ef9ac799-02f3-4b3f-9d96-7576be0434e6";
+ let added_at = OffsetDateTime::parse("2023-02-03T13:37:01.234567Z", &Rfc3339).unwrap();
+ let taken_at = OffsetDateTime::parse("2023-01-01T13:37:01.234567Z", &Rfc3339).unwrap();
+ // create fake user - used as FOREIGN KEY in reference
+ sqlx::query("INSERT INTO users (uuid, email, password, lastname, firstname) VALUES ($1, $2, $3, $4, $5)")
+ .bind(user_id.clone())
+ .bind("info@photos.network")
+ .bind("unsecure")
+ .bind("Stuermer")
+ .bind("Benjamin")
+ .execute(&pool).await?;
+ // create fake media item - used as FOREIGN KEY in reference
+ sqlx::query("INSERT INTO media (uuid, owner, name, is_sensitive, added_at, taken_at) VALUES ($1, $2, $3, $4, $5, $6)")
+ .bind(media_id.clone())
+ .bind(user_id.clone())
+ .bind("DSC_1234")
+ .bind(false)
+ .bind(added_at)
+ .bind(taken_at)
+ .execute(&pool).await?;
+ let db = SqliteDatabase::new(
+ "target/sqlx/test-dbs/database/sqlite/tests/add_reference_should_succeed.sqlite",
+ ).await;
+
+ let filename = "DSC_1234.jpg";
+ let dir: PathBuf = testdir!();
+ let path = dir.join(filename);
+ let filepath = path.clone().to_str().unwrap().to_string();
+ std::fs::write(&path, "fake image data").ok();
+ let metadata = std::fs::metadata(path.clone()).unwrap();
+
+ let reference = Reference {
+ uuid: reference_id,
+ filepath,
+ filename: filename.to_string(),
+ size: metadata.len(),
+ description: "",
+ last_modified: OffsetDateTime::parse("2023-02-03T13:37:01.234567Z", &Rfc3339).unwrap(),
+ is_missing: false,
+ };
+
+ // when
+ let add_reference_result = db.add_reference(user_id.clone(), media_id.clone(), &reference).await;
+
+ // then
+ assert!(add_reference_result.is_ok());
+
+ Ok(())
+ }
}
diff --git a/crates/media/src/api/router.rs b/crates/media/src/api/router.rs
index d4cf2ed..5b3cdf0 100644
--- a/crates/media/src/api/router.rs
+++ b/crates/media/src/api/router.rs
@@ -41,7 +41,7 @@ impl MediaApi {
where
S: Send + Sync + Clone,
{
- let media_repository: MediaRepository =
+ let media_repository: MediaRepository =
MediaRepository::new(state.database.clone(), state.config.clone()).await;
let repository_state: MediaRepositoryState = Arc::new(media_repository);
@@ -169,6 +169,45 @@ mod tests {
assert_eq!(body, "list media items. limit=1000, offset=0");
}
+
+ #[sqlx::test]
+ async fn post_media_without_user_fail(pool: SqlitePool) {
+ // given
+ let state: ApplicationState = ApplicationState {
+ config: Configuration::empty(),
+ plugins: HashMap::new(),
+ router: None,
+ database: SqliteDatabase { pool },
+ };
+ let app = Router::new().nest("/", MediaApi::routes(state).await);
+
+ // when
+ let response = app
+ .oneshot(
+ Request::builder()
+ .uri("/media")
+ .method("POST")
+ .header(http::header::CONTENT_TYPE, mime::APPLICATION_JSON.as_ref())
+ .header(
+ "Content-Disposition",
+ "attachment; filename=\"DSC_1234.NEF\"",
+ )
+ //.body(Body::from(bytes))
+ //.body(Body::empty())
+ // TODO: add multipart file to body
+ .body(Body::from(
+ serde_json::to_vec(&json!([1, 2, 3, 4])).unwrap(),
+ ))
+ .unwrap(),
+ )
+ .await
+ .unwrap();
+
+ // then
+ assert_eq!(response.status(), StatusCode::NOT_IMPLEMENTED);
+ }
+
+
// TODO: test is failing due to missing multi-part body
//#[sqlx::test]
#[allow(dead_code)]
diff --git a/crates/media/src/api/routes/delete_media_id.rs b/crates/media/src/api/routes/delete_media_id.rs
index 34bcac2..f3a2654 100644
--- a/crates/media/src/api/routes/delete_media_id.rs
+++ b/crates/media/src/api/routes/delete_media_id.rs
@@ -1,3 +1,20 @@
+/* Photos.network · A privacy first photo storage and sharing service for fediverse.
+ * Copyright (C) 2020 Photos network developers
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
//! Deletes the given item owned by the user
//!
use axum::http::StatusCode;
diff --git a/crates/media/src/api/routes/get_albums.rs b/crates/media/src/api/routes/get_albums.rs
index 9e061a6..a415e99 100644
--- a/crates/media/src/api/routes/get_albums.rs
+++ b/crates/media/src/api/routes/get_albums.rs
@@ -1,3 +1,20 @@
+/* Photos.network · A privacy first photo storage and sharing service for fediverse.
+ * Copyright (C) 2020 Photos network developers
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
//! Returns the binary of a given entity
//!
diff --git a/crates/media/src/api/routes/get_albums_id.rs b/crates/media/src/api/routes/get_albums_id.rs
index 8b49213..efdda53 100644
--- a/crates/media/src/api/routes/get_albums_id.rs
+++ b/crates/media/src/api/routes/get_albums_id.rs
@@ -1,3 +1,20 @@
+/* Photos.network · A privacy first photo storage and sharing service for fediverse.
+ * Copyright (C) 2020 Photos network developers
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
use axum::http::StatusCode;
pub(crate) async fn get_albums_id() -> std::result::Result {
diff --git a/crates/media/src/api/routes/get_media.rs b/crates/media/src/api/routes/get_media.rs
index e18f42e..e225e78 100644
--- a/crates/media/src/api/routes/get_media.rs
+++ b/crates/media/src/api/routes/get_media.rs
@@ -1,3 +1,20 @@
+/* Photos.network · A privacy first photo storage and sharing service for fediverse.
+ * Copyright (C) 2020 Photos network developers
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
//! Returns a list of owned media items for current user
//!
use axum::extract::State;
@@ -24,7 +41,7 @@ pub(crate) async fn get_media(
Query(query): Query,
) -> Result, StatusCode> {
let items: Result, DataAccessError> =
- repo.get_media_items_for_user(Uuid::parse_str(user.uuid.as_str()).unwrap());
+ repo.get_media_items_for_user(Uuid::parse_str(user.uuid.as_str()).unwrap()).await;
match items {
Ok(i) => {
error!("Found {} items for user.", i.len());
diff --git a/crates/media/src/api/routes/get_media_id.rs b/crates/media/src/api/routes/get_media_id.rs
index 2b659a8..4b618b9 100644
--- a/crates/media/src/api/routes/get_media_id.rs
+++ b/crates/media/src/api/routes/get_media_id.rs
@@ -1,3 +1,20 @@
+/* Photos.network · A privacy first photo storage and sharing service for fediverse.
+ * Copyright (C) 2020 Photos network developers
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
//! Returns a specific owned or shared media item for current user
//!
diff --git a/crates/media/src/api/routes/patch_albums_id.rs b/crates/media/src/api/routes/patch_albums_id.rs
index d8fee22..796d652 100644
--- a/crates/media/src/api/routes/patch_albums_id.rs
+++ b/crates/media/src/api/routes/patch_albums_id.rs
@@ -1,3 +1,20 @@
+/* Photos.network · A privacy first photo storage and sharing service for fediverse.
+ * Copyright (C) 2020 Photos network developers
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
use axum::http::StatusCode;
pub(crate) async fn patch_albums_id() -> std::result::Result {
diff --git a/crates/media/src/api/routes/patch_albums_id_share.rs b/crates/media/src/api/routes/patch_albums_id_share.rs
index f0924af..549c4f2 100644
--- a/crates/media/src/api/routes/patch_albums_id_share.rs
+++ b/crates/media/src/api/routes/patch_albums_id_share.rs
@@ -1,3 +1,20 @@
+/* Photos.network · A privacy first photo storage and sharing service for fediverse.
+ * Copyright (C) 2020 Photos network developers
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
use axum::http::StatusCode;
pub(crate) async fn patch_albums_id_share() -> std::result::Result {
diff --git a/crates/media/src/api/routes/patch_albums_id_unshare.rs b/crates/media/src/api/routes/patch_albums_id_unshare.rs
index 012483e..6ab4fe2 100644
--- a/crates/media/src/api/routes/patch_albums_id_unshare.rs
+++ b/crates/media/src/api/routes/patch_albums_id_unshare.rs
@@ -1,3 +1,20 @@
+/* Photos.network · A privacy first photo storage and sharing service for fediverse.
+ * Copyright (C) 2020 Photos network developers
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
use axum::http::StatusCode;
pub(crate) async fn patch_albums_id_unshare() -> std::result::Result {
diff --git a/crates/media/src/api/routes/patch_media_id.rs b/crates/media/src/api/routes/patch_media_id.rs
index 6c84bfc..da24821 100644
--- a/crates/media/src/api/routes/patch_media_id.rs
+++ b/crates/media/src/api/routes/patch_media_id.rs
@@ -1,3 +1,20 @@
+/* Photos.network · A privacy first photo storage and sharing service for fediverse.
+ * Copyright (C) 2020 Photos network developers
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
//! Updates fields from a specific media item for current user
//!
diff --git a/crates/media/src/api/routes/photo_details.rs b/crates/media/src/api/routes/photo_details.rs
index aef6227..20deac1 100644
--- a/crates/media/src/api/routes/photo_details.rs
+++ b/crates/media/src/api/routes/photo_details.rs
@@ -1,7 +1,22 @@
-//! Returns the details of a given media item
-//!
-pub(crate) async fn photo_details(
+/* Photos.network · A privacy first photo storage and sharing service for fediverse.
+ * Copyright (C) 2020 Photos network developers
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
-) -> std::result::Result {
+//! Returns the details of a given media item
+//!
+pub(crate) async fn photo_details() -> std::result::Result {
Err(StatusCode::NOT_IMPLEMENTED)
}
diff --git a/crates/media/src/api/routes/post_albums.rs b/crates/media/src/api/routes/post_albums.rs
index 9b7cad6..93bd775 100644
--- a/crates/media/src/api/routes/post_albums.rs
+++ b/crates/media/src/api/routes/post_albums.rs
@@ -1,3 +1,20 @@
+/* Photos.network · A privacy first photo storage and sharing service for fediverse.
+ * Copyright (C) 2020 Photos network developers
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
//! Returns the binary of a given entity
//!
diff --git a/crates/media/src/api/routes/post_media.rs b/crates/media/src/api/routes/post_media.rs
index e1adc30..4eff1cf 100644
--- a/crates/media/src/api/routes/post_media.rs
+++ b/crates/media/src/api/routes/post_media.rs
@@ -1,11 +1,30 @@
+/* Photos.network · A privacy first photo storage and sharing service for fediverse.
+ * Copyright (C) 2020 Photos network developers
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
//! Creates a new media item to aggregate related files for current user
//!
use axum::{
extract::{Multipart, State},
http::StatusCode,
};
+use time::format_description::well_known::Rfc3339;
+use time::OffsetDateTime;
use common::auth::user::User;
-use tracing::{debug, error};
+use tracing::debug;
use uuid::Uuid;
use crate::{data::error::DataAccessError, repository::MediaRepositoryState};
@@ -14,21 +33,15 @@ pub(crate) async fn post_media(
State(repo): State,
user: User,
mut multipart: Multipart,
-) -> std::result::Result {
+) -> Result {
let mut name = None;
let mut date_taken = None;
while let Some(field) = multipart.next_field().await.unwrap() {
if let Some(field_name) = field.name() {
match field_name {
- "name" => {
- name = Some(field.text().await.unwrap());
- // debug!("name={}", field.text().await.unwrap());
- }
- "date_taken" => {
- date_taken = Some(field.text().await.unwrap());
- // debug!("date_taken={}", field.text().await.unwrap());
- }
+ "name" => name = Some(field.text().await.unwrap()),
+ "date_taken" => date_taken = Some(field.text().await.unwrap()),
_ => continue,
}
}
@@ -38,18 +51,23 @@ pub(crate) async fn post_media(
return Err(StatusCode::BAD_REQUEST);
}
+ let date = OffsetDateTime::parse(date_taken.unwrap().as_str(), &Rfc3339);
+ if date.is_err() {
+ return Err(StatusCode::CREATED);
+ }
+
let result = repo.create_media_item_for_user(
Uuid::parse_str(user.uuid.as_str()).unwrap(),
name.clone().unwrap(),
- date_taken.clone().unwrap(),
- );
+ date.unwrap(),
+ ).await;
match result {
Ok(uuid) => {
debug!(
"name={}, taken={} => id={}",
name.unwrap(),
- date_taken.unwrap(),
+ date.unwrap(),
uuid.clone().hyphenated().to_string()
);
@@ -68,3 +86,132 @@ pub(crate) async fn post_media(
}
}
}
+
+#[cfg(test)]
+mod tests {
+ use std::collections::HashMap;
+ use std::io;
+
+ use axum::Router;
+ use common::{config::configuration::Configuration, ApplicationState};
+ use database::sqlite::SqliteDatabase;
+ use hyper::{Body, Request};
+ use mime::BOUNDARY;
+ use sqlx::SqlitePool;
+ use tokio::fs::File;
+ use tower::ServiceExt;
+
+ use crate::api::router::MediaApi;
+ use std::io::Write;
+ use std::path::PathBuf;
+ use axum::http::header::CONTENT_TYPE;
+ use hyper::header::CONNECTION;
+ use testdir::testdir;
+ use tokio::io::AsyncReadExt;
+
+ use super::*;
+
+ #[sqlx::test]
+ async fn post_media_unauthorized_should_fail(pool: SqlitePool) {
+ // given
+ let state: ApplicationState = ApplicationState {
+ config: Configuration::empty(),
+ plugins: HashMap::new(),
+ router: None,
+ database: SqliteDatabase { pool },
+ };
+
+ let app = Router::new().nest("/", MediaApi::routes(state).await);
+
+ // when
+ let response = app
+ .oneshot(
+ Request::builder()
+ .method("POST")
+ .uri("/media")
+ .body(Body::empty())
+ .unwrap(),
+ )
+ .await
+ .unwrap();
+
+ // then
+ assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
+ }
+
+ #[sqlx::test]
+ async fn post_media_authorized_without_name_field(pool: SqlitePool) {
+ // given
+ let state: ApplicationState = ApplicationState {
+ config: Configuration::empty(),
+ plugins: HashMap::new(),
+ router: None,
+ database: SqliteDatabase { pool },
+ };
+ let app = Router::new().nest("/", MediaApi::routes(state).await);
+ let data = media_item_form_data().await.unwrap();
+
+ // when
+ let response = app
+ .oneshot(
+ Request::builder()
+ .method("POST")
+ .uri("/media")
+ .header("Authorization", "FakeAuth")
+ .header(CONNECTION, "Keep-Alive")
+ .header(
+ CONTENT_TYPE,
+ format!("multipart/form-data; boundary={}", BOUNDARY)
+ )
+ // .header(CONTENT_TYPE, &*format!("multipart/form-data; boundary={}", BOUNDARY))
+ .body(data.into())
+ .unwrap(),
+ )
+ .await
+ .unwrap();
+
+ // then
+ assert_eq!(response.status(), StatusCode::OK);
+ }
+
+ async fn media_item_form_data() -> io::Result> {
+ let mut data: Vec = Vec::new();
+
+ write!(data, "--{}\r\n", BOUNDARY)?;
+ write!(data, "Content-Disposition: form-data; name=\"name\";\r\n")?;
+ write!(data, "\r\n")?;
+ write!(data, "DSC_1234")?;
+ write!(data, "\r\n")?;
+
+ write!(data, "--{}\r\n", BOUNDARY)?;
+ write!(data, "Content-Disposition: form-data; name=\"date_taken\";\r\n")?;
+ write!(data, "\r\n")?;
+ write!(data, "1985-04-12T23:20:50.52Z")?;
+ write!(data, "\r\n")?;
+
+ write!(data, "--{}--\r\n", BOUNDARY)?;
+
+ Ok(data)
+ }
+
+ #[allow(dead_code)]
+ async fn image_data() -> io::Result> {
+ let dir: PathBuf = testdir!();
+ let path = dir.join("11.jpg");
+ std::fs::write(&path, "fake image data").ok();
+
+ let mut data: Vec = Vec::new();
+ write!(data, "--{}\r\n", BOUNDARY)?;
+ write!(data, "Content-Disposition: form-data; name=\"DSC_1234\"; filename=\"11.jpg\"\r\n")?;
+ write!(data, "Content-Type: image/jpeg\r\n")?;
+ write!(data, "\r\n")?;
+
+ let mut f = File::open(path).await?;
+ f.read_to_end(&mut data).await?;
+
+ write!(data, "\r\n")?; // The key thing you are missing
+ write!(data, "--{}--\r\n", BOUNDARY)?;
+
+ Ok(data)
+ }
+}
diff --git a/crates/media/src/api/routes/post_media_id.rs b/crates/media/src/api/routes/post_media_id.rs
index fa66d6b..13f5d3c 100644
--- a/crates/media/src/api/routes/post_media_id.rs
+++ b/crates/media/src/api/routes/post_media_id.rs
@@ -1,3 +1,20 @@
+/* Photos.network · A privacy first photo storage and sharing service for fediverse.
+ * Copyright (C) 2020 Photos network developers
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
//! Add files for a specific media item
//!
diff --git a/crates/media/src/repository.rs b/crates/media/src/repository.rs
index cdeef35..f6040c8 100644
--- a/crates/media/src/repository.rs
+++ b/crates/media/src/repository.rs
@@ -17,18 +17,18 @@
use axum::async_trait;
use common::config::configuration::Configuration;
+use common::database::Database;
+use database::sqlite::SqliteDatabase;
use std::sync::Arc;
-use std::time::Instant;
use time::OffsetDateTime;
use tracing::info;
use uuid::Uuid;
-
use crate::data::error::DataAccessError;
use crate::data::media_item::MediaItem;
#[allow(dead_code)]
-pub struct MediaRepository {
- pub(crate) database: D,
+pub struct MediaRepository {
+ pub(crate) database: SqliteDatabase,
pub(crate) config: Configuration,
}
@@ -38,50 +38,67 @@ pub type MediaRepositoryState = Arc;
#[cfg_attr(test, mockall::automock)]
#[async_trait]
pub trait MediaRepositoryTrait {
- // Gets a list of media items from the DB filted by user_id
- fn get_media_items_for_user(&self, user_id: Uuid) -> Result, DataAccessError>;
+ // Gets a list of media items from the DB filtered by user_id
+ async fn get_media_items_for_user(&self, user_id: Uuid) -> Result, DataAccessError>;
/// Create a new media item for the given user
- fn create_media_item_for_user(
+ async fn create_media_item_for_user(
&self,
user_id: Uuid,
name: String,
- date_taken: String,
+ date_taken: OffsetDateTime,
) -> Result;
}
-impl MediaRepository {
- pub async fn new(database: D, config: Configuration) -> Self {
+impl MediaRepository {
+ pub async fn new(database: SqliteDatabase, config: Configuration) -> Self {
Self { database, config }
}
}
#[async_trait]
-impl MediaRepositoryTrait for MediaRepository {
- fn get_media_items_for_user(&self, user_id: Uuid) -> Result, DataAccessError> {
+impl MediaRepositoryTrait for MediaRepository {
+ async fn get_media_items_for_user(&self, user_id: Uuid) -> Result, DataAccessError> {
info!("get items for user {}", user_id);
- // TODO: read from database
- // TODO: read from filesystem
- Ok(vec![MediaItem {
- uuid: "",
- name: "",
- date_added: Instant::now(),
- date_taken: None,
- details: None,
- tags: None,
- location: None,
- references: None,
- }])
+ let items_result = &self.database.get_media_items(user_id.hyphenated().to_string().as_str()).await;
+ match items_result {
+ Ok(items) => return Ok(
+ items
+ .into_iter()
+ .map(|d| MediaItem {
+ // TODO: fill in missing info like references, details, tags
+ // TODO: check references on filesystem
+ uuid: d.uuid,
+ name: d.name,
+ date_added: d.added_at,
+ date_taken: d.taken_at,
+ details: None,
+ tags: None,
+ location: None,
+ references: None,
+ })
+ .collect()
+ ),
+ Err(_) => return Err(DataAccessError::OtherError),
+ }
}
/// inside impl
- fn create_media_item_for_user(
+ async fn create_media_item_for_user(
&self,
user_id: Uuid,
name: String,
- date_taken: String,
+ date_taken: OffsetDateTime,
) -> Result {
+
+ // TODO: map result to
+ let _ = &self.database.create_media_item(
+ user_id.hyphenated().to_string().as_str(),
+ name.as_str(),
+ date_taken
+ ).await;
+
// Err(DataAccessError::AlreadyExist)
Ok(Uuid::new_v4())
}
@@ -116,7 +133,7 @@ mod tests {
let repository = MediaRepository::new(db, Configuration::empty()).await;
// when
- let result = repository.get_media_items_for_user(Uuid::new_v4());
+ let result = repository.get_media_items_for_user(Uuid::new_v4()).await;
// then
assert_eq!(result.is_ok(), true);
diff --git a/crates/plugin_interface/src/lib.rs b/crates/plugin_interface/src/lib.rs
index 470f070..27bdf73 100644
--- a/crates/plugin_interface/src/lib.rs
+++ b/crates/plugin_interface/src/lib.rs
@@ -1,3 +1,20 @@
+/* Photos.network · A privacy first photo storage and sharing service for fediverse.
+ * Copyright (C) 2020 Photos network developers
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
//! This is the plugin interface definition for Photos.network
//!
//! The Photos.network core will look for available plugins during start up and
diff --git a/crates/plugin_interface/src/model/command.rs b/crates/plugin_interface/src/model/command.rs
index 086d74d..dcb4760 100644
--- a/crates/plugin_interface/src/model/command.rs
+++ b/crates/plugin_interface/src/model/command.rs
@@ -1,3 +1,20 @@
+/* Photos.network · A privacy first photo storage and sharing service for fediverse.
+ * Copyright (C) 2020 Photos network developers
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
#[repr(C)]
#[derive(Debug, Clone, PartialEq, StableAbi)]
pub struct PluginCommand {
diff --git a/crates/plugin_interface/src/model/response.rs b/crates/plugin_interface/src/model/response.rs
index 72e9d49..f78b232 100644
--- a/crates/plugin_interface/src/model/response.rs
+++ b/crates/plugin_interface/src/model/response.rs
@@ -1,3 +1,20 @@
+/* Photos.network · A privacy first photo storage and sharing service for fediverse.
+ * Copyright (C) 2020 Photos network developers
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
#[repr(C)]
#[derive(Debug, Clone, PartialEq, Eq, StableAbi)]
pub struct PluginResponse {
diff --git a/src/lib.rs b/src/lib.rs
index 4cd718e..3a9a7af 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -75,7 +75,7 @@ pub async fn start_server() -> Result<()> {
tracing::subscriber::set_global_default(
fmt::Subscriber::builder()
// subscriber configuration
- .with_max_level(tracing::Level::DEBUG)
+ .with_max_level(tracing::Level::TRACE)
.with_target(false)
.finish()
// add additional writers
@@ -105,9 +105,14 @@ pub async fn start_server() -> Result<()> {
.write(true)
.create_new(true)
.open("data/core.sqlite3");
- let db = SqliteDatabase::new("data/core.sqlite3").await;
- let _ = db.clone().setup().await;
- let users = db.clone().get_users().await;
+
+ let mut db = SqliteDatabase::new("data/core.sqlite3").await;
+
+ {
+ let _ = db.setup().await;
+ }
+
+ let users = db.get_users().await;
if users.unwrap().is_empty() {
info!("No user found, create a default admin user. Please check `data/credentials.txt` for details.");
let default_user = "photo@photos.network";
diff --git a/tests/api/authentication/signin.rs b/tests/api/authentication/signin.rs
index f02a11e..733866c 100644
--- a/tests/api/authentication/signin.rs
+++ b/tests/api/authentication/signin.rs
@@ -1,3 +1,20 @@
+/* Photos.network · A privacy first photo storage and sharing service for fediverse.
+ * Copyright (C) 2020 Photos network developers
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
//! Test authentication behaviour like invalid user or password
//!
@@ -9,7 +26,7 @@ mod tests {
fn it_works() {
let a = 3;
let b = 1 + 1;
-
+
assert_eq!(a, b, "we are testing addition with {} and {}", a, b);
}