Compare commits

..

4 Commits

10 changed files with 82 additions and 11 deletions

42
.github/workflows/ci.yml vendored Normal file
View File

@@ -0,0 +1,42 @@
name: CI
on:
push:
branches: ["**"]
pull_request:
env:
CARGO_TERM_COLOR: always
RUST_BACKTRACE: 1
jobs:
ci:
name: Check / Test / Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Rust stable
uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt
- name: Cache cargo
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
restore-keys: ${{ runner.os }}-cargo-
- name: fmt
run: cargo fmt --all -- --check
- name: test
run: cargo test
- name: build (release)
run: cargo build --release -p presentation -p worker

View File

@@ -12,6 +12,18 @@ use domain::models::{
collections::Paginated,
};
mod filters {
#[askama::filter_fn]
pub fn poster_src<T: std::fmt::Display>(path: T, _env: &dyn askama::Values) -> askama::Result<String> {
let p = path.to_string();
if p.starts_with("http://") || p.starts_with("https://") {
Ok(p)
} else {
Ok(format!("/images/{}", p))
}
}
}
struct PageItem {
number: u32,
is_current: bool,

View File

@@ -26,7 +26,7 @@
<article class="entry">
{% if let Some(poster) = entry.movie().poster_path() %}
<div class="poster">
<img src="/posters/{{ poster.value() }}" alt="">
<img src="{{ poster.value()|poster_src }}" alt="">
</div>
{% endif %}
<div class="entry-body">

View File

@@ -5,7 +5,7 @@
<article class="entry">
{% if let Some(poster) = entry.movie().poster_path() %}
<div class="poster">
<img src="/posters/{{ poster.value() }}" alt="">
<img src="{{ poster.value()|poster_src }}" alt="">
</div>
{% endif %}
<div class="entry-body">

View File

@@ -6,7 +6,7 @@
{% endif %}
<form method="POST" action="/users/{{ user_id }}/follow" class="follow-form">
<input type="hidden" name="_csrf" value="{{ ctx.csrf_token }}">
<input type="hidden" name="redirect_after" value="/social/following">
<input type="hidden" name="redirect_after" value="/users/{{ user_id }}/following-list">
<input type="text" name="handle" placeholder="@user@instance.tld" required>
<button type="submit">Follow</button>
</form>

View File

@@ -5,7 +5,7 @@
{# ── Hero ── #}
<article class="entry" style="margin-bottom:1.5rem">
{% if let Some(poster) = movie.poster_path() %}
<div class="poster"><img src="/posters/{{ poster.value() }}" alt=""></div>
<div class="poster"><img src="{{ poster.value()|poster_src }}" alt=""></div>
{% endif %}
<div class="entry-body">
<div class="entry-title">

View File

@@ -115,7 +115,7 @@
{% for entry in month.entries %}
<article class="entry">
{% if let Some(poster) = entry.movie().poster_path() %}
<div class="poster"><img src="/posters/{{ poster.value() }}" alt=""></div>
<div class="poster"><img src="{{ poster.value()|poster_src }}" alt=""></div>
{% endif %}
<div class="entry-body">
<div class="entry-title"><a href="/movies/{{ entry.movie().id().value() }}" class="movie-title-link">{{ entry.movie().title().value() }}</a> <span class="year">({{ entry.movie().release_year().value() }})</span></div>
@@ -183,7 +183,7 @@
<article class="entry">
{% if let Some(poster) = entry.movie().poster_path() %}
<div class="poster">
<img src="/posters/{{ poster.value() }}" alt="">
<img src="{{ poster.value()|poster_src }}" alt="">
</div>
{% endif %}
<div class="entry-body">

View File

@@ -10,13 +10,16 @@ fn make_user() -> User {
UserRole::Standard,
None,
None,
None,
None,
vec![],
)
}
#[test]
fn update_profile_sets_fields() {
let mut user = make_user();
user.update_profile(Some("My bio".to_string()), Some("avatars/abc".to_string()));
user.update_profile(Some("My bio".to_string()), Some("avatars/abc".to_string()), None, None);
assert_eq!(user.bio(), Some("My bio"));
assert_eq!(user.avatar_path(), Some("avatars/abc"));
}
@@ -24,8 +27,8 @@ fn update_profile_sets_fields() {
#[test]
fn update_profile_clears_with_none() {
let mut user = make_user();
user.update_profile(Some("bio".to_string()), Some("path".to_string()));
user.update_profile(None, None);
user.update_profile(Some("bio".to_string()), Some("path".to_string()), None, None);
user.update_profile(None, None, None, None);
assert_eq!(user.bio(), None);
assert_eq!(user.avatar_path(), None);
}

View File

@@ -215,11 +215,16 @@ impl UserRepository for Panic {
async fn list_with_stats(&self) -> Result<Vec<domain::models::UserSummary>, DomainError> {
panic!()
}
async fn update_profile(&self, _: &UserId, _: Option<String>, _: Option<String>) -> Result<(), DomainError> {
async fn update_profile(&self, _: &UserId, _: Option<String>, _: Option<String>, _: Option<String>, _: Option<String>) -> Result<(), DomainError> {
panic!()
}
}
#[async_trait::async_trait]
impl domain::ports::UserProfileFieldsRepository for Panic {
async fn get_fields(&self, _: &UserId) -> Result<Vec<domain::models::ProfileField>, DomainError> { panic!() }
async fn set_fields(&self, _: &UserId, _: Vec<domain::models::ProfileField>) -> Result<(), DomainError> { panic!() }
}
#[async_trait::async_trait]
impl EventPublisher for Panic {
async fn publish(&self, _: &DomainEvent) -> Result<(), DomainError> {
panic!()
@@ -414,6 +419,7 @@ pub fn make_test_state(auth_service: Arc<dyn AuthService>) -> crate::state::AppS
import_profile_repository: Arc::clone(&repo) as _,
movie_profile_repository: Arc::clone(&repo) as _,
watchlist_repository: Arc::clone(&repo) as _,
profile_fields_repository: Arc::clone(&repo) as _,
#[cfg(feature = "federation")]
remote_watchlist_repository: Arc::clone(&repo) as _,
person_command: Arc::clone(&repo) as _,

View File

@@ -109,11 +109,18 @@ impl UserRepository for NobodyUserRepo {
async fn list_with_stats(&self) -> Result<Vec<domain::models::UserSummary>, DomainError> {
panic!()
}
async fn update_profile(&self, _: &UserId, _: Option<String>, _: Option<String>) -> Result<(), DomainError> {
async fn update_profile(&self, _: &UserId, _: Option<String>, _: Option<String>, _: Option<String>, _: Option<String>) -> Result<(), DomainError> {
Ok(())
}
}
struct PanicProfileFields;
#[async_trait]
impl domain::ports::UserProfileFieldsRepository for PanicProfileFields {
async fn get_fields(&self, _: &UserId) -> Result<Vec<domain::models::ProfileField>, DomainError> { Ok(vec![]) }
async fn set_fields(&self, _: &UserId, _: Vec<domain::models::ProfileField>) -> Result<(), DomainError> { panic!() }
}
struct PanicExporter;
#[async_trait]
impl domain::ports::DiaryExporter for PanicExporter {
@@ -259,6 +266,7 @@ async fn test_app() -> Router {
import_profile_repository: Arc::new(PanicImportProfile),
movie_profile_repository: Arc::new(PanicMovieProfile),
watchlist_repository: Arc::new(PanicWatchlist),
profile_fields_repository: Arc::new(PanicProfileFields),
#[cfg(feature = "federation")]
remote_watchlist_repository: Arc::new(PanicRemoteWatchlist),
person_command: Arc::new(PanicPersonCommand),