Commit 01223afa authored by Eliot Berriot's avatar Eliot Berriot
Browse files

Resolve "Add optional donation/contribution link in-app"

parent 5936dfc2
......@@ -884,3 +884,7 @@ FEDERATION_OBJECT_FETCH_DELAY = env.int(
MODERATION_EMAIL_NOTIFICATIONS_ENABLED = env.bool(
"MODERATION_EMAIL_NOTIFICATIONS_ENABLED", default=True
)
# Delay in days after signup before we show the "support us" messages
INSTANCE_SUPPORT_MESSAGE_DELAY = env.int("INSTANCE_SUPPORT_MESSAGE_DELAY", default=15)
FUNKWHALE_SUPPORT_MESSAGE_DELAY = env.int("FUNKWHALE_SUPPORT_MESSAGE_DELAY", default=15)
......@@ -82,6 +82,35 @@ class InstanceContactEmail(types.StringPreference):
field_kwargs = {"required": False}
@global_preferences_registry.register
class InstanceSupportMessage(types.StringPreference):
show_in_api = True
section = instance
name = "support_message"
verbose_name = "Support message"
default = ""
help_text = (
"A short message that will be displayed periodically to local users. "
"Use it to ask for financial support or anything else you might need. "
"(markdown allowed)."
)
widget = widgets.Textarea
field_kwargs = {"required": False}
@global_preferences_registry.register
class InstanceFunkwhaleSupportMessageEnabled(types.BooleanPreference):
show_in_api = True
section = instance
name = "funkwhale_support_message_enabled"
verbose_name = "Funkwhale Support message"
default = True
help_text = (
"If this is enabled, we will periodically display a message to encourage "
"local users to support Funkwhale."
)
@global_preferences_registry.register
class RavenDSN(types.StringPreference):
show_in_api = True
......
......@@ -63,6 +63,10 @@ def get():
{"type": t, "label": l, "anonymous": t in unauthenticated_report_types}
for t, l in moderation_models.REPORT_TYPES
],
"funkwhaleSupportMessageEnabled": all_preferences.get(
"instance__funkwhale_support_message_enabled"
),
"instanceSupportMessage": all_preferences.get("instance__support_message"),
},
}
......
......@@ -90,6 +90,15 @@ class UserAdmin(AuthUserAdmin):
},
),
(_("Important dates"), {"fields": ("last_login", "date_joined")}),
(
_("Other"),
{
"fields": (
"instance_support_message_display_date",
"funkwhale_support_message_display_date",
)
},
),
(_("Useless fields"), {"fields": ("user_permissions", "groups")}),
)
......
# Generated by Django 2.2.4 on 2019-09-20 08:57
import datetime
from django.conf import settings
from django.db import migrations, models
import django.utils.timezone
import funkwhale_api.users.models
def set_display_date(apps, schema_editor):
"""
Set display date for instance/funkwhale support message on existing users
"""
User = apps.get_model("users", "User")
now = django.utils.timezone.now()
instance_support_message_display_date = now + datetime.timedelta(days=settings.INSTANCE_SUPPORT_MESSAGE_DELAY)
funkwhale_support_message_display_date = now + datetime.timedelta(days=settings.FUNKWHALE_SUPPORT_MESSAGE_DELAY)
User.objects.update(instance_support_message_display_date=instance_support_message_display_date)
User.objects.update(funkwhale_support_message_display_date=funkwhale_support_message_display_date)
def rewind(*args, **kwargs):
pass
class Migration(migrations.Migration):
dependencies = [
('users', '0015_application_scope'),
]
operations = [
migrations.AddField(
model_name='user',
name='funkwhale_support_message_display_date',
field=models.DateTimeField(blank=True, null=True, default=funkwhale_api.users.models.get_default_funkwhale_support_message_display_date),
),
migrations.AddField(
model_name='user',
name='instance_support_message_display_date',
field=models.DateTimeField(blank=True, null=True, default=funkwhale_api.users.models.get_default_instance_support_message_display_date),
),
migrations.RunPython(set_display_date, rewind),
]
......@@ -82,6 +82,18 @@ PERMISSIONS = sorted(PERMISSIONS_CONFIGURATION.keys())
get_file_path = common_utils.ChunkedPath("users/avatars", preserve_file_name=False)
def get_default_instance_support_message_display_date():
return timezone.now() + datetime.timedelta(
days=settings.INSTANCE_SUPPORT_MESSAGE_DELAY
)
def get_default_funkwhale_support_message_display_date():
return timezone.now() + datetime.timedelta(
days=settings.FUNKWHALE_SUPPORT_MESSAGE_DELAY
)
@python_2_unicode_compatible
class User(AbstractUser):
......@@ -149,6 +161,15 @@ class User(AbstractUser):
upload_quota = models.PositiveIntegerField(null=True, blank=True)
instance_support_message_display_date = models.DateTimeField(
default=get_default_instance_support_message_display_date, null=True, blank=True
)
funkwhale_support_message_display_date = models.DateTimeField(
default=get_default_funkwhale_support_message_display_date,
null=True,
blank=True,
)
def __str__(self):
return self.username
......
......@@ -109,7 +109,13 @@ class UserWriteSerializer(serializers.ModelSerializer):
class Meta:
model = models.User
fields = ["name", "privacy_level", "avatar"]
fields = [
"name",
"privacy_level",
"avatar",
"instance_support_message_display_date",
"funkwhale_support_message_display_date",
]
class UserReadSerializer(serializers.ModelSerializer):
......@@ -146,7 +152,11 @@ class MeSerializer(UserReadSerializer):
quota_status = serializers.SerializerMethodField()
class Meta(UserReadSerializer.Meta):
fields = UserReadSerializer.Meta.fields + ["quota_status"]
fields = UserReadSerializer.Meta.fields + [
"quota_status",
"instance_support_message_display_date",
"funkwhale_support_message_display_date",
]
def get_quota_status(self, o):
return o.get_quota_status() if o.actor else 0
......
......@@ -87,6 +87,10 @@ def test_nodeinfo_dump(preferences, mocker, avatar):
},
{"type": "other", "label": "Other", "anonymous": True},
],
"funkwhaleSupportMessageEnabled": preferences[
"instance__funkwhale_support_message_enabled"
],
"instanceSupportMessage": preferences["instance__support_message"],
},
}
assert nodeinfo.get() == expected
......@@ -151,6 +155,10 @@ def test_nodeinfo_dump_stats_disabled(preferences, mocker):
},
{"type": "other", "label": "Other", "anonymous": True},
],
"funkwhaleSupportMessageEnabled": preferences[
"instance__funkwhale_support_message_enabled"
],
"instanceSupportMessage": preferences["instance__support_message"],
},
}
assert nodeinfo.get() == expected
......
......@@ -220,3 +220,20 @@ def test_user_get_quota_status(factories, preferences, mocker):
"errored": 3,
"finished": 4,
}
@pytest.mark.parametrize(
"setting_name, field",
[
("INSTANCE_SUPPORT_MESSAGE_DELAY", "instance_support_message_display_date"),
("FUNKWHALE_SUPPORT_MESSAGE_DELAY", "funkwhale_support_message_display_date"),
],
)
def test_creating_user_set_support_display_date(
setting_name, field, settings, factories, now
):
setattr(settings, setting_name, 66) # display message every 66 days
expected = now + datetime.timedelta(days=66)
user = factories["users.User"]()
assert getattr(user, field) == expected
Added periodical message to incite people to support their pod and Funkwhale (#839)
......@@ -113,6 +113,16 @@ your pod.
If you want to enable this feature on your pod, or learn more, please refer to `our documentation <https://docs.funkwhale.audio/moderator/listing.html>`_!
Periodic message to incite people to support their pod and Funkwhale
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Users will now be reminded on a regular basis that they can help Funkwhale by donating or contributing.
If specified by the pod admin, a separate and custom message will also be displayed in a similar way to provide instructions and links to support the pod.
Both messages will appear for the first time 15 days after signup, in the notifications tab. For each message, users can schedule a reminder for a later time, or disable the messages entirely.
Replaced Daphne by Gunicorn/Uvicorn [manual action required, non-docker only]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
......
......@@ -46,9 +46,9 @@
<i class="feed icon"></i>
<translate translate-context="*/Notifications/*">Notifications</translate>
<div
v-if="$store.state.ui.notifications.inbox > 0"
v-if="$store.state.ui.notifications.inbox + additionalNotifications > 0"
:class="['ui', 'teal', 'label']">
{{ $store.state.ui.notifications.inbox }}</div>
{{ $store.state.ui.notifications.inbox + additionalNotifications }}</div>
</router-link>
<router-link class="item" v-if="$store.state.auth.authenticated" :to="{name: 'logout'}"><i class="sign out icon"></i><translate translate-context="Sidebar/Login/List item.Link/Verb">Logout</translate></router-link>
<template v-else>
......@@ -186,7 +186,7 @@
</template>
<script>
import { mapState, mapActions } from "vuex"
import { mapState, mapActions, mapGetters } from "vuex"
import Player from "@/components/audio/Player"
import Logo from "@/components/Logo"
......@@ -219,6 +219,9 @@ export default {
}
},
computed: {
...mapGetters({
additionalNotifications: "ui/additionalNotifications",
}),
...mapState({
queue: state => state.queue,
url: state => state.route.path
......
import Vue from 'vue'
import axios from 'axios'
import jwtDecode from 'jwt-decode'
import logger from '@/logging'
import router from '@/router'
import lodash from '@/lodash'
export default {
namespaced: true,
......@@ -73,6 +75,11 @@ export default {
},
permission: (state, {key, status}) => {
state.availablePermissions[key] = status
},
profilePartialUpdate: (state, payload) => {
lodash.keys(payload).forEach((k) => {
Vue.set(state.profile, k, payload[k])
})
}
},
actions: {
......
......@@ -28,6 +28,12 @@ export default {
},
long_description: {
value: ''
},
funkwhale_support_message_enabled: {
value: true
},
support_message: {
value: ''
}
},
users: {
......
......@@ -26,6 +26,44 @@ export default {
},
pageTitle: null
},
getters: {
showInstanceSupportMessage: (state, getters, rootState) => {
if (!rootState.auth.profile) {
return false
}
if (!rootState.instance.settings.instance.support_message.value) {
return false
}
let displayDate = rootState.auth.profile.instance_support_message_display_date
if (!displayDate) {
return false
}
return moment(displayDate) < moment(state.lastDate)
},
showFunkwhaleSupportMessage: (state, getters, rootState) => {
if (!rootState.auth.profile) {
return false
}
if (!rootState.instance.settings.instance.funkwhale_support_message_enabled.value) {
return false
}
let displayDate = rootState.auth.profile.funkwhale_support_message_display_date
if (!displayDate) {
return false
}
return moment(displayDate) < moment(state.lastDate)
},
additionalNotifications: (state, getters) => {
let count = 0
if (getters.showInstanceSupportMessage) {
count += 1
}
if (getters.showFunkwhaleSupportMessage) {
count += 1
}
return count
}
},
mutations: {
addWebsocketEventHandler: (state, {eventName, id, handler}) => {
state.websocketEventsHandlers[eventName][id] = handler
......
......@@ -2,6 +2,71 @@
<main class="main pusher" v-title="labels.title">
<section class="ui vertical aligned stripe segment">
<div class="ui container">
<div class="ui container" v-if="additionalNotifications">
<h1 class="ui header"><translate translate-context="Content/Notifications/Title">Your messages</translate></h1>
<div class="ui two column stackable grid">
<div class="column" v-if="showInstanceSupportMessage">
<div class="ui attached info message">
<div class="header">
<translate translate-context="Content/Notifications/Header">Support this Funkwhale pod</translate>
</div>
<div v-html="markdown.makeHtml($store.state.instance.settings.instance.support_message.value)"></div>
</div>
<div class="ui bottom attached segment">
<form @submit.prevent="setDisplayDate('instance_support_message_display_date', instanceSupportMessageDelay)" class="ui inline form">
<div class="inline field">
<label>
<translate translate-context="Content/Notifications/Label">Remind me in:</translate>
</label>
<select v-model="instanceSupportMessageDelay">
<option :value="30"><translate translate-context="*/*/*">30 days</translate></option>
<option :value="60"><translate translate-context="*/*/*">60 days</translate></option>
<option :value="90"><translate translate-context="*/*/*">90 days</translate></option>
<option :value="null"><translate translate-context="*/*/*">Never</translate></option>
</select>
<button type="submit" class="ui right floated basic button">
<translate translate-context="Content/Notifications/Button.Label">Got it!</translate>
</button>
</div>
</form>
</div>
</div>
<div class="column" v-if="showFunkwhaleSupportMessage">
<div class="ui info attached message">
<div class="header">
<translate translate-context="Content/Notifications/Header">Do you like Funkwhale?</translate>
</div>
<p>
<translate translate-context="Content/Notifications/Paragraph">We noticed you've been here for a while. If Funkwhale is useful to you, we could use your help to make it even better!</translate>
</p>
<a href="https://funkwhale.audio/support-us" _target="blank" rel="noopener" class="ui primary inverted button">
<translate translate-context="Content/Notifications/Button.Label/Verb">Donate</translate>
</a>
<a href="https://contribute.funkwhale.audio" _target="blank" rel="noopener" class="ui secondary inverted button">
<translate translate-context="Content/Notifications/Button.Label/Verb">Discover other ways to help</translate>
</a>
</div>
<div class="ui bottom attached segment">
<form @submit.prevent="setDisplayDate('funkwhale_support_message_display_date', funkwhaleSupportMessageDelay)" class="ui inline form">
<div class="inline field">
<label>
<translate translate-context="Content/Notifications/Label">Remind me in:</translate>
</label>
<select v-model="funkwhaleSupportMessageDelay">
<option :value="30"><translate translate-context="*/*/*">30 days</translate></option>
<option :value="60"><translate translate-context="*/*/*">60 days</translate></option>
<option :value="90"><translate translate-context="*/*/*">90 days</translate></option>
<option :value="null"><translate translate-context="*/*/*">Never</translate></option>
</select>
<button type="submit" class="ui right floated basic button">
<translate translate-context="Content/Notifications/Button.Label">Got it!</translate>
</button>
</div>
</form>
</div>
</div>
</div>
</div>
<h1 class="ui header"><translate translate-context="Content/Notifications/Title">Your notifications</translate></h1>
<div class="ui toggle checkbox">
<input v-model="filters.is_read" type="checkbox">
......@@ -25,7 +90,7 @@
<notification-row :item="item" v-for="item in notifications.results" :key="item.id" />
</tbody>
</table>
<p v-else>
<p v-else-if="additionalNotifications === 0">
<translate translate-context="Content/Notifications/Paragraph">No notification to show.</translate>
</p>
</div>
......@@ -34,9 +99,11 @@
</template>
<script>
import { mapState } from "vuex"
import { mapState, mapGetters } from "vuex"
import axios from "axios"
import logger from "@/logging"
import showdown from 'showdown'
import moment from 'moment'
import NotificationRow from "@/components/notifications/NotificationRow"
......@@ -44,7 +111,10 @@ export default {
data() {
return {
isLoading: false,
markdown: new showdown.Converter(),
notifications: {count: 0, results: []},
instanceSupportMessageDelay: 60,
funkwhaleSupportMessageDelay: 60,
filters: {
is_read: false
}
......@@ -71,6 +141,11 @@ export default {
...mapState({
events: state => state.instance.events
}),
...mapGetters({
additionalNotifications: 'ui/additionalNotifications',
showInstanceSupportMessage: 'ui/showInstanceSupportMessage',
showFunkwhaleSupportMessage: 'ui/showFunkwhaleSupportMessage',
}),
labels() {
return {
title: this.$pgettext('*/Notifications/*', "Notifications")
......@@ -82,6 +157,20 @@ export default {
this.notifications.count += 1
this.notifications.results.unshift(event.item)
},
setDisplayDate (field, days) {
let payload = {}
let newDisplayDate
if (days) {
newDisplayDate = moment().add({days})
} else {
newDisplayDate = null
}
payload[field] = newDisplayDate
let self = this
axios.patch(`users/users/${this.$store.state.auth.username}/`, payload).then((response) => {
self.$store.commit('auth/profilePartialUpdate', response.data)
})
},
fetch(params) {
this.isLoading = true
let self = this
......
......@@ -99,6 +99,7 @@ export default {
"instance__rules",
"instance__terms",
"instance__banner",
"instance__support_message"
]
},
{
......@@ -152,7 +153,7 @@ export default {
{
label: uiLabel,
id: "ui",
settings: ["ui__custom_css"]
settings: ["ui__custom_css", "instance__funkwhale_support_message_enabled"]
},
{
label: statisticsLabel,
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment