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:
parent
3972b902d8
commit
60ab9246c6
14 changed files with 595 additions and 694 deletions
30
INSTALL.md
30
INSTALL.md
|
@ -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'
|
||||||
```
|
```
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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'/>
|
||||||
|
|
|
@ -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> ·
|
<a href='http://iqdb.org/?url=<%- encodeURIComponent(ctx.post.fullContentUrl) %>'>IQDB</a> ·
|
||||||
<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'>
|
||||||
|
|
|
@ -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];
|
||||||
|
|
|
@ -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');
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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');
|
||||||
|
|
|
@ -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
1140
client/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -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",
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in a new issue