Skip to content

Commit a397ece

Browse files
SamTV12345SamTV12345
andauthored
Feature/tags (#464)
* Added backend implementation. * Added backend implementation. * Added backend implementation. * Added delete and create podcasts. * Added api doc. * Added manifest.json in backend. * Added tagging system in backend * Added tagging. * Added tagging system * Fixed clippy --------- Co-authored-by: SamTV12345 <noreply+samtv1235@github.com>
1 parent d259137 commit a397ece

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+1036
-3560
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
-- This file should undo anything in `up.sql`
2+
DROP TABLE tags;
3+
DROP TABLE
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
-- Your SQL goes here
2+
3+
CREATE TABLE tags (
4+
id TEXT NOT NULL PRIMARY KEY,
5+
name TEXT NOT NULL,
6+
username TEXT NOT NULL,
7+
description TEXT,
8+
created_at TIMESTAMP NOT NULL,
9+
color TEXT NOT NULL,
10+
UNIQUE (name, username)
11+
);
12+
13+
CREATE TABLE tags_podcasts
14+
(
15+
tag_id TEXT NOT NULL,
16+
podcast_id INTEGER NOT NULL,
17+
FOREIGN KEY (tag_id) REFERENCES tags (id),
18+
FOREIGN KEY (podcast_id) REFERENCES podcasts (id),
19+
PRIMARY KEY (tag_id, podcast_id)
20+
);
21+
22+
-- INDEXES
23+
CREATE INDEX idx_tags_name ON tags (name);
24+
CREATE INDEX idx_tags_username ON tags (username);
25+
CREATE INDEX idx_devices ON devices(name);
26+
CREATE INDEX idx_episodes_podcast ON episodes(podcast);
27+
CREATE INDEX idx_episodes_episode ON episodes(episode);
28+
CREATE INDEX idx_podcast_episodes ON podcast_episodes(podcast_id);
29+
CREATE INDEX idx_podcast_episodes_url ON podcast_episodes(url);
30+
CREATE INDEX idx_podcasts_name ON podcasts(name);
31+
CREATE INDEX idx_podcasts_rssfeed ON podcasts(rssfeed);
32+
CREATE INDEX idx_subscriptions ON subscriptions(username);
33+
CREATE INDEX idx_subscriptions_device ON subscriptions(device);
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
-- This file should undo anything in `up.sql`
2+
DROP TABLE tags;
3+
DROP TABLE
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
-- Your SQL goes here
2+
3+
CREATE TABLE tags (
4+
id TEXT NOT NULL PRIMARY KEY,
5+
name TEXT NOT NULL,
6+
username TEXT NOT NULL,
7+
description TEXT,
8+
created_at TIMESTAMP NOT NULL,
9+
color TEXT NOT NULL,
10+
UNIQUE (name, username)
11+
);
12+
13+
CREATE TABLE tags_podcasts
14+
(
15+
tag_id TEXT NOT NULL,
16+
podcast_id INTEGER NOT NULL,
17+
FOREIGN KEY (tag_id) REFERENCES tags (id),
18+
FOREIGN KEY (podcast_id) REFERENCES podcasts (id),
19+
PRIMARY KEY (tag_id, podcast_id)
20+
);
21+
22+
23+
-- INDEXES
24+
CREATE INDEX idx_tags_name ON tags (name);
25+
CREATE INDEX idx_tags_username ON tags (username);
26+
CREATE INDEX idx_devices ON devices(name);
27+
CREATE INDEX idx_episodes_podcast ON episodes(podcast);
28+
CREATE INDEX idx_episodes_episode ON episodes(episode);
29+
CREATE INDEX idx_podcast_episodes ON podcast_episodes(podcast_id);
30+
CREATE INDEX idx_podcast_episodes_url ON podcast_episodes(url);
31+
CREATE INDEX idx_podcasts_name ON podcasts(name);
32+
CREATE INDEX idx_podcasts_rssfeed ON podcasts(rssfeed);
33+
CREATE INDEX idx_subscriptions ON subscriptions(username);
34+
CREATE INDEX idx_subscriptions_device ON subscriptions(device);

src/controllers/api_doc.rs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@ use utoipa::{
3333
openapi::security::{ApiKey, ApiKeyValue, SecurityScheme},
3434
Modify, OpenApi,
3535
};
36-
36+
use crate::models::tag::Tag;
37+
use crate::controllers::tags_controller::*;
38+
use crate::controllers::playlist_controller::*;
3739
#[derive(OpenApi)]
3840
#[openapi(
3941
paths(
@@ -57,13 +59,14 @@ paths(
5759
dismiss_notifications,get_public_config,onboard_user,
5860
get_watchtime,get_timeline,download_podcast_episodes_of_podcast,update_name,get_sys_info,
5961
get_filter,search_podcasts,add_podcast_by_feed,refresh_all_podcasts,update_active_podcast,
60-
delete_podcast,proxy_podcast
62+
add_playlist,update_playlist,get_all_playlists,get_playlist_by_id,delete_playlist_by_id,delete_playlist_item,
63+
delete_podcast,proxy_podcast,insert_tag, get_tags, delete_tag, update_tag, add_podcast_to_tag, delete_podcast_from_tag
6164
),
6265
components(
6366
schemas(Podcast, PodcastEpisode, ItunesModel,PodcastFavorUpdateModel,
6467
PodcastWatchedEpisodeModel, PodcastWatchedPostModel, PodcastAddModel,Notification, Setting,
65-
Invite,
66-
Filter,OpmlModel,DeletePodcast, UpdateNameSettings,SysExtraInfo,UserOnboardingModel,User,InvitePostModel)
68+
Invite,LoginRequest,PlaylistDtoPost,Tag,
69+
Filter,OpmlModel,DeletePodcast, UpdateNameSettings,SysExtraInfo,UserOnboardingModel,User,InvitePostModel, TagCreate,TagWithPodcast)
6770
),
6871
tags(
6972
(name = "podcasts", description = "Podcast management endpoints."),
@@ -73,6 +76,7 @@ tags(
7376
(name = "settings", description = "Settings management endpoints. Settings are globally scoped."),
7477
(name = "info", description = "Gets multiple information about your installation."),
7578
(name = "playlist", description = "Playlist management endpoints."),
79+
(name = "tags", description = "Tag management endpoints."),
7680
),
7781
modifiers(&SecurityAddon)
7882
)]
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
use actix_web::{get, HttpResponse};
2+
use crate::constants::inner_constants::ENVIRONMENT_SERVICE;
3+
use crate::utils::error::CustomError;
4+
5+
#[derive(Serialize)]
6+
pub struct Icon {
7+
pub src: String,
8+
pub sizes: String,
9+
pub r#type: String
10+
}
11+
12+
13+
#[derive(Serialize)]
14+
pub struct Manifest {
15+
pub name: String,
16+
pub short_name: String,
17+
pub start_url: String,
18+
pub icons: Vec<Icon>,
19+
pub theme_color: String,
20+
pub background_color: String,
21+
pub display: String,
22+
pub orientation: String
23+
}
24+
25+
26+
27+
#[get("manifest.json")]
28+
pub async fn get_manifest() -> Result<HttpResponse, CustomError> {
29+
let env_service = ENVIRONMENT_SERVICE.get().unwrap();
30+
let mut icons = Vec::new();
31+
let icon = Icon{
32+
src: env_service.server_url.to_string()+"ui/logo.png",
33+
sizes: "512x512".to_string(),
34+
r#type: "image/png".to_string()
35+
};
36+
icons.push(icon);
37+
38+
39+
let manifest = Manifest{
40+
name: "PodFetch".to_string(),
41+
short_name: "PodFetch".to_string(),
42+
start_url: env_service.server_url.to_string(),
43+
icons,
44+
orientation: "landscape".to_string(),
45+
theme_color: "#ffffff".to_string(),
46+
display: "fullscreen".to_string(),
47+
background_color: "#ffffff".to_string()
48+
};
49+
Ok(HttpResponse::Ok().json(manifest))
50+
}

src/controllers/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,6 @@ pub mod user_controller;
1010
pub mod watch_time_controller;
1111
pub mod web_socket;
1212
pub mod websocket_controller;
13+
pub mod tags_controller;
1314
pub mod server;
15+
pub mod manifest_controller;

src/controllers/playlist_controller.rs

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,20 @@ use crate::DbPool;
66
use actix_web::web::Data;
77
use actix_web::{delete, get, post, put, web, HttpResponse};
88
use std::ops::DerefMut;
9+
use utoipa::ToSchema;
910

10-
#[derive(Serialize, Deserialize, Clone)]
11+
#[derive(Serialize, Deserialize, Clone, ToSchema)]
1112
pub struct PlaylistDtoPost {
1213
pub name: String,
1314
pub items: Vec<PlaylistItem>,
1415
}
1516

16-
#[derive(Serialize, Deserialize, Clone)]
17+
#[derive(Serialize, Deserialize, Clone,ToSchema)]
1718
pub struct PlaylistItem {
1819
pub episode: i32,
1920
}
2021

21-
#[derive(Serialize, Deserialize, Clone)]
22+
#[derive(Serialize, Deserialize, Clone, ToSchema)]
2223
pub struct PlaylistDto {
2324
pub id: String,
2425
pub name: String,
@@ -28,7 +29,7 @@ pub struct PlaylistDto {
2829
#[utoipa::path(
2930
context_path="/api/v1",
3031
responses(
31-
(status = 200, description = "Adds a new playlist for the user",body= Vec<PlaylistDto>)),
32+
(status = 200, description = "Adds a new playlist for the user",body= PlaylistDtoPost)),
3233
tag="playlist"
3334
)]
3435
#[post("/playlist")]
@@ -52,7 +53,7 @@ pub async fn add_playlist(
5253
#[utoipa::path(
5354
context_path="/api/v1",
5455
responses(
55-
(status = 200, description = "Updates a playlist of the user",body= Vec<PlaylistDto>)),
56+
(status = 200, description = "Updates a playlist of the user",body= PlaylistDtoPost)),
5657
tag="playlist"
5758
)]
5859
#[put("/playlist/{playlist_id}")]
@@ -75,6 +76,12 @@ pub async fn update_playlist(
7576
Ok(HttpResponse::Ok().json(res))
7677
}
7778

79+
#[utoipa::path(
80+
context_path="/api/v1",
81+
responses(
82+
(status = 200, description = "Gets all playlists of the user")),
83+
tag="playlist"
84+
)]
7885
#[get("/playlist")]
7986
pub async fn get_all_playlists(
8087
requester: Option<web::ReqData<User>>,
@@ -87,6 +94,12 @@ pub async fn get_all_playlists(
8794
.map(|playlists| HttpResponse::Ok().json(playlists))
8895
}
8996

97+
#[utoipa::path(
98+
context_path="/api/v1",
99+
responses(
100+
(status = 200, description = "Gets a specific playlist of a user")),
101+
tag="playlist"
102+
)]
90103
#[get("/playlist/{playlist_id}")]
91104
pub async fn get_playlist_by_id(
92105
requester: Option<web::ReqData<User>>,
@@ -108,6 +121,12 @@ pub async fn get_playlist_by_id(
108121
Ok(HttpResponse::Ok().json(playlist))
109122
}
110123

124+
#[utoipa::path(
125+
context_path="/api/v1",
126+
responses(
127+
(status = 200, description = "Deletes a specific playlist of a user")),
128+
tag="playlist"
129+
)]
111130
#[delete("/playlist/{playlist_id}")]
112131
pub async fn delete_playlist_by_id(
113132
requester: Option<web::ReqData<User>>,
@@ -123,6 +142,12 @@ pub async fn delete_playlist_by_id(
123142
Ok(HttpResponse::Ok().json(()))
124143
}
125144

145+
#[utoipa::path(
146+
context_path="/api/v1",
147+
responses(
148+
(status = 200, description = "Deletes a specific playlist item of a user")),
149+
tag="playlist"
150+
)]
126151
#[delete("/playlist/{playlist_id}/episode/{episode_id}")]
127152
pub async fn delete_playlist_item(
128153
requester: Option<web::ReqData<User>>,

src/controllers/podcast_controller.rs

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ pub struct PodcastSearchModel {
4949
title: Option<String>,
5050
order_option: Option<OrderOption>,
5151
favored_only: bool,
52+
tag: Option<String>
5253
}
5354

5455
#[utoipa::path(
@@ -87,6 +88,7 @@ pub async fn search_podcasts(
8788
let query = query.into_inner();
8889
let _order = query.order.unwrap_or(OrderCriteria::Asc);
8990
let _latest_pub = query.order_option.unwrap_or(OrderOption::Title);
91+
let tag = query.tag;
9092

9193
let opt_filter = Filter::get_filter_by_username(
9294
requester.clone().unwrap().username.clone(),
@@ -122,6 +124,7 @@ pub async fn search_podcasts(
122124
_latest_pub.clone(),
123125
conn.get().map_err(map_r2d2_error)?.deref_mut(),
124126
username,
127+
tag
125128
)?;
126129
}
127130
Ok(HttpResponse::Ok().json(podcasts))
@@ -135,29 +138,35 @@ pub async fn search_podcasts(
135138
_latest_pub.clone(),
136139
&mut conn.get().unwrap(),
137140
username,
141+
tag
138142
)?;
139143
}
140144
Ok(HttpResponse::Ok().json(podcasts))
141145
}
142146
}
143147
}
144148

149+
145150
#[utoipa::path(
146151
context_path="/api/v1",
147152
responses(
148-
(status = 200, description = "Find a podcast by its collection id", body = [Podcast])
153+
(status = 200, description = "Find a podcast by its collection id", body = [(Podcast, Tags)])
149154
),
150155
tag="podcasts"
151156
)]
152157
#[get("/podcast/{id}")]
153158
pub async fn find_podcast_by_id(
154159
id: Path<String>,
155160
conn: Data<DbPool>,
161+
user: Option<web::ReqData<User>>,
156162
) -> Result<HttpResponse, CustomError> {
157163
let id_num = from_str::<i32>(&id).unwrap();
164+
let username = user.unwrap().username.clone();
165+
158166
let podcast =
159167
PodcastService::get_podcast(conn.get().map_err(map_r2d2_error)?.deref_mut(), id_num)?;
160-
let mapped_podcast = MappingService::map_podcast_to_podcast_dto(&podcast);
168+
let tags = Tag::get_tags_of_podcast(conn.get().map_err(map_r2d2_error)?.deref_mut(), id_num, &username)?;
169+
let mapped_podcast = MappingService::map_podcast_to_podcast_dto(&podcast, tags);
161170
Ok(HttpResponse::Ok().json(mapped_podcast))
162171
}
163172

@@ -177,6 +186,7 @@ pub async fn find_all_podcasts(
177186

178187
let podcasts =
179188
PodcastService::get_podcasts(conn.get().map_err(map_r2d2_error)?.deref_mut(), username)?;
189+
180190
Ok(HttpResponse::Ok().json(podcasts))
181191
}
182192

@@ -485,7 +495,7 @@ pub async fn refresh_all_podcasts(
485495
podcast_episode: None,
486496
type_of: PodcastType::RefreshPodcast,
487497
message: format!("Refreshed podcast: {}", podcast.name),
488-
podcast: Option::from(podcast.clone()),
498+
podcast: Option::from(MappingService::map_podcast_to_podcast_dto(&podcast, vec![])),
489499
podcast_episodes: None,
490500
}).unwrap());
491501
}
@@ -690,7 +700,7 @@ async fn insert_outline(
690700
let _ = lobby.send_broadcast(MAIN_ROOM.parse().unwrap(), serde_json::to_string(&BroadcastMessage {
691701
type_of: PodcastType::OpmlAdded,
692702
message: "Refreshed podcasts".to_string(),
693-
podcast: Option::from(podcast),
703+
podcast: Option::from(MappingService::map_podcast_to_podcast_dto(&podcast, vec![])),
694704
podcast_episodes: None,
695705
podcast_episode: None,
696706
}).unwrap()).await;
@@ -719,12 +729,14 @@ async fn insert_outline(
719729
}
720730
use crate::models::episode::Episode;
721731
use utoipa::ToSchema;
732+
use crate::models::tag::Tag;
722733

723734
use crate::controllers::podcast_episode_controller::EpisodeFormatDto;
724735
use crate::controllers::server::ChatServerHandle;
725736
use crate::controllers::websocket_controller::RSSAPiKey;
726737
use crate::models::podcast_settings::PodcastSetting;
727738
use crate::models::settings::Setting;
739+
use crate::models::tags_podcast::TagsPodcast;
728740
use crate::utils::environment_variables::is_env_var_present_and_true;
729741

730742
use crate::utils::error::{map_r2d2_error, map_reqwest_error, CustomError};
@@ -763,6 +775,8 @@ pub async fn delete_podcast(
763775
}
764776
Episode::delete_watchtime(&mut db.get().unwrap(), *id)?;
765777
PodcastEpisode::delete_episodes_of_podcast(&mut db.get().unwrap(), *id)?;
778+
TagsPodcast::delete_tags_by_podcast_id(&mut db.get().unwrap(), *id)?;
779+
766780
Podcast::delete_podcast(&mut db.get().unwrap(), *id)?;
767781
Ok(HttpResponse::Ok().into())
768782
}

src/controllers/sys_info_controller.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ pub async fn login(
116116
Err(CustomError::Forbidden)
117117
}
118118

119-
#[derive(Debug, Serialize, Deserialize, Clone)]
119+
#[derive(Debug, Serialize, Deserialize, Clone, ToSchema)]
120120
pub struct LoginRequest {
121121
pub username: String,
122122
pub password: String,

0 commit comments

Comments
 (0)