pinstripe 0.29.0 → 0.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +1 -3
  2. package/lib/apps/docs.js +6 -0
  3. package/lib/commands/list_apps.js +1 -1
  4. package/lib/commands/list_views.js +3 -21
  5. package/lib/component.js +6 -36
  6. package/lib/database/migrator.js +5 -5
  7. package/lib/extensions/_file_importer.js +1 -0
  8. package/lib/extensions/multi-tenant/database/row.js +26 -0
  9. package/lib/extensions/multi-tenant/database/table.js +30 -0
  10. package/lib/extensions/multi-tenant/database.js +18 -0
  11. package/lib/extensions/multi-tenant/index.js +4 -0
  12. package/lib/extensions/multi-tenant/migrations/1627976174_create_tenant_table_and_add_tenant_id_to_existing_tables.js +20 -0
  13. package/lib/extensions/multi-tenant/migrations/_file_importer.js +2 -0
  14. package/lib/extensions/multi-tenant/services/_file_importer.js +2 -0
  15. package/lib/extensions/multi-tenant/services/database.js +32 -0
  16. package/lib/markdown.js +1 -1
  17. package/lib/proof_of_work.js +93 -0
  18. package/lib/services/cookies.js +1 -1
  19. package/lib/services/docs.js +93 -0
  20. package/lib/services/fetch.js +18 -47
  21. package/lib/services/render_form.js +2 -7
  22. package/lib/services/render_view.js +1 -1
  23. package/lib/view.js +6 -1
  24. package/lib/views/docs/_404.js +16 -0
  25. package/lib/views/docs/_footer.js +67 -0
  26. package/lib/views/docs/_header.js +17 -0
  27. package/lib/views/docs/_hero.js +59 -0
  28. package/lib/views/docs/_layout.js +118 -0
  29. package/lib/views/docs/_navbar.js +99 -0
  30. package/lib/views/docs/_sidebar.js +31 -0
  31. package/lib/views/docs/default.js +6 -0
  32. package/lib/views/docs/docs/default.js +24 -0
  33. package/lib/views/docs/docs/guides/introduction.md +18 -0
  34. package/lib/views/docs/images/logo.svg +7 -0
  35. package/lib/views/docs/index.js +16 -0
  36. package/lib/views/shared/_content.js +1 -5
  37. package/lib/views/shared/_form.js +17 -28
  38. package/lib/views/shared/_navbar.js +0 -28
  39. package/lib/views/shared/_shell/index.js +25 -0
  40. package/lib/views/shared/_shell/stylesheets/all.css +4 -0
  41. package/package.json +4 -4
  42. package/lib/services/session.js +0 -19
  43. package/lib/views/shared/assets/stylesheets/all.css +0 -4
  44. /package/lib/views/shared/{assets → _shell}/javascripts/all.js.js +0 -0
  45. /package/lib/views/shared/{assets → _shell}/javascripts/all.js.map.js +0 -0
  46. /package/lib/views/shared/{assets → _shell}/stylesheets/global.css +0 -0
  47. /package/lib/views/shared/{assets → _shell}/stylesheets/reset.css +0 -0
  48. /package/lib/views/shared/{assets → _shell}/stylesheets/vars.css +0 -0
  49. /package/lib/views/shared/{assets → _shell}/stylesheets/view.css.js +0 -0
package/README.md CHANGED
@@ -1,9 +1,7 @@
1
1
 
2
2
  # Welcome to Pinstripe
3
3
 
4
- ## What is Pinstripe?
5
-
6
- Pinstripe is a slick [content management system (CMS)](https://en.wikipedia.org/wiki/Content_management_system) for Node.js.
4
+ A slick web framework for Node.js.
7
5
 
8
6
  ## License
9
7
 
@@ -0,0 +1,6 @@
1
+
2
+ export default {
3
+ compose(){
4
+ return ['shared', 'docs'];
5
+ }
6
+ };
@@ -8,7 +8,7 @@ export default {
8
8
  console.log('The following apps are available:');
9
9
  console.log('');
10
10
  App.names.forEach(appName => {
11
- console.log(` * ${chalk.green(appName)} (composed of ${JSON.stringify(App.create(appName, this.context).compose())} views)`);
11
+ console.log(` * ${chalk.green(appName)}`);
12
12
  });
13
13
  console.log('');
14
14
  }
@@ -6,31 +6,13 @@ export default {
6
6
  run(){
7
7
  const { extractOptions } = this.cliUtils;
8
8
 
9
- const { app } = extractOptions();
9
+ const { app = 'main' } = extractOptions();
10
10
 
11
- if(app){
12
- this.listComposedViews(typeof app == 'string' ? app : 'main');
13
- } else {
14
- this.listAllViews();
15
- }
16
- },
17
-
18
- listComposedViews(appName){
19
- const { viewNames, resolveView } = View.mapperFor(App.create(appName, this.context).compose());
20
- console.log('');
21
- console.log(`The following views have been composed for app "${appName}":`);
22
- console.log('');
23
- viewNames.forEach(viewName => {
24
- console.log(` * ${chalk.green(viewName)} -> ${chalk.green(resolveView(viewName))}`);
25
- });
26
- console.log('');
27
- },
28
-
29
- listAllViews(){
11
+ const { viewNames } = View.mapperFor(App.create(app, this.context).compose());
30
12
  console.log('');
31
13
  console.log(`The following views are available:`);
32
14
  console.log('');
33
- View.names.forEach(viewName => {
15
+ viewNames.forEach(viewName => {
34
16
  console.log(` * ${chalk.green(viewName)}`);
35
17
  });
36
18
  console.log('');
package/lib/component.js CHANGED
@@ -2,12 +2,13 @@
2
2
  import { fileURLToPath } from 'url'; // pinstripe-if-client: const fileURLToPath = undefined;
3
3
 
4
4
  import { Class } from './class.js';
5
- import { TEXT_ONLY_TAGS, IS_SERVER } from './constants.js';
5
+ import { TEXT_ONLY_TAGS } from './constants.js';
6
6
  import { Inflector } from './inflector.js';
7
7
  import { VirtualNode } from './virtual_node.js';
8
8
  import { Registry } from './registry.js';
9
9
  import { ComponentEvent } from './component_event.js';
10
10
  import { Client } from './client.js'; // pinstripe-if-client: const Client = undefined;
11
+ import { generateProofOfWork } from './proof_of_work.js';
11
12
 
12
13
  export const Component = Class.extend().include({
13
14
  meta(){
@@ -462,7 +463,10 @@ export const Component = Class.extend().include({
462
463
  if(!(otherOptions.body instanceof FormData)) throw new Error(`Proof of work requires form data to be present`);
463
464
  const values = {};
464
465
  otherOptions.body.forEach((value, key) => values[key] = value);
465
- otherOptions.body.append('_proofOfWork', await generateProofOfWork(values, abortController.signal))
466
+ otherOptions.body.append('_proofOfWork', await generateProofOfWork(values, {
467
+ abortSignal: abortController.signal,
468
+ onProgress: progress => this.trigger('proofOfWorkProgress', { data: progress, bubbles: false })
469
+ }))
466
470
  }
467
471
  const promises = [
468
472
  fetch(normalizedUrl, { signal: abortController.signal, ...otherOptions }),
@@ -712,38 +716,4 @@ function normalizeVirtualNode(){
712
716
  }
713
717
  }
714
718
 
715
- async function generateProofOfWork(values, abortSignal){
716
- const stringifiedValues = JSON.stringify(values);
717
- const timestamp = getUTCTimestamp();
718
- const random = btoa(`${Math.random()}`);
719
- let counter = 0;
720
- while(!abortSignal.aborted){
721
- const candidateSolution = `1:20:${timestamp}:?::${random}:${btoa(`${counter}`)}`;
722
- const hash = await createSha1Hash(candidateSolution.replace(/\?/, stringifiedValues));
723
- if(hash.match(/^00000/)) return candidateSolution;
724
- counter++;
725
- }
726
- return '';
727
- }
728
-
729
- function getUTCTimestamp() {
730
- const now = new Date();
731
-
732
- const year = now.getUTCFullYear() % 100;
733
- const month = String(now.getUTCMonth() + 1).padStart(2, '0');
734
- const day = String(now.getUTCDate()).padStart(2, '0');
735
- const hours = String(now.getUTCHours()).padStart(2, '0');
736
- const minutes = String(now.getUTCMinutes()).padStart(2, '0');
737
- const seconds = String(now.getUTCSeconds()).padStart(2, '0');
738
-
739
- return `${year}${month}${day}${hours}${minutes}${seconds}`;
740
- }
741
-
742
-
743
- async function createSha1Hash(input) {
744
- const buffer = new TextEncoder().encode(input);
745
- const hashArray = Array.from(new Uint8Array(await crypto.subtle.digest('SHA-1', buffer)));
746
- return hashArray.map(byte => byte.toString(16).padStart(2, '0')).join('');
747
- }
748
-
749
719
  ComponentEvent.Component = Component;
@@ -8,20 +8,20 @@ export const Migrator = Class.extend().include({
8
8
  },
9
9
 
10
10
  async migrate(){
11
- if(!await this.database.table('pinstripeAppliedMigrations').exists){
12
- await this.database.table('pinstripeAppliedMigrations', async pinstripeAppliedMigrations => {
13
- await pinstripeAppliedMigrations.addColumn('schemaVersion', 'integer');
11
+ if(!await this.database.table('appliedMigrations').exists){
12
+ await this.database.table('appliedMigrations', async appliedMigrations => {
13
+ await appliedMigrations.addColumn('schemaVersion', 'integer');
14
14
  });
15
15
  }
16
16
  const isDevelopmentEnvironment = (process.env.NODE_ENV || 'development') == 'development';
17
17
  const migrations = Migration.names.map(name => Migration.for(name)).sort((a, b) => a.schemaVersion - b.schemaVersion);
18
18
  for(let i in migrations){
19
19
  const migration = migrations[i];
20
- const isMigrationApplied = await this.database.table('pinstripeAppliedMigrations').where({ schemaVersion: migration.schemaVersion }).count() > 0;
20
+ const isMigrationApplied = await this.database.table('appliedMigrations').where({ schemaVersion: migration.schemaVersion }).count() > 0;
21
21
  if(!isMigrationApplied){
22
22
  if(isDevelopmentEnvironment) console.log(`Applying migration: ${migration.name}`);
23
23
  await migration.new(this.database).migrate();
24
- await this.database.table('pinstripeAppliedMigrations').insert({ schemaVersion: migration.schemaVersion });
24
+ await this.database.table('appliedMigrations').insert({ schemaVersion: migration.schemaVersion });
25
25
  }
26
26
  }
27
27
  }
@@ -0,0 +1 @@
1
+ export default undefined;
@@ -0,0 +1,26 @@
1
+
2
+ import { Table, Row } from 'pinstripe/database';
3
+
4
+ Row.include({
5
+ meta(){
6
+ const { initialize, update } = this.prototype;
7
+
8
+ this.include({
9
+ initialize(database, fields, exists){
10
+ const columns = Table.for(this.constructor.collectionName).columns;
11
+ if(columns.tenantId && database.tenant){
12
+ return initialize.call(this, database, { ...fields, tenantId: database.tenant.id }, exists);
13
+ }
14
+ return initialize.call(this, database, fields, exists);
15
+ },
16
+
17
+ update(fields){
18
+ const columns = Table.for(this.constructor.collectionName).columns;
19
+ if(columns.tenantId && this.database.tenant){
20
+ return update.call(this, { ...fields, tenantId: this.database.tenant.id });
21
+ }
22
+ return update.call(this, fields);
23
+ }
24
+ });
25
+ }
26
+ });
@@ -0,0 +1,30 @@
1
+
2
+ import { Table } from 'pinstripe/database';
3
+
4
+ Table.include({
5
+ meta(){
6
+ const { create, initialize } = this.prototype;
7
+
8
+ this.include({
9
+ async create(...args){
10
+ if(this.exists) return;
11
+ await create.call(this, ...args);
12
+ if(this.constructor.name == 'appliedMigrations') return;
13
+ if(this.constructor.name == 'tenants') return;
14
+ await this.addColumn('tenantId', 'foreign_key');
15
+ },
16
+
17
+ initialize(...args){
18
+ const out = initialize.call(this, ...args);
19
+ if(this.constructor.columns.tenantId && this.database.scopedByTenant){
20
+ if(this.database.tenant){
21
+ this.where({ tenantId: this.database.tenant.id });
22
+ } else {
23
+ this.where('1 = 2')
24
+ }
25
+ }
26
+ return out;
27
+ }
28
+ });
29
+ }
30
+ });
@@ -0,0 +1,18 @@
1
+
2
+ import { Database } from 'pinstripe';
3
+
4
+ Database.include({
5
+ meta(){
6
+ const { singleton } = this.prototype;
7
+
8
+ this.include({
9
+ singleton(...args){
10
+ if(this.tenant) return singleton.call(this, ...args);
11
+ }
12
+ });
13
+ },
14
+
15
+ get scopedByTenant(){
16
+ return this.hasOwnProperty('tenant');
17
+ }
18
+ });
@@ -0,0 +1,4 @@
1
+
2
+ import { importAll } from 'pinstripe';
3
+
4
+ importAll(import.meta.url);
@@ -0,0 +1,20 @@
1
+
2
+ export default {
3
+ async migrate(){
4
+ await this.database.table('tenants', async tenants => {
5
+ await tenants.addColumn('name', 'string', { index: true });
6
+ await tenants.addColumn('host', 'string', { index: true });
7
+ });
8
+
9
+ const tableNames = Object.keys(this.database.info).filter(name => this.database.info[name] == 'table');
10
+
11
+ while(tableNames.length){
12
+ const tableName = tableNames.shift();
13
+ if(tableName == 'appliedMigrations') continue;
14
+ if(tableName == 'tenants') continue;
15
+
16
+ if(this.database[tableName].constructor.columns.tenantId) continue;
17
+ await this.database[tableName].addColumn('tenantId', 'foreign_key');
18
+ }
19
+ }
20
+ };
@@ -0,0 +1,2 @@
1
+
2
+ export { Migration as default } from 'pinstripe/database';
@@ -0,0 +1,2 @@
1
+
2
+ export { ServiceFactory as default } from 'pinstripe';
@@ -0,0 +1,32 @@
1
+
2
+ import { Database } from 'pinstripe';
3
+ import { Client } from "pinstripe/database";
4
+
5
+ export default {
6
+ create(){
7
+ return this.defer(async () => {
8
+ if(!this.context.root.databaseClient){
9
+ this.context.root.databaseClient = Client.new(await this.config.database);
10
+ }
11
+
12
+ const out = await Database.new(this.context.root.databaseClient);
13
+
14
+ if(out.info.tenants){
15
+ const headers = this.initialParams._headers;
16
+ const hostname = this.initialParams._url.hostname;
17
+ const host = (headers['host'] || hostname).replace(/\:\d+$/, '').toLowerCase();
18
+ const { primaryDomain = '' } = await this.config;
19
+ const domainSuffix = `\.${primaryDomain}`.toLowerCase();
20
+
21
+ if(host.endsWith(domainSuffix)){
22
+ const name = host.substr(0, host.length - domainSuffix.length);
23
+ out.tenant = await out.tenants.where({ name }).first();
24
+ } else {
25
+ out.tenant = await out.tenants.where({ host }).first();
26
+ }
27
+ }
28
+
29
+ return out;
30
+ });
31
+ }
32
+ };
package/lib/markdown.js CHANGED
@@ -39,7 +39,7 @@ export const Markdown = Class.extend().include({
39
39
  paragraph.attributes = {
40
40
  ...paragraph.attributes,
41
41
  'data-component': 'pinstripe-frame',
42
- 'data-url': `/markdown/slash_blocks/${name}?args=${encodeURIComponent(args)}`,
42
+ 'data-url': `/_markdown_slash_blocks/${name}?args=${encodeURIComponent(args)}`,
43
43
  };
44
44
  paragraph.children = [];
45
45
  });
@@ -0,0 +1,93 @@
1
+
2
+ import crypto from 'crypto' // pinstripe-if-client: const crypto = window.crypto;
3
+
4
+ import { Class } from './class.js';
5
+ import { Singleton } from './singleton.js';
6
+
7
+ const HASH_CASH_TARGET = Math.pow(2, 32 - 20);
8
+
9
+ export const ProofOfWork = Class.extend().include({
10
+ meta(){
11
+ this.include(Singleton)
12
+ },
13
+
14
+ async generateProofOfWork(input, options = {}){
15
+ const { difficulty = 1, steps = 1000, onProgress = () => {}, abortSignal = { aborted: false } } = options;
16
+ const inputHash = await this.createSha1Hash(JSON.stringify(input));
17
+ const salt = await this.createSha1Hash(Math.random());
18
+ const timestamp = this.getUTCTimestamp();
19
+ const target = this.calculateTarget(difficulty, steps);
20
+ const solution = [];
21
+
22
+ let counter = -1;
23
+ for(let i = 1; i <= steps && !abortSignal.aborted; i++){
24
+ while(!abortSignal.aborted){
25
+ counter++;
26
+ const hash = await this.createSha1Hash(`${difficulty}:${steps}:${inputHash}:${salt}:${timestamp}:${counter}`);
27
+ const integers = this.hexToIntegers(hash);
28
+ if(integers[0] <= target) break;
29
+ }
30
+ solution.push(counter);
31
+ const progressPercentage = Math.floor(i * (100 / steps) * 100) / 100;
32
+ onProgress(progressPercentage);
33
+ }
34
+
35
+ if(abortSignal.aborted) return;
36
+
37
+ return JSON.stringify({
38
+ salt,
39
+ timestamp,
40
+ solution,
41
+ });
42
+ },
43
+
44
+ async verifyProofOfWork(input, proofOfWork, options = {}){
45
+ const { difficulty = 1 } = options;
46
+ const { salt, timestamp, solution } = JSON.parse(proofOfWork);
47
+ const steps = solution.length;
48
+ const inputHash = await this.createSha1Hash(JSON.stringify(input));
49
+ const target = this.calculateTarget(difficulty, steps);
50
+ for(let i = 0; i < steps; i++){
51
+ const counter = solution[i];
52
+ const hash = await this.createSha1Hash(`${difficulty}:${steps}:${inputHash}:${salt}:${timestamp}:${counter}`.toString(), true);
53
+ const integers = this.hexToIntegers(hash);
54
+ if(integers[0] > target) return false;
55
+ }
56
+ return true;
57
+ },
58
+
59
+ async createSha1Hash(input) {
60
+ const buffer = new TextEncoder().encode(input);
61
+ const hashArray = Array.from(new Uint8Array(await crypto.subtle.digest('SHA-1', buffer)));
62
+ return hashArray.map(byte => byte.toString(16).padStart(2, '0')).join('');
63
+ },
64
+
65
+ hexToIntegers(hex){
66
+ const out = [];
67
+ for(let i = 0; i < hex.length; i += 8){
68
+ out.push(parseInt(hex.slice(i, i + 8), 16));
69
+ }
70
+ return out;
71
+ },
72
+
73
+ calculateTarget(difficulty, steps){
74
+ return (HASH_CASH_TARGET / difficulty) * steps;
75
+ },
76
+
77
+ getUTCTimestamp() {
78
+ const now = new Date();
79
+
80
+ const year = now.getUTCFullYear() % 100;
81
+ const month = String(now.getUTCMonth() + 1).padStart(2, '0');
82
+ const day = String(now.getUTCDate()).padStart(2, '0');
83
+ const hours = String(now.getUTCHours()).padStart(2, '0');
84
+ const minutes = String(now.getUTCMinutes()).padStart(2, '0');
85
+ const seconds = String(now.getUTCSeconds()).padStart(2, '0');
86
+
87
+ return `${year}${month}${day}${hours}${minutes}${seconds}`;
88
+ }
89
+ });
90
+
91
+ export const generateProofOfWork = (...args) => ProofOfWork.instance.generateProofOfWork(...args);
92
+
93
+ export const verifyProofOfWork = (...args) => ProofOfWork.instance.verifyProofOfWork(...args);
@@ -3,7 +3,7 @@ export default {
3
3
  create(){
4
4
  if(!this.context.root.hasOwnProperty('cookies')){
5
5
  const cookies = {};
6
- const cookieHeader = this.params._headers?.cookie;
6
+ const cookieHeader = this.initialParams._headers?.cookie;
7
7
  if(cookieHeader){
8
8
  cookieHeader.split(/;/).forEach(cookie => {
9
9
  const matches = cookie.trim().match(/^([^=]+)=(.*)$/);
@@ -0,0 +1,93 @@
1
+
2
+ import { existsSync } from 'fs';
3
+ import { realpath, readFile } from 'fs/promises';
4
+ import { registries } from 'pinstripe/util';
5
+
6
+ export default {
7
+ async create(){
8
+ if(!this.context.root.docs){
9
+ this.context.root.docs = {
10
+ apps: await this.extractDocs('App', 'apps'),
11
+ commands: await this.extractDocs('Command', 'commands'),
12
+ components: await this.extractDocs('Component', 'components'),
13
+ migrations: await this.extractDocs('Migration', 'migrations'),
14
+ models: await this.extractDocs('Row', 'models'),
15
+ services: await this.extractDocs('ServiceFactory', 'services'),
16
+ views: await this.extractDocs('View', 'views'),
17
+ };
18
+ }
19
+ return this.context.root.docs;
20
+ },
21
+
22
+
23
+
24
+ async extractDocs(name, namespace){
25
+ const out = {};
26
+ const registry = registries[name];
27
+ const { names } = registry;
28
+ for(let name of names){
29
+ const slug = `${namespace}/${name}`;
30
+
31
+ const Class = registry.for(name);
32
+
33
+ const filePath = Class.filePaths[Class.filePaths.length - 1];
34
+
35
+ if(!filePath || !filePath.match(/\.(js|css)/)) continue;
36
+
37
+ const packageRootPath = await this.getPackageRootPath(filePath);
38
+
39
+ if(packageRootPath == undefined) continue;
40
+
41
+ const packageJson = await this.getPackageJson(packageRootPath);
42
+
43
+ const packageName = packageJson.name;
44
+
45
+ const packageRelativePath = filePath.substr(packageRootPath.length).replace(/^\//, '');
46
+ const repositoryRelativePath = `${packageJson.repository?.directory ? `${packageJson.repository.directory}/` : ''}${packageRelativePath}`;
47
+
48
+ const code = (await readFile(filePath)).toString('utf8');
49
+
50
+ const markdown = code.split(/\/\*\//).map((segment, i) => {
51
+ if(i % 2 == 0 && segment.trim().length){
52
+ return `\`\`\`\n${segment}\n\`\`\``;
53
+ }
54
+ return segment;
55
+ }).join('');
56
+
57
+ out[name] = {
58
+ name,
59
+ slug,
60
+ packageName,
61
+ packageRelativePath,
62
+ repositoryRelativePath,
63
+ markdown
64
+ };
65
+ }
66
+ return out;
67
+ },
68
+
69
+ async getPackageRootPath(path){
70
+ const packageJsonPath = (await this.findInPath('package.json', path.replace(/[^/]*$/, ''))).shift();
71
+ if(!packageJsonPath) return;
72
+ return packageJsonPath.replace(/package.json$/, '');
73
+ },
74
+
75
+ async getPackageJson(packageRootPath){
76
+ return JSON.parse((await readFile(`${packageRootPath}package.json`)).toString('utf8'));
77
+ },
78
+
79
+ async findInPath(offset, base){
80
+ const out = [];
81
+ while(base) {
82
+ const candidatePath = `${base}/${offset}`;
83
+ if(existsSync(candidatePath)){
84
+ out.push(await realpath(candidatePath));
85
+ }
86
+ if(base == '/'){
87
+ break;
88
+ }
89
+ base = await realpath(`${base}/..`);
90
+ }
91
+ return out;
92
+ }
93
+ };
@@ -15,28 +15,18 @@ export default {
15
15
  const normalizedParams = this.normalizeParams(params);
16
16
  this.context.params = normalizedParams;
17
17
 
18
- const viewName = normalizedParams._url.pathname.replace(/^\/|\/$/g, '');
18
+ const viewName = normalizedParams._url.pathname.replace(/^\/|\/$/g, '') || 'index';
19
19
 
20
20
  let out = await this.renderGuardViews(viewName, normalizedParams);
21
- if(out){
22
- return out;
23
- }
21
+ if(out) return out;
24
22
 
25
- if(!viewName.match(/(^|\/)_/)){
26
- out = this.normalizeResponse(await this.renderView(viewName != '' ? `${viewName}/index`: 'index', normalizedParams));
27
- if(out){
28
- return out;
29
- }
30
- out = this.normalizeResponse(await this.renderView(viewName, normalizedParams));
31
- if(out){
32
- return out;
33
- }
23
+ if(!viewName.match(/(^|\/)_[^\/]+(|\/index)$/)){
24
+ out = this.normalizeResponse(await this.app.renderView(viewName, normalizedParams));
25
+ if(out) return out;
34
26
  }
35
27
 
36
28
  out = await this.renderDefaultViews(viewName, normalizedParams);
37
- if(out){
38
- return out;
39
- }
29
+ if(out) return out;
40
30
 
41
31
  return [404, {'content-type': 'text/plain'}, ['Not found']];
42
32
  },
@@ -44,22 +34,12 @@ export default {
44
34
  async renderGuardViews(viewName, params){
45
35
  const viewNameSegments = viewName != '' ? viewName.split(/\//) : [];
46
36
 
47
- const candidateIndexView = [...viewNameSegments, 'index'].join('/');
48
- const candidateDefaultView = [...viewNameSegments, 'default'].join('/');
49
-
50
- if(!this.app.isView(candidateIndexView) && !this.app.isView(candidateDefaultView)){
51
- viewNameSegments.pop();
52
- }
53
-
54
37
  const prefixSegments = [];
55
38
  while(true){
56
- const out = this.normalizeResponse(await this.renderView(prefixSegments.length ? [...prefixSegments, 'guard'].join('/') : 'guard', params));
57
- if(out){
58
- return out;
59
- }
60
- if(viewNameSegments.length == 0){
61
- break;
62
- }
39
+ const candidateGuardViewName = prefixSegments.length ? [...prefixSegments, 'guard'].join('/') : 'guard';
40
+ const out = this.normalizeResponse(await this.app.renderView(candidateGuardViewName, params));
41
+ if(out) return out;
42
+ if(viewNameSegments.length == 0) break;
63
43
  prefixSegments.push(viewNameSegments.shift());
64
44
  }
65
45
  },
@@ -67,28 +47,19 @@ export default {
67
47
  async renderDefaultViews(viewName, params){
68
48
  const prefixSegments = viewName != '' ? viewName.split(/\//) : [];
69
49
  while(true){
70
- const out = this.normalizeResponse(await this.renderView(prefixSegments.length ? [...prefixSegments, 'default'].join('/') : 'default', params));
71
- if(out){
72
- return out;
73
- }
74
- if(prefixSegments.length == 0){
75
- break;
76
- }
50
+ const candidateDefaultViewName = prefixSegments.length ? [...prefixSegments, 'default'].join('/') : 'default';
51
+ const out = this.normalizeResponse(await this.app.renderView(candidateDefaultViewName, params));
52
+ if(out) return out;
53
+ if(prefixSegments.length == 0) break;
77
54
  prefixSegments.pop();
78
55
  }
79
56
  },
80
57
 
81
58
  normalizeParams(params){
82
59
  const out = { ...params }
83
- if(!out._method){
84
- out._method = 'get';
85
- }
86
- if(!params._url){
87
- out._url = new URL('http://localhost')
88
- }
89
- if(!params._headers){
90
- out._headers = {};
91
- }
60
+ if(!out._method) out._method = 'get';
61
+ if(!params._url) out._url = new URL('http://localhost');
62
+ if(!params._headers) out._headers = {};
92
63
  return out;
93
64
  },
94
65
 
@@ -108,7 +79,7 @@ export default {
108
79
  const normalizedHeaders = {};
109
80
  Object.keys(headers).forEach(name => {
110
81
  normalizedHeaders[name.toLowerCase()] = headers[name];
111
- })
82
+ });
112
83
 
113
84
  return [ status, normalizedHeaders, body ];
114
85
  }
@@ -2,6 +2,7 @@ import * as crypto from 'crypto';
2
2
 
3
3
  import { ValidationError } from '../validation_error.js';
4
4
  import { Inflector } from '../inflector.js';
5
+ import { verifyProofOfWork } from '../proof_of_work.js';
5
6
 
6
7
  export default {
7
8
  create(){
@@ -24,7 +25,7 @@ export default {
24
25
  try {
25
26
  if(requiresProofOfWork){
26
27
  if(!this.params._proofOfWork) throw new ValidationError({ _proofOfWork: 'Must not be blank' });
27
- if(!this.verifyProofOfWork(this.params._proofOfWork, values)) throw new ValidationError({ _proofOfWork: 'Must be a valid stamp' });
28
+ if(!verifyProofOfWork(values, this.params._proofOfWork)) throw new ValidationError({ _proofOfWork: 'Must be a valid stamp' });
28
29
  }
29
30
  return await formAdapter.submit(values, success) || this.renderHtml`
30
31
  <span data-component="pinstripe-anchor" data-target="_parent">
@@ -74,12 +75,6 @@ export default {
74
75
  submitTitle,
75
76
  cancelTitle
76
77
  });
77
- },
78
-
79
- verifyProofOfWork(proofOfWork, values){
80
- const hash = crypto.createHash('sha1').update(proofOfWork.replace(/\?/, JSON.stringify(values)), 'binary').digest('hex');
81
- if(hash.match(/^00000/)) return true;
82
- return false;
83
78
  }
84
79
  }
85
80
 
@@ -1,6 +1,6 @@
1
1
 
2
2
  export default {
3
3
  create(){
4
- return (...args) => this.app.renderView(...args);
4
+ return (name, params = {}) => this.app.renderView(name, params);
5
5
  }
6
6
  };