mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-20 01:38:19 +08:00
core&ui: cache uicontext.domain
This commit is contained in:
1 parent
43fb00e3c7
commit
e39a5fa87f
8 files changed
+71
-10
No files matched your search
@@ -436,6 +436,10 @@ export const DomainApi = {
|
|||||||
return ddoc;
|
return ddoc;
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
'domain.current': Query(
|
||||||
|
Schema.object({}),
|
||||||
|
async (handler) => ({ domain: handler.domain as DomainDoc }),
|
||||||
|
),
|
||||||
groups: Query(
|
groups: Query(
|
||||||
Schema.object({
|
Schema.object({
|
||||||
domainId: Schema.string().required(),
|
domainId: Schema.string().required(),
|
||||||
|
|||||||
@@ -13,6 +13,18 @@ import UserModel, { deleteUserCache } from './user';
|
|||||||
const coll = db.collection('domain');
|
const coll = db.collection('domain');
|
||||||
const collUser = db.collection('domain.user');
|
const collUser = db.collection('domain.user');
|
||||||
const cache = new LRUCache<string, DomainDoc>({ max: 1000, ttl: 300 * 1000 });
|
const cache = new LRUCache<string, DomainDoc>({ max: 1000, ttl: 300 * 1000 });
|
||||||
|
const cacheVersion = Symbol('domainCacheVersion');
|
||||||
|
|
||||||
|
function setCache(key: string, ddoc: DomainDoc | null) {
|
||||||
|
if (ddoc) {
|
||||||
|
Object.defineProperty(ddoc, cacheVersion, {
|
||||||
|
value: Date.now(),
|
||||||
|
enumerable: false,
|
||||||
|
configurable: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
cache.set(key, ddoc);
|
||||||
|
}
|
||||||
|
|
||||||
interface DomainUserArg {
|
interface DomainUserArg {
|
||||||
_id: number;
|
_id: number;
|
||||||
@@ -22,6 +34,7 @@ interface DomainUserArg {
|
|||||||
class DomainModel {
|
class DomainModel {
|
||||||
static coll = coll;
|
static coll = coll;
|
||||||
static collUser = collUser;
|
static collUser = collUser;
|
||||||
|
static getVersion = (ddoc) => ddoc ? ddoc[cacheVersion] : null;
|
||||||
|
|
||||||
static JOIN_METHOD_NONE = 0;
|
static JOIN_METHOD_NONE = 0;
|
||||||
static JOIN_METHOD_ALL = 1;
|
static JOIN_METHOD_ALL = 1;
|
||||||
@@ -73,7 +86,7 @@ class DomainModel {
|
|||||||
const result = await coll.findOne(query);
|
const result = await coll.findOne(query);
|
||||||
if (result) {
|
if (result) {
|
||||||
await bus.parallel('domain/get', result);
|
await bus.parallel('domain/get', result);
|
||||||
cache.set(key, result);
|
setCache(key, result);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -88,7 +101,7 @@ class DomainModel {
|
|||||||
const result = await coll.findOne(query);
|
const result = await coll.findOne(query);
|
||||||
if (result) {
|
if (result) {
|
||||||
await bus.parallel('domain/get', result);
|
await bus.parallel('domain/get', result);
|
||||||
cache.set(key, result);
|
setCache(key, result);
|
||||||
} else {
|
} else {
|
||||||
cache.set(key, null);
|
cache.set(key, null);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { cloneDeep, omit } from 'lodash';
|
|||||||
import type { KoaContext } from '@hydrooj/framework';
|
import type { KoaContext } from '@hydrooj/framework';
|
||||||
import { randomPick, Time } from '@hydrooj/utils';
|
import { randomPick, Time } from '@hydrooj/utils';
|
||||||
import { PERM } from '../../model/builtin';
|
import { PERM } from '../../model/builtin';
|
||||||
|
import domain from '../../model/domain';
|
||||||
import system from '../../model/system';
|
import system from '../../model/system';
|
||||||
import token from '../../model/token';
|
import token from '../../model/token';
|
||||||
|
|
||||||
@@ -34,7 +35,7 @@ export default async (ctx: KoaContext, next: Next) => {
|
|||||||
UiContext.cdn_dynamic = system.get('server.cdn_dynamic');
|
UiContext.cdn_dynamic = system.get('server.cdn_dynamic');
|
||||||
}
|
}
|
||||||
UiContext.domainId = domainId;
|
UiContext.domainId = domainId;
|
||||||
UiContext.domain = domainInfo;
|
UiContext.domainVersion = domain.getVersion(domainInfo);
|
||||||
ctx.HydroContext.UiContext = UiContext;
|
ctx.HydroContext.UiContext = UiContext;
|
||||||
ctx.HydroContext.domain = domainInfo;
|
ctx.HydroContext.domain = domainInfo;
|
||||||
ctx.HydroContext.args = args;
|
ctx.HydroContext.args = args;
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ const page = new NamedPage('domain_user', () => {
|
|||||||
<p>${i18n('Users will have to manually join the domain first before selected roles can be applied.')}</p>
|
<p>${i18n('Users will have to manually join the domain first before selected roles can be applied.')}</p>
|
||||||
<p>${i18n('To join the domain, users can click the "Join Domain" button on "My Domain" page.')}</p>
|
<p>${i18n('To join the domain, users can click the "Join Domain" button on "My Domain" page.')}</p>
|
||||||
<p>${i18n('Or use the following link:')}</p>
|
<p>${i18n('Or use the following link:')}</p>
|
||||||
<p><a href="/domain/join?target=${UiContext.domain._id}">/domain/join?target=${UiContext.domain._id}</a></p>
|
<p><a href="/domain/join?target=${UiContext.domainId}">/domain/join?target=${UiContext.domainId}</a></p>
|
||||||
</div>`,
|
</div>`,
|
||||||
}).open();
|
}).open();
|
||||||
});
|
});
|
||||||
@@ -37,7 +37,7 @@ const page = new NamedPage('domain_user', () => {
|
|||||||
label: 'Role',
|
label: 'Role',
|
||||||
options: UiContext.roles.filter((i) => !['default', 'guest'].includes(i)),
|
options: UiContext.roles.filter((i) => !['default', 'guest'].includes(i)),
|
||||||
},
|
},
|
||||||
...((UiContext.canForceJoin && UiContext.domain._id !== 'system') ? {
|
...((UiContext.canForceJoin && UiContext.domainId !== 'system') ? {
|
||||||
join: {
|
join: {
|
||||||
type: 'checkbox',
|
type: 'checkbox',
|
||||||
label: i18n('Mark user as joined using admin privilege'),
|
label: i18n('Mark user as joined using admin privilege'),
|
||||||
|
|||||||
@@ -3,7 +3,9 @@ import ProblemSelectAutoComplete from 'vj/components/autocomplete/ProblemSelectA
|
|||||||
import UserSelectAutoComplete from 'vj/components/autocomplete/UserSelectAutoComplete';
|
import UserSelectAutoComplete from 'vj/components/autocomplete/UserSelectAutoComplete';
|
||||||
import Notification from 'vj/components/notification';
|
import Notification from 'vj/components/notification';
|
||||||
import { NamedPage } from 'vj/misc/Page';
|
import { NamedPage } from 'vj/misc/Page';
|
||||||
import { getAvailableLangs, request, tpl } from 'vj/utils';
|
import {
|
||||||
|
getAvailableLangs, getDomainInfo, request, tpl,
|
||||||
|
} from 'vj/utils';
|
||||||
|
|
||||||
const page = new NamedPage('record_main', async () => {
|
const page = new NamedPage('record_main', async () => {
|
||||||
const [{ default: WebSocket }, { DiffDOM }] = await Promise.all([
|
const [{ default: WebSocket }, { DiffDOM }] = await Promise.all([
|
||||||
@@ -37,7 +39,8 @@ const page = new NamedPage('record_main', async () => {
|
|||||||
ProblemSelectAutoComplete.getOrConstruct($('[name="pid"]'), {
|
ProblemSelectAutoComplete.getOrConstruct($('[name="pid"]'), {
|
||||||
clearDefaultValue: false,
|
clearDefaultValue: false,
|
||||||
});
|
});
|
||||||
const langs = UiContext.domain.langs?.split(',').map((i) => i.trim()).filter((i) => i);
|
const domain = await getDomainInfo();
|
||||||
|
const langs = domain.langs?.split(',').map((i) => i.trim()).filter((i) => i);
|
||||||
const availableLangs = getAvailableLangs(langs?.length ? langs : undefined);
|
const availableLangs = getAvailableLangs(langs?.length ? langs : undefined);
|
||||||
Object.keys(availableLangs).map(
|
Object.keys(availableLangs).map(
|
||||||
(i) => ($('select[name="lang"]').append(tpl`<option value="${i}" key="${i}">${availableLangs[i].display}</option>`)));
|
(i) => ($('select[name="lang"]').append(tpl`<option value="${i}" key="${i}">${availableLangs[i].display}</option>`)));
|
||||||
|
|||||||
@@ -35,7 +35,7 @@
|
|||||||
{% else %}
|
{% else %}
|
||||||
{% if model.system.get('ui-default.domainNavigation') %}
|
{% if model.system.get('ui-default.domainNavigation') %}
|
||||||
<li class="nav__list-item" data-dropdown-pos="bottom right" data-dropdown-custom-class="nav__dropdown" data-dropdown-target="#menu-nav-domain" data-dropdown-disabledconstrainToWindow data-dropdown-trigger-desktop-only>
|
<li class="nav__list-item" data-dropdown-pos="bottom right" data-dropdown-custom-class="nav__dropdown" data-dropdown-target="#menu-nav-domain" data-dropdown-disabledconstrainToWindow data-dropdown-trigger-desktop-only>
|
||||||
<span class="nav__item"><span class="icon"><img class="small user-profile-avatar v-center" loading="lazy" src="{{ avatarUrl(UiContext.domain.avatar|default('')) }}" width="20" height="20"></span> {{ UiContext.domain.name }} <span class="icon icon-expand_more nojs--hide"></span></span>
|
<span class="nav__item"><span class="icon"><img class="small user-profile-avatar v-center" loading="lazy" src="{{ avatarUrl(handler.domain.avatar|default('')) }}" width="20" height="20"></span> {{ handler.domain.name }} <span class="icon icon-expand_more nojs--hide"></span></span>
|
||||||
<ol class="dropdown-target menu" id="menu-nav-domain">
|
<ol class="dropdown-target menu" id="menu-nav-domain">
|
||||||
{%- if handler.user.domains|length -%}
|
{%- if handler.user.domains|length -%}
|
||||||
{%- for ddoc in handler.user.domains -%}
|
{%- for ddoc in handler.user.domains -%}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { openDB as _open } from 'idb';
|
import { openDB as _open } from 'idb';
|
||||||
import { alert } from 'vj/components/dialog';
|
import { alert } from 'vj/components/dialog';
|
||||||
|
|
||||||
export const openDB = _open('hydro', 1, {
|
export const openDB = _open('hydro', 2, {
|
||||||
upgrade(db, oldVersion) {
|
upgrade(db, oldVersion) {
|
||||||
if (oldVersion < 1) {
|
if (oldVersion < 1) {
|
||||||
const solutionStore = db.createObjectStore('solutions', { keyPath: 'id' });
|
const solutionStore = db.createObjectStore('solutions', { keyPath: 'id' });
|
||||||
@@ -17,6 +17,7 @@ export const openDB = _open('hydro', 1, {
|
|||||||
if (/^0\.\d+$/.test(key)) localStorage.removeItem(key);
|
if (/^0\.\d+$/.test(key)) localStorage.removeItem(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (oldVersion < 2) db.createObjectStore('domain-info', { keyPath: 'id' });
|
||||||
},
|
},
|
||||||
blocked(currentVersion, blockedVersion) {
|
blocked(currentVersion, blockedVersion) {
|
||||||
console.error('IDB Blocked by version', blockedVersion, 'want', currentVersion);
|
console.error('IDB Blocked by version', blockedVersion, 'want', currentVersion);
|
||||||
|
|||||||
@@ -1,13 +1,52 @@
|
|||||||
import 'streamsaver/examples/zip-stream';
|
import 'streamsaver/examples/zip-stream';
|
||||||
|
|
||||||
import { request } from './base';
|
import { request } from './base';
|
||||||
|
import { openDB } from './db';
|
||||||
|
|
||||||
export async function api(method: string, args: Record<string, any>, projection: any) {
|
export async function api(method: string, args: Record<string, any>, projection?: any) {
|
||||||
const res = await request.post(`/d/${UiContext.domainId}/api/${encodeURIComponent(method)}`, { args, projection });
|
const res = await request.post(`/d/${UiContext.domainId}/api/${encodeURIComponent(method)}`, { args, projection });
|
||||||
if (res.error) throw new Error(res.error);
|
if (res.error) throw new Error(res.error);
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface DomainInfoCache {
|
||||||
|
id: string;
|
||||||
|
version: number;
|
||||||
|
domain: Record<string, any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const domainInfoCacheStore = 'domain-info';
|
||||||
|
let domainInfoPromise: Promise<Record<string, any>> | null = null;
|
||||||
|
|
||||||
|
async function readDomainInfoCache(domainId: string) {
|
||||||
|
return await (await openDB).get(domainInfoCacheStore, domainId) as DomainInfoCache | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function writeDomainInfoCache(cache: DomainInfoCache) {
|
||||||
|
await (await openDB).put(domainInfoCacheStore, cache);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadDomainInfo() {
|
||||||
|
let cached: DomainInfoCache | null = null;
|
||||||
|
try {
|
||||||
|
cached = await readDomainInfoCache(UiContext.domainId);
|
||||||
|
} catch (e) { }
|
||||||
|
if (cached && cached.version >= UiContext.domainVersion) return cached.domain;
|
||||||
|
const res = await api('domain.current', {});
|
||||||
|
const { domain } = res;
|
||||||
|
if (!domain) throw new Error('Failed to load domain info');
|
||||||
|
writeDomainInfoCache({ id: UiContext.domainId, version: UiContext.domainVersion, domain }).catch(() => { });
|
||||||
|
return domain;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getDomainInfo() {
|
||||||
|
domainInfoPromise ||= loadDomainInfo().catch((e) => {
|
||||||
|
domainInfoPromise = null;
|
||||||
|
throw e;
|
||||||
|
});
|
||||||
|
return domainInfoPromise;
|
||||||
|
}
|
||||||
|
|
||||||
export function getAvailableLangs(langsList?: string[]) {
|
export function getAvailableLangs(langsList?: string[]) {
|
||||||
const prefixes = new Set(Object.keys(window.LANGS).filter((i) => i.includes('.')).map((i) => i.split('.')[0]));
|
const prefixes = new Set(Object.keys(window.LANGS).filter((i) => i.includes('.')).map((i) => i.split('.')[0]));
|
||||||
const Langs = {};
|
const Langs = {};
|
||||||
|
|||||||
Reference in new issue
Block a user