Compare commits
4 Commits
fdd61ae701
...
7415b91e23
| Author | SHA1 | Date | |
|---|---|---|---|
| 7415b91e23 | |||
| 25cd6c9294 | |||
| c420826474 | |||
| 5edac78add |
42
.github/workflows/ci.yml
vendored
Normal file
42
.github/workflows/ci.yml
vendored
Normal 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
|
||||||
@@ -12,6 +12,18 @@ use domain::models::{
|
|||||||
collections::Paginated,
|
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 {
|
struct PageItem {
|
||||||
number: u32,
|
number: u32,
|
||||||
is_current: bool,
|
is_current: bool,
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
<article class="entry">
|
<article class="entry">
|
||||||
{% if let Some(poster) = entry.movie().poster_path() %}
|
{% if let Some(poster) = entry.movie().poster_path() %}
|
||||||
<div class="poster">
|
<div class="poster">
|
||||||
<img src="/posters/{{ poster.value() }}" alt="">
|
<img src="{{ poster.value()|poster_src }}" alt="">
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="entry-body">
|
<div class="entry-body">
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<article class="entry">
|
<article class="entry">
|
||||||
{% if let Some(poster) = entry.movie().poster_path() %}
|
{% if let Some(poster) = entry.movie().poster_path() %}
|
||||||
<div class="poster">
|
<div class="poster">
|
||||||
<img src="/posters/{{ poster.value() }}" alt="">
|
<img src="{{ poster.value()|poster_src }}" alt="">
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="entry-body">
|
<div class="entry-body">
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
<form method="POST" action="/users/{{ user_id }}/follow" class="follow-form">
|
<form method="POST" action="/users/{{ user_id }}/follow" class="follow-form">
|
||||||
<input type="hidden" name="_csrf" value="{{ ctx.csrf_token }}">
|
<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>
|
<input type="text" name="handle" placeholder="@user@instance.tld" required>
|
||||||
<button type="submit">Follow</button>
|
<button type="submit">Follow</button>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
{# ── Hero ── #}
|
{# ── Hero ── #}
|
||||||
<article class="entry" style="margin-bottom:1.5rem">
|
<article class="entry" style="margin-bottom:1.5rem">
|
||||||
{% if let Some(poster) = movie.poster_path() %}
|
{% 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 %}
|
{% endif %}
|
||||||
<div class="entry-body">
|
<div class="entry-body">
|
||||||
<div class="entry-title">
|
<div class="entry-title">
|
||||||
|
|||||||
@@ -115,7 +115,7 @@
|
|||||||
{% for entry in month.entries %}
|
{% for entry in month.entries %}
|
||||||
<article class="entry">
|
<article class="entry">
|
||||||
{% if let Some(poster) = entry.movie().poster_path() %}
|
{% 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 %}
|
{% endif %}
|
||||||
<div class="entry-body">
|
<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>
|
<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">
|
<article class="entry">
|
||||||
{% if let Some(poster) = entry.movie().poster_path() %}
|
{% if let Some(poster) = entry.movie().poster_path() %}
|
||||||
<div class="poster">
|
<div class="poster">
|
||||||
<img src="/posters/{{ poster.value() }}" alt="">
|
<img src="{{ poster.value()|poster_src }}" alt="">
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="entry-body">
|
<div class="entry-body">
|
||||||
|
|||||||
@@ -10,13 +10,16 @@ fn make_user() -> User {
|
|||||||
UserRole::Standard,
|
UserRole::Standard,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
vec![],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn update_profile_sets_fields() {
|
fn update_profile_sets_fields() {
|
||||||
let mut user = make_user();
|
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.bio(), Some("My bio"));
|
||||||
assert_eq!(user.avatar_path(), Some("avatars/abc"));
|
assert_eq!(user.avatar_path(), Some("avatars/abc"));
|
||||||
}
|
}
|
||||||
@@ -24,8 +27,8 @@ fn update_profile_sets_fields() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn update_profile_clears_with_none() {
|
fn update_profile_clears_with_none() {
|
||||||
let mut user = make_user();
|
let mut user = make_user();
|
||||||
user.update_profile(Some("bio".to_string()), Some("path".to_string()));
|
user.update_profile(Some("bio".to_string()), Some("path".to_string()), None, None);
|
||||||
user.update_profile(None, None);
|
user.update_profile(None, None, None, None);
|
||||||
assert_eq!(user.bio(), None);
|
assert_eq!(user.bio(), None);
|
||||||
assert_eq!(user.avatar_path(), None);
|
assert_eq!(user.avatar_path(), None);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -215,11 +215,16 @@ impl UserRepository for Panic {
|
|||||||
async fn list_with_stats(&self) -> Result<Vec<domain::models::UserSummary>, DomainError> {
|
async fn list_with_stats(&self) -> Result<Vec<domain::models::UserSummary>, DomainError> {
|
||||||
panic!()
|
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!()
|
panic!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[async_trait::async_trait]
|
#[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 {
|
impl EventPublisher for Panic {
|
||||||
async fn publish(&self, _: &DomainEvent) -> Result<(), DomainError> {
|
async fn publish(&self, _: &DomainEvent) -> Result<(), DomainError> {
|
||||||
panic!()
|
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 _,
|
import_profile_repository: Arc::clone(&repo) as _,
|
||||||
movie_profile_repository: Arc::clone(&repo) as _,
|
movie_profile_repository: Arc::clone(&repo) as _,
|
||||||
watchlist_repository: Arc::clone(&repo) as _,
|
watchlist_repository: Arc::clone(&repo) as _,
|
||||||
|
profile_fields_repository: Arc::clone(&repo) as _,
|
||||||
#[cfg(feature = "federation")]
|
#[cfg(feature = "federation")]
|
||||||
remote_watchlist_repository: Arc::clone(&repo) as _,
|
remote_watchlist_repository: Arc::clone(&repo) as _,
|
||||||
person_command: Arc::clone(&repo) as _,
|
person_command: Arc::clone(&repo) as _,
|
||||||
|
|||||||
@@ -109,11 +109,18 @@ impl UserRepository for NobodyUserRepo {
|
|||||||
async fn list_with_stats(&self) -> Result<Vec<domain::models::UserSummary>, DomainError> {
|
async fn list_with_stats(&self) -> Result<Vec<domain::models::UserSummary>, DomainError> {
|
||||||
panic!()
|
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(())
|
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;
|
struct PanicExporter;
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl domain::ports::DiaryExporter for PanicExporter {
|
impl domain::ports::DiaryExporter for PanicExporter {
|
||||||
@@ -259,6 +266,7 @@ async fn test_app() -> Router {
|
|||||||
import_profile_repository: Arc::new(PanicImportProfile),
|
import_profile_repository: Arc::new(PanicImportProfile),
|
||||||
movie_profile_repository: Arc::new(PanicMovieProfile),
|
movie_profile_repository: Arc::new(PanicMovieProfile),
|
||||||
watchlist_repository: Arc::new(PanicWatchlist),
|
watchlist_repository: Arc::new(PanicWatchlist),
|
||||||
|
profile_fields_repository: Arc::new(PanicProfileFields),
|
||||||
#[cfg(feature = "federation")]
|
#[cfg(feature = "federation")]
|
||||||
remote_watchlist_repository: Arc::new(PanicRemoteWatchlist),
|
remote_watchlist_repository: Arc::new(PanicRemoteWatchlist),
|
||||||
person_command: Arc::new(PanicPersonCommand),
|
person_command: Arc::new(PanicPersonCommand),
|
||||||
|
|||||||
Reference in New Issue
Block a user