Skip to content

Commit

Permalink
Added tagging system in backend
Browse files Browse the repository at this point in the history
  • Loading branch information
SamTV12345 committed Aug 30, 2024
1 parent 6c6a492 commit 06654eb
Show file tree
Hide file tree
Showing 17 changed files with 171 additions and 50 deletions.
17 changes: 8 additions & 9 deletions src/controllers/podcast_controller.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,15 +157,13 @@ pub async fn find_podcast_by_id(
user: Option<web::ReqData<User>>,
) -> Result<HttpResponse, CustomError> {
let id_num = from_str::<i32>(&id).unwrap();
let username = user.unwrap().username.clone();

let podcast =
PodcastService::get_podcast(conn.get().map_err(map_r2d2_error)?.deref_mut(), id_num)?;
let mapped_podcast = MappingService::map_podcast_to_podcast_dto(&podcast);
let tags = Tag::get_tags_of_podcast(
conn.get().map_err(map_r2d2_error)?.deref_mut(),
podcast.id,
&user.unwrap().username,
)?;
Ok(HttpResponse::Ok().json((mapped_podcast, tags)))
let tags = Tag::get_tags_of_podcast(conn.get().map_err(map_r2d2_error)?.deref_mut(), id_num, &username)?;
let mapped_podcast = MappingService::map_podcast_to_podcast_dto(&podcast, tags);
Ok(HttpResponse::Ok().json(mapped_podcast))
}

#[utoipa::path(
Expand All @@ -184,6 +182,7 @@ pub async fn find_all_podcasts(

let podcasts =
PodcastService::get_podcasts(conn.get().map_err(map_r2d2_error)?.deref_mut(), username)?;

Ok(HttpResponse::Ok().json(podcasts))
}

Expand Down Expand Up @@ -492,7 +491,7 @@ pub async fn refresh_all_podcasts(
podcast_episode: None,
type_of: PodcastType::RefreshPodcast,
message: format!("Refreshed podcast: {}", podcast.name),
podcast: Option::from(podcast.clone()),
podcast: Option::from(MappingService::map_podcast_to_podcast_dto(&podcast, vec![])),
podcast_episodes: None,
}).unwrap());
}
Expand Down Expand Up @@ -697,7 +696,7 @@ async fn insert_outline(
let _ = lobby.send_broadcast(MAIN_ROOM.parse().unwrap(), serde_json::to_string(&BroadcastMessage {
type_of: PodcastType::OpmlAdded,
message: "Refreshed podcasts".to_string(),
podcast: Option::from(podcast),
podcast: Option::from(MappingService::map_podcast_to_podcast_dto(&podcast, vec![])),
podcast_episodes: None,
podcast_episode: None,
}).unwrap()).await;
Expand Down
3 changes: 1 addition & 2 deletions src/controllers/tags_controller.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,9 @@ responses(
tag="tags"
)]
#[get("/tags")]
pub async fn get_tags(conn: Data<DbPool>, requester: Option<web::ReqData<User>>, _mapping_service: Data<Mutex<MappingService>>) ->
pub async fn get_tags(conn: Data<DbPool>, requester: Option<web::ReqData<User>>) ->
Result<HttpResponse, CustomError> {
let tags = Tag::get_tags(&mut conn.get().unwrap(), requester.unwrap().username.clone())?;

Ok(HttpResponse::Ok().json(tags))
}

Expand Down
3 changes: 2 additions & 1 deletion src/models/messages.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::constants::inner_constants::PodcastType;
use crate::models::podcast_dto::PodcastDto;
use crate::models::podcast_episode::PodcastEpisode;
use crate::models::podcasts::Podcast;

Check warning on line 4 in src/models/messages.rs

View workflow job for this annotation

GitHub Actions / Rust project

unused import: `crate::models::podcasts::Podcast`

Check failure on line 4 in src/models/messages.rs

View workflow job for this annotation

GitHub Actions / Rust lint

unused import: `crate::models::podcasts::Podcast`

Check warning on line 4 in src/models/messages.rs

View workflow job for this annotation

GitHub Actions / Rust project

unused import: `crate::models::podcasts::Podcast`

Expand All @@ -7,7 +8,7 @@ use crate::models::podcasts::Podcast;
pub struct BroadcastMessage {
pub type_of: PodcastType,
pub message: String,
pub podcast: Option<Podcast>,
pub podcast: Option<PodcastDto>,
pub podcast_episodes: Option<Vec<PodcastEpisode>>,
pub podcast_episode: Option<PodcastEpisode>,
}
1 change: 1 addition & 0 deletions src/models/podcast_dto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ pub struct PodcastDto {
pub(crate) id: i32,
pub(crate) name: String,
pub directory_id: String,
pub directory_name: String,
pub(crate) rssfeed: String,
pub image_url: String,
pub summary: Option<String>,
Expand Down
10 changes: 5 additions & 5 deletions src/models/tag.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,15 @@ pub struct Tag {
#[diesel(sql_type = Text)]
pub(crate) id: String,
#[diesel(sql_type = Text)]
name: String,
pub name: String,
#[diesel(sql_type = Text)]
username: String,
pub username: String,
#[diesel(sql_type = Nullable<Text>)]
description: Option<String>,
pub description: Option<String>,
#[diesel(sql_type = Timestamp)]
created_at: NaiveDateTime,
pub created_at: NaiveDateTime,
#[diesel(sql_type = Text)]
color: String,
pub color: String,
}

impl Tag {
Expand Down
11 changes: 8 additions & 3 deletions src/service/mapping_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ use crate::service::environment_service;
#[derive(Clone)]
pub struct MappingService {}



impl MappingService {
pub fn map_podcast_to_podcast_dto(podcast: &Podcast) -> Podcast {
Podcast {
pub fn map_podcast_to_podcast_dto(podcast: &Podcast, tags: Vec<Tag>) -> PodcastDto {
PodcastDto {
id: podcast.id,
name: podcast.name.clone(),
directory_id: podcast.directory_id.clone(),
Expand All @@ -27,7 +29,9 @@ impl MappingService {
author: podcast.author.clone(),
active: podcast.active,
original_image_url: podcast.original_image_url.clone(),
directory_name: podcast.directory_name.clone()
directory_name: podcast.directory_name.clone(),
tags,
favorites: false
}
}

Expand All @@ -54,6 +58,7 @@ impl MappingService {
active: podcast_favorite_grouped.0.active,
original_image_url: podcast_favorite_grouped.0.original_image_url.clone(),
favorites: favorite,
directory_name: podcast_favorite_grouped.0.directory_name.clone(),
tags,
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/service/podcast_episode_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ impl PodcastEpisodeService {
"Episode {} is now available offline",
podcast_episode.name
),
podcast: Option::from(podcast.clone()),
podcast: Option::from(MappingService::map_podcast_to_podcast_dto(&podcast, vec![])),
type_of: PodcastType::AddPodcastEpisode,
podcast_episode: Some(mapped_dto),
podcast_episodes: None,
Expand Down
3 changes: 2 additions & 1 deletion src/service/rust_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ impl PodcastService {
message: format!("Added podcast: {}", inserted_podcast.name),
podcast: Option::from(MappingService::map_podcast_to_podcast_dto(
&podcast.clone().unwrap(),
vec![]
)),
podcast_episodes: None,
}).unwrap()).await;
Expand All @@ -187,7 +188,7 @@ impl PodcastService {
podcast_episode: None,
type_of: PodcastType::AddPodcastEpisodes,
message: format!("Added podcast episodes: {}", podcast.name),
podcast: Option::from(podcast.clone()),
podcast: Option::from(MappingService::map_podcast_to_podcast_dto(&podcast, vec![])),
podcast_episodes: Option::from(inserted_podcasts),
}).unwrap());
if let Err(e) =
Expand Down
2 changes: 1 addition & 1 deletion ui/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Podfetch</title>
<link rel="manifest" href="/manifest.json" />
<link rel="manifest" href="../manifest.json" />
</head>
<body>
<div id="root"></div>
Expand Down
1 change: 1 addition & 0 deletions ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"@fontsource/roboto": "^5.0.14",
"@fortawesome/fontawesome-free": "^6.6.0",
"@radix-ui/react-checkbox": "^1.1.1",
"@radix-ui/react-context-menu": "^2.2.1",
"@radix-ui/react-dialog": "^1.1.1",
"@radix-ui/react-dropdown-menu": "^2.1.1",
"@radix-ui/react-popover": "^1.1.1",
Expand Down
30 changes: 30 additions & 0 deletions ui/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

101 changes: 79 additions & 22 deletions ui/src/components/PodcastCard.tsx
Original file line number Diff line number Diff line change
@@ -1,44 +1,101 @@
import { createRef, FC } from 'react'
import { Link } from 'react-router-dom'
import {createRef, FC, useState} from 'react'
import {Link} from 'react-router-dom'
import axios from 'axios'
import {prependAPIKeyOnAuthEnabled} from '../utils/Utilities'
import useCommon, { Podcast } from '../store/CommonSlice'
import useCommon, {Podcast} from '../store/CommonSlice'
import 'material-symbols/outlined.css'
import * as Context from '@radix-ui/react-context-menu'
import {ContextMenu} from "@radix-ui/react-context-menu";
import {CustomInput} from "./CustomInput";
import {PlusIcon} from "../icons/PlusIcon";
import {PodcastTags} from "../models/PodcastTags";

type PodcastCardProps = {
podcast: Podcast
}

export const PodcastCard: FC<PodcastCardProps> = ({ podcast }) => {
export const PodcastCard: FC<PodcastCardProps> = ({podcast}) => {
const likeButton = createRef<HTMLElement>()
const updateLikePodcast = useCommon(state => state.updateLikePodcast)

const tags = useCommon(state=>state.tags)
const setTags = useCommon(state=>state.setPodcastTags)
const likePodcast = () => {
axios.put( '/podcast/favored', {
axios.put('/podcast/favored', {
id: podcast.id,
favored: !podcast.favorites
})
}
const [newTag, setNewTag] = useState<string>('')

return (
<Link className="group" to={podcast.id + '/episodes'}>
<div className="relative mb-2">
<img className={`rounded-xl transition-shadow group-hover:shadow-[0_4px_32px_rgba(0,0,0,var(--shadow-opacity))] ${!podcast.active ? 'opacity-20' : ''}`} src={prependAPIKeyOnAuthEnabled(podcast.image_url)} alt=""/>
<Context.Root modal={true} onOpenChange={()=>{

}}>
<Context.Trigger>
<Link className="group" to={podcast.id + '/episodes'}>
<div className="relative mb-2">
<img
className={`rounded-xl transition-shadow group-hover:shadow-[0_4px_32px_rgba(0,0,0,var(--shadow-opacity))] ${!podcast.active ? 'opacity-20' : ''}`}
src={prependAPIKeyOnAuthEnabled(podcast.image_url)} alt=""/>

<span ref={likeButton}
className={`material-symbols-outlined filled absolute top-2 right-2 h-6 w-6 filled ${podcast.favorites ? 'text-rose-700 hover:text-rose-600' : 'text-stone-200 hover:text-stone-100'}`}
onClick={(e) => {
// Prevent icon click from triggering link to podcast detail
e.preventDefault()

<span ref={likeButton} className={`material-symbols-outlined filled absolute top-2 right-2 h-6 w-6 filled ${podcast.favorites?'text-rose-700 hover:text-rose-600': 'text-stone-200 hover:text-stone-100'}`} onClick={(e) => {
// Prevent icon click from triggering link to podcast detail
likeButton.current?.classList.toggle('fill-amber-400')
likePodcast()
updateLikePodcast(podcast.id)
}}>favorite</span>
</div>

<div>
<span
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>
</div>
</Link>
</Context.Trigger>
<Context.Portal>
<Context.Content className="bg-[--bg-color] p-5" onClick={(e)=>{
e.preventDefault()
}}>
<h2 className="text-[--fg-color]">Tags</h2>
<hr className="mt-1 border-[1px] border-[--border-color] mb-2"/>
{
tags.map(t=>{
return <Context.Item 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">
{t.name}
</Context.Item>
})
}

<span className="relative">
<PlusIcon className="absolute right-5 fill-white h-5 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"
}]

likeButton.current?.classList.toggle('fill-amber-400')
likePodcast()
updateLikePodcast(podcast.id)
}}>favorite</span>
</div>

<div>
<span 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>
</div>
</Link>
setTags(newTags)
}}/>
<CustomInput placeholder="Add new tag" value={newTag} onChange={(event)=>{
setNewTag(event.target.value)
}}/>
</span>
</Context.Content>
</Context.Portal>
</Context.Root>
)
}
2 changes: 2 additions & 0 deletions ui/src/components/PodcastSettingsModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ export const PodcastSettingsModal:FC<PodcastSettingsModalProps> = ({setOpen,open
}, [podcastSettings, loaded])


console.log(podcast)

useEffect(() => {
axios.get<PodcastSetting>("/podcasts/"+podcast.id+"/settings").then((res) => {
if (res != null) {
Expand Down
7 changes: 4 additions & 3 deletions ui/src/icons/PlusIcon.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import {FC} from "react";

type PlusIconProps = {
className?: string
className?: string,
onClick?: ()=>void
}

export const PlusIcon:FC<PlusIconProps> = (className) => {
return <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" className={"h-8 mt-1 fill-amber-700 "+ className}><path d="M453-280h60v-166h167v-60H513v-174h-60v174H280v60h173v166Zm27.266 200q-82.734 0-155.5-31.5t-127.266-86q-54.5-54.5-86-127.341Q80-397.681 80-480.5q0-82.819 31.5-155.659Q143-709 197.5-763t127.341-85.5Q397.681-880 480.5-880q82.819 0 155.659 31.5Q709-817 763-763t85.5 127Q880-563 880-480.266q0 82.734-31.5 155.5T763-197.684q-54 54.316-127 86Q563-80 480.266-80Zm.234-60Q622-140 721-239.5t99-241Q820-622 721.188-721 622.375-820 480-820q-141 0-240.5 98.812Q140-622.375 140-480q0 141 99.5 240.5t241 99.5Zm-.5-340Z"/></svg>
export const PlusIcon:FC<PlusIconProps> = ({className, onClick}) => {
return <svg onClick={onClick} xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" className={"h-8 mt-1 fill-amber-700 "+ className}><path d="M453-280h60v-166h167v-60H513v-174h-60v174H280v60h173v166Zm27.266 200q-82.734 0-155.5-31.5t-127.266-86q-54.5-54.5-86-127.341Q80-397.681 80-480.5q0-82.819 31.5-155.659Q143-709 197.5-763t127.341-85.5Q397.681-880 480.5-880q82.819 0 155.659 31.5Q709-817 763-763t85.5 127Q880-563 880-480.266q0 82.734-31.5 155.5T763-197.684q-54 54.316-127 86Q563-80 480.266-80Zm.234-60Q622-140 721-239.5t99-241Q820-622 721.188-721 622.375-820 480-820q-141 0-240.5 98.812Q140-622.375 140-480q0 141 99.5 240.5t241 99.5Zm-.5-340Z"/></svg>
}
8 changes: 8 additions & 0 deletions ui/src/models/PodcastTags.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export type PodcastTags = {
id: string,
name: string,
username: string,
description?: string,
created_at: string,
color: string
}
Loading

0 comments on commit 06654eb

Please sign in to comment.