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 ### Preparing `szurubooru` for first run
1. Configure things: 1. Compile the frontend:
```console ```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$ cp config.yaml.dist config.yaml
user@host:szuru$ vim config.yaml user@host:szuru$ vim config.yaml
``` ```
Pay extra attention to these fields: Pay extra attention to these fields:
- base URL,
- API URL,
- data directory, - data directory,
- data URL, - data URL,
- database, - database,
- the `smtp` section. - the `smtp` section.
2. Compile the frontend:
```console
user@host:szuru$ cd client
user@host:szuru/client$ npm run build
```
3. Upgrade the database: 3. Upgrade the database:
```console ```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 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. 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 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! the one in the `config.yaml`, so that client knows how to access the backend!
@ -177,8 +185,6 @@ server {
**`config.yaml`**: **`config.yaml`**:
```yaml ```yaml
api_url: 'http://example.com/api/'
base_url: 'http://example.com/'
data_url: 'http://example.com/data/' data_url: 'http://example.com/data/'
data_dir: '/srv/www/booru/client/public/data' data_dir: '/srv/www/booru/client/public/data'
``` ```

View file

@ -5,20 +5,6 @@ const glob = require('glob');
const path = require('path'); const path = require('path');
const util = require('util'); const util = require('util');
const execSync = require('child_process').execSync; 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) { function readTextFile(path) {
return fs.readFileSync(path, 'utf-8'); return fs.readFileSync(path, 'utf-8');
@ -29,37 +15,27 @@ function writeFile(path, content) {
} }
function getVersion() { function getVersion() {
return execSync('git describe --always --dirty --long --tags') let build_info = process.env.BUILD_INFO;
.toString() if (build_info) {
.trim(); 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() { function getConfig() {
const yaml = require('js-yaml'); let config = {
const merge = require('merge'); meta: {
const camelcaseKeys = require('camelcase-keys'); version: getVersion(),
buildDate: new Date().toUTCString()
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(),
}; };
return config; return config;
@ -85,15 +61,11 @@ function minifyHtml(html) {
}).trim(); }).trim();
} }
function bundleHtml(config) { function bundleHtml() {
const underscore = require('underscore'); const underscore = require('underscore');
const babelify = require('babelify'); const babelify = require('babelify');
const baseHtml = readTextFile('./html/index.htm', 'utf-8'); const baseHtml = readTextFile('./html/index.htm', 'utf-8');
const finalHtml = baseHtml writeFile('./public/index.htm', minifyHtml(baseHtml));
.replace(
/(<title>)(.*)(<\/title>)/,
util.format('$1%s$3', config.name));
writeFile('./public/index.htm', minifyHtml(finalHtml));
glob('./html/**/*.tpl', {}, (er, files) => { glob('./html/**/*.tpl', {}, (er, files) => {
let compiledTemplateJs = '\'use strict\'\n'; let compiledTemplateJs = '\'use strict\'\n';
@ -143,7 +115,7 @@ function bundleCss() {
}); });
} }
function bundleJs(config) { function bundleJs() {
const browserify = require('browserify'); const browserify = require('browserify');
const external = [ const external = [
'underscore', 'underscore',
@ -170,7 +142,7 @@ function bundleJs(config) {
for (let lib of external) { for (let lib of external) {
b.require(lib); b.require(lib);
} }
if (config.transpile) { if (!process.argv.includes('--no-transpile')) {
b.add(require.resolve('babel-polyfill')); b.add(require.resolve('babel-polyfill'));
} }
writeJsBundle( writeJsBundle(
@ -179,15 +151,15 @@ function bundleJs(config) {
if (!process.argv.includes('--no-app-js')) { if (!process.argv.includes('--no-app-js')) {
let outputFile = fs.createWriteStream('./public/js/app.min.js'); let outputFile = fs.createWriteStream('./public/js/app.min.js');
let b = browserify({debug: config.debug}); let b = browserify({debug: process.argv.includes('--debug')});
if (config.transpile) { if (!process.argv.includes('--no-transpile')) {
b = b.transform('babelify'); b = b.transform('babelify');
} }
writeJsBundle( writeJsBundle(
b.external(external).add(files), b.external(external).add(files),
'./public/js/app.min.js', './public/js/app.min.js',
'Bundled app JS', 'Bundled app JS',
!config.debug); !process.argv.includes('--debug'));
} }
}); });
} }
@ -217,11 +189,11 @@ const config = getConfig();
bundleConfig(config); bundleConfig(config);
bundleBinaryAssets(); bundleBinaryAssets();
if (!process.argv.includes('--no-html')) { if (!process.argv.includes('--no-html')) {
bundleHtml(config); bundleHtml();
} }
if (!process.argv.includes('--no-css')) { if (!process.argv.includes('--no-css')) {
bundleCss(); bundleCss();
} }
if (!process.argv.includes('--no-js')) { if (!process.argv.includes('--no-js')) {
bundleJs(config); bundleJs();
} }

View file

@ -3,7 +3,7 @@
<head> <head>
<meta charset='utf-8'/> <meta charset='utf-8'/>
<meta name='viewport' content='width=device-width, initial-scale=1, maximum-scale=1'> <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/app.min.css' rel='stylesheet' type='text/css'/>
<link href='/css/vendor.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'/> <link rel='shortcut icon' type='image/png' href='/img/favicon.png'/>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,7 +1,6 @@
'use strict'; 'use strict';
const marked = require('marked'); const marked = require('marked');
const config = require('../config.js');
class BaseMarkdownWrapper { class BaseMarkdownWrapper {
preprocess(text) { preprocess(text) {
@ -64,15 +63,12 @@ class TagPermalinkFixWrapper extends BaseMarkdownWrapper {
class EntityPermalinkWrapper extends BaseMarkdownWrapper { class EntityPermalinkWrapper extends BaseMarkdownWrapper {
preprocess(text) { preprocess(text) {
// URL-based permalinks // URL-based permalinks
let baseUrl = config.baseUrl.replace(/\/+$/, '');
text = text.replace( text = text.replace(
new RegExp('\\b' + baseUrl + '/post/(\\d+)/?\\b', 'g'), '@$1'); new RegExp('\\b/post/(\\d+)/?\\b', 'g'), '@$1');
text = text.replace( text = text.replace(
new RegExp('\\b' + baseUrl + '/tag/([a-zA-Z0-9_-]+?)/?', 'g'), new RegExp('\\b/tag/([a-zA-Z0-9_-]+?)/?', 'g'), '#$1');
'#$1');
text = text.replace( text = text.replace(
new RegExp('\\b' + baseUrl + '/user/([a-zA-Z0-9_-]+?)/?', 'g'), new RegExp('\\b/user/([a-zA-Z0-9_-]+?)/?', 'g'), '+$1');
'+$1');
text = text.replace( text = text.replace(
/(^|^\(|(?:[^\]])\(|[\s<>\[\]\)])([+#@][a-zA-Z0-9_-]+)/g, /(^|^\(|(?:[^\]])\(|[\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", "babel-preset-es2015": "^6.24.1",
"babelify": "^7.2.0", "babelify": "^7.2.0",
"browserify": "^13.0.0", "browserify": "^13.0.0",
"camelcase": "^2.1.1",
"camelcase-keys": "^4.2.0",
"csso": "^1.8.0", "csso": "^1.8.0",
"font-awesome": "^4.6.1", "font-awesome": "^4.6.1",
"glob": "^7.1.2", "glob": "^7.1.2",
"html-minifier": "^1.3.1", "html-minifier": "^1.3.1",
"ios-inner-height": "^1.0.3", "ios-inner-height": "^1.0.3",
"js-cookie": "^2.2.0", "js-cookie": "^2.2.0",
"js-yaml": "^3.10.0",
"marked": "^0.3.9", "marked": "^0.3.9",
"merge": "^1.2.0",
"mousetrap": "^1.6.1", "mousetrap": "^1.6.1",
"nprogress": "^0.2.0", "nprogress": "^0.2.0",
"stylus": "^0.54.2", "stylus": "^0.54.2",

View file

@ -2,12 +2,9 @@
# and override only what you need. # and override only what you need.
name: szurubooru # shown in the website title and on the front page 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? 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 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_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/ 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 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 %r hasn\'t supplied email. Cannot reset password.' % (
user_name)) user_name))
token = auth.generate_authentication_token(user) token = auth.generate_authentication_token(user)
url = '%s/password-reset/%s:%s' % ( url = '/password-reset/%s:%s' % (user.name, token)
config.config['base_url'].rstrip('/'), user.name, token)
mailer.send_mail( mailer.send_mail(
'noreply@%s' % config.config['name'], 'noreply@%s' % config.config['name'],
user.email, user.email,

View file

@ -79,7 +79,7 @@ def validate_config() -> None:
'Default rank %r is not on the list of known ranks' % ( 'Default rank %r is not on the list of known ranks' % (
config.config['default_rank'])) 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]: if not config.config[key]:
raise errors.ConfigError( raise errors.ConfigError(
'Service is not configured: %r is missing' % key) 'Service is not configured: %r is missing' % key)