Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/target
/Cargo.lock
secrets.toml
vscode/
vscode/
.env
4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,6 @@ rocket = { version = "0.5.0-rc.2", features = ["json"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
# canyon_sql = { git = "https://github.com/zerodaycode/Canyon-SQL.git" }
canyon_sql = { version = "0.4.2", features = ["postgres"] }
canyon_sql = { version = "0.4.2", features = ["postgres"] }
dotenvy = "0.15.7"
dotenvy_macro = "0.15.7"
8 changes: 4 additions & 4 deletions canyon.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
name = 'postgres'

[canyon_sql.datasources.auth]
postgresql = { basic = { username = 'zdc', password = 'ggprueba'}}
postgresql = { basic = { username = 'postgres', password = 'postgres'}}

[canyon_sql.datasources.properties]
host = '192.168.1.250'
port = 5432
db_name = 'triforce'
host = 'localhost'
port = 5438
db_name = 'postgres'
291 changes: 291 additions & 0 deletions src/api/controllers/lolesports.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,291 @@
use crate::{
models::{league_with_streams::LeagueWithStreams, stream::Stream},
utils::triforce_catalog::TriforceCatalog,
};
use rocket::get;
use rocket::http::Status;
use rocket::response::status;
use rocket::serde::json::Json;

use crate::api::request_handling::api_key::{ApiKeyError, ApiKeyResult};

use canyon_sql::{
crud::{CrudOperations, Transaction},
query::{operators::Comp, ops::QueryBuilder},
};

use crate::models::{
leagues::League, players::*, search_bar::SearchBarData, teams::*, tournaments::Tournament,
ts::TeamSchedule,
};

#[get("/leagues")]
async fn leagues(
key_result: ApiKeyResult,
) -> Result<status::Custom<Json<Vec<League>>>, ApiKeyError> {
match key_result {
ApiKeyResult::Ok(_key) => {
let all_leagues: Result<Vec<League>, _> = League::find_all().await;
match all_leagues {
Ok(leagues) => Ok(status::Custom(Status::Accepted, Json(leagues))),
Err(e) => {
eprintln!("Error on leagues: {:?}", e);
Ok(status::Custom(Status::InternalServerError, Json(vec![])))
}
}
}
ApiKeyResult::Err(err) => Err(err),
}
}

#[get("/tournaments")]
async fn tournaments(
key_result: ApiKeyResult,
) -> Result<status::Custom<Json<Vec<Tournament>>>, ApiKeyError> {
match key_result {
ApiKeyResult::Ok(_key) => {
let all_tournaments: Vec<Tournament> = Tournament::find_all_unchecked().await;
Ok(status::Custom(Status::Accepted, Json(all_tournaments)))
}
ApiKeyResult::Err(err) => Err(err),
}
}

#[get("/preview-incoming-events")]
async fn preview_incoming_events(
key_result: ApiKeyResult,
) -> Result<status::Custom<Json<Vec<TeamSchedule>>>, ApiKeyError> {
match key_result {
ApiKeyResult::Ok(_key) => {
let query = format!(
"SELECT s.id, s.start_time, s.state, s.event_type, s.blockname, s.match_id, s.strategy, s.strategy_count,
s.team_left_id, s.team_left_wins, s.team_right_id, s.team_right_wins,
tl.code AS team_left_name,
tr.code AS team_right_name,
tl.image_url AS team_left_img_url,
tr.image_url AS team_right_img_url,
l.\"name\" AS league_name
FROM schedule s
JOIN team tl ON s.team_left_id = tl.id
JOIN team tr ON s.team_right_id = tr.id
JOIN league l ON s.league_id = l.id
WHERE s.state <> 'completed'
AND s.event_type = 'match'
AND NOT (tl.slug = 'tbd' AND tr.slug = 'tbd')
ORDER BY s.start_time ASC
FETCH FIRST 30 ROWS ONLY"
);
let schedules = TeamSchedule::query(query, [], "")
.await
.map(|r| r.into_results::<TeamSchedule>());
match schedules {
Ok(v) => Ok(status::Custom(Status::Accepted, Json(v))),
Err(e) => {
eprintln!("{e}");
Ok(status::Custom(
Status::InternalServerError,
Json(Vec::new()),
))
}
}
}
ApiKeyResult::Err(err) => Err(err),
}
}

#[get("/team/<team_id>/schedule")]
async fn find_team_schedule(
key_result: ApiKeyResult,
team_id: i64,
) -> Result<status::Custom<Json<Vec<TeamSchedule>>>, ApiKeyError> {
match key_result {
ApiKeyResult::Ok(_key) => {
let query = format!(
"SELECT s.id, s.start_time, s.state, s.event_type, s.blockname, s.match_id, s.strategy, s.strategy_count,
s.team_left_id, s.team_left_wins, s.team_right_id, s.team_right_wins,
tl.name AS team_left_name,
tr.name AS team_right_name,
tl.image_url AS team_left_img_url,
tr.image_url AS team_right_img_url,
l.\"name\" AS league_name
FROM schedule s
JOIN team tl ON s.team_left_id = tl.id
JOIN team tr ON s.team_right_id = tr.id
JOIN league l ON s.league_id = l.id
WHERE s.team_left_id = {team_id} OR s.team_right_id = {team_id}
ORDER BY s.start_time DESC"
);

let schedules = TeamSchedule::query(query, [], "")
.await
.map(|r| r.into_results::<TeamSchedule>());

match schedules {
Ok(v) => Ok(status::Custom(Status::Accepted, Json(v))),
Err(e) => {
eprintln!("{e}");
Ok(status::Custom(
Status::InternalServerError,
Json(Vec::new()),
)) // TODO Replace the empty json
}
}
}
ApiKeyResult::Err(err) => Err(err),
}
}

#[get("/teams")]
async fn teams(key_result: ApiKeyResult) -> Result<status::Custom<Json<Vec<Team>>>, ApiKeyError> {
match key_result {
ApiKeyResult::Ok(_key) => {
let all_teams: Vec<Team> = Team::find_all_unchecked().await;
Ok(status::Custom(Status::Accepted, Json(all_teams)))
}
ApiKeyResult::Err(err) => Err(err),
}
}

#[get("/players")]
async fn players(
key_result: ApiKeyResult,
) -> Result<status::Custom<Json<Vec<Player>>>, ApiKeyError> {
match key_result {
ApiKeyResult::Ok(_key) => {
let all_players: Vec<Player> = Player::find_all_unchecked().await;
Ok(status::Custom(Status::Accepted, Json(all_players)))
}
ApiKeyResult::Err(err) => Err(err),
}
}

#[get("/leagues-with-streams")]
async fn leagues_with_streams(
key_result: ApiKeyResult,
) -> Result<status::Custom<Json<Vec<LeagueWithStreams>>>, ApiKeyError> {
match key_result {
ApiKeyResult::Ok(_key) => {
let all_leagues: Result<Vec<League>, _> = League::find_all().await;

let query = format!(
"SELECT
s.league_id,
s.league_id,
st.provider,
st.parameter,
st.locale,
st.english_name
FROM
public.stream st
JOIN
public.schedule s ON st.event_id = s.id AND s.state = 'inProgress'"
);

let streams = Stream::query(query, [], "")
.await
.map(|r| r.into_results::<Stream>());

match (all_leagues, streams) {
(Ok(leagues), Ok(current_streams)) => {
let mut leagues_with_streams: Vec<LeagueWithStreams> = Vec::new();

for league in leagues {
let streams_for_league: Vec<Stream> = current_streams
.iter()
.filter(|stream| stream.league_id == Some(league.id.into()))
.cloned()
.collect();
let league_with_streams =
LeagueWithStreams::from_league_and_streams(league, streams_for_league);

leagues_with_streams.push(league_with_streams);
}
Ok(status::Custom(Status::Accepted, Json(leagues_with_streams)))
}
// TODO Implement Error Control
_ => Ok(status::Custom(Status::InternalServerError, Json(vec![]))),
}
}
ApiKeyResult::Err(err) => Err(err),
}
}

#[get("/search-bar-data/<query>")]
async fn search_bar_data(
key_result: ApiKeyResult,
query: &str,
) -> Result<status::Custom<Json<Vec<SearchBarData>>>, ApiKeyError> {
match key_result {
ApiKeyResult::Ok(_key) => {
let mut search_bar_entities: Vec<SearchBarData> = Vec::new();

let query_teams = format!(
"SELECT * FROM team t
WHERE t.\"name\" ILIKE '%{query}%'
OR t.slug ILIKE '%{query}%'
OR t.code ILIKE '%{query}%'
ORDER BY t.id DESC"
);

let query_players = format!(
"SELECT * FROM player p
WHERE p.first_name ILIKE '%{query}%'
OR p.last_name ILIKE '%{query}%'
OR p.summoner_name ILIKE '%{query}%'"
);

let all_teams = Team::query(query_teams, [], "")
.await
.map(|r| r.into_results::<Team>());

let all_players = Player::query(query_players, [], "")
.await
.map(|r| r.into_results::<Player>());

if let Ok(teams) = all_teams {
teams.into_iter().for_each(|team| {
search_bar_entities.push(SearchBarData {
id: team.id,
kind: TriforceCatalog::Team,
entity_name: team.name,
entity_image_url: team.image_url,
entity_alt_data: team.slug,
player_role: None,
})
});
} else {
eprintln!("Error on teams: {:?}", all_teams.err().unwrap());
}

if let Ok(players) = all_players {
players.into_iter().for_each(|player| {
search_bar_entities.push(SearchBarData {
id: player.id,
kind: TriforceCatalog::Player,
entity_name: player.summoner_name,
entity_image_url: player.image_url.unwrap_or_default(),
entity_alt_data: format!("{} {}", player.first_name, player.last_name),
player_role: Some(player.role),
})
});
} else {
eprintln!("Error on players: {:?}", all_players.err().unwrap());
}

Ok(status::Custom(Status::Accepted, Json(search_bar_entities)))
}
ApiKeyResult::Err(err) => Err(err),
}
}
pub fn routes() -> Vec<rocket::Route> {
rocket::routes![
leagues,
tournaments,
preview_incoming_events,
find_team_schedule,
teams,
players,
leagues_with_streams,
search_bar_data
]
}
1 change: 1 addition & 0 deletions src/api/controllers/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod lolesports;
2 changes: 2 additions & 0 deletions src/api/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub mod controllers;
pub mod request_handling;
65 changes: 65 additions & 0 deletions src/api/request_handling/api_key.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
use rocket::response::{self};

use dotenvy_macro::dotenv;
use rocket::http::Status;
use rocket::request::{FromRequest, Outcome};
use rocket::response::Responder;
use rocket::{Request, Response};
use serde_json::json;

use rocket::http::ContentType;

use std::io::Cursor as SyncCursor;

pub struct ApiKey(String);
pub struct ApiKeyError {
message: String,
status: Status,
}
pub enum ApiKeyResult {
Ok(ApiKey),
Err(ApiKeyError),
}

#[rocket::async_trait]
impl<'r> FromRequest<'r> for ApiKeyResult {
type Error = std::convert::Infallible;

async fn from_request(
request: &'r Request<'_>,
) -> rocket::request::Outcome<ApiKeyResult, Self::Error> {
let keys: Vec<_> = request.headers().get("x-api-key").collect();
match keys.len() {
0 => Outcome::Success(ApiKeyResult::Err(ApiKeyError {
message: "Missing x-api-key".into(),
status: Status::BadRequest,
})),
1 if keys[0] == option_env!("API_KEY").unwrap_or(dotenv!("API_KEY")) => {
Outcome::Success(ApiKeyResult::Ok(ApiKey(keys[0].to_string())))
}
1 => Outcome::Success(ApiKeyResult::Err(ApiKeyError {
message: "Invalid x-api-key".into(),
status: Status::Unauthorized,
})),
_ => Outcome::Success(ApiKeyResult::Err(ApiKeyError {
message: "Multiple x-api-keys".into(),
status: Status::BadRequest,
})),
}
}
}

impl<'r> Responder<'r, 'static> for ApiKeyError {
fn respond_to(self, _: &'r Request<'_>) -> response::Result<'static> {
let mut response = Response::new();
response.set_status(self.status);
response.set_header(ContentType::JSON);

let body = json!({ "error": self.message }).to_string();
let cursor = SyncCursor::new(body.into_bytes());

response.set_sized_body(None, cursor);

Ok(response)
}
}
1 change: 1 addition & 0 deletions src/api/request_handling/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod api_key;
Loading