fix: implement i64 serialization for JavaScript compatibility, fixing avatar URL display

This commit is contained in:
2026-01-29 15:50:50 -06:00
parent e880126281
commit e41b970d6e
3 changed files with 44 additions and 3 deletions
+42 -1
View File
@@ -1,10 +1,46 @@
//! `sqlx` models for the database schema.
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde_json::Value;
use ts_rs::TS;
/// Serialize an `i64` as a string to avoid JavaScript precision loss for values exceeding 2^53.
fn serialize_i64_as_string<S: Serializer>(value: &i64, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(&value.to_string())
}
/// Deserialize an `i64` from either a number or a string.
fn deserialize_i64_from_string<'de, D: Deserializer<'de>>(
deserializer: D,
) -> Result<i64, D::Error> {
use serde::de;
struct I64OrStringVisitor;
impl<'de> de::Visitor<'de> for I64OrStringVisitor {
type Value = i64;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("an integer or a string containing an integer")
}
fn visit_i64<E: de::Error>(self, value: i64) -> Result<i64, E> {
Ok(value)
}
fn visit_u64<E: de::Error>(self, value: u64) -> Result<i64, E> {
i64::try_from(value).map_err(|_| E::custom(format!("u64 {value} out of i64 range")))
}
fn visit_str<E: de::Error>(self, value: &str) -> Result<i64, E> {
value.parse().map_err(de::Error::custom)
}
}
deserializer.deserialize_any(I64OrStringVisitor)
}
/// Represents a meeting time stored as JSONB in the courses table.
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[ts(export)]
@@ -162,6 +198,11 @@ pub struct ScrapeJob {
#[serde(rename_all = "camelCase")]
#[ts(export)]
pub struct User {
#[serde(
serialize_with = "serialize_i64_as_string",
deserialize_with = "deserialize_i64_from_string"
)]
#[ts(type = "string")]
pub discord_id: i64,
pub discord_username: String,
pub discord_avatar_hash: Option<String>,
+1 -1
View File
@@ -167,7 +167,7 @@ export class BannerApiClient {
return this.request<User[]>("/admin/users");
}
async setUserAdmin(discordId: bigint, isAdmin: boolean): Promise<User> {
async setUserAdmin(discordId: string, isAdmin: boolean): Promise<User> {
const response = await this.fetchFn(`${this.baseUrl}/admin/users/${discordId}/admin`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
+1 -1
View File
@@ -6,7 +6,7 @@ import { Shield, ShieldOff } from "@lucide/svelte";
let users = $state<User[]>([]);
let error = $state<string | null>(null);
let updating = $state<bigint | null>(null);
let updating = $state<string | null>(null);
onMount(async () => {
try {