mirror of
https://github.com/PluralKit/PluralKit.git
synced 2026-02-16 10:40:12 +00:00
feat(api): allow unauthed requests to /systems/:id/settings
This commit is contained in:
parent
0406c32f6b
commit
0610701252
14 changed files with 334 additions and 70 deletions
6
Cargo.lock
generated
6
Cargo.lock
generated
|
|
@ -276,8 +276,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "axum"
|
name = "axum"
|
||||||
version = "0.8.4"
|
version = "0.8.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://github.com/pluralkit/axum?branch=v0.8.4-pluralkit#3b45806719f27e69aed912bac724056b910f4aa6"
|
||||||
checksum = "021e862c184ae977658b36c4500f7feac3221ca5da43e3f25bd04ab6c79a29b5"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"axum-core 0.5.2",
|
"axum-core 0.5.2",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
|
@ -327,8 +326,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "axum-core"
|
name = "axum-core"
|
||||||
version = "0.5.2"
|
version = "0.5.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://github.com/pluralkit/axum?branch=v0.8.4-pluralkit#3b45806719f27e69aed912bac724056b910f4aa6"
|
||||||
checksum = "68464cd0412f486726fb3373129ef5d2993f90c34bc2bc1c1e9943b2f4fc7ca6"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ resolver = "2"
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
axum = "0.8.4"
|
|
||||||
axum-macros = "0.4.1"
|
axum-macros = "0.4.1"
|
||||||
bytes = "1.6.0"
|
bytes = "1.6.0"
|
||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
|
|
@ -24,6 +23,8 @@ tracing = "0.1"
|
||||||
tracing-subscriber = { version = "0.3.16", features = ["env-filter", "json"] }
|
tracing-subscriber = { version = "0.3.16", features = ["env-filter", "json"] }
|
||||||
uuid = { version = "1.7.0", features = ["serde"] }
|
uuid = { version = "1.7.0", features = ["serde"] }
|
||||||
|
|
||||||
|
axum = { git = "https://github.com/pluralkit/axum", branch = "v0.8.4-pluralkit" }
|
||||||
|
|
||||||
twilight-gateway = { git = "https://github.com/pluralkit/twilight", branch = "pluralkit-fb4f590" }
|
twilight-gateway = { git = "https://github.com/pluralkit/twilight", branch = "pluralkit-fb4f590" }
|
||||||
twilight-cache-inmemory = { git = "https://github.com/pluralkit/twilight", branch = "pluralkit-fb4f590", features = ["permission-calculator"] }
|
twilight-cache-inmemory = { git = "https://github.com/pluralkit/twilight", branch = "pluralkit-fb4f590", features = ["permission-calculator"] }
|
||||||
twilight-util = { git = "https://github.com/pluralkit/twilight", branch = "pluralkit-fb4f590", features = ["permission-calculator"] }
|
twilight-util = { git = "https://github.com/pluralkit/twilight", branch = "pluralkit-fb4f590", features = ["permission-calculator"] }
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
use pluralkit_models::{PKSystem, PrivacyLevel, SystemId};
|
||||||
|
|
||||||
pub const INTERNAL_SYSTEMID_HEADER: &'static str = "x-pluralkit-systemid";
|
pub const INTERNAL_SYSTEMID_HEADER: &'static str = "x-pluralkit-systemid";
|
||||||
pub const INTERNAL_APPID_HEADER: &'static str = "x-pluralkit-appid";
|
pub const INTERNAL_APPID_HEADER: &'static str = "x-pluralkit-appid";
|
||||||
|
|
||||||
|
|
@ -19,4 +21,28 @@ impl AuthState {
|
||||||
pub fn app_id(&self) -> Option<i32> {
|
pub fn app_id(&self) -> Option<i32> {
|
||||||
self.app_id
|
self.app_id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn access_level_for(&self, a: &impl Authable) -> PrivacyLevel {
|
||||||
|
if self
|
||||||
|
.system_id
|
||||||
|
.map(|id| id == a.authable_system_id())
|
||||||
|
.unwrap_or(false)
|
||||||
|
{
|
||||||
|
PrivacyLevel::Private
|
||||||
|
} else {
|
||||||
|
PrivacyLevel::Public
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// authable trait/impls
|
||||||
|
|
||||||
|
pub trait Authable {
|
||||||
|
fn authable_system_id(&self) -> SystemId;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Authable for PKSystem {
|
||||||
|
fn authable_system_id(&self) -> SystemId {
|
||||||
|
self.id
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1 +1,2 @@
|
||||||
pub mod private;
|
pub mod private;
|
||||||
|
pub mod system;
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,7 @@ use axum::{
|
||||||
};
|
};
|
||||||
use hyper::StatusCode;
|
use hyper::StatusCode;
|
||||||
use libpk::config;
|
use libpk::config;
|
||||||
use pluralkit_models::{PKSystem, PKSystemConfig};
|
use pluralkit_models::{PKSystem, PKSystemConfig, PrivacyLevel};
|
||||||
use reqwest::ClientBuilder;
|
use reqwest::ClientBuilder;
|
||||||
|
|
||||||
#[derive(serde::Deserialize, Debug)]
|
#[derive(serde::Deserialize, Debug)]
|
||||||
|
|
@ -151,14 +151,12 @@ pub async fn discord_callback(
|
||||||
.await
|
.await
|
||||||
.expect("failed to query");
|
.expect("failed to query");
|
||||||
|
|
||||||
if system.is_none() {
|
let Some(system) = system else {
|
||||||
return json_err(
|
return json_err(
|
||||||
StatusCode::BAD_REQUEST,
|
StatusCode::BAD_REQUEST,
|
||||||
"user does not have a system registered".to_string(),
|
"user does not have a system registered".to_string(),
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
let system = system.unwrap();
|
|
||||||
|
|
||||||
let system_config: Option<PKSystemConfig> = sqlx::query_as(
|
let system_config: Option<PKSystemConfig> = sqlx::query_as(
|
||||||
r#"
|
r#"
|
||||||
|
|
@ -179,7 +177,7 @@ pub async fn discord_callback(
|
||||||
(
|
(
|
||||||
StatusCode::OK,
|
StatusCode::OK,
|
||||||
serde_json::to_string(&serde_json::json!({
|
serde_json::to_string(&serde_json::json!({
|
||||||
"system": system.to_json(),
|
"system": system.to_json(PrivacyLevel::Private),
|
||||||
"config": system_config.to_json(),
|
"config": system_config.to_json(),
|
||||||
"user": user,
|
"user": user,
|
||||||
"token": token,
|
"token": token,
|
||||||
|
|
|
||||||
68
crates/api/src/endpoints/system.rs
Normal file
68
crates/api/src/endpoints/system.rs
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
use axum::{
|
||||||
|
extract::State,
|
||||||
|
http::StatusCode,
|
||||||
|
response::{IntoResponse, Response},
|
||||||
|
Extension,
|
||||||
|
};
|
||||||
|
use serde_json::json;
|
||||||
|
use sqlx::Postgres;
|
||||||
|
use tracing::error;
|
||||||
|
|
||||||
|
use pluralkit_models::{PKSystem, PKSystemConfig, PrivacyLevel};
|
||||||
|
|
||||||
|
use crate::{auth::AuthState, util::json_err, ApiContext};
|
||||||
|
|
||||||
|
pub async fn get_system_settings(
|
||||||
|
Extension(auth): Extension<AuthState>,
|
||||||
|
Extension(system): Extension<PKSystem>,
|
||||||
|
State(ctx): State<ApiContext>,
|
||||||
|
) -> Response {
|
||||||
|
let access_level = auth.access_level_for(&system);
|
||||||
|
|
||||||
|
let config = match sqlx::query_as::<Postgres, PKSystemConfig>(
|
||||||
|
"select * from system_config where system = $1",
|
||||||
|
)
|
||||||
|
.bind(system.id)
|
||||||
|
.fetch_optional(&ctx.db)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(Some(config)) => config,
|
||||||
|
Ok(None) => {
|
||||||
|
error!(
|
||||||
|
system = system.id,
|
||||||
|
"failed to find system config for existing system"
|
||||||
|
);
|
||||||
|
return json_err(
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
r#"{"message": "500: Internal Server Error", "code": 0}"#.to_string(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
error!(?err, "failed to query system config");
|
||||||
|
return json_err(
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
r#"{"message": "500: Internal Server Error", "code": 0}"#.to_string(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
(
|
||||||
|
StatusCode::OK,
|
||||||
|
serde_json::to_string(&match access_level {
|
||||||
|
PrivacyLevel::Private => config.to_json(),
|
||||||
|
PrivacyLevel::Public => json!({
|
||||||
|
"pings_enabled": config.pings_enabled,
|
||||||
|
"latch_timeout": config.latch_timeout,
|
||||||
|
"case_sensitive_proxy_tags": config.case_sensitive_proxy_tags,
|
||||||
|
"proxy_error_message_enabled": config.proxy_error_message_enabled,
|
||||||
|
"hid_display_split": config.hid_display_split,
|
||||||
|
"hid_display_caps": config.hid_display_caps,
|
||||||
|
"hid_list_padding": config.hid_list_padding,
|
||||||
|
"proxy_switch": config.proxy_switch,
|
||||||
|
"name_format": config.name_format,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
.into_response()
|
||||||
|
}
|
||||||
|
|
@ -77,7 +77,7 @@ fn router(ctx: ApiContext) -> Router {
|
||||||
Router::new()
|
Router::new()
|
||||||
.route("/v2/systems/{system_id}", get(rproxy))
|
.route("/v2/systems/{system_id}", get(rproxy))
|
||||||
.route("/v2/systems/{system_id}", patch(rproxy))
|
.route("/v2/systems/{system_id}", patch(rproxy))
|
||||||
.route("/v2/systems/{system_id}/settings", get(rproxy))
|
.route("/v2/systems/{system_id}/settings", get(endpoints::system::get_system_settings))
|
||||||
.route("/v2/systems/{system_id}/settings", patch(rproxy))
|
.route("/v2/systems/{system_id}/settings", patch(rproxy))
|
||||||
|
|
||||||
.route("/v2/systems/{system_id}/members", get(rproxy))
|
.route("/v2/systems/{system_id}/members", get(rproxy))
|
||||||
|
|
@ -134,10 +134,12 @@ fn router(ctx: ApiContext) -> Router {
|
||||||
.route("/v2/groups/{group_id}/oembed.json", get(rproxy))
|
.route("/v2/groups/{group_id}/oembed.json", get(rproxy))
|
||||||
|
|
||||||
.layer(middleware::ratelimit::ratelimiter(middleware::ratelimit::do_request_ratelimited)) // this sucks
|
.layer(middleware::ratelimit::ratelimiter(middleware::ratelimit::do_request_ratelimited)) // this sucks
|
||||||
.layer(axum::middleware::from_fn(middleware::ignore_invalid_routes))
|
|
||||||
.layer(axum::middleware::from_fn(middleware::cors))
|
|
||||||
.layer(axum::middleware::from_fn(middleware::logger))
|
|
||||||
|
|
||||||
|
.layer(axum::middleware::from_fn(middleware::ignore_invalid_routes::ignore_invalid_routes))
|
||||||
|
.layer(axum::middleware::from_fn(middleware::cors::cors))
|
||||||
|
.layer(axum::middleware::from_fn(middleware::logger::logger))
|
||||||
|
|
||||||
|
.layer(axum::middleware::from_fn_with_state(ctx.clone(), middleware::params::params))
|
||||||
.layer(axum::middleware::from_fn_with_state(ctx.clone(), middleware::auth::auth))
|
.layer(axum::middleware::from_fn_with_state(ctx.clone(), middleware::auth::auth))
|
||||||
|
|
||||||
.layer(tower_http::catch_panic::CatchPanicLayer::custom(util::handle_panic))
|
.layer(tower_http::catch_panic::CatchPanicLayer::custom(util::handle_panic))
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,6 @@
|
||||||
mod cors;
|
|
||||||
pub use cors::cors;
|
|
||||||
|
|
||||||
mod logger;
|
|
||||||
pub use logger::logger;
|
|
||||||
|
|
||||||
mod ignore_invalid_routes;
|
|
||||||
pub use ignore_invalid_routes::ignore_invalid_routes;
|
|
||||||
|
|
||||||
pub mod ratelimit;
|
|
||||||
|
|
||||||
pub mod auth;
|
pub mod auth;
|
||||||
|
pub mod cors;
|
||||||
|
pub mod ignore_invalid_routes;
|
||||||
|
pub mod logger;
|
||||||
|
pub mod params;
|
||||||
|
pub mod ratelimit;
|
||||||
|
|
|
||||||
123
crates/api/src/middleware/params.rs
Normal file
123
crates/api/src/middleware/params.rs
Normal file
|
|
@ -0,0 +1,123 @@
|
||||||
|
use axum::{
|
||||||
|
extract::{Request, State},
|
||||||
|
http::StatusCode,
|
||||||
|
middleware::Next,
|
||||||
|
response::Response,
|
||||||
|
routing::url_params::UrlParams,
|
||||||
|
};
|
||||||
|
|
||||||
|
use sqlx::{types::Uuid, Postgres};
|
||||||
|
use tracing::error;
|
||||||
|
|
||||||
|
use crate::auth::AuthState;
|
||||||
|
use crate::{util::json_err, ApiContext};
|
||||||
|
use pluralkit_models::PKSystem;
|
||||||
|
|
||||||
|
pub async fn params(State(ctx): State<ApiContext>, mut req: Request, next: Next) -> Response {
|
||||||
|
let pms = match req.extensions().get::<UrlParams>() {
|
||||||
|
None => Vec::new(),
|
||||||
|
Some(UrlParams::Params(pms)) => pms.clone(),
|
||||||
|
_ => {
|
||||||
|
return json_err(
|
||||||
|
StatusCode::BAD_REQUEST,
|
||||||
|
r#"{"error": "400: Bad Request", "code": 0}"#.to_string(),
|
||||||
|
)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (key, value) in pms {
|
||||||
|
match key.as_ref() {
|
||||||
|
"system_id" => match value.as_str() {
|
||||||
|
"@me" => {
|
||||||
|
let Some(system_id) = req
|
||||||
|
.extensions()
|
||||||
|
.get::<AuthState>()
|
||||||
|
.expect("missing auth state")
|
||||||
|
.system_id()
|
||||||
|
else {
|
||||||
|
return json_err(
|
||||||
|
StatusCode::UNAUTHORIZED,
|
||||||
|
r#"{"error": "401: Missing or invalid Authorization header", "code": 0}"#.to_string(),
|
||||||
|
)
|
||||||
|
.into();
|
||||||
|
};
|
||||||
|
|
||||||
|
match sqlx::query_as::<Postgres, PKSystem>(
|
||||||
|
"select * from systems where id = $1",
|
||||||
|
)
|
||||||
|
.bind(system_id)
|
||||||
|
.fetch_optional(&ctx.db)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(Some(system)) => {
|
||||||
|
req.extensions_mut().insert(system);
|
||||||
|
}
|
||||||
|
Ok(None) => {
|
||||||
|
error!(
|
||||||
|
?system_id,
|
||||||
|
"could not find previously authenticated system in db"
|
||||||
|
);
|
||||||
|
return json_err(
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
r#"{"message": "500: Internal Server Error", "code": 0}"#
|
||||||
|
.to_string(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
error!(
|
||||||
|
?err,
|
||||||
|
"failed to query previously authenticated system in db"
|
||||||
|
);
|
||||||
|
return json_err(
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
r#"{"message": "500: Internal Server Error", "code": 0}"#
|
||||||
|
.to_string(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
id => {
|
||||||
|
match match Uuid::parse_str(id) {
|
||||||
|
Ok(uuid) => sqlx::query_as::<Postgres, PKSystem>(
|
||||||
|
"select * from systems where uuid = $1",
|
||||||
|
)
|
||||||
|
.bind(uuid),
|
||||||
|
Err(_) => sqlx::query_as::<Postgres, PKSystem>(
|
||||||
|
"select * from systems where hid = $1",
|
||||||
|
)
|
||||||
|
.bind(id),
|
||||||
|
}
|
||||||
|
.fetch_optional(&ctx.db)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(Some(system)) => {
|
||||||
|
req.extensions_mut().insert(system);
|
||||||
|
}
|
||||||
|
Ok(None) => {
|
||||||
|
return json_err(
|
||||||
|
StatusCode::NOT_FOUND,
|
||||||
|
r#"{"message":"System not found.","code":20001}"#.to_string(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
error!(?err, ?id, "failed to query system from path in db");
|
||||||
|
return json_err(
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
r#"{"message": "500: Internal Server Error", "code": 0}"#
|
||||||
|
.to_string(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"member_id" => {}
|
||||||
|
"group_id" => {}
|
||||||
|
"switch_id" => {}
|
||||||
|
"guild_id" => {}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
next.run(req).await
|
||||||
|
}
|
||||||
|
|
@ -16,6 +16,7 @@ struct ModelField {
|
||||||
patch: ElemPatchability,
|
patch: ElemPatchability,
|
||||||
json: Option<Expr>,
|
json: Option<Expr>,
|
||||||
is_privacy: bool,
|
is_privacy: bool,
|
||||||
|
privacy: Option<Expr>,
|
||||||
default: Option<Expr>,
|
default: Option<Expr>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -26,6 +27,7 @@ fn parse_field(field: syn::Field) -> ModelField {
|
||||||
patch: ElemPatchability::None,
|
patch: ElemPatchability::None,
|
||||||
json: None,
|
json: None,
|
||||||
is_privacy: false,
|
is_privacy: false,
|
||||||
|
privacy: None,
|
||||||
default: None,
|
default: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -61,6 +63,12 @@ fn parse_field(field: syn::Field) -> ModelField {
|
||||||
}
|
}
|
||||||
f.json = Some(nv.value.clone());
|
f.json = Some(nv.value.clone());
|
||||||
}
|
}
|
||||||
|
"privacy" => {
|
||||||
|
if f.privacy.is_some() {
|
||||||
|
panic!("cannot set privacy multiple times for same field");
|
||||||
|
}
|
||||||
|
f.privacy = Some(nv.value.clone());
|
||||||
|
}
|
||||||
"default" => {
|
"default" => {
|
||||||
if f.default.is_some() {
|
if f.default.is_some() {
|
||||||
panic!("cannot set default multiple times for same field");
|
panic!("cannot set default multiple times for same field");
|
||||||
|
|
@ -107,8 +115,6 @@ pub fn macro_impl(
|
||||||
panic!("fields of a struct must be named");
|
panic!("fields of a struct must be named");
|
||||||
};
|
};
|
||||||
|
|
||||||
// println!("{}: {:#?}", tname, fields);
|
|
||||||
|
|
||||||
let tfields = mk_tfields(fields.clone());
|
let tfields = mk_tfields(fields.clone());
|
||||||
let from_json = mk_tfrom_json(fields.clone());
|
let from_json = mk_tfrom_json(fields.clone());
|
||||||
let _from_sql = mk_tfrom_sql(fields.clone());
|
let _from_sql = mk_tfrom_sql(fields.clone());
|
||||||
|
|
@ -137,9 +143,7 @@ pub fn macro_impl(
|
||||||
#from_json
|
#from_json
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_json(self) -> serde_json::Value {
|
#to_json
|
||||||
#to_json
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
|
@ -188,19 +192,28 @@ fn mk_tfrom_sql(_fields: Vec<ModelField>) -> TokenStream {
|
||||||
quote! { unimplemented!(); }
|
quote! { unimplemented!(); }
|
||||||
}
|
}
|
||||||
fn mk_tto_json(fields: Vec<ModelField>) -> TokenStream {
|
fn mk_tto_json(fields: Vec<ModelField>) -> TokenStream {
|
||||||
// todo: check privacy access
|
let has_privacy = fields.iter().any(|f| f.privacy.is_some());
|
||||||
let fielddefs: TokenStream = fields
|
let fielddefs: TokenStream = fields
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|f| {
|
.filter_map(|f| {
|
||||||
f.json.as_ref().map(|v| {
|
f.json.as_ref().map(|v| {
|
||||||
let tname = f.name.clone();
|
let tname = f.name.clone();
|
||||||
if let Some(default) = f.default.as_ref() {
|
let maybepriv = if let Some(privacy) = f.privacy.as_ref() {
|
||||||
quote! {
|
quote! {
|
||||||
#v: self.#tname.unwrap_or(#default),
|
#v: crate::_util::privacy_lookup!(self.#tname, self.#privacy, lookup_level)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
quote! {
|
quote! {
|
||||||
#v: self.#tname,
|
#v: self.#tname
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if let Some(default) = f.default.as_ref() {
|
||||||
|
quote! {
|
||||||
|
#maybepriv.unwrap_or(#default),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
quote! {
|
||||||
|
#maybepriv,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
@ -222,13 +235,35 @@ fn mk_tto_json(fields: Vec<ModelField>) -> TokenStream {
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
quote! {
|
let privdef = if has_privacy {
|
||||||
serde_json::json!({
|
quote! {
|
||||||
#fielddefs
|
, lookup_level: crate::PrivacyLevel
|
||||||
"privacy": {
|
}
|
||||||
#privacyfielddefs
|
} else {
|
||||||
|
quote! {}
|
||||||
|
};
|
||||||
|
|
||||||
|
let privacy_fielddefs = if has_privacy {
|
||||||
|
quote! {
|
||||||
|
"privacy": if matches!(lookup_level, crate::PrivacyLevel::Private) {
|
||||||
|
Some(serde_json::json!({
|
||||||
|
#privacyfielddefs
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
} else {
|
||||||
|
quote! {}
|
||||||
|
};
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
pub fn to_json(self #privdef) -> serde_json::Value {
|
||||||
|
serde_json::json!({
|
||||||
|
#fielddefs
|
||||||
|
#privacy_fielddefs
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,3 +33,17 @@ macro_rules! fake_enum_impls {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) use fake_enum_impls;
|
pub(crate) use fake_enum_impls;
|
||||||
|
|
||||||
|
macro_rules! privacy_lookup {
|
||||||
|
($v:expr, $vprivacy:expr, $lookup_level:expr) => {
|
||||||
|
if matches!($vprivacy, crate::PrivacyLevel::Public)
|
||||||
|
|| matches!($lookup_level, crate::PrivacyLevel::Private)
|
||||||
|
{
|
||||||
|
Some($v)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) use privacy_lookup;
|
||||||
|
|
|
||||||
|
|
@ -9,3 +9,25 @@ macro_rules! model {
|
||||||
|
|
||||||
model!(system);
|
model!(system);
|
||||||
model!(system_config);
|
model!(system_config);
|
||||||
|
|
||||||
|
#[derive(serde::Serialize, Debug, Clone)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub enum PrivacyLevel {
|
||||||
|
Public,
|
||||||
|
Private,
|
||||||
|
}
|
||||||
|
|
||||||
|
// this sucks, put it somewhere else
|
||||||
|
use sqlx::{postgres::PgTypeInfo, Database, Decode, Postgres, Type};
|
||||||
|
use std::error::Error;
|
||||||
|
_util::fake_enum_impls!(PrivacyLevel);
|
||||||
|
|
||||||
|
impl From<i32> for PrivacyLevel {
|
||||||
|
fn from(value: i32) -> Self {
|
||||||
|
match value {
|
||||||
|
1 => PrivacyLevel::Public,
|
||||||
|
2 => PrivacyLevel::Private,
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,36 +1,13 @@
|
||||||
use std::error::Error;
|
|
||||||
|
|
||||||
use pk_macros::pk_model;
|
use pk_macros::pk_model;
|
||||||
|
|
||||||
use chrono::NaiveDateTime;
|
use chrono::NaiveDateTime;
|
||||||
use sqlx::{postgres::PgTypeInfo, Database, Decode, Postgres, Type};
|
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::_util::fake_enum_impls;
|
use crate::PrivacyLevel;
|
||||||
|
|
||||||
// todo: fix this
|
// todo: fix this
|
||||||
pub type SystemId = i32;
|
pub type SystemId = i32;
|
||||||
|
|
||||||
// todo: move this
|
|
||||||
#[derive(serde::Serialize, Debug, Clone)]
|
|
||||||
#[serde(rename_all = "snake_case")]
|
|
||||||
pub enum PrivacyLevel {
|
|
||||||
Public,
|
|
||||||
Private,
|
|
||||||
}
|
|
||||||
|
|
||||||
fake_enum_impls!(PrivacyLevel);
|
|
||||||
|
|
||||||
impl From<i32> for PrivacyLevel {
|
|
||||||
fn from(value: i32) -> Self {
|
|
||||||
match value {
|
|
||||||
1 => PrivacyLevel::Public,
|
|
||||||
2 => PrivacyLevel::Private,
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[pk_model]
|
#[pk_model]
|
||||||
struct System {
|
struct System {
|
||||||
id: SystemId,
|
id: SystemId,
|
||||||
|
|
@ -40,21 +17,25 @@ struct System {
|
||||||
#[json = "uuid"]
|
#[json = "uuid"]
|
||||||
uuid: Uuid,
|
uuid: Uuid,
|
||||||
#[json = "name"]
|
#[json = "name"]
|
||||||
|
#[privacy = name_privacy]
|
||||||
name: Option<String>,
|
name: Option<String>,
|
||||||
#[json = "description"]
|
#[json = "description"]
|
||||||
|
#[privacy = description_privacy]
|
||||||
description: Option<String>,
|
description: Option<String>,
|
||||||
#[json = "tag"]
|
#[json = "tag"]
|
||||||
tag: Option<String>,
|
tag: Option<String>,
|
||||||
#[json = "pronouns"]
|
#[json = "pronouns"]
|
||||||
|
#[privacy = pronoun_privacy]
|
||||||
pronouns: Option<String>,
|
pronouns: Option<String>,
|
||||||
#[json = "avatar_url"]
|
#[json = "avatar_url"]
|
||||||
|
#[privacy = avatar_privacy]
|
||||||
avatar_url: Option<String>,
|
avatar_url: Option<String>,
|
||||||
#[json = "banner_image"]
|
#[json = "banner"]
|
||||||
|
#[privacy = banner_privacy]
|
||||||
banner_image: Option<String>,
|
banner_image: Option<String>,
|
||||||
#[json = "color"]
|
#[json = "color"]
|
||||||
color: Option<String>,
|
color: Option<String>,
|
||||||
token: Option<String>,
|
token: Option<String>,
|
||||||
#[json = "webhook_url"]
|
|
||||||
webhook_url: Option<String>,
|
webhook_url: Option<String>,
|
||||||
webhook_token: Option<String>,
|
webhook_token: Option<String>,
|
||||||
#[json = "created"]
|
#[json = "created"]
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ pub const DEFAULT_GROUP_LIMIT: i32 = 250;
|
||||||
|
|
||||||
#[derive(serde::Serialize, Debug, Clone)]
|
#[derive(serde::Serialize, Debug, Clone)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
enum HidPadFormat {
|
pub enum HidPadFormat {
|
||||||
#[serde(rename = "off")]
|
#[serde(rename = "off")]
|
||||||
None,
|
None,
|
||||||
Left,
|
Left,
|
||||||
|
|
@ -31,7 +31,7 @@ impl From<i32> for HidPadFormat {
|
||||||
|
|
||||||
#[derive(serde::Serialize, Debug, Clone)]
|
#[derive(serde::Serialize, Debug, Clone)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
enum ProxySwitchAction {
|
pub enum ProxySwitchAction {
|
||||||
Off,
|
Off,
|
||||||
New,
|
New,
|
||||||
Add,
|
Add,
|
||||||
|
|
@ -83,7 +83,8 @@ struct SystemConfig {
|
||||||
#[json = "proxy_switch"]
|
#[json = "proxy_switch"]
|
||||||
proxy_switch: ProxySwitchAction,
|
proxy_switch: ProxySwitchAction,
|
||||||
#[json = "name_format"]
|
#[json = "name_format"]
|
||||||
name_format: String,
|
#[default = "{name} {tag}".to_string()]
|
||||||
|
name_format: Option<String>,
|
||||||
#[json = "description_templates"]
|
#[json = "description_templates"]
|
||||||
description_templates: Vec<String>,
|
description_templates: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue