License federation (#343)
* Federate license * Make it possible to use no license
This commit is contained in:
parent
e9f2f769be
commit
b73fbd3768
|
@ -220,3 +220,12 @@ pub struct Source {
|
|||
}
|
||||
|
||||
impl Object for Source {}
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize, Properties)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Licensed {
|
||||
#[activitystreams(concrete(String), functional)]
|
||||
pub license: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
impl Object for Licensed {}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use activitypub::{
|
||||
CustomObject,
|
||||
activity::{Create, Delete, Update},
|
||||
link,
|
||||
object::{Article, Image, Tombstone},
|
||||
|
@ -18,7 +19,7 @@ use plume_api::posts::PostEndpoint;
|
|||
use plume_common::{
|
||||
activity_pub::{
|
||||
inbox::{Deletable, FromActivity},
|
||||
Hashtag, Id, IntoId, Source, PUBLIC_VISIBILTY,
|
||||
Hashtag, Id, IntoId, Licensed, Source, PUBLIC_VISIBILTY,
|
||||
},
|
||||
utils::md_to_html,
|
||||
};
|
||||
|
@ -32,6 +33,8 @@ use tags::Tag;
|
|||
use users::User;
|
||||
use {ap_url, Connection, BASE_URL};
|
||||
|
||||
pub type LicensedArticle = CustomObject<Licensed, Article>;
|
||||
|
||||
#[derive(Queryable, Identifiable, Serialize, Clone, AsChangeset)]
|
||||
#[changeset_options(treat_none_as_null = "true")]
|
||||
pub struct Post {
|
||||
|
@ -418,7 +421,7 @@ impl Post {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn to_activity(&self, conn: &Connection) -> Article {
|
||||
pub fn to_activity(&self, conn: &Connection) -> LicensedArticle {
|
||||
let mut to = self.get_receivers_urls(conn);
|
||||
to.push(PUBLIC_VISIBILTY.to_string());
|
||||
|
||||
|
@ -516,7 +519,9 @@ impl Post {
|
|||
.object_props
|
||||
.set_cc_link_vec::<Id>(vec![])
|
||||
.expect("Post::to_activity: cc error");
|
||||
article
|
||||
let mut license = Licensed::default();
|
||||
license.set_license_string(self.license.clone()).expect("Post::to_activity: license error");
|
||||
LicensedArticle::new(article, license)
|
||||
}
|
||||
|
||||
pub fn create_activity(&self, conn: &Connection) -> Create {
|
||||
|
@ -527,7 +532,7 @@ impl Post {
|
|||
.expect("Post::create_activity: id error");
|
||||
act.object_props
|
||||
.set_to_link_vec::<Id>(
|
||||
article
|
||||
article.object
|
||||
.object_props
|
||||
.to_link_vec()
|
||||
.expect("Post::create_activity: Couldn't copy 'to'"),
|
||||
|
@ -535,7 +540,7 @@ impl Post {
|
|||
.expect("Post::create_activity: to error");
|
||||
act.object_props
|
||||
.set_cc_link_vec::<Id>(
|
||||
article
|
||||
article.object
|
||||
.object_props
|
||||
.cc_link_vec()
|
||||
.expect("Post::create_activity: Couldn't copy 'cc'"),
|
||||
|
@ -558,7 +563,7 @@ impl Post {
|
|||
.expect("Post::update_activity: id error");
|
||||
act.object_props
|
||||
.set_to_link_vec::<Id>(
|
||||
article
|
||||
article.object
|
||||
.object_props
|
||||
.to_link_vec()
|
||||
.expect("Post::update_activity: Couldn't copy 'to'"),
|
||||
|
@ -566,7 +571,7 @@ impl Post {
|
|||
.expect("Post::update_activity: to error");
|
||||
act.object_props
|
||||
.set_cc_link_vec::<Id>(
|
||||
article
|
||||
article.object
|
||||
.object_props
|
||||
.cc_link_vec()
|
||||
.expect("Post::update_activity: Couldn't copy 'cc'"),
|
||||
|
@ -577,44 +582,48 @@ impl Post {
|
|||
.expect("Post::update_activity: actor error");
|
||||
act.update_props
|
||||
.set_object_object(article)
|
||||
.expect("Article::update_activity: object error");
|
||||
.expect("Post::update_activity: object error");
|
||||
act
|
||||
}
|
||||
|
||||
pub fn handle_update(conn: &Connection, updated: &Article, searcher: &Searcher) {
|
||||
let id = updated
|
||||
pub fn handle_update(conn: &Connection, updated: &LicensedArticle, searcher: &Searcher) {
|
||||
let id = updated.object
|
||||
.object_props
|
||||
.id_string()
|
||||
.expect("Post::handle_update: id error");
|
||||
let mut post = Post::find_by_ap_url(conn, &id).expect("Post::handle_update: finding error");
|
||||
|
||||
if let Ok(title) = updated.object_props.name_string() {
|
||||
if let Ok(title) = updated.object.object_props.name_string() {
|
||||
post.slug = title.to_kebab_case();
|
||||
post.title = title;
|
||||
}
|
||||
|
||||
if let Ok(content) = updated.object_props.content_string() {
|
||||
if let Ok(content) = updated.object.object_props.content_string() {
|
||||
post.content = SafeString::new(&content);
|
||||
}
|
||||
|
||||
if let Ok(subtitle) = updated.object_props.summary_string() {
|
||||
if let Ok(subtitle) = updated.object.object_props.summary_string() {
|
||||
post.subtitle = subtitle;
|
||||
}
|
||||
|
||||
if let Ok(ap_url) = updated.object_props.url_string() {
|
||||
if let Ok(ap_url) = updated.object.object_props.url_string() {
|
||||
post.ap_url = ap_url;
|
||||
}
|
||||
|
||||
if let Ok(source) = updated.ap_object_props.source_object::<Source>() {
|
||||
if let Ok(source) = updated.object.ap_object_props.source_object::<Source>() {
|
||||
post.source = source.content;
|
||||
}
|
||||
|
||||
if let Ok(license) = updated.custom_props.license_string() {
|
||||
post.license = license;
|
||||
}
|
||||
|
||||
let mut txt_hashtags = md_to_html(&post.source)
|
||||
.2
|
||||
.into_iter()
|
||||
.map(|s| s.to_camel_case())
|
||||
.collect::<HashSet<_>>();
|
||||
if let Some(serde_json::Value::Array(mention_tags)) = updated.object_props.tag.clone() {
|
||||
if let Some(serde_json::Value::Array(mention_tags)) = updated.object.object_props.tag.clone() {
|
||||
let mut mentions = vec![];
|
||||
let mut tags = vec![];
|
||||
let mut hashtags = vec![];
|
||||
|
@ -782,8 +791,10 @@ impl Post {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a> FromActivity<Article, (&'a Connection, &'a Searcher)> for Post {
|
||||
fn from_activity((conn, searcher): &(&'a Connection, &'a Searcher), article: Article, _actor: Id) -> Post {
|
||||
impl<'a> FromActivity<LicensedArticle, (&'a Connection, &'a Searcher)> for Post {
|
||||
fn from_activity((conn, searcher): &(&'a Connection, &'a Searcher), article: LicensedArticle, _actor: Id) -> Post {
|
||||
let license = article.custom_props.license_string().unwrap_or_default();
|
||||
let article = article.object;
|
||||
if let Some(post) = Post::find_by_ap_url(
|
||||
conn,
|
||||
&article.object_props.id_string().unwrap_or_default(),
|
||||
|
@ -829,7 +840,7 @@ impl<'a> FromActivity<Article, (&'a Connection, &'a Searcher)> for Post {
|
|||
.expect("Post::from_activity: content error"),
|
||||
),
|
||||
published: true,
|
||||
license: String::from("CC-BY-SA"), // TODO
|
||||
license: license,
|
||||
// FIXME: This is wrong: with this logic, we may use the display URL as the AP ID. We need two different fields
|
||||
ap_url: article.object_props.url_string().unwrap_or_else(|_|
|
||||
article
|
||||
|
|
7
po/de.po
7
po/de.po
|
@ -609,6 +609,13 @@ msgstr "Administration"
|
|||
msgid "None"
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "Let it empty reserve all rights"
|
||||
msgstr "Falls es dies nicht gibt, lass es leer"
|
||||
|
||||
msgid "All rights reserved."
|
||||
msgstr ""
|
||||
|
||||
#~ msgid "Home to"
|
||||
#~ msgstr "Heimat von"
|
||||
|
||||
|
|
6
po/en.po
6
po/en.po
|
@ -593,3 +593,9 @@ msgstr ""
|
|||
|
||||
msgid "None"
|
||||
msgstr ""
|
||||
|
||||
msgid "Let it empty reserve all rights"
|
||||
msgstr ""
|
||||
|
||||
msgid "All rights reserved."
|
||||
msgstr ""
|
||||
|
|
7
po/fr.po
7
po/fr.po
|
@ -609,3 +609,10 @@ msgstr "Illustration"
|
|||
|
||||
msgid "None"
|
||||
msgstr "Aucun"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Let it empty reserve all rights"
|
||||
msgstr "Laisser vide s’il n’y en a pas"
|
||||
|
||||
msgid "All rights reserved."
|
||||
msgstr ""
|
||||
|
|
7
po/gl.po
7
po/gl.po
|
@ -600,6 +600,13 @@ msgstr "Administración"
|
|||
msgid "None"
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "Let it empty reserve all rights"
|
||||
msgstr "Deixar baldeiro si non hai ningunha"
|
||||
|
||||
msgid "All rights reserved."
|
||||
msgstr ""
|
||||
|
||||
#~ msgid "Home to"
|
||||
#~ msgstr "Fogar de"
|
||||
|
||||
|
|
7
po/it.po
7
po/it.po
|
@ -603,6 +603,13 @@ msgstr "Amministrazione"
|
|||
msgid "None"
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "Let it empty reserve all rights"
|
||||
msgstr "Lascialo vuoto se non è presente nessuno"
|
||||
|
||||
msgid "All rights reserved."
|
||||
msgstr ""
|
||||
|
||||
#~ msgid "Home to"
|
||||
#~ msgstr "Casa di"
|
||||
|
||||
|
|
7
po/ja.po
7
po/ja.po
|
@ -595,6 +595,13 @@ msgstr "図"
|
|||
msgid "None"
|
||||
msgstr "なし"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Let it empty reserve all rights"
|
||||
msgstr "不要な場合は空にしてください"
|
||||
|
||||
msgid "All rights reserved."
|
||||
msgstr ""
|
||||
|
||||
#~ msgid "Welcome to {{ instance_name | escape }}"
|
||||
#~ msgstr "{{ instance_name | escape }} へようこそ"
|
||||
|
||||
|
|
6
po/nb.po
6
po/nb.po
|
@ -615,6 +615,12 @@ msgstr "Administrasjon"
|
|||
msgid "None"
|
||||
msgstr ""
|
||||
|
||||
msgid "Let it empty reserve all rights"
|
||||
msgstr ""
|
||||
|
||||
msgid "All rights reserved."
|
||||
msgstr ""
|
||||
|
||||
#~ msgid "Home to"
|
||||
#~ msgstr "Hjem for"
|
||||
|
||||
|
|
7
po/pl.po
7
po/pl.po
|
@ -609,6 +609,13 @@ msgstr "Ilustracja"
|
|||
msgid "None"
|
||||
msgstr "Brak"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Let it empty reserve all rights"
|
||||
msgstr "Pozostaw puste, jeżeli niepotrzebne"
|
||||
|
||||
msgid "All rights reserved."
|
||||
msgstr ""
|
||||
|
||||
#~ msgid "Home to"
|
||||
#~ msgstr "Dom dla"
|
||||
|
||||
|
|
|
@ -579,3 +579,9 @@ msgstr ""
|
|||
|
||||
msgid "None"
|
||||
msgstr ""
|
||||
|
||||
msgid "Let it empty reserve all rights"
|
||||
msgstr ""
|
||||
|
||||
msgid "All rights reserved."
|
||||
msgstr ""
|
||||
|
|
7
po/ru.po
7
po/ru.po
|
@ -620,6 +620,13 @@ msgstr "Иллюстрация"
|
|||
msgid "None"
|
||||
msgstr "Нет"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Let it empty reserve all rights"
|
||||
msgstr "Оставьте пустым если нет"
|
||||
|
||||
msgid "All rights reserved."
|
||||
msgstr ""
|
||||
|
||||
#~ msgid "Home to"
|
||||
#~ msgstr "Дом для"
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
use activitypub::object::Article;
|
||||
use chrono::Utc;
|
||||
use heck::{CamelCase, KebabCase};
|
||||
use rocket::request::LenientForm;
|
||||
|
@ -89,7 +88,7 @@ pub fn details_response(blog: String, slug: String, conn: DbConn, user: Option<U
|
|||
}
|
||||
|
||||
#[get("/~/<blog>/<slug>", rank = 3)]
|
||||
pub fn activity_details(blog: String, slug: String, conn: DbConn, _ap: ApRequest) -> Result<ActivityStream<Article>, Option<String>> {
|
||||
pub fn activity_details(blog: String, slug: String, conn: DbConn, _ap: ApRequest) -> Result<ActivityStream<LicensedArticle>, Option<String>> {
|
||||
let blog = Blog::find_by_fqn(&*conn, &blog).ok_or(None)?;
|
||||
let post = Post::find_by_slug(&*conn, &slug, blog.id).ok_or(None)?;
|
||||
if post.published {
|
||||
|
@ -123,11 +122,13 @@ pub fn new(blog: String, user: User, conn: DbConn, intl: I18n) -> Option<Ructe>
|
|||
&(&*conn, &intl.catalog, Some(user)),
|
||||
b,
|
||||
false,
|
||||
&NewPostForm::default(),
|
||||
&NewPostForm {
|
||||
license: Instance::get_local(&*conn).map(|i| i.default_license).unwrap_or_else(||String::from("CC-BY-SA")),
|
||||
..NewPostForm::default()
|
||||
},
|
||||
true,
|
||||
None,
|
||||
ValidationErrors::default(),
|
||||
Instance::get_local(&*conn).expect("posts::new error: Local instance is null").default_license,
|
||||
medias
|
||||
)))
|
||||
}
|
||||
|
@ -171,7 +172,6 @@ pub fn edit(blog: String, slug: String, user: User, conn: DbConn, intl: I18n) ->
|
|||
!post.published,
|
||||
Some(post),
|
||||
ValidationErrors::default(),
|
||||
Instance::get_local(&*conn).expect("posts::new error: Local instance is null").default_license,
|
||||
medias
|
||||
)))
|
||||
}
|
||||
|
@ -209,12 +209,6 @@ pub fn update(blog: String, slug: String, user: User, conn: DbConn, form: Lenien
|
|||
} else {
|
||||
let (content, mentions, hashtags) = utils::md_to_html(form.content.to_string().as_ref());
|
||||
|
||||
let license = if !form.license.is_empty() {
|
||||
form.license.to_string()
|
||||
} else {
|
||||
Instance::get_local(&*conn).map(|i| i.default_license).unwrap_or_else(|| String::from("CC-BY-SA"))
|
||||
};
|
||||
|
||||
// update publication date if when this article is no longer a draft
|
||||
let newly_published = if !post.published && !form.draft {
|
||||
post.published = true;
|
||||
|
@ -229,7 +223,7 @@ pub fn update(blog: String, slug: String, user: User, conn: DbConn, form: Lenien
|
|||
post.subtitle = form.subtitle.clone();
|
||||
post.content = SafeString::new(&content);
|
||||
post.source = form.content.clone();
|
||||
post.license = license;
|
||||
post.license = form.license.clone();
|
||||
post.cover_id = form.cover;
|
||||
post.update(&*conn, &searcher);
|
||||
let post = post.update_ap_url(&*conn);
|
||||
|
@ -270,7 +264,6 @@ pub fn update(blog: String, slug: String, user: User, conn: DbConn, form: Lenien
|
|||
form.draft.clone(),
|
||||
Some(post),
|
||||
errors.clone(),
|
||||
Instance::get_local(&*conn).expect("posts::new error: Local instance is null").default_license,
|
||||
medias.clone()
|
||||
));
|
||||
Err(Some(temp))
|
||||
|
@ -330,11 +323,7 @@ pub fn create(blog_name: String, form: LenientForm<NewPostForm>, user: User, con
|
|||
title: form.title.to_string(),
|
||||
content: SafeString::new(&content),
|
||||
published: !form.draft,
|
||||
license: if !form.license.is_empty() {
|
||||
form.license.to_string()
|
||||
} else {
|
||||
Instance::get_local(&*conn).map(|i| i.default_license).unwrap_or_else(||String::from("CC-BY-SA"))
|
||||
},
|
||||
license: form.license.clone(),
|
||||
ap_url: "".to_string(),
|
||||
creation_date: None,
|
||||
subtitle: form.subtitle.clone(),
|
||||
|
@ -390,7 +379,6 @@ pub fn create(blog_name: String, form: LenientForm<NewPostForm>, user: User, con
|
|||
form.draft,
|
||||
None,
|
||||
errors.clone(),
|
||||
Instance::get_local(&*conn).expect("posts::new error: Local instance is null").default_license,
|
||||
medias
|
||||
))))
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use activitypub::{activity::Create, collection::OrderedCollection, object::Article};
|
||||
use activitypub::{activity::Create, collection::OrderedCollection};
|
||||
use atom_syndication::{Entry, FeedBuilder};
|
||||
use rocket::{
|
||||
http::{ContentType, Cookies},
|
||||
|
@ -18,7 +18,7 @@ use plume_common::activity_pub::{
|
|||
};
|
||||
use plume_common::utils;
|
||||
use plume_models::{
|
||||
blogs::Blog, db_conn::DbConn, follows, headers::Headers, instance::Instance, posts::Post,
|
||||
blogs::Blog, db_conn::DbConn, follows, headers::Headers, instance::Instance, posts::{LicensedArticle, Post},
|
||||
reshares::Reshare, users::*,
|
||||
};
|
||||
use routes::Page;
|
||||
|
@ -56,7 +56,7 @@ pub fn details(
|
|||
let searcher = searcher.clone();
|
||||
worker.execute(move || {
|
||||
for create_act in user_clone.fetch_outbox::<Create>() {
|
||||
match create_act.create_props.object_object::<Article>() {
|
||||
match create_act.create_props.object_object::<LicensedArticle>() {
|
||||
Ok(article) => {
|
||||
Post::from_activity(
|
||||
&(&*fetch_articles_conn, &searcher),
|
||||
|
|
|
@ -53,7 +53,13 @@
|
|||
@Html(&article.content)
|
||||
</article>
|
||||
<div class="article-meta">
|
||||
<p>@i18n!(ctx.1, "This article is under the {0} license."; &article.license)</p>
|
||||
<p>
|
||||
@if article.license.is_empty() {
|
||||
@i18n!(ctx.1, "All rights reserved."; &article.license)
|
||||
} else {
|
||||
@i18n!(ctx.1, "This article is under the {0} license."; &article.license)
|
||||
}
|
||||
</p>
|
||||
<ul class="tags">
|
||||
@for tag in tags {
|
||||
@if !tag.is_hashtag {
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
@use routes::posts::NewPostForm;
|
||||
@use routes::*;
|
||||
|
||||
@(ctx: BaseContext, blog: Blog, editing: bool, form: &NewPostForm, is_draft: bool, article: Option<Post>, errors: ValidationErrors, default_license: String, medias: Vec<Media>)
|
||||
@(ctx: BaseContext, blog: Blog, editing: bool, form: &NewPostForm, is_draft: bool, article: Option<Post>, errors: ValidationErrors, medias: Vec<Media>)
|
||||
|
||||
@:base(ctx, &i18n!(ctx.1, if editing { "Edit {0}" } else { "New post" }; &form.title), {}, {}, {
|
||||
<h1>
|
||||
|
@ -35,7 +35,7 @@
|
|||
|
||||
@input!(ctx.1, tags (optional text), "Tags, separated by commas", form, errors.clone(), "")
|
||||
|
||||
@input!(ctx.1, license (optional text), "License", &i18n!(ctx.1, "Default license will be {0}"; &default_license), form, errors, "")
|
||||
@input!(ctx.1, license (optional text), "License", "Let it empty reserve all rights", form, errors, "")
|
||||
|
||||
<label for="cover">@i18n!(ctx.1, "Illustration")<small>@i18n!(ctx.1, "Optional")</small></label>
|
||||
<select id="cover" name="cover">
|
||||
|
|
Loading…
Reference in New Issue