Replaced Gravatar with self-hosted avatars

Avatars are now obtained from Blender ID. They are downloaded from
Blender ID and stored in the users' home project storage.

Avatars can be synced via Celery and triggered from a webhook.

The avatar can be obtained from the current user object in Python, or
via pillar.api.users.avatar.url(user_dict).

Avatars can be shown in the web frontend by:

- an explicit image (like before but with a non-Gravatar URL)
- a Vue.js component `user-avatar`
- a Vue.js component `current-user-avatar`

The latter is the most efficient for the current user, as it uses user
info that's already injected into the webpage (so requires no extra
queries).
This commit is contained in:
2019-05-24 17:36:06 +02:00
parent 8a19efe7a7
commit 47474ac936
33 changed files with 516 additions and 93 deletions

View File

@@ -1,9 +1,14 @@
export const UserEvents = {
USER_LOADED: 'user-loaded',
}
let currentUserEventBus = new Vue();
class User{
constructor(kwargs) {
this.user_id = kwargs['user_id'] || '';
this.username = kwargs['username'] || '';
this.full_name = kwargs['full_name'] || '';
this.gravatar = kwargs['gravatar'] || '';
this.avatar_url = kwargs['avatar_url'] || '';
this.email = kwargs['email'] || '';
this.capabilities = kwargs['capabilities'] || [];
this.badges_html = kwargs['badges_html'] || '';
@@ -12,7 +17,7 @@ class User{
/**
* """Returns True iff the user has one or more of the given capabilities."""
* @param {...String} args
* @param {...String} args
*/
hasCap(...args) {
for(let cap of args) {
@@ -25,10 +30,16 @@ class User{
let currentUser;
function initCurrentUser(kwargs){
currentUser = new User(kwargs);
currentUserEventBus.$emit(UserEvents.USER_LOADED, currentUser);
}
function getCurrentUser() {
return currentUser;
}
export { getCurrentUser, initCurrentUser }
function updateCurrentUser(user) {
currentUser = user;
currentUserEventBus.$emit(UserEvents.USER_LOADED, currentUser);
}
export { getCurrentUser, initCurrentUser, updateCurrentUser, currentUserEventBus }

View File

@@ -1,6 +1,6 @@
export { transformPlaceholder } from './placeholder'
export { prettyDate } from './prettydate'
export { getCurrentUser, initCurrentUser } from './currentuser'
export { getCurrentUser, initCurrentUser, updateCurrentUser, currentUserEventBus, UserEvents } from './currentuser'
export { thenLoadImage } from './files'
@@ -19,7 +19,7 @@ export function debounced(fn, delay=1000) {
/**
* Extracts error message from error of type String, Error or xhrError
* @param {*} err
* @param {*} err
* @returns {String}
*/
export function messageFromError(err){

View File

@@ -19,6 +19,7 @@ import { StatusFilter } from './table/rows/filter/StatusFilter'
import { TextFilter } from './table/rows/filter/TextFilter'
import { NameFilter } from './table/rows/filter/NameFilter'
import { UserAvatar } from './user/Avatar'
import './user/CurrentUserAvatar'
let mixins = {
UnitOfWorkTracker,

View File

@@ -1,7 +1,7 @@
const TEMPLATE = `
<div class="user-avatar">
<img
:src="user.gravatar"
:src="user.avatar_url"
:alt="user.full_name">
</div>
`;

View File

@@ -0,0 +1,23 @@
const TEMPLATE = `
<img class="user-avatar" :src="avatarUrl" alt="Your avatar">
`
export let CurrentUserAvatar = Vue.component("current-user-avatar", {
data: function() { return {
avatarUrl: "",
}},
template: TEMPLATE,
created: function() {
pillar.utils.currentUserEventBus.$on(pillar.utils.UserEvents.USER_LOADED, this.updateAvatarURL);
this.updateAvatarURL(pillar.utils.getCurrentUser());
},
methods: {
updateAvatarURL(user) {
if (typeof user === 'undefined') {
this.avatarUrl = '';
return;
}
this.avatarUrl = user.avatar_url;
},
},
});