client: improved build.js, use relative links

* Removed unnecessary require('config.js') calls
* 'markdown.js' now uses rel. links in EntityPermalinkWrapper
* 'password_reset.py' now generates rel. links
* Removed 'Base URL' config parameter
* Removed 'API URL' config parameter
* 'build.js' no longer reads/requires config.yaml
* Updated documentation
* Removed unnecessary node packages used in 'build.js'

abandon api_url parameter
This commit is contained in:
Shyam Sunder 2018-07-05 19:25:08 -04:00 committed by Marcin Kurczewski
parent 3972b902d8
commit 60ab9246c6
14 changed files with 595 additions and 694 deletions

View file

@ -84,29 +84,32 @@ user@host:szuru/server$ source python_modules/bin/activate # enters the sandbox
### Preparing `szurubooru` for first run
1. Configure things:
1. Compile the frontend:
```console
user@host:szuru$ cd client
user@host:szuru/client$ node build.js
```
You can include the flags `--no-transpile` to disable the JavaScript
transpiler, which provides compatibility with older browsers, and
`--debug` to generate JS source mappings.
2. Configure things:
```console
user@host:szuru/client$ cd ..
user@host:szuru$ cp config.yaml.dist config.yaml
user@host:szuru$ vim config.yaml
```
Pay extra attention to these fields:
- base URL,
- API URL,
- data directory,
- data URL,
- database,
- the `smtp` section.
2. Compile the frontend:
```console
user@host:szuru$ cd client
user@host:szuru/client$ npm run build
```
3. Upgrade the database:
```console
@ -140,6 +143,11 @@ meant to be exposed directly to the end users.
The API should be exposed using WSGI server such as `waitress`, `gunicorn` or
similar. Other configurations might be possible but I didn't pursue them.
API calls are made to the relative URL `/api/`. Your HTTP server should be
configured to proxy this URL format to the WSGI server. Some users may prefer
to use a dedicated reverse proxy for this, to incorporate additional features
such as load balancing and SSL.
Note that the API URL in the virtual host configuration needs to be the same as
the one in the `config.yaml`, so that client knows how to access the backend!
@ -177,8 +185,6 @@ server {
**`config.yaml`**:
```yaml
api_url: 'http://example.com/api/'
base_url: 'http://example.com/'
data_url: 'http://example.com/data/'
data_dir: '/srv/www/booru/client/public/data'
```

View file

@ -5,20 +5,6 @@ const glob = require('glob');
const path = require('path');
const util = require('util');
const execSync = require('child_process').execSync;
const camelcase = require('camelcase');
function convertKeysToCamelCase(input) {
let result = {};
Object.keys(input).map((key, _) => {
const value = input[key];
if (value !== null && value.constructor == Object) {
result[camelcase(key)] = convertKeysToCamelCase(value);
} else {
result[camelcase(key)] = value;
}
});
return result;
}
function readTextFile(path) {
return fs.readFileSync(path, 'utf-8');
@ -29,37 +15,27 @@ function writeFile(path, content) {
}
function getVersion() {
return execSync('git describe --always --dirty --long --tags')
.toString()
.trim();
let build_info = process.env.BUILD_INFO;
if (build_info) {
return build_info.trim();
} else {
try {
build_info = execSync('git describe --always --dirty --long --tags')
.toString();
} catch (e) {
console.warn('Cannot find build version');
return 'unknown';
}
return build_info.trim();
}
}
function getConfig() {
const yaml = require('js-yaml');
const merge = require('merge');
const camelcaseKeys = require('camelcase-keys');
function parseConfigFile(path) {
let result = yaml.load(readTextFile(path, 'utf-8'));
return convertKeysToCamelCase(result);
}
let config = parseConfigFile('../config.yaml.dist');
try {
const localConfig = parseConfigFile('../config.yaml');
config = merge.recursive(config, localConfig);
} catch (e) {
console.warn('Local config does not exist, ignoring');
}
config.canSendMails = !!config.smtp.host;
delete config.secret;
delete config.smtp;
delete config.database;
config.meta = {
version: getVersion(),
buildDate: new Date().toUTCString(),
let config = {
meta: {
version: getVersion(),
buildDate: new Date().toUTCString()
}
};
return config;
@ -85,15 +61,11 @@ function minifyHtml(html) {
}).trim();
}
function bundleHtml(config) {
function bundleHtml() {
const underscore = require('underscore');
const babelify = require('babelify');
const baseHtml = readTextFile('./html/index.htm', 'utf-8');
const finalHtml = baseHtml
.replace(
/(<title>)(.*)(<\/title>)/,
util.format('$1%s$3', config.name));
writeFile('./public/index.htm', minifyHtml(finalHtml));
writeFile('./public/index.htm', minifyHtml(baseHtml));
glob('./html/**/*.tpl', {}, (er, files) => {
let compiledTemplateJs = '\'use strict\'\n';
@ -143,7 +115,7 @@ function bundleCss() {
});
}
function bundleJs(config) {
function bundleJs() {
const browserify = require('browserify');
const external = [
'underscore',
@ -170,7 +142,7 @@ function bundleJs(config) {
for (let lib of external) {
b.require(lib);
}
if (config.transpile) {
if (!process.argv.includes('--no-transpile')) {
b.add(require.resolve('babel-polyfill'));
}
writeJsBundle(
@ -179,15 +151,15 @@ function bundleJs(config) {
if (!process.argv.includes('--no-app-js')) {
let outputFile = fs.createWriteStream('./public/js/app.min.js');
let b = browserify({debug: config.debug});
if (config.transpile) {
let b = browserify({debug: process.argv.includes('--debug')});
if (!process.argv.includes('--no-transpile')) {
b = b.transform('babelify');
}
writeJsBundle(
b.external(external).add(files),
'./public/js/app.min.js',
'Bundled app JS',
!config.debug);
!process.argv.includes('--debug'));
}
});
}
@ -217,11 +189,11 @@ const config = getConfig();
bundleConfig(config);
bundleBinaryAssets();
if (!process.argv.includes('--no-html')) {
bundleHtml(config);
bundleHtml();
}
if (!process.argv.includes('--no-css')) {
bundleCss();
}
if (!process.argv.includes('--no-js')) {
bundleJs(config);
bundleJs();
}

View file

@ -3,7 +3,7 @@
<head>
<meta charset='utf-8'/>
<meta name='viewport' content='width=device-width, initial-scale=1, maximum-scale=1'>
<title><!-- configured in the config file --></title>
<title>Loading...</title>
<link href='/css/app.min.css' rel='stylesheet' type='text/css'/>
<link href='/css/vendor.min.css' rel='stylesheet' type='text/css'/>
<link rel='shortcut icon' type='image/png' href='/img/favicon.png'/>

View file

@ -36,8 +36,8 @@
<section class='search'>
Search on
<a href='http://iqdb.org/?url=<%- encodeURIComponent(ctx.post.contentUrl) %>'>IQDB</a> &middot;
<a href='https://www.google.com/searchbyimage?&image_url=<%- encodeURIComponent(ctx.post.contentUrl) %>'>Google Images</a>
<a href='http://iqdb.org/?url=<%- encodeURIComponent(ctx.post.fullContentUrl) %>'>IQDB</a> &middot;
<a href='https://www.google.com/searchbyimage?&image_url=<%- encodeURIComponent(ctx.post.fullContentUrl) %>'>Google Images</a>
</section>
<section class='social'>

View file

@ -2,7 +2,6 @@
const cookies = require('js-cookie');
const request = require('superagent');
const config = require('./config.js');
const events = require('./events.js');
const progress = require('./util/progress.js');
const uri = require('./util/uri.js');
@ -257,7 +256,7 @@ class Api extends events.EventTarget {
_getFullUrl(url) {
const fullUrl =
(config.apiUrl + '/' + url).replace(/([^:])\/+/g, '$1/');
('/api/' + url).replace(/([^:])\/+/g, '$1/');
const matches = fullUrl.match(/^([^?]*)\??(.*)$/);
const baseUrl = matches[1];
const request = matches[2];

View file

@ -1,7 +1,6 @@
'use strict';
const api = require('../api.js');
const config = require('../config.js');
const events = require('../events.js');
const views = require('../util/views.js');

View file

@ -30,6 +30,7 @@ class Post extends events.EventTarget {
get user() { return this._user; }
get safety() { return this._safety; }
get contentUrl() { return this._contentUrl; }
get fullContentUrl() { return this._fullContentUrl; }
get thumbnailUrl() { return this._thumbnailUrl; }
get canvasWidth() { return this._canvasWidth || 800; }
get canvasHeight() { return this._canvasHeight || 450; }
@ -275,6 +276,7 @@ class Post extends events.EventTarget {
_user: response.user,
_safety: response.safety,
_contentUrl: response.contentUrl,
_fullContentUrl: new URL(response.contentUrl, window.location.href).href,
_thumbnailUrl: response.thumbnailUrl,
_canvasWidth: response.canvasWidth,
_canvasHeight: response.canvasHeight,

View file

@ -1,7 +1,6 @@
'use strict';
const settings = require('../models/settings.js');
const config = require('../config.js');
const api = require('../api.js');
const uri = require('../util/uri.js');
const AbstractList = require('./abstract_list.js');

View file

@ -1,7 +1,6 @@
'use strict';
const marked = require('marked');
const config = require('../config.js');
class BaseMarkdownWrapper {
preprocess(text) {
@ -64,15 +63,12 @@ class TagPermalinkFixWrapper extends BaseMarkdownWrapper {
class EntityPermalinkWrapper extends BaseMarkdownWrapper {
preprocess(text) {
// URL-based permalinks
let baseUrl = config.baseUrl.replace(/\/+$/, '');
text = text.replace(
new RegExp('\\b' + baseUrl + '/post/(\\d+)/?\\b', 'g'), '@$1');
new RegExp('\\b/post/(\\d+)/?\\b', 'g'), '@$1');
text = text.replace(
new RegExp('\\b' + baseUrl + '/tag/([a-zA-Z0-9_-]+?)/?', 'g'),
'#$1');
new RegExp('\\b/tag/([a-zA-Z0-9_-]+?)/?', 'g'), '#$1');
text = text.replace(
new RegExp('\\b' + baseUrl + '/user/([a-zA-Z0-9_-]+?)/?', 'g'),
'+$1');
new RegExp('\\b/user/([a-zA-Z0-9_-]+?)/?', 'g'), '+$1');
text = text.replace(
/(^|^\(|(?:[^\]])\(|[\s<>\[\]\)])([+#@][a-zA-Z0-9_-]+)/g,

1140
client/package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -10,17 +10,13 @@
"babel-preset-es2015": "^6.24.1",
"babelify": "^7.2.0",
"browserify": "^13.0.0",
"camelcase": "^2.1.1",
"camelcase-keys": "^4.2.0",
"csso": "^1.8.0",
"font-awesome": "^4.6.1",
"glob": "^7.1.2",
"html-minifier": "^1.3.1",
"ios-inner-height": "^1.0.3",
"js-cookie": "^2.2.0",
"js-yaml": "^3.10.0",
"marked": "^0.3.9",
"merge": "^1.2.0",
"mousetrap": "^1.6.1",
"nprogress": "^0.2.0",
"stylus": "^0.54.2",

View file

@ -2,12 +2,9 @@
# and override only what you need.
name: szurubooru # shown in the website title and on the front page
debug: 0 # generate source maps for JS debugging?
debug: 0 # generate server logs?
show_sql: 0 # show sql in server logs?
transpile: 1 # generate bigger JS to support older browsers?
secret: change # used to salt the users' password hashes
api_url: # where frontend connects to, example: http://api.example.com/
base_url: # used to form links to frontend, example: http://example.com/
data_url: # used to form links to posts and avatars, example: http://example.com/data/
data_dir: # absolute path for posts and avatars storage, example: /srv/www/booru/client/public/data/
user_agent: # user agent name used to download files from the web on behalf of the api users

View file

@ -21,8 +21,7 @@ def start_password_reset(
'User %r hasn\'t supplied email. Cannot reset password.' % (
user_name))
token = auth.generate_authentication_token(user)
url = '%s/password-reset/%s:%s' % (
config.config['base_url'].rstrip('/'), user.name, token)
url = '/password-reset/%s:%s' % (user.name, token)
mailer.send_mail(
'noreply@%s' % config.config['name'],
user.email,

View file

@ -79,7 +79,7 @@ def validate_config() -> None:
'Default rank %r is not on the list of known ranks' % (
config.config['default_rank']))
for key in ['base_url', 'api_url', 'data_url', 'data_dir']:
for key in ['data_url', 'data_dir']:
if not config.config[key]:
raise errors.ConfigError(
'Service is not configured: %r is missing' % key)