Compare commits

...

13 commits
1.2.0 ... main

Author SHA1 Message Date
e8f931392e Update .forgejo/workflows/build+release.yml
All checks were successful
Run Tests on Code / run-tests (push) Successful in 0s
2023-12-18 08:07:23 +00:00
36709b6b80 Update .forgejo/workflows/build+release.yml
All checks were successful
Run Tests on Code / run-tests (push) Successful in 1s
2023-12-18 08:06:12 +00:00
cd79f47caa Fix Version Check in Actions
All checks were successful
Run Tests on Code / run-tests (push) Successful in 0s
2023-12-18 07:50:58 +00:00
48143979e5
Fix actions version check directory
All checks were successful
Run Tests on Code / run-tests (push) Successful in 4s
2023-12-13 19:08:05 +01:00
717e3fda49
Cargo.toml version bump
Some checks failed
Build and Release Binary File / run-tests (push) Failing after 3s
Run Tests on Code / run-tests (push) Successful in 0s
Build and Release Binary File / build (push) Successful in 1m17s
Build and Release Binary File / upload-release (push) Successful in 13s
2023-12-13 19:07:00 +01:00
22fa59334c
move API docs to different route than API endpoints 2023-12-13 19:06:46 +01:00
5c0c03fd7a
Fix Param Naming mismatch 2023-12-13 19:06:32 +01:00
c8ed40c3cc
Add Version Checking to Actions file 2023-12-13 19:05:58 +01:00
8373b278cc
Various Fixes to API v1
All checks were successful
Run Tests on Code / run-tests (push) Successful in 0s
2023-12-12 22:11:32 +01:00
c0799484bb
Add SigTerm handling for systemctl stop
All checks were successful
Build and Release Binary File / run-tests (push) Successful in 0s
Run Tests on Code / run-tests (push) Successful in 0s
Build and Release Binary File / build (push) Successful in 1m12s
Build and Release Binary File / upload-release (push) Successful in 11s
2023-12-12 21:25:57 +01:00
83a1af59d9
General SIGINT handling instead of CTRLC handling
All checks were successful
Run Tests on Code / run-tests (push) Successful in 1s
Build and Release Binary File / run-tests (push) Successful in 0s
Build and Release Binary File / build (push) Successful in 1m14s
Build and Release Binary File / upload-release (push) Successful in 10s
2023-12-12 21:15:50 +01:00
c01ed85e7a
Postgres Watchdog Fix
All checks were successful
Run Tests on Code / run-tests (push) Successful in 0s
Build and Release Binary File / run-tests (push) Successful in 0s
Build and Release Binary File / build (push) Successful in 1m13s
Build and Release Binary File / upload-release (push) Successful in 9s
2023-12-12 21:10:04 +01:00
12938d1ee4
Various linting changes
All checks were successful
Run Tests on Code / run-tests (push) Successful in 0s
2023-12-12 20:49:39 +01:00
8 changed files with 101 additions and 76 deletions

View file

@ -7,12 +7,25 @@ on:
- '[0-9]+.[0-9]+.[0-9]+' - '[0-9]+.[0-9]+.[0-9]+'
- '[0-9]+.[0-9]+.[0-9]+rc[0-9]+' - '[0-9]+.[0-9]+.[0-9]+rc[0-9]+'
jobs: jobs:
run-tests: test:
runs-on: docker runs-on: docker
steps: steps:
-
name: Checking Out Repository Code
uses: https://code.forgejo.org/actions/checkout@v3
- -
name: Placeholder name: Placeholder
run: echo Placeholder Job run: echo Placeholder Job
-
name: Check if Version in Cargo.toml matches Tag
run: |
VERSION=$(cat Cargo.toml | grep -E "(^|\|)version =" | cut -f2- -d= | tr -d \" | tr -d " ")
if test $VERSION != "${{ github.ref_name }}"; then
echo "Expected Version is: '${{ github.ref_name }}' actual Version is: '$VERSION'";
exit 1
else
echo "Version is: '$VERSION'";
fi
build: build:
needs: test needs: test

2
Cargo.lock generated
View file

@ -397,7 +397,7 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]] [[package]]
name = "chellaris-rust-api" name = "chellaris-rust-api"
version = "1.0.3" version = "1.2.4"
dependencies = [ dependencies = [
"actix-web", "actix-web",
"chrono", "chrono",

View file

@ -1,6 +1,6 @@
[package] [package]
name = "chellaris-rust-api" name = "chellaris-rust-api"
version = "1.0.3" version = "1.2.4"
edition = "2021" edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

View file

@ -13,6 +13,7 @@ use chrono::Local;
use actix_web::{middleware::Logger, web, App, HttpServer}; use actix_web::{middleware::Logger, web, App, HttpServer};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sqlx::{PgPool, Pool, Postgres, Connection}; use sqlx::{PgPool, Pool, Postgres, Connection};
use tokio::signal::unix::SignalKind;
use utoipa::{OpenApi, openapi::security::{SecurityScheme, ApiKey, ApiKeyValue}, Modify}; use utoipa::{OpenApi, openapi::security::{SecurityScheme, ApiKey, ApiKeyValue}, Modify};
use utoipa_swagger_ui::{Config, SwaggerUi, Url}; use utoipa_swagger_ui::{Config, SwaggerUi, Url};
@ -220,13 +221,11 @@ async fn main() {
struct ApiDocV1; struct ApiDocV1;
let openapi_urls = vec![ let openapi_urls = vec![
Url::new("v1", concat!(api_base_1!(), "/openapi.json")), Url::new("v1", concat!(api_base!(), "-docs/openapi1.json")),
Url::new("v2-L", concat!(api_base_2!(), "/openapi.json")), Url::new("v2-L", concat!(api_base!(), "-docs/openapi2l.json")),
Url::new("v3-L", concat!(api_base_3!(), "/openapi.json")), Url::new("v3-L", concat!(api_base!(), "-docs/openapi3l.json")),
]; ];
let is_alive: Arc<AtomicBool> = Arc::new(AtomicBool::new(true));
loop { loop {
let db_auth_tokens = config.auth.clone(); let db_auth_tokens = config.auth.clone();
let pool = PgPool::connect(dotenv!("DATABASE_URL")).await.unwrap(); let pool = PgPool::connect(dotenv!("DATABASE_URL")).await.unwrap();
@ -249,14 +248,19 @@ async fn main() {
println!(" -> http://[{}]:{}", Ipv6Addr::UNSPECIFIED, 8080); println!(" -> http://[{}]:{}", Ipv6Addr::UNSPECIFIED, 8080);
println!(" -> http://{}:{}", Ipv4Addr::UNSPECIFIED, 8080); println!(" -> http://{}:{}", Ipv4Addr::UNSPECIFIED, 8080);
let watchdog_thread = tokio::spawn(async move { postgres_watchdog(pool_copy, shutdown_clone) }); let watchdog_thread = tokio::spawn(async move { postgres_watchdog(pool_copy, shutdown_clone).await });
tokio::spawn(async move { tokio::spawn(async move {
actix_web::rt::signal::ctrl_c().await.unwrap(); actix_web::rt::signal::unix::signal(SignalKind::terminate()).unwrap().recv().await;
println!("Ctrl-C received, killing Server"); println!("SIGTERM received, killing Server");
abort()
});
tokio::spawn(async move {
actix_web::rt::signal::unix::signal(SignalKind::interrupt()).unwrap().recv().await;
println!("SIGINT received, killing Server");
abort() abort()
}); });
let server = HttpServer::new(move || { let _ = HttpServer::new(move || {
App::new() App::new()
.app_data(web::Data::new(AppState { db: pool.clone(), auth_tokens: db_auth_tokens.clone() })) .app_data(web::Data::new(AppState { db: pool.clone(), auth_tokens: db_auth_tokens.clone() }))
.wrap(Logger::default()) .wrap(Logger::default())
@ -295,15 +299,15 @@ async fn main() {
SwaggerUi::new(concat!(api_base!(), "/swagger/{_:.*}")) SwaggerUi::new(concat!(api_base!(), "/swagger/{_:.*}"))
.urls(vec![ .urls(vec![
( (
Url::new("v1", concat!(api_base_1!(), "/openapi.json")), Url::new("v1", concat!(api_base!(), "-docs/openapi1.json")),
openapi_v1.clone(), openapi_v1.clone(),
), ),
( (
Url::new("v2-l", concat!(api_base_2!(), "/openapi.json")), Url::new("v2-l", concat!(api_base!(), "-docs/openapi2l.json")),
openapi_v2_l.clone(), openapi_v2_l.clone(),
), ),
( (
Url::new("v3-l", concat!(api_base_3!(), "/openapi.json")), Url::new("v3-l", concat!(api_base!(), "-docs/openapi3l.json")),
openapi_v3_l.clone(), openapi_v3_l.clone(),
), ),
]) ])

View file

@ -1,5 +1,4 @@
use crate::{db, AppState}; use crate::{db, AppState};
use actix_web::web::Json;
use actix_web::{delete, get, post, put, web, HttpRequest, HttpResponse, Responder}; use actix_web::{delete, get, post, put, web, HttpRequest, HttpResponse, Responder};
use std::collections::HashMap; use std::collections::HashMap;
use std::ops::Deref; use std::ops::Deref;
@ -9,7 +8,7 @@ use sqlx::QueryBuilder;
pub(crate) mod schemas; pub(crate) mod schemas;
fn get_auth_header<'a>(req: &'a HttpRequest) -> Option<&'a str> { fn get_auth_header(req: &HttpRequest) -> Option<&str> {
req.headers().get("x-api-key")?.to_str().ok() req.headers().get("x-api-key")?.to_str().ok()
} }
@ -49,7 +48,7 @@ async fn verify_user_auth(data: &web::Data<AppState>, auth_token: &str, user_tok
// User Endpoints // User Endpoints
#[utoipa::path( #[utoipa::path(
request_body = schemas::GetUserParams, request_body = GetUserParams,
responses( responses(
(status = 200, description = "OK", body = User), (status = 200, description = "OK", body = User),
(status = 403, description = "Unauthorized"), (status = 403, description = "Unauthorized"),
@ -59,8 +58,8 @@ async fn verify_user_auth(data: &web::Data<AppState>, auth_token: &str, user_tok
("api_key" = []) ("api_key" = [])
), ),
)] )]
#[get("/api/v1/user")] #[post("/api/v1/user")]
async fn get_user( pub(crate) async fn get_user(
data: web::Data<AppState>, data: web::Data<AppState>,
params: web::Json<schemas::GetUserParams>, params: web::Json<schemas::GetUserParams>,
req: HttpRequest, req: HttpRequest,
@ -90,18 +89,18 @@ async fn get_user(
Err(_) => return HttpResponse::InternalServerError().finish(), Err(_) => return HttpResponse::InternalServerError().finish(),
}; };
let mut permissions: HashMap<String, bool> = HashMap::new(); let mut user_permissions: HashMap<String, bool> = HashMap::new();
permissions.insert("game_permissions".to_string(), user.game_permissions); user_permissions.insert("game_permissions".to_string(), user.game_permissions);
permissions.insert("empire_permissions".to_string(), user.empire_permissions); user_permissions.insert("empire_permissions".to_string(), user.empire_permissions);
permissions.insert("data_permissions".to_string(), user.data_permissions); user_permissions.insert("data_permissions".to_string(), user.data_permissions);
permissions.insert("user_permissions".to_string(), user.user_permissions); user_permissions.insert("user_permissions".to_string(), user.user_permissions);
let return_data = schemas::User { let return_data = schemas::User {
user_token: user.token, user_token: user.token,
discord_handle: user.discord_id, discord_handle: user.discord_id,
profile_picture: user.picture_url, profile_picture: user.picture_url,
permissions: permissions permissions: user_permissions
}; };
return HttpResponse::Ok().json(return_data); return HttpResponse::Ok().json(return_data);
@ -117,7 +116,7 @@ async fn get_user(
(status = 500, description = "Internal Server Error") (status = 500, description = "Internal Server Error")
), ),
)] )]
#[post("/api/v1/user")] #[post("/api/v1/user/create")]
pub(crate) async fn create_user( pub(crate) async fn create_user(
data: web::Data<AppState>, data: web::Data<AppState>,
) -> impl Responder { ) -> impl Responder {
@ -196,10 +195,18 @@ pub(crate) async fn update_user(
None => return HttpResponse::Unauthorized().finish(), None => return HttpResponse::Unauthorized().finish(),
}; };
let mut user_permissions: HashMap<String, bool> = HashMap::new();
match params.permissions {
Some(data) => {user_permissions = data.clone()},
None => {},
}
let mut elevated_auth = false; let mut elevated_auth = false;
if params.permissions["game_permissions"] || params.permissions["empire_permissions"] || params.permissions["data_permissions"] || params.permissions["user_permissions"] { if user_permissions.len() != 0 {
if user_permissions["game_permissions"] || user_permissions["empire_permissions"] || user_permissions["data_permissions"] || user_permissions["user_permissions"] {
elevated_auth = true; elevated_auth = true;
} }
}
let auth = verify_user_auth(&data, &auth_token, &params.user_token, schemas::TablePermission::User, elevated_auth).await; let auth = verify_user_auth(&data, &auth_token, &params.user_token, schemas::TablePermission::User, elevated_auth).await;
@ -226,7 +233,8 @@ pub(crate) async fn update_user(
any_param_present = true; any_param_present = true;
} }
for (entry, value) in params.permissions.iter() { if user_permissions.len() != 0 {
for (entry, value) in user_permissions.iter() {
match entry.deref() { match entry.deref() {
"game_permissions" => { "game_permissions" => {
user_query_separated.push( " game_permissions = "); user_query_separated.push( " game_permissions = ");
@ -263,6 +271,7 @@ pub(crate) async fn update_user(
_ => {} _ => {}
} }
} }
}
if any_param_present { if any_param_present {
user_query_separated.push_unseparated(" WHERE token = ").push_bind_unseparated(params.user_token); user_query_separated.push_unseparated(" WHERE token = ").push_bind_unseparated(params.user_token);
@ -290,17 +299,17 @@ pub(crate) async fn update_user(
}; };
} }
let mut permissions: HashMap<String, bool> = HashMap::new(); let mut user_permissions: HashMap<String, bool> = HashMap::new();
permissions.insert("game_permissions".to_string(), user.game_permissions); user_permissions.insert("game_permissions".to_string(), user.game_permissions);
permissions.insert("empire_permissions".to_string(), user.empire_permissions); user_permissions.insert("empire_permissions".to_string(), user.empire_permissions);
permissions.insert("data_permissions".to_string(), user.data_permissions); user_permissions.insert("data_permissions".to_string(), user.data_permissions);
permissions.insert("user_permissions".to_string(), user.user_permissions); user_permissions.insert("user_permissions".to_string(), user.user_permissions);
let return_data = schemas::User { let return_data = schemas::User {
user_token: user.token, user_token: user.token,
discord_handle: user.discord_id, discord_handle: user.discord_id,
profile_picture: user.picture_url, profile_picture: user.picture_url,
permissions: permissions permissions: user_permissions
}; };
return HttpResponse::Ok().json(return_data); return HttpResponse::Ok().json(return_data);
} else { } else {
@ -309,7 +318,7 @@ pub(crate) async fn update_user(
} }
#[utoipa::path( #[utoipa::path(
request_body = schemas::DeleteUserParams, request_body = DeleteUserParams,
responses( responses(
(status = 200, description = "OK"), (status = 200, description = "OK"),
(status = 403, description = "Unauthorized"), (status = 403, description = "Unauthorized"),
@ -348,7 +357,7 @@ pub(crate) async fn delete_user(
.await .await
{ {
Ok(_) => {} Ok(_) => {}
Err(e) => { Err(_) => {
return HttpResponse::InternalServerError().finish(); return HttpResponse::InternalServerError().finish();
} }
}; };

View file

@ -1,7 +1,6 @@
use std::collections::HashMap; use std::collections::HashMap;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use utoipa::{IntoParams, ToSchema}; use utoipa::{IntoParams, ToSchema};
use crate::v3::schemas::ChellarisGameLegacy;
// DB Permission Enums // DB Permission Enums
@ -55,7 +54,7 @@ pub struct UpdateUserParams {
[\"user_permissions\"]: false, [\"user_permissions\"]: false,
}\ }\
")] ")]
pub permissions: HashMap<String, bool>, pub permissions: Option<HashMap<String, bool>>,
} }
#[derive(Serialize, Deserialize, ToSchema, Debug)] #[derive(Serialize, Deserialize, ToSchema, Debug)]

View file

@ -31,7 +31,7 @@ fn verify_auth(token: Option<&str>, data: &AppState) -> schemas::AuthReturn {
return auth_return; return auth_return;
} }
fn get_auth_header<'a>(req: &'a HttpRequest) -> Option<&'a str> { fn get_auth_header(req: &HttpRequest) -> Option<&str> {
req.headers().get("x-api-key")?.to_str().ok() req.headers().get("x-api-key")?.to_str().ok()
} }

View file

@ -1,4 +1,4 @@
use std::{collections::HashMap, hash::Hash}; use std::{collections::HashMap};
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
use utoipa::{ToSchema, IntoParams}; use utoipa::{ToSchema, IntoParams};