core: add avatar provider API

This commit is contained in:
undefined committed 2021-04-19 23:39:51 +08:00
1 parent 2db1570d1b
commit f1f956202f
42 files changed
+122 -80

No files matched your search

-2
View File
@@ -35,7 +35,6 @@ __langname: 한국어
'Role {1} already exists in domain {0}.': '역할 {1}이(가) 이미 {0}에 존재합니다.'
'Solved {0} problems, RP: {1} (No. {2})': '총 {0} 문제 해결.RP: {1} (No. {2})'
'The value `{1}` of {0} already exists.': '{0} 의 값 `{1}` 이 이미 존재합니다.'
'We use <a href="https://en.gravatar.com/" target="_blank">Gravatar</a> to present your avatar icon.': '<a href="https://cn.gravatar.com/" target="_blank">Gravatar</a>를 만들어 보세요.'
'Yes': 예
"Effects only when Difficulty is not 'Use algorithm calculated'.": 난이도가 알고리즘에 의해 정해지지 않은 경우에만 적용됩니다.
"Split by ', '.": “, ”“,”으로 구분
@@ -312,7 +311,6 @@ Formula blocks: 공식 블록
fs_upload: 업로드
Gender Visibility: 성별 공개
Gender: 성
Gravatar Email: Gravatar 이메일
Hard Deadline: 마감일
Hash: 해시
Have ALL PERMISSIONS in this domain: 이 그룹에 대한 모든 권한
+1 -2
View File
@@ -35,7 +35,6 @@ __langname: 简体中文
'Role {1} already exists in domain {0}.': '域 {0} 中已存在角色 {1}。'
'Solved {0} problems, RP: {1} (No. {2})': '解决了 {0} 道题目,RP: {1} (No. {2})'
'The value `{1}` of {0} already exists.': '{0} 的值 `{1}` 已经存在。'
'We use <a href="https://en.gravatar.com/" target="_blank">Gravatar</a> to present your avatar icon.': '我们使用 <a href="https://cn.gravatar.com/" target="_blank">Gravatar</a> 服务来呈现您的头像。'
'Yes': 是
"Effects only when Difficulty is not 'Use algorithm calculated'.": 仅当难度不为“使用算法计算”时才起效。
"Split by ',' '.'": 由“, ”或“,”分隔。
@@ -81,6 +80,7 @@ Attend Contest: 参加比赛
Attend contests: 参加比赛
Attended: 已参加
author: 作者
Avatar: 头像
Be Copied: 被复制
Begin at: 开始于
Begin Date: 开始日期
@@ -326,7 +326,6 @@ Formula blocks: 公式区块
fs_upload: 上传文件
Gender Visibility: 性别可见性
Gender: 性别
Gravatar Email: Gravatar Email 地址
Hard Deadline: 最终截止时间
Hash: 散列
Have ALL PERMISSIONS in this domain: 在此域中拥有全部权限
-2
View File
@@ -195,7 +195,6 @@ Forgot password or username?: 忘記密碼或者使用者名?
Formula blocks: 公式區塊
Gender Visibility: 性別可見性
Gender: 性別
Gravatar Email: Gravatar Email 地址
Hard Deadline: 最終截止時間
Have ALL PERMISSIONS in this domain: 在此域中擁有全部許可權
Help: 幫助
@@ -539,7 +538,6 @@ Visible to registered users: 對註冊使用者可見
Visit: 檢視
Vote problem solutions: 為題解投票
Vote: 投票
We use Gravatar to present your avatar icon.: 我們使用 Gravatar 服務來呈現您的頭像。
We will check code similarity after this contest.: 我們會在賽後檢查程式碼相似度。
WeChat Visibility: 微信可見性
WeChat: 微信
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "hydrooj",
"version": "2.23.6",
"version": "2.23.7",
"bin": "bin/hydrooj.js",
"main": "dist/loader.js",
"typings": "dist/loader.d.ts",
+1
View File
@@ -13,6 +13,7 @@ export const builtinLib = [
'jwt', 'download', 'i18n', 'mail', 'useragent',
'crypto', 'misc', 'paginate', 'hash.hydro', 'rank',
'validator', 'ui', 'testdataConfig', 'difficulty', 'content',
'avatar',
];
export const builtinModel = [
+2 -2
View File
@@ -14,7 +14,7 @@ import { DOMAIN_SETTINGS, DOMAIN_SETTINGS_BY_KEY } from '../model/setting';
import {
DEFAULT_NODES, PERM, PERMS_BY_FAMILY, PRIV,
} from '../model/builtin';
import { gravatar } from '../lib/misc';
import avatar from '../lib/avatar';
import paginate from '../lib/paginate';
import {
Route, Handler, Types, param, query, post,
@@ -297,7 +297,7 @@ class DomainSearchHandler extends Handler {
async get(domainId: string, q: string) {
const ddocs = await domain.getPrefixSearch(q, 20);
for (let i = 0; i < ddocs.length; i++) {
ddocs[i].gravatar = ddocs[i].gravatar ? gravatar(ddocs[i].gravatar) : '/img/team_avatar.png';
ddocs[i].avatarUrl = ddocs[i].avatar ? avatar(ddocs[i].avatar, 64) : '/img/team_avatar.png';
}
this.response.body = ddocs;
}
+9 -9
View File
@@ -10,8 +10,9 @@ import * as bus from '../service/bus';
import {
Route, Connection, Handler, ConnectionHandler, param, Types,
} from '../service/server';
import * as misc from '../lib/misc';
import { md5 } from '../lib/crypto';
import { isPassword, isEmail } from '../lib/validator';
import avatar from '../lib/avatar';
import * as mail from '../lib/mail';
import * as contest from '../model/contest';
import message from '../model/message';
@@ -24,7 +25,6 @@ import * as discussion from '../model/discussion';
import token from '../model/token';
import * as training from '../model/training';
import { PERM, PRIV } from '../model/builtin';
import { isPassword, isEmail } from '../lib/validator';
const { geoip, useragent } = global.Hydro.lib;
@@ -309,13 +309,13 @@ class HomeDomainCreateHandler extends Handler {
@param('id', Types.Name)
@param('name', Types.Title)
@param('bulletin', Types.Content)
@param('gravatar', Types.Content, true, isEmail)
async post(_: string, id: string, name: string, bulletin: string, gravatar: string) {
@param('avatar', Types.Content, true, isEmail)
async post(_: string, id: string, name: string, bulletin: string, avatar: string) {
const doc = await domain.get(id);
if (doc) throw new DomainAlreadyExistsError(id);
gravatar = gravatar || this.user.gravatar || this.user.mail || 'guest@hydro.local';
avatar = avatar || this.user.avatar || `gravatar:${this.user.mail}` || 'gravatar:guest@hydro.local';
const domainId = await domain.add(id, this.user._id, name, bulletin);
await domain.edit(domainId, { gravatar });
await domain.edit(domainId, { avatar });
await domain.setUserRole(domainId, this.user._id, 'root');
this.response.redirect = this.url('domain_dashboard', { domainId });
this.response.body = { domainId };
@@ -338,7 +338,7 @@ class HomeMessagesHandler extends Handler {
if (!parsed[target]) {
parsed[target] = {
_id: target,
udoc: { ...udict[target], gravatar: misc.gravatar(udict[target].gravatar) },
udoc: { ...udict[target], avatarUrl: avatar(udict[target].avatar) },
messages: [],
};
}
@@ -358,7 +358,7 @@ class HomeMessagesHandler extends Handler {
async postSend(domainId: string, uid: number, content: string) {
const udoc = await user.getById('system', uid);
if (!udoc) throw new UserNotFoundError(uid);
if (udoc.gravatar) udoc.gravatar = misc.gravatar(udoc.gravatar);
if (udoc.avatar) udoc.avatarUrl = avatar(udoc.avatar);
const mdoc = await message.send(this.user._id, uid, content, message.FLAG_UNREAD);
this.back({ mdoc, udoc });
}
@@ -391,7 +391,7 @@ class HomeMessagesConnectionHandler extends ConnectionHandler {
async onMessageReceived(uid: number, mdoc: Mdoc) {
if (uid !== this.user._id) return;
const udoc = await user.getById(this.domainId, mdoc.from);
udoc.gravatar_url = misc.gravatar(udoc.gravatar, 64);
udoc.avatarUrl = avatar(udoc.avatar, 64);
this.send({ udoc, mdoc });
}
+3 -3
View File
@@ -4,6 +4,7 @@ import {
UserNotFoundError, SystemError, BlacklistedError,
UserFacingError, LoginError,
} from '../error';
import { User } from '../interface';
import {
Route, Handler, Types, param, post,
} from '../service/server';
@@ -17,9 +18,8 @@ import * as system from '../model/system';
import { PERM, PRIV } from '../model/builtin';
import { isEmail, isPassword, isUname } from '../lib/validator';
import { sendMail } from '../lib/mail';
import * as misc from '../lib/misc';
import paginate from '../lib/paginate';
import { User } from '../interface';
import avatar from '../lib/avatar';
class UserLoginHandler extends Handler {
async get() {
@@ -241,7 +241,7 @@ class UserSearchHandler extends Handler {
if (udoc) udocs.push(udoc);
if (!exactMatch) udocs.push(...await user.getPrefixList(domainId, q, 20));
for (const i in udocs) {
udocs[i].gravatar = misc.gravatar(udocs[i].gravatar || '');
udocs[i].avatarUrl = avatar(udocs[i].avatar);
}
this.response.body = udocs;
}
+1 -1
View File
@@ -301,7 +301,7 @@ export interface DomainDoc extends Record<string, any> {
_id: string,
owner: number,
roles: Dictionary<string>,
gravatar: string,
avatar: string,
bulletin: string,
_join?: any,
host?: string,
+20
View File
@@ -0,0 +1,20 @@
import { md5 } from './crypto';
type AvatarProvider = (src: string, size: number) => string;
export const providers: Record<string, AvatarProvider> = {
gravatar: (email, size) => `//gravatar.loli.net/avatar/${md5((email || '').toString().trim().toLowerCase())}?d=mm&s=${size || 32}`,
qq: (id) => `//q1.qlogo.cn/g?b=qq&nk=${/(\d+)/g.exec(id)[1]}&s=640`,
github: (id, size) => `//github.com/${id}.png?size=${Math.min(size, 460)}`,
};
function avatar(src: string, size = 64) {
const index = src.indexOf(':');
if (index === -1) return providers.gravatar('', size);
const [provider, str] = [src.substr(0, index), src.substr(index + 1, src.length)];
if (!providers[provider] || !str) return providers.gravatar('', size);
return providers[provider](str, size);
}
export default avatar;
global.Hydro.lib.avatar = avatar;
+1 -6
View File
@@ -1,7 +1,6 @@
import AnsiUp from 'ansi_up';
import { ObjectID } from 'mongodb';
import { size, formatSeconds } from '@hydrooj/utils/lib/utils';
import { md5 } from './crypto';
export { size, formatSeconds } from '@hydrooj/utils/lib/utils';
@@ -11,10 +10,6 @@ export function ansiToHtml(str: string) {
return AU.ansi_to_html(str);
}
export function gravatar(email: string, s = 32) {
return `//gravatar.loli.net/avatar/${md5((email || '').toString().trim().toLowerCase())}?d=mm&s=${s}`;
}
export function datetimeSpan(dt: Date | ObjectID, relative = true, format = '%Y-%m-%d %H:%M:%S') {
if (!dt) return 'DATETIME_SPAN_ERROR';
if (dt instanceof ObjectID) dt = new Date(dt.generationTime * 1000);
@@ -53,5 +48,5 @@ export function* paginate(page: number, numPages: number) {
}
global.Hydro.lib.misc = {
gravatar, datetimeSpan, paginate, size, formatSeconds, ansiToHtml,
datetimeSpan, paginate, size, formatSeconds, ansiToHtml,
};
+1 -1
View File
@@ -47,7 +47,7 @@ class DomainModel {
name,
bulletin,
roles: {},
gravatar: '',
avatar: '',
};
await bus.serial('domain/create', ddoc);
await coll.insertOne(ddoc);
+3 -3
View File
@@ -107,8 +107,8 @@ PreferenceSetting(
);
AccountSetting(
Setting('setting_info', 'gravatar', null, 'text', 'Gravatar Email',
'We use <a href="https://en.gravatar.com/" target="_blank">Gravatar</a> to present your avatar icon.'),
Setting('setting_info', 'avatar', '', 'text', 'Avatar',
'Allow using gravatar:email qq:id github:name format.'),
Setting('setting_info', 'qq', null, 'text', 'QQ'),
Setting('setting_info', 'gender', builtin.USER_GENDER_OTHER, builtin.USER_GENDER_RANGE, 'Gender'),
Setting('setting_info', 'bio', null, 'markdown', 'Bio'),
@@ -122,7 +122,7 @@ AccountSetting(
DomainSetting(
Setting('setting_domain', 'name', 'New domain', 'text', 'name'),
Setting('setting_domain', 'gravatar', '', 'text', 'gravatar', 'Will be used as the domain icon.'),
Setting('setting_domain', 'avatar', '', 'text', 'avatar', 'Will be used as the domain icon.'),
Setting('setting_domain', 'bulletin', '', 'markdown', 'Bulletin'),
Setting('setting_storage', 'host', '', 'text', 'Custom host', null, FLAG_HIDDEN | FLAG_DISABLED),
);
+2 -2
View File
@@ -101,7 +101,7 @@ class UserModel {
_id: 0,
uname: 'Unknown User',
unameLower: 'unknown user',
gravatar: 'unknown@hydro.local',
avatar: 'gravatar:unknown@hydro.local',
mail: 'unknown@hydro.local',
mailLower: 'unknown@hydro.local',
salt: '',
@@ -213,7 +213,7 @@ class UserModel {
loginat: new Date(),
loginip: regip,
priv,
gravatar: mail,
avatar: `gravatar:${mail}`,
});
} catch (e) {
logger.warn('%o', e);
+7 -1
View File
@@ -1,6 +1,7 @@
/* eslint-disable no-await-in-loop */
import { DomainDoc } from './loader';
import type { DomainDoc, Udoc } from './interface';
import domain from './model/domain';
import user from './model/user';
import problem, { Field, Pdoc } from './model/problem';
export async function iterateAllDomain(cb: (ddoc: DomainDoc, current?: number, total?: number) => Promise<any>) {
@@ -8,6 +9,11 @@ export async function iterateAllDomain(cb: (ddoc: DomainDoc, current?: number, t
for (const i in ddocs) await cb(ddocs[i], +i, ddocs.length);
}
export async function iterateAllUser(cb: (udoc: Udoc, current?: number, total?: number) => Promise<any>) {
const udocs = await user.getMulti().toArray();
for (const i in udocs) await cb(udocs[i], +i, udocs.length);
}
interface PartialPdoc extends Pdoc {
[key: string]: any,
}
+3 -2
View File
@@ -28,8 +28,8 @@ import {
CsrfTokenError, InvalidOperationError, MethodNotAllowedError,
NotFoundError, HydroError, SystemError,
} from '../error';
import * as misc from '../lib/misc';
import { isContent, isName, isTitle } from '../lib/validator';
import avatar from '../lib/avatar';
import user from '../model/user';
import domain from '../model/domain';
import * as system from '../model/system';
@@ -322,7 +322,7 @@ export class HandlerCommon {
async renderHTML(name: string, context: any): Promise<string> {
const UserContext: any = {
...this.user,
gravatar: misc.gravatar(this.user.gravatar || '', 128),
avatar: avatar(this.user.avatar || '', 128),
perm: this.user.perm.toString(),
viewLang: this.translate('__id'),
};
@@ -508,6 +508,7 @@ export class Handler extends HandlerCommon {
this.user = await user.getById(domainId, this.session.uid, this.session.scope);
}
if (this.user._id === 0 && this.session.viewLang) this.user.viewLang = this.session.viewLang;
this.user.avatarUrl = avatar(this.user.avatar, 128);
this.csrfToken = this.getCsrfToken(this.session._id || String.random(32));
this.UiContext.csrfToken = this.csrfToken;
this.loginMethods = filter(Object.keys(global.Hydro.lib), (str) => str.startsWith('oauth_'))
+9 -1
View File
@@ -10,12 +10,13 @@ import { Progress } from './ui';
import { Rdoc } from './interface';
import { Logger } from './logger';
import { streamToBuffer } from './utils';
import { iterateAllDomain, iterateAllProblem } from './pipelineUtils';
import { iterateAllDomain, iterateAllProblem, iterateAllUser } from './pipelineUtils';
import gridfs from './service/gridfs';
import storage from './service/storage';
import db from './service/db';
import difficultyAlgorithm from './lib/difficulty';
import problem from './model/problem';
import user from './model/user';
import domain from './model/domain';
import * as document from './model/document';
import * as system from './model/system';
@@ -299,6 +300,13 @@ const scripts: UpgradeScript[] = [
});
return true;
},
async function _22_23() {
await iterateAllUser(async (udoc) => {
if (!udoc.gravatar) return;
await user.setById(udoc._id, { avatar: `gravatar:${udoc.gravatar}` }, { gravatar: '' });
});
return true;
},
];
export default scripts;