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
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ jobs:
DATABASE_URL: mysql://api:${{ secrets.MARIADB_PW }}@localhost:3306/master_db
REDIS_URL: redis://localhost:6379
GQL_API_CURSOR_SECRET_KEY: ${{ secrets.GQL_API_CURSOR_SECRET_KEY }}
RECORDS_API_SESSION_KEY: ${{ secrets.RECORDS_API_SESSION_KEY }}
run: RUST_BACKTRACE=1 cargo test -F mysql

lint:
Expand Down
15 changes: 12 additions & 3 deletions crates/game_api/src/env.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
use std::error::Error;

use actix_web::cookie::Key;
use mkenv::{make_config, prelude::*};
use once_cell::sync::OnceCell;
use records_lib::{DbEnv, LibEnv};

fn parse_session_key(input: &str) -> Result<Key, Box<dyn Error>> {
Key::try_from(input.as_bytes()).map_err(From::from)
}

#[cfg(not(debug_assertions))]
mkenv::make_config! {
pub struct DynamicApiEnv {
pub sess_key: {
var_name: "RECORDS_API_SESSION_KEY_FILE",
layers: [file_read()],
layers: [
file_read(),
parsed<Key>(parse_session_key),
],
description: "The path to the file containing the session key used by the API",
},

Expand All @@ -30,9 +40,8 @@ mkenv::make_config! {
pub struct DynamicApiEnv {
pub sess_key: {
var_name: "RECORDS_API_SESSION_KEY",
layers: [or_default()],
layers: [parsed<Key>(parse_session_key)],
description: "The session key used by the API",
default_val_fmt: "empty",
},

pub mp_client_id: {
Expand Down
25 changes: 11 additions & 14 deletions crates/game_api/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,7 @@ use actix_session::{
config::{CookieContentSecurity, PersistentSession},
storage::CookieSessionStore,
};
use actix_web::{
App, HttpServer,
cookie::{Key, time::Duration as CookieDuration},
middleware,
};
use actix_web::{App, HttpServer, cookie::time::Duration as CookieDuration, middleware};
use anyhow::Context;
use game_api_lib::configure;
use migration::MigratorTrait;
Expand Down Expand Up @@ -76,8 +72,6 @@ async fn main() -> anyhow::Result<()> {

tracing::info!("Using max connections: {max_connections}");

let sess_key = Key::from(game_api_lib::env().dynamic.sess_key.get().as_bytes());

HttpServer::new(move || {
let cors = Cors::default()
.supports_credentials()
Expand All @@ -95,13 +89,16 @@ async fn main() -> anyhow::Result<()> {
.wrap(middleware::from_fn(configure::fit_request_id))
.wrap(TracingLogger::<configure::RootSpanBuilder>::new())
.wrap(
SessionMiddleware::builder(CookieSessionStore::default(), sess_key.clone())
.cookie_secure(cfg!(not(debug_assertions)))
.cookie_content_security(CookieContentSecurity::Private)
.session_lifecycle(PersistentSession::default().session_ttl(
CookieDuration::seconds(game_api_lib::env().auth_token_ttl.get() as i64),
))
.build(),
SessionMiddleware::builder(
CookieSessionStore::default(),
game_api_lib::env().dynamic.sess_key.get(),
)
.cookie_secure(cfg!(not(debug_assertions)))
.cookie_content_security(CookieContentSecurity::Private)
.session_lifecycle(PersistentSession::default().session_ttl(
CookieDuration::seconds(game_api_lib::env().auth_token_ttl.get() as i64),
))
.build(),
)
.configure(|cfg| configure::configure(cfg, db.clone()))
})
Expand Down
5 changes: 4 additions & 1 deletion crates/graphql-api/src/objects/mappack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,12 +146,15 @@ impl Mappack {
async fn leaderboard<'a>(
&'a self,
ctx: &async_graphql::Context<'_>,
limit: Option<isize>,
) -> GqlResult<Vec<MappackPlayer<'a>>> {
let db = ctx.data_unchecked::<Database>();
let mut redis_conn = db.redis_pool.get().await?;

let limit = limit.map(|l| l.saturating_sub(1)).unwrap_or(-1);

let leaderboard: Vec<u32> = redis_conn
.zrange(mappack_lb_key(AnyMappackId::Id(&self.mappack_id)), 0, -1)
.zrange(mappack_lb_key(AnyMappackId::Id(&self.mappack_id)), 0, limit)
.await?;

let mut out = Vec::with_capacity(leaderboard.len());
Expand Down
59 changes: 56 additions & 3 deletions crates/graphql-api/src/objects/root.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,25 @@
use std::borrow::Cow;

use async_graphql::{
ID,
connection::{self, CursorType},
};
use deadpool_redis::redis::{AsyncCommands, ToRedisArgs};
use entity::{
event as event_entity, event_edition, functions, global_records, maps, players, records,
event as event_entity, event_edition, event_edition_records, functions, global_records, maps,
players, records,
};
use records_lib::{
Database, RedisConnection, RedisPool, must,
Database, RedisConnection, RedisPool, internal, must,
opt_event::OptEvent,
ranks,
redis_key::{MapRanking, PlayerRanking, map_ranking, player_ranking},
sync,
};
use sea_orm::{
ColumnTrait, ConnectionTrait, DbConn, EntityTrait, FromQueryResult, Identity, QueryFilter as _,
QueryOrder as _, QuerySelect, Select, SelectModel, StreamTrait, TransactionTrait,
QueryOrder as _, QuerySelect, RelationTrait as _, Select, SelectModel, StreamTrait,
TransactionTrait,
prelude::Expr,
sea_query::{Asterisk, ExprTrait as _, Func, IntoIden, IntoValueTuple, SelectStatement},
};
Expand Down Expand Up @@ -257,6 +261,55 @@ impl QueryRoot {
Ok(r)
}

async fn trending_event_editions(
&self,
ctx: &async_graphql::Context<'_>,
last_days: Option<u8>,
limit: Option<u64>,
) -> GqlResult<Vec<EventEdition<'_>>> {
let conn = ctx.data_unchecked::<DbConn>();

let limit = limit.unwrap_or(3).min(5);
let last_days = last_days.unwrap_or(7).min(30);

let editions = event_edition::Entity::find()
.reverse_join(event_edition_records::Entity)
.join(
sea_orm::JoinType::InnerJoin,
event_edition_records::Relation::Records.def(),
)
.filter(
Expr::current_timestamp().lt(Func::cust("TIMESTAMPADD")
.arg(Expr::custom_keyword("DAY"))
.arg(last_days)
.arg(Expr::col((records::Entity, records::Column::RecordDate)))),
)
.find_also_related(event_entity::Entity)
.expr_as(Expr::col(Asterisk).count(), "records_count")
.group_by(event_edition::Column::EventId)
.group_by(event_edition::Column::Id)
.order_by_desc(Expr::col("records_count"))
.limit(limit)
.all(conn)
.await?
.into_iter()
.map(|(edition, event)| {
GqlResult::Ok(EventEdition {
event: Cow::Owned(
event
.ok_or_else(|| {
internal!("event {} should be present", edition.event_id)
})?
.into(),
),
inner: edition,
})
})
.collect::<Result<Vec<_>, _>>()?;

Ok(editions)
}

async fn record(
&self,
ctx: &async_graphql::Context<'_>,
Expand Down
Loading