From 221b43e4cf71985b918d112bfa71c204fd63d71e Mon Sep 17 00:00:00 2001 From: yggverse Date: Fri, 9 Jan 2026 18:27:27 +0200 Subject: [PATCH] implement image persistence db features, minor corrections --- crates/mysql/database/0.1.0.sql | 39 ++++++++++++++++- crates/mysql/src/lib.rs | 78 ++++++++++++++++++++++++++++++--- 2 files changed, 110 insertions(+), 7 deletions(-) diff --git a/crates/mysql/database/0.1.0.sql b/crates/mysql/database/0.1.0.sql index 5eb70ff..6c318f2 100644 --- a/crates/mysql/database/0.1.0.sql +++ b/crates/mysql/database/0.1.0.sql @@ -1,5 +1,5 @@ -- MySQL Script generated by MySQL Workbench --- Thu 08 Jan 2026 12:40:45 AM EET +-- пт, 09-січ-2026 17:57:03 +0200 -- Model: New Model Version: 1.0 -- MySQL Workbench Forward Engineering @@ -65,7 +65,7 @@ ENGINE = InnoDB; -- Table `rssto`.`content` -- ----------------------------------------------------- CREATE TABLE IF NOT EXISTS `rssto`.`content` ( - `content_id` BIGINT NOT NULL AUTO_INCREMENT, + `content_id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, `channel_item_id` INT NOT NULL, `provider_id` INT NULL, `title` VARCHAR(255) NOT NULL, @@ -87,6 +87,41 @@ CREATE TABLE IF NOT EXISTS `rssto`.`content` ( ENGINE = InnoDB; +-- ----------------------------------------------------- +-- Table `rssto`.`image` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `rssto`.`image` ( + `image_id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, + `source` VARCHAR(2048) NOT NULL, + `data` MEDIUMBLOB NOT NULL, + PRIMARY KEY (`image_id`), + UNIQUE INDEX `source_UNIQUE` (`source` ASC) VISIBLE) +ENGINE = InnoDB; + + +-- ----------------------------------------------------- +-- Table `rssto`.`content_image` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `rssto`.`content_image` ( + `content_image_id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, + `content_id` BIGINT UNSIGNED NOT NULL, + `image_id` BIGINT UNSIGNED NOT NULL, + PRIMARY KEY (`content_image_id`), + INDEX `fk_content_image_content_idx` (`content_id` ASC) VISIBLE, + INDEX `fk_content_image_image_idx` (`image_id` ASC) VISIBLE, + CONSTRAINT `fk_content_image_content` + FOREIGN KEY (`content_id`) + REFERENCES `rssto`.`content` (`content_id`) + ON DELETE NO ACTION + ON UPDATE NO ACTION, + CONSTRAINT `fk_content_image_image` + FOREIGN KEY (`image_id`) + REFERENCES `rssto`.`image` (`image_id`) + ON DELETE NO ACTION + ON UPDATE NO ACTION) +ENGINE = InnoDB; + + SET SQL_MODE=@OLD_SQL_MODE; SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS; SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS; diff --git a/crates/mysql/src/lib.rs b/crates/mysql/src/lib.rs index f3e168f..a0cbdb4 100644 --- a/crates/mysql/src/lib.rs +++ b/crates/mysql/src/lib.rs @@ -23,13 +23,12 @@ impl Mysql { } pub fn channels_by_url(&self, url: &str, limit: Option) -> Result, Error> { - self.pool.get_conn()?.exec_map( + self.pool.get_conn()?.exec( format!( "SELECT `channel_id`, `url` FROM `channel` WHERE `url` = ? LIMIT {}", limit.unwrap_or(DEFAULT_LIMIT) ), (url,), - |(channel_id, url)| Channel { channel_id, url }, ) } @@ -171,7 +170,8 @@ impl Mysql { `channel_item_id`, `provider_id`, `title`, - `description` FROM `content` WHERE `channel_item_id` = ? AND `provider_id` <=> ? LIMIT {}", + `description` FROM `content` + WHERE `channel_item_id` = ? AND `provider_id` <=> ? LIMIT {}", limit.unwrap_or(DEFAULT_LIMIT) ), (channel_item_id, provider_id), @@ -187,8 +187,58 @@ impl Mysql { ) -> Result { let mut c = self.pool.get_conn()?; c.exec_drop( - "INSERT INTO `content` SET `channel_item_id` = ?, `provider_id` = ?, `title` = ?, `description` = ?", - (channel_item_id, provider_id, title, description ), + "INSERT INTO `content` SET `channel_item_id` = ?, + `provider_id` = ?, + `title` = ?, + `description` = ?", + (channel_item_id, provider_id, title, description), + )?; + Ok(c.last_insert_id()) + } + + pub fn content_image(&self, content_image_id: u64) -> Result, Error> { + self.pool.get_conn()?.exec_first( + "SELECT `content_image_id`, + `content_id`, + `image_id`, + `data`, + `source` FROM `content_image` + JOIN `image` ON (`image`.`image_id` = `content_image`.`image_id`) + WHERE `content_image_id` = ? LIMIT 1", + (content_image_id,), + ) + } + + pub fn insert_content_image(&self, content_id: u64, image_id: u64) -> Result { + let mut c = self.pool.get_conn()?; + c.exec_drop( + "INSERT INTO `content_image` SET `content_id` = ?, `image_id` = ?", + (content_id, image_id), + )?; + Ok(c.last_insert_id()) + } + + pub fn image_by_source(&self, source: &str) -> Result, Error> { + self.pool.get_conn()?.exec_first( + "SELECT `image_id`, + `source`, + `data` FROM `image` WHERE `source` = ? LIMIT 1", + (source,), + ) + } + + pub fn images(&self, limit: Option) -> Result, Error> { + self.pool.get_conn()?.query(format!( + "SELECT `image_id`, `source`, `data` FROM `image` LIMIT {}", + limit.unwrap_or(DEFAULT_LIMIT) + )) + } + + pub fn insert_image(&self, source: &str, data: &[u8]) -> Result { + let mut c = self.pool.get_conn()?; + c.exec_drop( + "INSERT INTO `image` SET `source` = ?, `data` = ?", + (source, data), )?; Ok(c.last_insert_id()) } @@ -243,6 +293,24 @@ pub struct Provider { pub name: String, } +#[derive(Debug, PartialEq, Eq, FromRow)] +pub struct Image { + pub image_id: u64, + pub source: String, + pub data: Vec, +} + +/// Includes joined `image` table members +#[derive(Debug, PartialEq, Eq, FromRow)] +pub struct ContentImage { + pub content_image_id: u64, + pub content_id: u64, + pub image_id: u64, + // Image members (JOIN) + pub data: Vec, + pub source: String, +} + pub enum Sort { Asc, Desc,