mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-20 01:38:19 +08:00
core: init graphql
This commit is contained in:
1 parent
2097c7257f
commit
c39c406535
16 files changed
+131
-23
No files matched your search
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "hydrooj",
|
"name": "hydrooj",
|
||||||
"version": "2.32.35",
|
"version": "2.33.0",
|
||||||
"bin": "bin/hydrooj.js",
|
"bin": "bin/hydrooj.js",
|
||||||
"main": "src/loader",
|
"main": "src/loader",
|
||||||
"module": "src/loader",
|
"module": "src/loader",
|
||||||
@@ -19,6 +19,7 @@
|
|||||||
"cookies": "^0.8.0",
|
"cookies": "^0.8.0",
|
||||||
"detect-browser": "^5.2.0",
|
"detect-browser": "^5.2.0",
|
||||||
"fs-extra": "^10.0.0",
|
"fs-extra": "^10.0.0",
|
||||||
|
"graphql": "^15.5.1",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
"koa": "^2.13.1",
|
"koa": "^2.13.1",
|
||||||
"koa-body": "^4.2.0",
|
"koa-body": "^4.2.0",
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export const builtinModel = [
|
|||||||
export const builtinHandler = [
|
export const builtinHandler = [
|
||||||
'home', 'problem', 'record', 'judge', 'user',
|
'home', 'problem', 'record', 'judge', 'user',
|
||||||
'contest', 'training', 'discussion', 'manage', 'import',
|
'contest', 'training', 'discussion', 'manage', 'import',
|
||||||
'misc', 'homework', 'domain', 'status',
|
'misc', 'homework', 'domain', 'status', 'api',
|
||||||
];
|
];
|
||||||
|
|
||||||
export const builtinScript = [
|
export const builtinScript = [
|
||||||
|
|||||||
@@ -78,5 +78,7 @@ export async function load(call: Entry) {
|
|||||||
await modelSystem.set('db.ver', dbVer);
|
await modelSystem.set('db.ver', dbVer);
|
||||||
}
|
}
|
||||||
await bus.serial('app/started');
|
await bus.serial('app/started');
|
||||||
return await modelSystem.get('server.worker');
|
return typeof argv.options.worker === 'number'
|
||||||
|
? argv.options.worker
|
||||||
|
: await modelSystem.get('server.worker');
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import graphql from 'graphql';
|
||||||
|
import UserModel from '../model/user';
|
||||||
|
import { Handler, Route } from '../service/server';
|
||||||
|
|
||||||
|
const schemaStr = `\
|
||||||
|
type Query {
|
||||||
|
user(id: Int, uname: String, mail: String): User
|
||||||
|
problem(id: Int): String
|
||||||
|
}
|
||||||
|
|
||||||
|
type User {
|
||||||
|
_id: Int!
|
||||||
|
uname: String!
|
||||||
|
mail: String!
|
||||||
|
}`;
|
||||||
|
|
||||||
|
const schema = graphql.buildSchema(schemaStr);
|
||||||
|
const root = {
|
||||||
|
user: (arg, ctx) => {
|
||||||
|
if (arg.id) return UserModel.getById(ctx.domainId, arg.id);
|
||||||
|
if (arg.mail) return UserModel.getByEmail(ctx.domainId, arg.mail);
|
||||||
|
if (arg.uname) return UserModel.getByUname(ctx.domainId, arg.uname);
|
||||||
|
return ctx.user;
|
||||||
|
},
|
||||||
|
problem: async (arg, ctx, node) => {
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
class ApiHandler extends Handler {
|
||||||
|
async get() {
|
||||||
|
const q = decodeURIComponent(this.ctx.request.querystring);
|
||||||
|
if (q === 'schema') {
|
||||||
|
this.response.body = { schema: schemaStr };
|
||||||
|
} else if (q) {
|
||||||
|
this.response.type = 'application/json';
|
||||||
|
this.response.body = await graphql.graphql(schema, q, root, this);
|
||||||
|
} else this.response.template = 'api.html';
|
||||||
|
}
|
||||||
|
|
||||||
|
async post() {
|
||||||
|
this.response.type = 'application/json';
|
||||||
|
this.response.body = await graphql.graphql(schema, this.args.query, root, this, this.args.variables);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function apply() {
|
||||||
|
Route('api', '/api', ApiHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
global.Hydro.handler.api = apply;
|
||||||
@@ -246,7 +246,7 @@ class RecordMainConnectionHandler extends ConnectionHandler {
|
|||||||
else {
|
else {
|
||||||
this.send({
|
this.send({
|
||||||
html: await this.renderHTML('record_main_tr.html', {
|
html: await this.renderHTML('record_main_tr.html', {
|
||||||
rdoc, udoc, pdoc, tdoc,
|
rdoc, udoc, pdoc, tdoc, all: this.all,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -439,7 +439,7 @@ export class Handler extends HandlerCommon {
|
|||||||
query: ctx.query,
|
query: ctx.query,
|
||||||
path: ctx.path,
|
path: ctx.path,
|
||||||
params: ctx.params,
|
params: ctx.params,
|
||||||
referer: ctx.request.headers.referer,
|
referer: ctx.request.headers.referer || '',
|
||||||
json: (ctx.request.headers.accept || '').includes('application/json'),
|
json: (ctx.request.headers.accept || '').includes('application/json'),
|
||||||
};
|
};
|
||||||
this.response = {
|
this.response = {
|
||||||
@@ -679,11 +679,9 @@ async function handle(ctx, HandlerClass, checker) {
|
|||||||
h.domainId = args.domainId;
|
h.domainId = args.domainId;
|
||||||
try {
|
try {
|
||||||
const method = ctx.method.toLowerCase();
|
const method = ctx.method.toLowerCase();
|
||||||
let operation: string;
|
const operation = (method === 'post' && ctx.request.body.operation)
|
||||||
if (method === 'post' && ctx.request.body.operation) {
|
? `_${ctx.request.body.operation}`.replace(/_([a-z])/gm, (s) => s[1].toUpperCase())
|
||||||
operation = `_${ctx.request.body.operation}`
|
: '';
|
||||||
.replace(/_([a-z])/gm, (s) => s[1].toUpperCase());
|
|
||||||
}
|
|
||||||
|
|
||||||
await bus.serial('handler/create', h);
|
await bus.serial('handler/create', h);
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,9 @@ require.extensions['.js'] = function loader(module, filename) {
|
|||||||
const content = fs.readFileSync(filename, 'utf-8');
|
const content = fs.readFileSync(filename, 'utf-8');
|
||||||
return module._compile(content, filename);
|
return module._compile(content, filename);
|
||||||
};
|
};
|
||||||
|
require.extensions['.ts'] = function loader(module, filename) {
|
||||||
|
return module._compile(transform(filename), filename);
|
||||||
|
};
|
||||||
const main = require('./main.js');
|
const main = require('./main.js');
|
||||||
|
|
||||||
if (!module.parent) main();
|
if (!module.parent) main();
|
||||||
@@ -2,8 +2,8 @@ import { Page } from './Page';
|
|||||||
|
|
||||||
export default class PageLoader {
|
export default class PageLoader {
|
||||||
constructor() {
|
constructor() {
|
||||||
const pages = require.context('../pages/', true, /\.page\.jsx?$/i);
|
const pages = require.context('../pages/', true, /\.page\.[jt]sx?$/i);
|
||||||
const components = require.context('../components/', true, /\.page\.jsx?$/i);
|
const components = require.context('../components/', true, /\.page\.[jt]sx?$/i);
|
||||||
this.pageInstances = [
|
this.pageInstances = [
|
||||||
...pages.keys().map((key) => {
|
...pages.keys().map((key) => {
|
||||||
const page = pages(key).default;
|
const page = pages(key).default;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@hydrooj/ui-default",
|
"name": "@hydrooj/ui-default",
|
||||||
"version": "4.17.8",
|
"version": "4.18.0",
|
||||||
"author": "undefined <i@undefined.moe>",
|
"author": "undefined <i@undefined.moe>",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"main": "hydro.js",
|
"main": "hydro.js",
|
||||||
@@ -41,6 +41,7 @@
|
|||||||
"file-loader": "^6.2.0",
|
"file-loader": "^6.2.0",
|
||||||
"friendly-errors-webpack-plugin": "^1.7.0",
|
"friendly-errors-webpack-plugin": "^1.7.0",
|
||||||
"fs-extra": "^10.0.0",
|
"fs-extra": "^10.0.0",
|
||||||
|
"graphiql": "^1.4.2",
|
||||||
"gulp": "^4.0.2",
|
"gulp": "^4.0.2",
|
||||||
"gulp-cli": "^2.3.0",
|
"gulp-cli": "^2.3.0",
|
||||||
"gulp-iconfont": "^11.0.0",
|
"gulp-iconfont": "^11.0.0",
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
.graphiql-container .doc-explorer-title, .graphiql-container .history-title
|
||||||
|
padding: 2px !important
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import { NamedPage } from 'vj/misc/Page';
|
||||||
|
import request from 'vj/utils/request';
|
||||||
|
import React from 'react';
|
||||||
|
import { render } from 'react-dom';
|
||||||
|
import 'graphiql/graphiql.css';
|
||||||
|
|
||||||
|
const Logo = () => <span>Hydro API Console </span>;
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
|
||||||
|
const page = new NamedPage('api', async () => {
|
||||||
|
const [{ default: GraphiQL }, { buildSchema }, res] = await Promise.all([
|
||||||
|
import('graphiql'),
|
||||||
|
import('graphql'),
|
||||||
|
request.get('/api?schema'),
|
||||||
|
]);
|
||||||
|
// @ts-ignore
|
||||||
|
GraphiQL.Logo = Logo;
|
||||||
|
console.log(res);
|
||||||
|
const App = () => (
|
||||||
|
<GraphiQL
|
||||||
|
schema={buildSchema(res.schema)}
|
||||||
|
style={{ height: '100vh' }}
|
||||||
|
fetcher={async (graphQLParams) => {
|
||||||
|
const data = await fetch(
|
||||||
|
'/api',
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
Accept: 'application/json',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify(graphQLParams),
|
||||||
|
credentials: 'same-origin',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return data.json().catch(() => data.text());
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
render(<App />, document.getElementById('graphiql'));
|
||||||
|
});
|
||||||
|
|
||||||
|
export default page;
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{% extends "layout/basic.html" %}
|
||||||
|
{% block content %}
|
||||||
|
<div id="graphiql" style="height:90vh;"></div>
|
||||||
|
{% endblock %}
|
||||||
@@ -1,9 +1,12 @@
|
|||||||
{% macro render_status_td(rdoc, rid_key='_id', class='') %}
|
{% macro render_status_td(rdoc, rid_key='_id', class='', allDomain = false) %}
|
||||||
<td class="col--status record-status--border {{ class }} {{ model.builtin.STATUS_CODES[rdoc.status] }}">
|
<td class="col--status record-status--border {{ class }} {{ model.builtin.STATUS_CODES[rdoc.status] }}">
|
||||||
<div class="col--status__text">
|
<div class="col--status__text">
|
||||||
<span class="icon record-status--icon {{ model.builtin.STATUS_CODES[rdoc.status] }}"></span>
|
<span class="icon record-status--icon {{ model.builtin.STATUS_CODES[rdoc.status] }}"></span>
|
||||||
<span style="color: {{ utils.status.getScoreColor(rdoc.score|default(0)) }}">{{ rdoc.score|default(0) }}</span>
|
<span style="color: {{ utils.status.getScoreColor(rdoc.score|default(0)) }}">{{ rdoc.score|default(0) }}</span>
|
||||||
<a href="{{ url('record_detail', rid=rdoc[rid_key]) }}" class="record-status--text {{ model.builtin.STATUS_CODES[rdoc.status] }}">
|
<a
|
||||||
|
href="{{ url('record_detail', rid=rdoc[rid_key], domainId=rdoc.domainId) if allDomain else url('record_detail', rid=rdoc[rid_key]) }}"
|
||||||
|
class="record-status--text {{ model.builtin.STATUS_CODES[rdoc.status] }}"
|
||||||
|
>
|
||||||
{{ model.builtin.STATUS_TEXTS[rdoc.status] }}
|
{{ model.builtin.STATUS_TEXTS[rdoc.status] }}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -10,14 +10,14 @@
|
|||||||
<h1 class="section__title">{{ tdoc.title }}</h1>
|
<h1 class="section__title">{{ tdoc.title }}</h1>
|
||||||
</div>
|
</div>
|
||||||
<div class="section__body">
|
<div class="section__body">
|
||||||
<span class="bp3-tag bp3-large bp3-minimal problem__tag-item">{{ _('Status') }}: {{ _(model.contest.statusText(tdoc)) }}</span>
|
<span class="bp3-tag bp3-large bp3-minimal problem__tag-item">{{ _(model.contest.statusText(tdoc)) }}</span>
|
||||||
<span class="bp3-tag bp3-large bp3-minimal problem__tag-item bp3-icon-standard bp3-icon-book">{{ _(model.contest.RULES[tdoc.rule].TEXT) }}</span>
|
<span class="bp3-tag bp3-large bp3-minimal problem__tag-item icon icon-award">{{ _(model.contest.RULES[tdoc.rule].TEXT) }}</span>
|
||||||
<span class="bp3-tag bp3-large bp3-minimal problem__tag-item">{{ _('Start at') }}: {{ contest.render_time(tdoc.beginAt) }}</span>
|
<span class="bp3-tag bp3-large bp3-minimal problem__tag-item">{{ _('Start at') }}: {{ contest.render_time(tdoc.beginAt) }}</span>
|
||||||
<span class="bp3-tag bp3-large bp3-minimal problem__tag-item">{{ _('End at') }}: {{ contest.render_time(tdoc.endAt) }}</span>
|
<span class="bp3-tag bp3-large bp3-minimal problem__tag-item icon icon-schedule">{{ contest.render_duration(tdoc) }} {{ _('hour(s)') }}</span>
|
||||||
<span class="bp3-tag bp3-large bp3-minimal problem__tag-item">{{ _('Host') }}: {{ user.render_inline(udict[tdoc.owner], badge=false) }}</span>
|
<span class="bp3-tag bp3-large bp3-minimal problem__tag-item">{{ _('Host') }}: {{ user.render_inline(udict[tdoc.owner], badge=false) }}</span>
|
||||||
<span class="bp3-tag bp3-large bp3-minimal problem__tag-item">{{ _('Partic.') }}: {{ tdoc['attend']|default(0) }}</span>
|
<span class="bp3-tag bp3-large bp3-minimal problem__tag-item icon icon-user--multiple">{{ tdoc['attend']|default(0) }}</span>
|
||||||
{% if attended %}
|
{% if attended %}
|
||||||
<span class="bp3-tag bp3-large bp3-minimal problem__tag-item bp3-icon-standard bp3-icon-tick">{{ _('Attended') }}</span>
|
<span class="bp3-tag bp3-large bp3-minimal problem__tag-item icon icon-check">{{ _('Attended') }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
{% set no_path_section = true %}
|
|
||||||
{% extends "layout/basic.html" %}
|
{% extends "layout/basic.html" %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="error__container clearfix">
|
<div class="error__container clearfix">
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
{% import "components/record.html" as record with context %}
|
{% import "components/record.html" as record with context %}
|
||||||
{% import "components/problem.html" as problem with context %}
|
{% import "components/problem.html" as problem with context %}
|
||||||
<tr data-rid="{{ rdoc['_id'] }}">
|
<tr data-rid="{{ rdoc['_id'] }}">
|
||||||
{{ record.render_status_td(rdoc) }}
|
{{ record.render_status_td(rdoc, allDomain = all) }}
|
||||||
<td class="col--problem col--problem-name">
|
<td class="col--problem col--problem-name">
|
||||||
{% if handler.user.hasPerm(perm.PERM_REJUDGE) %}
|
{% if handler.user.hasPerm(perm.PERM_REJUDGE) %}
|
||||||
<form class="form--inline" method="post" action="{{ url('record_detail', rid=rdoc._id) }}">
|
<form class="form--inline" method="post" action="{{ url('record_detail', rid=rdoc._id, domainId=rdoc.domainId) }}">
|
||||||
<input type="hidden" name="operation" value="rejudge">
|
<input type="hidden" name="operation" value="rejudge">
|
||||||
<input type="hidden" name="csrfToken" value="{{ handler.csrfToken }}">
|
<input type="hidden" name="csrfToken" value="{{ handler.csrfToken }}">
|
||||||
<button type="submit" class="link text-maroon lighter">
|
<button type="submit" class="link text-maroon lighter">
|
||||||
|
|||||||
Reference in new issue
Block a user