Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
43e5571
fix(migrations): split the property clear into three migrations
disconsented Oct 8, 2026
094ede7
feat(search): add a created filter, presets and two sort orders
disconsented Oct 8, 2026
4e4dff0
refactor(properties): use the field shorthand in a trace call
disconsented Oct 8, 2026
0f889e8
style: format the search form and order enum
disconsented Oct 8, 2026
d4ad5dc
feat(items): hide items that Steam reports as unavailable
disconsented Oct 8, 2026
ebf2cde
fix(search): rename the depended-on preset
disconsented Oct 8, 2026
5a87ace
perf(web): share one compression hoop across routes
disconsented Oct 8, 2026
10bce61
feat(items): make the whole item card a link
disconsented Oct 8, 2026
65766a3
feat(classification): tag continued mods with a continuation type
disconsented Oct 12, 2026
c9ea6e2
fix(items): insert the item before the classifier queues it
disconsented Oct 12, 2026
bee6849
feat(properties): hide system properties that only one item has
disconsented Oct 12, 2026
b09f36f
refactor: remove dead code and fix clippy findings
disconsented Oct 12, 2026
d46b6fa
test(search): pass the new query_inner arguments
disconsented Oct 12, 2026
f0503c3
style(ui): sort the author link classes
disconsented Oct 12, 2026
5194c14
test(items): add the hidden field to the test schemas
disconsented Oct 12, 2026
56ee3b3
refactor(items): log hidden items at trace level
disconsented Oct 12, 2026
5109428
fix(items): add right padding to the item body
disconsented Oct 12, 2026
159fd45
refactor: fix clippy pedantic findings
disconsented Oct 13, 2026
f52902a
refactor(search): spell out the date filter parameter names
disconsented Oct 13, 2026
c2ae519
refactor(db): split the model module into one file per type
disconsented Oct 13, 2026
27d98b7
refactor(tags): expect the mutable key type lint
disconsented Oct 13, 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
5 changes: 5 additions & 0 deletions clippy.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,8 @@ allow-dbg-in-tests = true
allow-unwrap-in-tests = true
allow-unwrap-types = ["std::sync::LockResult"]
disallowed-methods = ["surrealdb_types::value::record_id::RecordId::new"]
ignore-interior-mutability = [
"workshop_walker::db::IAppID",
"workshop_walker::db::IUsernameID",
"workshop_walker::db::model::InternalTag",
]
6 changes: 0 additions & 6 deletions migrations/1791338545615_full_properties_clear.surql
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,3 @@
DELETE FROM workshop_item_properties;
DELETE FROM properties;
DELETE FROM votes;
Comment thread
disconsented marked this conversation as resolved.
Comment thread
disconsented marked this conversation as resolved.
Comment thread
disconsented marked this conversation as resolved.

-- Clear all the indexes just in case
REBUILD INDEX property_full_text_property ON properties CONCURRENTLY;
REBUILD INDEX unique_workshop_item_properties ON workshop_item_properties CONCURRENTLY;

UPDATE workshop_items SET ml_last_run = NONE;
3 changes: 3 additions & 0 deletions migrations/1791344039944_full_properties_clear_2.surql
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
-- Clear all the indexes just in case
REBUILD INDEX property_full_text_property ON properties CONCURRENTLY;
REBUILD INDEX unique_workshop_item_properties ON workshop_item_properties CONCURRENTLY;
1 change: 1 addition & 0 deletions migrations/1791344104781_full_properties_clear_3.surql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
UPDATE workshop_items SET ml_last_run = NONE;
1 change: 1 addition & 0 deletions migrations/1791407236100_index_item_created.surql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
DEFINE INDEX item_created ON workshop_items FIELDS created CONCURRENTLY;
3 changes: 3 additions & 0 deletions migrations/1791418699899_tombstone_items.surql
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
DEFINE FIELD hidden ON workshop_items TYPE bool DEFAULT false PERMISSIONS FULL;
UPDATE workshop_items SET hidden = false;
DEFINE INDEX item_hidden ON workshop_items FIELDS hidden CONCURRENTLY;
26 changes: 26 additions & 0 deletions migrations/1791426889401_hide_uncommon_properties.surql
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
-- New properties from the ML are "hidden" (-2) until they occur more than once, then they're accepted directly.
-- Should cut down on the one offs.
DEFINE FIELD OVERWRITE status ON workshop_item_properties TYPE -2 | -1 | 0 | 1 DEFAULT 0 PERMISSIONS FULL;
-- Hide all the one offs
LET $groups = (
SELECT
id,
out,
in.*.app as app
FROM workshop_item_properties
WHERE
source = "system"
AND status == 1
GROUP BY out, app
).filter(
|$property_group| {
$property_group.id.len() == 1;
}
);
FOR $group IN $groups {
FOR $id IN {
$group.id;
} {
UPDATE $id SET status = -2;
};
};
19 changes: 17 additions & 2 deletions prompts/features.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,17 @@ when the rest is most of the text:
- links to the author's other mods, "please rate and favourite"
- jokes, and text written in character

Something that appears only in an ignored section is not a type and not a feature.
Something that appears only in an ignored section is not a type and not a feature. The one
exception is a credit that says the mod continues the work of a different author. That credit
decides the "continuation" type.

OUTPUT

Give this JSON and nothing else:

{"types": ["array", "of", "types"], "features": ["array", "of", "features"]}

TYPES, AT MOST 2
TYPES, AT MOST 3

- overhaul: changes gameplay or a game system
- expansion: adds a large amount of new content
Expand All @@ -36,9 +38,15 @@ TYPES, AT MOST 2
its own strings in several languages is not a translation, and translation credits in the
description do not make it one.
- ai generated: AI made a part of the mod: code, art, textures, voices, music or text
- continuation: a re-upload that keeps a different author's abandoned mod working, usually on a
newer game version. Signs are "(Continued)", "Continued" or "Unofficial update" in the title, or
"original mod by" in the description. An update by the original author is not a continuation,
and a mod that only takes ideas from another mod is not one.

Choose the one type that fits best. Add a second type only when the mod clearly does two jobs.
"utility" together with "overhaul" is almost always wrong, so pick the one that a user notices.
"continuation" is the exception to this rule: put the type of the content first and "continuation"
second.

FEATURES, AT MOST 5, MOST IMPORTANT FIRST

Expand Down Expand Up @@ -117,6 +125,13 @@ Output: {"types": ["overhaul"], "features": ["prisoner recruitment"]}
Reason: mood, traits, pain and progress are inputs to the one feature. The button and the settings
page are not features. The localization is change history.

Input: "[1.5] Dubs Mint Menus (Continued) - The original mod by Dubwise is no longer updated, so
this version keeps it working on 1.5. Adds a meal planner and food restrictions per colonist.
Credit to Dubwise for the original."
Output: {"types": ["QOL", "continuation"], "features": ["meal planning", "food restrictions"]}
Reason: the credit line is the evidence for "continuation". The features come from what the mod
does, the same as for any other mod.

Input: "EdB Prepare Carefully - Customize your starting colonists, their gear and their pets, and
save the result as a preset. Now with better error checking"
Output: {"types": ["utility"], "features": ["colonist customization", "presets", "starting gear"]}
Expand Down
4 changes: 4 additions & 0 deletions src/actors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ use crate::{
};

#[instrument(skip_all)]
#[expect(
clippy::too_many_lines,
reason = "Simple function, mostly boiler plate, little value in refactoring this."
)]
pub async fn spawn(config: &Config, db: &Surreal<Db>) -> Result<(), Whatever> {
let reqwest_client = Client::new();

Expand Down
22 changes: 20 additions & 2 deletions src/application/items_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ use tracing::trace;

use crate::{
db::{
IItemID, IUserID,
IAppID, IItemID, IPropertyID, IUserID,
model::{
HistoryPair, InsertableWorkshopItem, InternalFullWorkshopItem, InternalWorkshopItem,
Status,
},
},
domain::items::{History, ItemsError, ItemsPort},
Expand Down Expand Up @@ -105,6 +106,7 @@ impl<R: ItemsPort> ItemsService<R> {
title: item.title,
score: item.score,
tags: tags.into_iter().map(|tag| tag.id).collect::<Vec<_>>(),
hidden: false,
lifetime_subscriptions: item.lifetime_subscriptions,
subscriptions: item.subscriptions,
views: item.views,
Expand All @@ -129,9 +131,25 @@ impl<R: ItemsPort> ItemsService<R> {
.whatever_context("inserting item")
}

pub(crate) async fn update_ml_last_run(&self, id: IItemID) -> Result<(), ItemsError> {
pub async fn update_ml_last_run(&self, id: IItemID) -> Result<(), ItemsError> {
self.repo.update_ml_last_run(id).await
}

pub async fn set_hidden(&self, id: IItemID, hidden: bool) -> Result<(), ItemsError> {
self.repo.set_hidden(id, hidden).await
}

pub async fn set_property_status(
&self,
app: IAppID,
properties: Vec<IPropertyID>,
new_status: Status,
test_status: Option<Status>,
) -> Result<(), ItemsError> {
self.repo
.set_property_status(app, properties, new_status, test_status)
.await
}
}

impl<R: ItemsPort> ItemsService<R> {
Expand Down
22 changes: 19 additions & 3 deletions src/application/properties_service.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::{
db::{
IUserID,
IAppID, IPropertyID, IUserID,
model::{InternalSource, Property, Status},
},
domain::properties::{
Expand Down Expand Up @@ -63,6 +63,14 @@ impl<R: PropertiesPort> PropertiesService<R> {
.await
}

pub async fn get_hidden_system_props(
&self,
property: IPropertyID,
app: IAppID,
) -> Result<(Vec<IPropertyID>, usize), PropertiesError> {
self.repo.get_hidden_system_props(property, app).await
}

pub async fn vote(
&self,
vote: InternalVoteData,
Expand Down Expand Up @@ -104,7 +112,7 @@ mod tests {
use super::PropertiesService;
use crate::{
db::{
IItemID, IUserID,
IAppID, IItemID, IPropertyID, IUserID,
model::{Class, Property},
},
domain::properties::{
Expand All @@ -131,6 +139,14 @@ mod tests {
Ok(())
}

async fn get_hidden_system_props(
&self,
_: IPropertyID,
_: IAppID,
) -> Result<(Vec<IPropertyID>, usize), PropertiesError> {
unimplemented!()
}

async fn vote(&self, _: InternalVoteData, _: IUserID) -> Result<(), PropertiesError> {
self.vote_calls.fetch_add(1, Ordering::SeqCst);
Ok(())
Expand All @@ -152,7 +168,7 @@ mod tests {
_: usize,
_: usize,
) -> Result<Vec<Property>, PropertiesError> {
todo!()
unimplemented!()
}
}

Expand Down
40 changes: 27 additions & 13 deletions src/db/item_update_actor.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use ractor::{Actor, ActorProcessingErr, ActorRef, async_trait};
use surrealdb::{Surreal, engine::local::Db};
use tracing::error;
use tracing::{error, trace};

use crate::{
application::items_service::ItemsService,
Expand All @@ -12,7 +12,7 @@ use crate::{
ml_queue_actor::MLQueueMsg,
},
steam::{
model::{Child, EResult, IPublishedResponse, IPublishedStruct, SteamRoot},
model::{Child, IPublishedResponse, IPublishedStruct, SteamRoot},
steam_user_actor::SteamUserMsg,
},
};
Expand All @@ -39,6 +39,7 @@ pub struct ItemUpdateState {
pub enum ItemUpdateMsg {
DeserializeRawFiles(SteamRoot<IPublishedResponse>),
MainlineProcessing(IPublishedStruct),
Hide(IPublishedStruct),
Upsert((InternalWorkshopItem, Vec<Child>)),
/// Intended to be invoked from user intent signals
MaybeQueue(IItemID),
Expand Down Expand Up @@ -75,9 +76,7 @@ impl Actor for ItemUpdateActor {
for file in steam_root.response.publishedfiledetails {
match serde_json::from_value::<IPublishedStruct>(file) {
Ok(file) => {
if file.result == EResult::OK as i32 {
myself.send_message(ItemUpdateMsg::MainlineProcessing(file))?;
}
myself.send_message(ItemUpdateMsg::MainlineProcessing(file))?;
}
Err(error) => {
error!(?error, "deserializing raw file");
Expand Down Expand Up @@ -111,19 +110,21 @@ impl Actor for ItemUpdateActor {
.steam_user_actor
.send_message(SteamUserMsg::Fetch(item.author.id.clone()));

if let Some(ml_queue) = &state.ml_queue
if let Err(error) = state
.items_service
.insert_data(item.clone(), children)
.await
{
error!(?error, title, ?item_id, "upserting item");
} else if let Some(ml_queue) = &state.ml_queue
&& let Ok(true) = state.items_service.should_queue_ml(&item, false).await
{
let _ = ml_queue.send_message(MLQueueMsg::Queue(
item.id.clone(),
item.title.clone(),
item.description.clone(),
item.id,
item.title,
item.description,
));
}

if let Err(error) = state.items_service.insert_data(item, children).await {
error!(?error, title, ?item_id, "upserting item");
}
}
ItemUpdateMsg::MaybeQueue(id) => {
if let Some(ml_queue) = &state.ml_queue {
Expand All @@ -150,6 +151,19 @@ impl Actor for ItemUpdateActor {
}
}
}
ItemUpdateMsg::Hide(file) => {
match file.publishedfileid.parse::<i64>().map(IItemID::from) {
Ok(id) => {
trace!(?id, reason = ?file.result, "Hiding item");
if let Err(error) = state.items_service.set_hidden(id.clone(), true).await {
error!(?error, ?id, "setting hidden item");
}
}
Err(error) => {
error!(?error, "parsing id to set hidden");
}
}
}
}

Ok(())
Expand Down
Loading
Loading