From c39c4065359fa44f6a6c99c13275cd58bbde5477 Mon Sep 17 00:00:00 2001 From: undefined Date: Mon, 23 Aug 2021 02:39:58 +0800 Subject: [PATCH] core: init graphql --- packages/hydrooj/package.json | 3 +- packages/hydrooj/src/entry/common.ts | 2 +- packages/hydrooj/src/entry/master.ts | 4 +- packages/hydrooj/src/handler/api.ts | 50 +++++++++++++++++++ packages/hydrooj/src/handler/record.ts | 2 +- packages/hydrooj/src/service/server.ts | 10 ++-- packages/ui-default/build/index.js | 3 ++ packages/ui-default/misc/PageLoader.js | 4 +- packages/ui-default/package.json | 3 +- packages/ui-default/pages/api.page.styl | 2 + packages/ui-default/pages/api.page.tsx | 45 +++++++++++++++++ packages/ui-default/templates/api.html | 4 ++ .../templates/components/record.html | 7 ++- .../ui-default/templates/contest_detail.html | 10 ++-- packages/ui-default/templates/error.html | 1 - .../ui-default/templates/record_main_tr.html | 4 +- 16 files changed, 131 insertions(+), 23 deletions(-) create mode 100644 packages/hydrooj/src/handler/api.ts create mode 100644 packages/ui-default/pages/api.page.styl create mode 100644 packages/ui-default/pages/api.page.tsx create mode 100644 packages/ui-default/templates/api.html diff --git a/packages/hydrooj/package.json b/packages/hydrooj/package.json index 81a50e755..bf29640b0 100644 --- a/packages/hydrooj/package.json +++ b/packages/hydrooj/package.json @@ -1,6 +1,6 @@ { "name": "hydrooj", - "version": "2.32.35", + "version": "2.33.0", "bin": "bin/hydrooj.js", "main": "src/loader", "module": "src/loader", @@ -19,6 +19,7 @@ "cookies": "^0.8.0", "detect-browser": "^5.2.0", "fs-extra": "^10.0.0", + "graphql": "^15.5.1", "js-yaml": "^4.1.0", "koa": "^2.13.1", "koa-body": "^4.2.0", diff --git a/packages/hydrooj/src/entry/common.ts b/packages/hydrooj/src/entry/common.ts index 7be2d1336..6eaac22c1 100644 --- a/packages/hydrooj/src/entry/common.ts +++ b/packages/hydrooj/src/entry/common.ts @@ -26,7 +26,7 @@ export const builtinModel = [ export const builtinHandler = [ 'home', 'problem', 'record', 'judge', 'user', 'contest', 'training', 'discussion', 'manage', 'import', - 'misc', 'homework', 'domain', 'status', + 'misc', 'homework', 'domain', 'status', 'api', ]; export const builtinScript = [ diff --git a/packages/hydrooj/src/entry/master.ts b/packages/hydrooj/src/entry/master.ts index 99b3fa4ff..2f5e4fcd0 100644 --- a/packages/hydrooj/src/entry/master.ts +++ b/packages/hydrooj/src/entry/master.ts @@ -78,5 +78,7 @@ export async function load(call: Entry) { await modelSystem.set('db.ver', dbVer); } 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'); } diff --git a/packages/hydrooj/src/handler/api.ts b/packages/hydrooj/src/handler/api.ts new file mode 100644 index 000000000..1b2315657 --- /dev/null +++ b/packages/hydrooj/src/handler/api.ts @@ -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; diff --git a/packages/hydrooj/src/handler/record.ts b/packages/hydrooj/src/handler/record.ts index 103829e38..76616402e 100644 --- a/packages/hydrooj/src/handler/record.ts +++ b/packages/hydrooj/src/handler/record.ts @@ -246,7 +246,7 @@ class RecordMainConnectionHandler extends ConnectionHandler { else { this.send({ html: await this.renderHTML('record_main_tr.html', { - rdoc, udoc, pdoc, tdoc, + rdoc, udoc, pdoc, tdoc, all: this.all, }), }); } diff --git a/packages/hydrooj/src/service/server.ts b/packages/hydrooj/src/service/server.ts index b22c31a88..7bf1cdcda 100644 --- a/packages/hydrooj/src/service/server.ts +++ b/packages/hydrooj/src/service/server.ts @@ -439,7 +439,7 @@ export class Handler extends HandlerCommon { query: ctx.query, path: ctx.path, params: ctx.params, - referer: ctx.request.headers.referer, + referer: ctx.request.headers.referer || '', json: (ctx.request.headers.accept || '').includes('application/json'), }; this.response = { @@ -679,11 +679,9 @@ async function handle(ctx, HandlerClass, checker) { h.domainId = args.domainId; try { const method = ctx.method.toLowerCase(); - let operation: string; - if (method === 'post' && ctx.request.body.operation) { - operation = `_${ctx.request.body.operation}` - .replace(/_([a-z])/gm, (s) => s[1].toUpperCase()); - } + const operation = (method === 'post' && ctx.request.body.operation) + ? `_${ctx.request.body.operation}`.replace(/_([a-z])/gm, (s) => s[1].toUpperCase()) + : ''; await bus.serial('handler/create', h); diff --git a/packages/ui-default/build/index.js b/packages/ui-default/build/index.js index 7cad145c2..b364940c6 100644 --- a/packages/ui-default/build/index.js +++ b/packages/ui-default/build/index.js @@ -29,6 +29,9 @@ require.extensions['.js'] = function loader(module, filename) { const content = fs.readFileSync(filename, 'utf-8'); return module._compile(content, filename); }; +require.extensions['.ts'] = function loader(module, filename) { + return module._compile(transform(filename), filename); +}; const main = require('./main.js'); if (!module.parent) main(); diff --git a/packages/ui-default/misc/PageLoader.js b/packages/ui-default/misc/PageLoader.js index 86a6e37b6..ddba57a40 100644 --- a/packages/ui-default/misc/PageLoader.js +++ b/packages/ui-default/misc/PageLoader.js @@ -2,8 +2,8 @@ import { Page } from './Page'; export default class PageLoader { constructor() { - const pages = require.context('../pages/', true, /\.page\.jsx?$/i); - const components = require.context('../components/', true, /\.page\.jsx?$/i); + const pages = require.context('../pages/', true, /\.page\.[jt]sx?$/i); + const components = require.context('../components/', true, /\.page\.[jt]sx?$/i); this.pageInstances = [ ...pages.keys().map((key) => { const page = pages(key).default; diff --git a/packages/ui-default/package.json b/packages/ui-default/package.json index bea069ac0..ae0b612e8 100644 --- a/packages/ui-default/package.json +++ b/packages/ui-default/package.json @@ -1,6 +1,6 @@ { "name": "@hydrooj/ui-default", - "version": "4.17.8", + "version": "4.18.0", "author": "undefined ", "license": "AGPL-3.0", "main": "hydro.js", @@ -41,6 +41,7 @@ "file-loader": "^6.2.0", "friendly-errors-webpack-plugin": "^1.7.0", "fs-extra": "^10.0.0", + "graphiql": "^1.4.2", "gulp": "^4.0.2", "gulp-cli": "^2.3.0", "gulp-iconfont": "^11.0.0", diff --git a/packages/ui-default/pages/api.page.styl b/packages/ui-default/pages/api.page.styl new file mode 100644 index 000000000..f5729688d --- /dev/null +++ b/packages/ui-default/pages/api.page.styl @@ -0,0 +1,2 @@ +.graphiql-container .doc-explorer-title, .graphiql-container .history-title + padding: 2px !important \ No newline at end of file diff --git a/packages/ui-default/pages/api.page.tsx b/packages/ui-default/pages/api.page.tsx new file mode 100644 index 000000000..10fb031ff --- /dev/null +++ b/packages/ui-default/pages/api.page.tsx @@ -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 = () => Hydro API Console ; + +// @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 = () => ( + { + 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(, document.getElementById('graphiql')); +}); + +export default page; diff --git a/packages/ui-default/templates/api.html b/packages/ui-default/templates/api.html new file mode 100644 index 000000000..445c111d6 --- /dev/null +++ b/packages/ui-default/templates/api.html @@ -0,0 +1,4 @@ +{% extends "layout/basic.html" %} +{% block content %} +
+{% endblock %} diff --git a/packages/ui-default/templates/components/record.html b/packages/ui-default/templates/components/record.html index 3dd570907..c734b914d 100644 --- a/packages/ui-default/templates/components/record.html +++ b/packages/ui-default/templates/components/record.html @@ -1,9 +1,12 @@ -{% macro render_status_td(rdoc, rid_key='_id', class='') %} +{% macro render_status_td(rdoc, rid_key='_id', class='', allDomain = false) %} diff --git a/packages/ui-default/templates/contest_detail.html b/packages/ui-default/templates/contest_detail.html index 6376fb127..759362ecd 100644 --- a/packages/ui-default/templates/contest_detail.html +++ b/packages/ui-default/templates/contest_detail.html @@ -10,14 +10,14 @@

{{ tdoc.title }}

- {{ _('Status') }}: {{ _(model.contest.statusText(tdoc)) }} - {{ _(model.contest.RULES[tdoc.rule].TEXT) }} + {{ _(model.contest.statusText(tdoc)) }} + {{ _(model.contest.RULES[tdoc.rule].TEXT) }} {{ _('Start at') }}: {{ contest.render_time(tdoc.beginAt) }} - {{ _('End at') }}: {{ contest.render_time(tdoc.endAt) }} + {{ contest.render_duration(tdoc) }} {{ _('hour(s)') }} {{ _('Host') }}: {{ user.render_inline(udict[tdoc.owner], badge=false) }} - {{ _('Partic.') }}: {{ tdoc['attend']|default(0) }} + {{ tdoc['attend']|default(0) }} {% if attended %} - {{ _('Attended') }} + {{ _('Attended') }} {% endif %}
diff --git a/packages/ui-default/templates/error.html b/packages/ui-default/templates/error.html index a86563e76..c190e4656 100644 --- a/packages/ui-default/templates/error.html +++ b/packages/ui-default/templates/error.html @@ -1,4 +1,3 @@ -{% set no_path_section = true %} {% extends "layout/basic.html" %} {% block content %}
diff --git a/packages/ui-default/templates/record_main_tr.html b/packages/ui-default/templates/record_main_tr.html index 7b0ed0903..5e0f758ca 100644 --- a/packages/ui-default/templates/record_main_tr.html +++ b/packages/ui-default/templates/record_main_tr.html @@ -1,10 +1,10 @@ {% import "components/record.html" as record with context %} {% import "components/problem.html" as problem with context %} - {{ record.render_status_td(rdoc) }} + {{ record.render_status_td(rdoc, allDomain = all) }} {% if handler.user.hasPerm(perm.PERM_REJUDGE) %} -
+