Skip to content

Commit

Permalink
Added tagging system
Browse files Browse the repository at this point in the history
  • Loading branch information
SamTV12345 committed Aug 30, 2024
1 parent 0cb5d02 commit ff00d5c
Show file tree
Hide file tree
Showing 14 changed files with 189 additions and 39 deletions.
7 changes: 7 additions & 0 deletions src/controllers/podcast_controller.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ pub struct PodcastSearchModel {
title: Option<String>,
order_option: Option<OrderOption>,
favored_only: bool,
tag: Option<String>
}

#[utoipa::path(
Expand Down Expand Up @@ -87,6 +88,7 @@ pub async fn search_podcasts(
let query = query.into_inner();
let _order = query.order.unwrap_or(OrderCriteria::Asc);
let _latest_pub = query.order_option.unwrap_or(OrderOption::Title);
let tag = query.tag;

let opt_filter = Filter::get_filter_by_username(
requester.clone().unwrap().username.clone(),
Expand Down Expand Up @@ -122,6 +124,7 @@ pub async fn search_podcasts(
_latest_pub.clone(),
conn.get().map_err(map_r2d2_error)?.deref_mut(),
username,
tag
)?;
}
Ok(HttpResponse::Ok().json(podcasts))
Expand All @@ -135,6 +138,7 @@ pub async fn search_podcasts(
_latest_pub.clone(),
&mut conn.get().unwrap(),
username,
tag
)?;
}
Ok(HttpResponse::Ok().json(podcasts))
Expand Down Expand Up @@ -732,6 +736,7 @@ use crate::controllers::server::ChatServerHandle;
use crate::controllers::websocket_controller::RSSAPiKey;
use crate::models::podcast_settings::PodcastSetting;
use crate::models::settings::Setting;
use crate::models::tags_podcast::TagsPodcast;
use crate::utils::environment_variables::is_env_var_present_and_true;

use crate::utils::error::{map_r2d2_error, map_reqwest_error, CustomError};
Expand Down Expand Up @@ -770,6 +775,8 @@ pub async fn delete_podcast(
}
Episode::delete_watchtime(&mut db.get().unwrap(), *id)?;
PodcastEpisode::delete_episodes_of_podcast(&mut db.get().unwrap(), *id)?;
TagsPodcast::delete_tags_by_podcast_id(&mut db.get().unwrap(), *id)?;

Podcast::delete_podcast(&mut db.get().unwrap(), *id)?;
Ok(HttpResponse::Ok().into())
}
Expand Down
3 changes: 2 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ use crate::dbconfig::DBType;
pub use controllers::controller_utils::*;
use crate::controllers::manifest_controller::get_manifest;
use crate::controllers::server::ChatServer;
use crate::controllers::tags_controller::{add_podcast_to_tag, delete_tag, get_tags, insert_tag, update_tag};
use crate::controllers::tags_controller::{add_podcast_to_tag, delete_podcast_from_tag, delete_tag, get_tags, insert_tag, update_tag};

mod constants;
mod db;
Expand Down Expand Up @@ -467,6 +467,7 @@ fn get_private_api() -> Scope<
.service(update_tag)
.service(get_tags)
.service(add_podcast_to_tag)
.service(delete_podcast_from_tag)
.service(retrieve_episode_sample_format)
.service(retrieve_podcast_sample_format)
.service(update_podcast_settings)
Expand Down
9 changes: 0 additions & 9 deletions src/models/episode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -277,15 +277,6 @@ impl Episode {
use crate::dbconfig::schema::podcasts::dsl::podcasts;
use crate::dbconfig::schema::podcasts::dsl::rssfeed;

let binding = DistinctDsl::distinct(episodes
.left_join(podcasts.on(podcast.eq(rssfeed)))
.select((device, podcast))
.filter(rssfeed.is_null()))
.filter(device.ne("webview"));
let sql = debug_query::<Sqlite, _>(&binding);


println!("SQL ist {}", sql);
let result = DistinctDsl::distinct(episodes
.left_join(podcasts.on(podcast.eq(rssfeed)))
.select((device, podcast))
Expand Down
3 changes: 2 additions & 1 deletion src/models/favorites.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use diesel::insert_into;
use diesel::prelude::*;
use diesel::sql_types::{Bool, Integer, Text};
use serde::{Deserialize, Serialize};
use crate::dbconfig::DBType;

Check warning on line 14 in src/models/favorites.rs

View workflow job for this annotation

GitHub Actions / Rust project

unused import: `crate::dbconfig::DBType`

Check failure on line 14 in src/models/favorites.rs

View workflow job for this annotation

GitHub Actions / Rust lint

unused import: `crate::dbconfig::DBType`

Check warning on line 14 in src/models/favorites.rs

View workflow job for this annotation

GitHub Actions / Rust project

unused import: `crate::dbconfig::DBType`
use crate::models::tag::Tag;

#[derive(
Expand Down Expand Up @@ -121,7 +122,7 @@ impl Favorite {
order: OrderCriteria,
title: Option<String>,
latest_pub: OrderOption,
designated_username: &str,
designated_username: &str
) -> Result<Vec<(Podcast, Favorite)>, CustomError> {
use crate::dbconfig::schema::podcast_episodes::dsl::*;
use crate::dbconfig::schema::podcasts::dsl::id as podcastsid;
Expand Down
14 changes: 14 additions & 0 deletions src/models/tags_podcast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,20 @@ use crate::dbconfig::schema::tags_podcasts::dsl::*;
.map_err(map_db_error))
}

pub fn delete_tags_by_podcast_id(conn: &mut DbConnection, podcast_id_to_delete: i32) -> Result<(),
CustomError> {
use crate::dbconfig::schema::tags_podcasts::dsl::*;
use crate::dbconfig::schema::tags_podcasts::table as t_podcasts;
use diesel::RunQueryDsl;
use diesel::ExpressionMethods;
use diesel::QueryDsl;

let _ = insert_with_conn!(conn, |conn| diesel::delete(t_podcasts.filter(podcast_id.eq(podcast_id_to_delete)))
.execute(conn)
.map_err(map_db_error));
Ok(())
}

pub fn delete_tag_podcasts(conn: &mut DbConnection, tag_id_to_delete: &str) -> Result<(),
CustomError> {
use crate::dbconfig::schema::tags_podcasts::dsl::*;
Expand Down
27 changes: 26 additions & 1 deletion src/service/rust_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ use crate::config::dbconfig::establish_connection;
use serde::Serialize;
use tokio::task::spawn_blocking;
use crate::controllers::server::ChatServerHandle;
use crate::dbconfig::DBType;

Check warning on line 23 in src/service/rust_service.rs

View workflow job for this annotation

GitHub Actions / Rust project

unused import: `crate::dbconfig::DBType`

Check failure on line 23 in src/service/rust_service.rs

View workflow job for this annotation

GitHub Actions / Rust lint

unused import: `crate::dbconfig::DBType`

Check warning on line 23 in src/service/rust_service.rs

View workflow job for this annotation

GitHub Actions / Rust project

unused import: `crate::dbconfig::DBType`
use crate::models::favorites::Favorite;
use crate::models::order_criteria::{OrderCriteria, OrderOption};
use crate::models::settings::Setting;
Expand Down Expand Up @@ -345,6 +346,7 @@ impl PodcastService {
latest_pub: OrderOption,
conn: &mut DbConnection,
designated_username: String,
tag: Option<String>,
) -> Result<Vec<impl Serialize>, CustomError> {
let podcasts =
Favorite::search_podcasts_favored(conn, order, title, latest_pub,
Expand All @@ -356,6 +358,17 @@ impl PodcastService {
MappingService::map_podcast_to_podcast_dto_with_favorites_option(&podcast, tags_of_podcast);
podcast_dto_vec.push(podcast_dto);
}

if let Some(tag) = tag {
let found_tag = Tag::get_tag_by_id_and_username(conn, &tag, &designated_username)?;

if let Some(foud_tag) = found_tag {
podcast_dto_vec = podcast_dto_vec.into_iter().filter(|p|{
p.tags.iter().any(|t| t.id == foud_tag.id)
}).collect::<Vec<PodcastDto>>()
}
}

Ok(podcast_dto_vec)
}

Expand All @@ -366,16 +379,28 @@ impl PodcastService {
latest_pub: OrderOption,
conn: &mut DbConnection,
designated_username: String,
tag: Option<String>,
) -> Result<Vec<PodcastDto>, CustomError> {
let podcasts =
Favorite::search_podcasts(conn, order, title, latest_pub, &designated_username)?;
let mapped_result = podcasts
let mut mapped_result = podcasts
.iter()
.map(|podcast| {
let tags = Tag::get_tags_of_podcast(conn, podcast.0.id, &designated_username).unwrap();
MappingService::map_podcast_to_podcast_dto_with_favorites(podcast, tags)
})
.collect::<Vec<PodcastDto>>();


if let Some(tag) = tag {
let found_tag = Tag::get_tag_by_id_and_username(conn, &tag, &designated_username)?;

if let Some(foud_tag) = found_tag {
mapped_result = mapped_result.into_iter().filter(|p|{
p.tags.iter().any(|t| t.id == foud_tag.id)
}).collect::<Vec<PodcastDto>>()
}
}
Ok(mapped_result)
}
}
4 changes: 4 additions & 0 deletions ui/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import {UserAdminInvites} from "./components/UserAdminInvites";
import {UserManagementPage} from "./pages/UserManagement";
import {configWSUrl} from "./utils/navigationUtils";
import {GPodderIntegration} from "./pages/GPodderIntegration";
import {TagsPage} from "./pages/TagsPage";

export const router = createBrowserRouter(createRoutesFromElements(
<>
Expand Down Expand Up @@ -84,6 +85,9 @@ export const router = createBrowserRouter(createRoutesFromElements(
<Route path={"profile"}>
<Route index element={<UserManagementPage/>}/>
</Route>
<Route path="tags">
<Route index element={<TagsPage/>}/>
</Route>
</Route>
<Route path="/login" element={<Login />} />
<Route path="/invite/:id" element={<AcceptInvite />}></Route>
Expand Down
7 changes: 4 additions & 3 deletions ui/src/components/CustomInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@ type CustomInputProps = {
required?: boolean,
type?: string,
value?: string | number,
disabled?: boolean
disabled?: boolean,
onBlur?: () => void
}

export const CustomInput: FC<CustomInputProps> = ({ autoComplete, className = '', id, name, onChange, disabled, placeholder, required, type = 'text', value }) => {
export const CustomInput: FC<CustomInputProps> = ({ autoComplete, onBlur, className = '', id, name, onChange, disabled, placeholder, required, type = 'text', value }) => {
return (
<input autoComplete={autoComplete} disabled={disabled} className={"bg-[--input-bg-color] px-4 py-2 rounded-lg text-sm text-[--input-fg-color] placeholder:text-[--input-fg-color-disabled] " + className} id={id} name={name} placeholder={placeholder} onChange={onChange} value={value} type={type} required={required} />
<input onBlur={onBlur} autoComplete={autoComplete} disabled={disabled} className={"bg-[--input-bg-color] px-4 py-2 rounded-lg text-sm text-[--input-fg-color] placeholder:text-[--input-fg-color-disabled] " + className} id={id} name={name} placeholder={placeholder} onChange={onChange} value={value} type={type} required={required} />
)
}
25 changes: 10 additions & 15 deletions ui/src/components/PodcastCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import {PodcastTags} from "../models/PodcastTags";
import {TagCreate} from "../models/Tags";
import {CustomCheckbox} from "./CustomCheckbox";
import {Tag} from "sanitize-html";
import {LuMinus, LuTags} from "react-icons/lu";
import {useTranslation} from "react-i18next";

type PodcastCardProps = {
podcast: Podcast
Expand All @@ -30,11 +32,12 @@ export const PodcastCard: FC<PodcastCardProps> = ({podcast}) => {
favored: !podcast.favorites
})
}
const {t} = useTranslation()
const [newTag, setNewTag] = useState<string>('')

return (
<Context.Root modal={true} onOpenChange={()=>{

setNewTag('')
}}>
<Context.Trigger>
<Link className="group" to={podcast.id + '/episodes'}>
Expand All @@ -60,6 +63,7 @@ export const PodcastCard: FC<PodcastCardProps> = ({podcast}) => {
className="block font-bold leading-[1.2] mb-2 text-[--fg-color] transition-colors group-hover:text-[--fg-color-hover]">{podcast.name}</span>
<span
className="block leading-[1.2] text-sm text-[--fg-secondary-color]">{podcast.author}</span>
<span className="flex gap-2 mb-2 text-[--fg-color]"><LuTags className="text-[--fg-secondary-color] text-2xl"/> <span className="self-center mb-2 text-[--fg-color]">{podcast.tags.length}</span> {t('tag', {count: tags.length})}</span>
</div>
</Link>
</Context.Trigger>
Expand All @@ -71,10 +75,10 @@ export const PodcastCard: FC<PodcastCardProps> = ({podcast}) => {
<hr className="mt-1 border-[1px] border-[--border-color] mb-2"/>
{
tags.map(t=>{
return <Context.Item onClick={(e)=>{
return <Context.Item key={t.id} onClick={(e)=>{
e.preventDefault()
}} className="group text-[13px] leading-none text-violet11 rounded-[3px] flex items-center h-[25px] px-[5px] relative pl-[25px] select-none outline-none data-[disabled]:text-mauve8 data-[disabled]:pointer-events-none data-[highlighted]:bg-violet9 data-[highlighted]:text-violet1 text-white">
<span className="grid grid-cols-2 gap-5">
}} className="group mt-2 mb-2 text-[13px] leading-none text-violet11 rounded-[3px] flex items-center h-[25px] px-[5px] relative pl-[25px] select-none outline-none data-[disabled]:text-mauve8 data-[disabled]:pointer-events-none data-[highlighted]:bg-violet9 data-[highlighted]:text-violet1 text-white">
<span className="grid grid-cols-3 gap-5">
<CustomCheckbox value={podcast.tags.filter(e=>e.name=== t.name).length>0} onChange={(v)=>{
if (v.valueOf() === true) {

Expand All @@ -99,7 +103,7 @@ export const PodcastCard: FC<PodcastCardProps> = ({podcast}) => {
.then(()=>{
setPodcasts(podcasts.map(p=>{
if (p.id === podcast.id) {
const tags = podcast.tags.filter(tag=>tag.id === t.id)
const tags = podcast.tags.filter(tag=>tag.id !== t.id)
return {
...p,
tags
Expand All @@ -117,19 +121,10 @@ export const PodcastCard: FC<PodcastCardProps> = ({podcast}) => {
}

<span className="relative">
<PlusIcon className="absolute right-5 fill-white h-5 top-2 -translate-y-1/2 cursor-pointer" onClick={()=>{
<PlusIcon className="absolute right-5 fill-white h-[19px] top-2 -translate-y-1/2 cursor-pointer" onClick={()=>{
if(tags.map(t=>t.name).includes(newTag)||!newTag.trim()) {
return
}
const newTags: PodcastTags[] = [...tags, {
name: newTag,
color: "ffff",
id: "test123",
username: 'test',
created_at: "123123",
description: "§123123"
}]


axios.post('/tags', {
color: 'Green',
Expand Down
1 change: 1 addition & 0 deletions ui/src/components/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export const Sidebar = () => {
<SidebarItem iconName="podcasts" path="podcasts" translationKey="all-subscriptions"/>
<SidebarItem iconName="favorite" path="favorites" translationKey="favorites"/>
<SidebarItem iconName="magic_button" path="timeline" translationKey="timeline"/>
<SidebarItem path="tags" translationKey="tag_other" iconName="sell"/>

<span className="display-only-mobile">
<SidebarItem iconName="search" path="/podcasts/search" translationKey="search-episodes"/>
Expand Down
4 changes: 3 additions & 1 deletion ui/src/language/json/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -186,5 +186,7 @@
"activated": "Aktiviert",
"manage-gpodder-podcasts": "GPodder Podcasts verwalten",
"device": "Gerät",
"add": "Hinzufügen"
"add": "Hinzufügen",
"tag_one": "Tag",
"tag_other": "Tags"
}
2 changes: 2 additions & 0 deletions ui/src/language/json/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,8 @@
"episode-numbering": "Episode numbering",
"activated": "Activated",
"manage-gpodder-podcasts": "Manage GPodder podcasts",
"tag_one": "Tag",
"tag_other": "Tags",
"device": "Device",
"add": "Add"
}
Loading

0 comments on commit ff00d5c

Please sign in to comment.