f0a1552
ZhenYi
feat(chat): add chat module with room management, message handling, and session revocation
f0a1552
use async_graphql::{InputValueError, InputValueResult, Scalar, ScalarType, Value};
use base64::Engine;
use base64::engine::general_purpose::STANDARD as BASE64_ENGINE;
/// Hex-encoded git object id (sha1/sha256).
#[derive(Clone, Copy, Debug)]
pub struct ObjectIdScalar(pub gix::hash::ObjectId);
#[Scalar(
name = "ObjectId",
specified_by_url = "https://git-scm.com/docs/gitglossary#def_object-id"
)]
impl ScalarType for ObjectIdScalar {
fn parse(value: Value) -> InputValueResult<Self> {
if let Value::String(s) = value {
let oid = gix::hash::ObjectId::from_hex(s.as_bytes())
.map_err(|e| InputValueError::custom(format!("invalid object id: {e}")))?;
return Ok(ObjectIdScalar(oid));
}
Err(InputValueError::custom("ObjectId must be a hex string"))
}
fn to_value(&self) -> Value {
Value::String(self.0.to_string())
}
}
impl From<gix::hash::ObjectId> for ObjectIdScalar {
fn from(id: gix::hash::ObjectId) -> Self {
ObjectIdScalar(id)
}
}
impl From<ObjectIdScalar> for gix::hash::ObjectId {
fn from(s: ObjectIdScalar) -> Self {
s.0
}
}
/// Date + offset in seconds (sign matters).
#[derive(Clone, Copy, Debug)]
pub struct DateTimeScalar {
pub timestamp: i64,
pub offset_seconds: i32,
}
#[Scalar(
name = "DateTime",
specified_by_url = "https://git-scm.com/docs/git-commit#_date_formats"
)]
impl ScalarType for DateTimeScalar {
fn parse(value: Value) -> InputValueResult<Self> {
if let Value::Object(mut obj) = value {
let ts = obj
.swap_remove("timestamp")
.ok_or_else(|| InputValueError::custom("DateTime: `timestamp` is required"))?;
let off = obj
.swap_remove("offsetSeconds")
.ok_or_else(|| InputValueError::custom("DateTime: `offsetSeconds` is required"))?;
let ts = match ts {
Value::Number(n) => n
.as_i64()
.ok_or_else(|| InputValueError::custom("DateTime: `timestamp` must be int"))?,
_ => {
return Err(InputValueError::custom(
"DateTime: `timestamp` must be a number",
));
}
};
let off = match off {
Value::Number(n) => n.as_i64().ok_or_else(|| {
InputValueError::custom("DateTime: `offsetSeconds` must be int")
})? as i32,
_ => {
return Err(InputValueError::custom(
"DateTime: `offsetSeconds` must be a number",
));
}
};
return Ok(DateTimeScalar {
timestamp: ts,
offset_seconds: off,
});
}
Err(InputValueError::custom(
"DateTime must be an object with `timestamp` and `offsetSeconds`",
))
}
fn to_value(&self) -> Value {
let mut obj = async_graphql::indexmap::IndexMap::new();
obj.insert(
async_graphql::Name::new("timestamp"),
Value::Number(self.timestamp.into()),
);
obj.insert(
async_graphql::Name::new("offsetSeconds"),
Value::Number((self.offset_seconds as i64).into()),
);
Value::Object(obj)
}
}
impl From<&gitbaby::commit::types::Signature> for DateTimeScalar {
fn from(sig: &gitbaby::commit::types::Signature) -> Self {
let ts = sig.time.unix_timestamp();
DateTimeScalar {
timestamp: ts,
offset_seconds: sig.time.offset().whole_seconds(),
}
}
}
impl From<time::OffsetDateTime> for DateTimeScalar {
fn from(dt: time::OffsetDateTime) -> Self {
DateTimeScalar {
timestamp: dt.unix_timestamp(),
offset_seconds: dt.offset().whole_seconds(),
}
}
}
/// Bytes ↔ base64 string for blob payloads.
#[derive(Clone, Debug)]
pub struct Base64Scalar(pub Vec<u8>);
#[Scalar(
name = "Base64",
specified_by_url = "https://www.rfc-editor.org/rfc/rfc4648"
)]
impl ScalarType for Base64Scalar {
fn parse(value: Value) -> InputValueResult<Self> {
if let Value::String(s) = value {
let bytes = BASE64_ENGINE
.decode(s.as_bytes())
.map_err(|e| InputValueError::custom(format!("invalid base64: {e}")))?;
return Ok(Base64Scalar(bytes));
}
Err(InputValueError::custom("Base64 must be a string"))
}
fn to_value(&self) -> Value {
Value::String(BASE64_ENGINE.encode(&self.0))
}
}