Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor: add top-level model module #1317

Merged
merged 4 commits into from
Jan 20, 2025
Merged
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
8 changes: 4 additions & 4 deletions .deployment/files/known-groups.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"ROLE_STUDENT": { "label": { "en": "Students", "de": "Studierende" }, "implies": [], "large": true },
"ROLE_STAFF": { "label": { "en": "Staff", "de": "Angestellte" }, "implies": [], "large": true },
"ROLE_INSTRUCTOR": { "label": { "en": "Lecturers", "de": "Vortragende" }, "implies": ["ROLE_STAFF"], "large": true },
"ROLE_TOBIRA_MODERATOR": { "label": { "en": "Moderators", "de": "Moderierende" }, "implies": ["ROLE_STAFF"], "large": false }
"ROLE_STUDENT": { "label": { "default": "Students", "de": "Studierende" }, "implies": [], "large": true },
"ROLE_STAFF": { "label": { "default": "Staff", "de": "Angestellte" }, "implies": [], "large": true },
"ROLE_INSTRUCTOR": { "label": { "default": "Lecturers", "de": "Vortragende" }, "implies": ["ROLE_STAFF"], "large": true },
"ROLE_TOBIRA_MODERATOR": { "label": { "default": "Moderators", "de": "Moderierende" }, "implies": ["ROLE_STAFF"], "large": false }
}
44 changes: 0 additions & 44 deletions backend/src/api/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ use crate::{
},
prelude::*,
search::Playlist as SearchPlaylist,
db::types::ExtraMetadata,
};


Expand Down Expand Up @@ -100,46 +99,3 @@ impl Cursor {
Ok(Self(s.into()))
}
}

// TODO: This uses `graphql_scalar` instead of `derive(GraphQLScalar)` because
// the type `ExtraMetadata` is defined in the `db` module and adding GraphQL
// code there seems wrong. However, I feel like we should move some types
// around anyway since we encountered problems like this before.
#[juniper::graphql_scalar(
name = "ExtraMetadata",
description = "Arbitrary metadata for events/series. Serialized as JSON object.",
with = Self,
parse_token(String),
)]
#[allow(dead_code)]
pub type ApiExtraMetadata = ExtraMetadata;

impl ExtraMetadata {
fn to_output<S: ScalarValue>(&self) -> juniper::Value<S> {
use juniper::Value;

std::iter::once(("dcterms", &self.dcterms))
.chain(self.extended.iter().map(|(k, v)| (&**k, v)))
.map(|(k, v)| {
let value = v.iter()
.map(|(k, v)| {
let elements = v.iter()
.map(|s| Value::Scalar(S::from(s.clone())))
.collect();
(k, Value::List(elements))
})
.collect::<juniper::Object<S>>();

(k, Value::Object(value))
})
.collect::<juniper::Object<S>>()
.pipe(Value::Object)
}

fn from_input<S: ScalarValue>(input: &InputValue<S>) -> Result<Self, String> {
// I did not want to waste time implementing this now, given that we
// likely never use it.
let _ = input;
todo!("ExtraMetadata cannot be used as input value yet")
}
}
51 changes: 3 additions & 48 deletions backend/src/api/id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize};
use static_assertions::const_assert;
use std::fmt;

use crate::{db::types::Key, util::{base64_decode, BASE64_DIGITS}};
use crate::model::Key;


/// An opaque, globally-unique identifier for all "nodes" that the GraphQL API
Expand Down Expand Up @@ -138,30 +138,6 @@ impl Id {
}
}

impl Key {
pub(crate) fn from_base64(s: &str) -> Option<Self> {
if s.len() != 11 {
return None;
}

decode_base64(s.as_bytes())
}

pub(crate) fn to_base64<'a>(&self, out: &'a mut [u8; 11]) -> &'a str {
// Base64 encoding. After this loop, `n` is always 0, because `u64::MAX`
// divided by 64 eleven times is 0.
let mut n = self.0;
for i in (0..out.len()).rev() {
out[i] = BASE64_DIGITS[(n % 64) as usize];
n /= 64;
}
debug_assert!(n == 0);

std::str::from_utf8(out)
.expect("bug: base64 did produce non-ASCII character")
}
}

impl std::str::FromStr for Id {
type Err = &'static str;

Expand Down Expand Up @@ -191,31 +167,10 @@ impl fmt::Display for Id {
}
}


fn decode_base64(src: &[u8]) -> Option<Key> {
let src: [u8; 11] = src.try_into().ok()?;

// Make sure the string doesn't decode to a number > `u64::MAX`. Luckily,
// checking that is easy. `u64::MAX` encodes to `P__________`, so the next
// higher number would carry through and make the highest digit a `Q`. So we
// just make sure the first digit is between 'A' and 'P'.
if src[0] > b'P' || src[0] < b'A' {
return None;
}

src.iter()
.rev()
.enumerate()
.map(|(i, &d)| base64_decode(d).map(|n| n as u64 * 64u64.pow(i as u32)))
.sum::<Option<u64>>()
.map(Key)
}


#[cfg(test)]
mod tests {
use std::str::FromStr;
use super::{Id, Key, BASE64_DIGITS};
use super::{Id, Key};

#[test]
fn simple() {
Expand Down Expand Up @@ -265,7 +220,7 @@ mod tests {
let id = Id { kind: Id::REALM_KIND, key: Key((n as u64) << shift) };
let s = id.to_string();
assert_eq!(s[..2].as_bytes(), Id::REALM_KIND);
assert!(s[2..].bytes().all(|d| BASE64_DIGITS.contains(&d)));
assert!(s[2..].bytes().all(|d| crate::util::BASE64_DIGITS.contains(&d)));
}
}
}
Expand Down
8 changes: 4 additions & 4 deletions backend/src/api/model/acl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use juniper::{GraphQLInputObject, GraphQLObject};
use postgres_types::BorrowToSql;
use serde::Serialize;

use crate::{api::{util::TranslatedString, Context, err::ApiResult}, db::util::select};
use crate::{api::{err::ApiResult, Context}, model::TranslatedString, db::util::select};



Expand Down Expand Up @@ -31,9 +31,9 @@ pub(crate) struct AclItem {
#[graphql(context = Context)]
pub(crate) struct RoleInfo {
/// A user-facing label for this role (group or person). If the label does
/// not depend on the language (e.g. a name), `{ "_": "Peter" }` is
/// not depend on the language (e.g. a name), `{ "default": "Peter" }` is
/// returned.
pub label: TranslatedString<String>,
pub label: TranslatedString,

/// For user roles this is `null`. For groups, it defines a list of other
/// group roles that this role implies. I.e. a user with this role always
Expand Down Expand Up @@ -66,7 +66,7 @@ where
known_groups.label,
case when users.display_name is null
then null
else hstore('_', users.display_name)
else hstore('default', users.display_name)
end
)",
);
Expand Down
3 changes: 2 additions & 1 deletion backend/src/api/model/block/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ use crate::{
Context,
Id,
},
db::{types::Key, util::impl_from_db},
model::Key,
db::util::impl_from_db,
prelude::*,
};

Expand Down
3 changes: 2 additions & 1 deletion backend/src/api/model/block/mutations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ use juniper::{GraphQLInputObject, GraphQLObject};

use crate::{
api::{Context, Id, err::{ApiResult, invalid_input}, model::realm::Realm},
db::{types::Key, util::select},
db::util::select,
model::Key,
prelude::*,
};
use super::{BlockValue, VideoListOrder, VideoListLayout};
Expand Down
3 changes: 2 additions & 1 deletion backend/src/api/model/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,10 @@ use crate::{
},
},
db::{
types::{EventCaption, EventSegment, EventState, EventTrack, ExtraMetadata, Key, Credentials},
types::{EventCaption, EventSegment, EventState, EventTrack, Credentials},
util::{impl_from_db, select},
},
model::{Key, ExtraMetadata},
prelude::*,
};

Expand Down
7 changes: 4 additions & 3 deletions backend/src/api/model/known_roles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ use meilisearch_sdk::search::{Selectors, MatchingStrategies};
use serde::Deserialize;

use crate::{
api::{Context, err::ApiResult, util::TranslatedString},
prelude::*,
api::{err::ApiResult, Context},
model::TranslatedString,
db::util::{impl_from_db, select},
prelude::*,
};
use super::search::{handle_search_result, measure_search_duration, SearchResults, SearchUnavailable};

Expand All @@ -16,7 +17,7 @@ use super::search::{handle_search_result, measure_search_duration, SearchResults
#[derive(juniper::GraphQLObject)]
pub struct KnownGroup {
pub(crate) role: String,
pub(crate) label: TranslatedString<String>,
pub(crate) label: TranslatedString,
pub(crate) implies: Vec<String>,
pub(crate) large: bool,
}
Expand Down
3 changes: 2 additions & 1 deletion backend/src/api/model/playlist/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ use crate::{
api::{
common::NotAllowed, err::ApiResult, Context, Id, Node, NodeValue
},
db::{types::Key, util::{impl_from_db, select}},
db::util::{impl_from_db, select},
model::Key,
prelude::*,
};

Expand Down
3 changes: 2 additions & 1 deletion backend/src/api/model/realm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ use crate::{
NodeValue,
},
auth::AuthContext,
db::{types::Key, util::{impl_from_db, select}},
db::util::{impl_from_db, select},
model::Key,
prelude::*,
};
use super::block::BlockValue;
Expand Down
2 changes: 1 addition & 1 deletion backend/src/api/model/realm/mutations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use crate::{
model::block::RemovedBlock,
},
auth::AuthContext,
db::types::Key,
model::Key,
prelude::*,
};
use super::{Realm, RealmOrder};
Expand Down
3 changes: 2 additions & 1 deletion backend/src/api/model/series.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ use crate::{
err::{invalid_input, ApiResult},
model::{event::AuthorizedEvent, realm::Realm},
},
db::{types::{ExtraMetadata, Key, SeriesState as State}, util::impl_from_db},
db::{types::{SeriesState as State}, util::impl_from_db},
model::{Key, ExtraMetadata},
prelude::*,
};

Expand Down
74 changes: 0 additions & 74 deletions backend/src/api/util.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,3 @@
use std::{collections::HashMap, fmt};

use bytes::BytesMut;
use fallible_iterator::FallibleIterator;
use juniper::{GraphQLScalar, InputValue, ScalarValue};
use postgres_types::{FromSql, ToSql};

use crate::prelude::*;



macro_rules! impl_object_with_dummy_field {
($ty:ident) => {
Expand All @@ -23,67 +13,3 @@ macro_rules! impl_object_with_dummy_field {
}

pub(crate) use impl_object_with_dummy_field;




/// A string in different languages.
#[derive(Debug, GraphQLScalar)]
#[graphql(
where(T: AsRef<str>),
parse_token(String),
)]
pub struct TranslatedString<T>(pub(crate) HashMap<T, String>);

impl<T: AsRef<str> + fmt::Debug> ToSql for TranslatedString<T> {
fn to_sql(
&self,
_: &postgres_types::Type,
out: &mut BytesMut,
) -> Result<postgres_types::IsNull, Box<dyn std::error::Error + Sync + Send>> {
let values = self.0.iter().map(|(k, v)| (k.as_ref(), Some(&**v)));
postgres_protocol::types::hstore_to_sql(values, out)?;
Ok(postgres_types::IsNull::No)
}

fn accepts(ty: &postgres_types::Type) -> bool {
ty.name() == "hstore"
}

postgres_types::to_sql_checked!();
}

impl<'a> FromSql<'a> for TranslatedString<String> {
fn from_sql(
_: &postgres_types::Type,
raw: &'a [u8],
) -> Result<Self, Box<dyn std::error::Error + Sync + Send>> {
postgres_protocol::types::hstore_from_sql(raw)?
.map(|(k, v)| {
v.map(|v| (k.to_owned(), v.to_owned()))
.ok_or("translated label contained null value in hstore".into())
})
.collect()
.map(Self)
}

fn accepts(ty: &postgres_types::Type) -> bool {
ty.name() == "hstore"
}
}

impl<T: AsRef<str>> TranslatedString<T> {
fn to_output<S: ScalarValue>(&self) -> juniper::Value<S> {
self.0.iter()
.map(|(k, v)| (k.as_ref(), juniper::Value::scalar(v.to_owned())))
.collect::<juniper::Object<S>>()
.pipe(juniper::Value::Object)
}

fn from_input<S: ScalarValue>(input: &InputValue<S>) -> Result<Self, String> {
// I did not want to waste time implementing this now, given that we
// likely never use it.
let _ = input;
todo!("TranslatedString cannot be used as input value yet")
}
}
2 changes: 1 addition & 1 deletion backend/src/auth/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use hyper::{http::HeaderName, Uri};
use secrecy::SecretString;
use serde::{Deserialize, Deserializer, de::Error};

use crate::{config::{parse_normal_http_uri, TranslatedString}, prelude::*};
use crate::{config::parse_normal_http_uri, model::TranslatedString, prelude::*};

use super::JwtConfig;

Expand Down
Loading
Loading