Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
1a95d5f
feat(labrinth): initial preferences structure
Sychic Aug 5, 2026
a18a4e7
feat(labrinth): use partially to derive partial structs
Sychic Aug 6, 2026
b825096
feat(labrinth): preference defaults
Sychic Aug 7, 2026
5aa40c1
fix(labrinth): derive debug
Sychic Aug 7, 2026
b58a03d
feat(labrinth): user preferences db setup
Sychic Aug 7, 2026
ac9f35a
feat(labrinth): user preferences routes
Sychic Aug 7, 2026
5a0ac93
fix(labrinth): nested partial structs
Sychic Aug 7, 2026
973b072
fix(labrinth): serialize enums as snake case
Sychic Aug 7, 2026
3d68c93
refactor(labrinth-derive): rename to component-derive
Sychic Aug 14, 2026
78fdf0f
feat(component-derive): nested components
Sychic Aug 14, 2026
794d3f6
refactor(component-derive): change suffixes to prefixes
Sychic Aug 15, 2026
e406c56
refactor(component-derive): rename edit to partial
Sychic Aug 15, 2026
acec9f3
feat(component-derive): skip serializing empty option
Sychic Aug 15, 2026
25823a6
refactor(labrinth): wrap errors
Sychic Aug 15, 2026
64270b9
feat(component-derive): diff function
Sychic Aug 16, 2026
fab49e4
style(labrinth): fmt
Sychic Aug 16, 2026
cff880d
refactor(labrinth): use component derive and only store overrides
Sychic Aug 16, 2026
733bc4e
chore: update query cache
Sychic Aug 16, 2026
c215624
docs(labrinth): add preferences to openapi
Sychic Aug 16, 2026
b600944
fix(labrinth): lock row for update
Sychic Aug 16, 2026
7695278
remove: partially
Sychic Aug 16, 2026
0efd585
refactor(component-derive): split impls to separate functions
Sychic Aug 17, 2026
b301bbf
feat(component-derive): wrap impls to isolate naming
Sychic Aug 17, 2026
c8a9ca5
refactor(labrinth): split out auth conditions
Sychic Aug 17, 2026
1866e29
feat(labrinth): store auto
Sychic Aug 17, 2026
72c6168
feat(labrinth): store hosting privacy
Sychic Aug 17, 2026
1b8229d
style(labrinth): cargo fmt
Sychic Aug 17, 2026
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
1 change: 1 addition & 0 deletions .idea/code.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 11 additions & 10 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ members = [
"apps/labrinth",
"packages/app-lib",
"packages/ariadne",
"packages/component-derive",
"packages/daedalus",
"packages/labrinth-derive",
"packages/modrinth-content-management",
"packages/modrinth-log",
"packages/modrinth-maxmind",
Expand Down Expand Up @@ -68,6 +68,7 @@ clap = "4.5.48"
clickhouse = "0.14.0"
color-eyre = "0.6.5"
color-thief = "0.2.2"
component-derive = { path = "packages/component-derive" }
const_format = "0.2.34"
core-foundation = "0.10.1"
core-graphics = "0.24.0"
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions apps/labrinth/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ clap = { workspace = true, features = ["derive"] }
clickhouse = { workspace = true, features = ["time", "uuid"] }
color-eyre = { workspace = true }
color-thief = { workspace = true }
component-derive = { workspace = true }
const_format = { workspace = true }
dashmap = { workspace = true }
derive_more = { workspace = true, features = ["deref", "deref_mut"] }
Expand Down
4 changes: 4 additions & 0 deletions apps/labrinth/migrations/20260806120000_user-preferences.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
CREATE TABLE user_preferences (
user_id BIGINT PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE,
preferences JSONB NOT NULL
);
1 change: 1 addition & 0 deletions apps/labrinth/src/database/models/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ pub mod team_item;
pub mod thread_item;
pub mod user_item;
pub mod user_limits;
pub mod user_preferences_item;
pub mod user_subscription_item;
pub mod users_compliance;
pub mod users_notifications_preferences_item;
Expand Down
75 changes: 75 additions & 0 deletions apps/labrinth/src/database/models/user_preferences_item.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
use crate::database::Executor;
use crate::database::models::DBUserId;
use crate::models::v3::preferences::PartialUserPreferences;
use sqlx::types::Json;

pub struct DBUserPreferences;

impl DBUserPreferences {
pub async fn get<'a, E>(
user_id: DBUserId,
exec: E,
) -> Result<Option<PartialUserPreferences>, sqlx::Error>
where
E: Executor<'a, Database = sqlx::Postgres>,
{
let row = sqlx::query!(
r#"
SELECT preferences AS "preferences: Json<PartialUserPreferences>"
FROM user_preferences
WHERE user_id = $1
"#,
user_id.0,
)
.fetch_optional(exec)
.await?;

Ok(row.map(|row| row.preferences.0))
}

pub async fn get_for_update<'a, E>(
user_id: DBUserId,
exec: E,
) -> Result<Option<PartialUserPreferences>, sqlx::Error>
where
E: Executor<'a, Database = sqlx::Postgres>,
{
let row = sqlx::query!(
r#"
SELECT preferences AS "preferences: Json<PartialUserPreferences>"
FROM user_preferences
WHERE user_id = $1
FOR UPDATE
"#,
user_id.0,
)
.fetch_optional(exec)
.await?;

Ok(row.map(|row| row.preferences.0))
}

pub async fn upsert<'a, E>(
user_id: DBUserId,
preferences: &PartialUserPreferences,
exec: E,
) -> Result<(), sqlx::Error>
where
E: Executor<'a, Database = sqlx::Postgres>,
{
sqlx::query!(
r#"
INSERT INTO user_preferences (user_id, preferences)
VALUES ($1, $2)
ON CONFLICT (user_id) DO UPDATE
SET preferences = EXCLUDED.preferences
"#,
user_id.0,
Json(preferences) as Json<&PartialUserPreferences>,
)
.execute(exec)
.await?;

Ok(())
}
}
1 change: 1 addition & 0 deletions apps/labrinth/src/models/v3/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ pub mod organizations;
pub mod pack;
pub mod pats;
pub mod payouts;
pub mod preferences;
pub mod projects;
pub mod reports;
pub mod sessions;
Expand Down
123 changes: 123 additions & 0 deletions apps/labrinth/src/models/v3/preferences.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
use component_derive::Component;
use serde::{Deserialize, Serialize};
use utoipa::ToSchema;

#[derive(Debug, Serialize, Deserialize, ToSchema, Default, Component)]
pub struct UserPreferences {
#[component(nested)]
pub appearance: AppearancePreferences,
#[component(nested)]
pub localization: LocalizationPreferences,
#[component(nested)]
pub layouts: LayoutPreferences,
#[component(nested)]
pub sidebars: SidebarPreferences,
#[component(nested)]
pub social: SocialPreferences,
}

#[derive(
Debug, Serialize, Deserialize, ToSchema, Default, PartialEq, Component,
)]
pub struct AppearancePreferences {
pub auto: bool,
pub theme: Theme,
}

#[derive(
Debug, Serialize, Deserialize, ToSchema, Default, Clone, PartialEq,
)]
#[serde(rename_all = "snake_case")]
pub enum Theme {
Light,
#[default]
Dark,
Oled,
Retro,
}

#[derive(Debug, Serialize, Deserialize, ToSchema, PartialEq, Component)]
pub struct LocalizationPreferences {
pub locale: String,
}

impl Default for LocalizationPreferences {
fn default() -> Self {
Self {
locale: "en-US".to_owned(),
}
}
}

#[derive(Debug, Serialize, Deserialize, ToSchema, PartialEq, Component)]
pub struct LayoutPreferences {
pub mods: LayoutOption,
pub plugins: LayoutOption,
pub datapacks: LayoutOption,
pub shaders: LayoutOption,
pub resourcepacks: LayoutOption,
pub modpacks: LayoutOption,
pub servers: LayoutOption,
pub users: LayoutOption,
}

impl Default for LayoutPreferences {
fn default() -> Self {
Self {
mods: LayoutOption::Rows,
plugins: LayoutOption::Rows,
datapacks: LayoutOption::Rows,
shaders: LayoutOption::Grid,
resourcepacks: LayoutOption::Grid,
modpacks: LayoutOption::Rows,
servers: LayoutOption::Rows,
users: LayoutOption::Rows,
}
}
}

#[derive(Debug, Serialize, Deserialize, ToSchema, Clone, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum LayoutOption {
Grid,
Rows,
}

#[derive(
Debug, Serialize, Deserialize, ToSchema, Default, PartialEq, Component,
)]
pub struct SidebarPreferences {
pub right_aligned_search: bool,
pub left_aligned_content: bool,
}

#[derive(
Debug, Serialize, Deserialize, ToSchema, Default, PartialEq, Component,
)]
pub struct SocialPreferences {
pub friend_privacy: FriendPrivacy,
pub shared_instances_privacy: InvitePrivacy,
pub hosting_access_privacy: InvitePrivacy,
}

#[derive(
Debug, Serialize, Deserialize, ToSchema, Default, Clone, PartialEq,
)]
#[serde(rename_all = "snake_case")]
pub enum FriendPrivacy {
None,
Mutual,
#[default]
Everyone,
}

#[derive(
Debug, Serialize, Deserialize, ToSchema, Default, Clone, PartialEq,
)]
#[serde(rename_all = "snake_case")]
pub enum InvitePrivacy {
None,
Friends,
#[default]
Everyone,
}
2 changes: 2 additions & 0 deletions apps/labrinth/src/routes/v3/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,8 @@ pub fn config(cfg: &mut web::ServiceConfig) {
users::user_delete_route,
users::user_follows_route,
users::user_notifications_route,
users::get_user_preferences,
users::edit_user_preferences,
version_creation::version_create_route,
version_creation::upload_file_to_version_route,
version_file::get_version_from_hash_route,
Expand Down
Loading
Loading