8 |
9 | `
10 | }
11 |
--------------------------------------------------------------------------------
/client/js/elements/error/index.js:
--------------------------------------------------------------------------------
1 | var html = require('choo/html')
2 |
3 | module.exports = (error) => {
4 | if (error) {
5 | if (error.message === 'no metadata' || error.message === 'Could not find entry') {
6 | return html`
`
11 | } else return ''
12 | }
13 |
--------------------------------------------------------------------------------
/client/js/elements/footer/index.js:
--------------------------------------------------------------------------------
1 | const html = require('choo/html')
2 | const css = require('sheetify')
3 |
4 | module.exports = function () {
5 | return html`
6 |
43 | `
44 | }
45 |
--------------------------------------------------------------------------------
/client/js/elements/gravatar/index.js:
--------------------------------------------------------------------------------
1 | const xtend = require('xtend')
2 | const html = require('choo/html')
3 | const gravatar = require('gravatar')
4 |
5 | module.exports = function (user, opts, cls) {
6 | if (!user || !user.email) return html``
7 | if (!opts) opts = {}
8 | if (!cls) cls = ''
9 | var _opts = xtend({s: '200', r: 'pg', d: 'retro'}, opts)
10 | var url = gravatar.url(user.email, _opts)
11 | return html`
12 |
13 | `
14 | }
15 |
--------------------------------------------------------------------------------
/client/js/elements/home-section/index.js:
--------------------------------------------------------------------------------
1 | const html = require('choo/html')
2 |
3 | /*
4 | Options:
5 |
6 | {
7 | bgColor: 'bg-white',
8 | color: 'neutral',
9 | title: 'Section',
10 | subtitle: 'Subtitle for this section',
11 | sections: [
12 | {
13 | title: 'First section',
14 | text: 'lorem bacon some things'
15 | },
16 | {
17 | title: 'Second section',
18 | text: 'lorem bacon some things'
19 | },
20 | {
21 | title: 'Third section',
22 | text: 'lorem bacon some things'
23 | }
24 | ],
25 | cta: {
26 | link: '#',
27 | text: 'Learn More'
28 | }
29 | }
30 | */
31 |
32 | module.exports = function (props) {
33 | props = Object.assign({
34 | bgColor: 'bg-white'
35 | }, props)
36 |
37 | if (!props.color && props.bgColor === 'bg-neutral') props.color = 'white'
38 |
39 | // Allow html
40 | const subtitle = html`
58 | `
59 |
60 | function cta () {
61 | if (!props.cta) return
62 | return html`
63 |
66 | `
67 | }
68 |
69 | function textSection (item) {
70 | const text = html`
77 | `
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/client/js/elements/hyperdrive-stats/index.js:
--------------------------------------------------------------------------------
1 | const html = require('choo/html')
2 | const prettyBytes = require('pretty-bytes')
3 |
4 | module.exports = function (props) {
5 | if (!props.downloaded && !props.uploaded) return ''
6 | return html`
`
7 | }
8 |
--------------------------------------------------------------------------------
/client/js/elements/icon/index.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const html = require('choo/html')
4 | const assert = require('assert')
5 | const css = require('sheetify')
6 |
7 | var prefix = css`
8 | :host {
9 | display: block;
10 | fill: currentColor;
11 | }
12 | `
13 |
14 | module.exports = iconElement
15 |
16 | function iconElement (iconName, opts) {
17 | opts = opts || {}
18 |
19 | assert.equal(typeof iconName, 'string', 'elements/icon: iconName should be type string')
20 | assert.equal(typeof opts, 'object', 'elements/icon: opts should be type object')
21 |
22 | var classNames = 'icon-' + iconName + ' ' + prefix
23 | if (opts.class) classNames += (' ' + opts.class)
24 |
25 | return html`
26 |
29 | `
30 | }
31 |
--------------------------------------------------------------------------------
/client/js/elements/import-button/index.js:
--------------------------------------------------------------------------------
1 | const html = require('choo/html')
2 |
3 | module.exports = function (emit) {
4 | const keydown = (e) => {
5 | if (e.keyCode === 13) {
6 | var link = e.target.value
7 | e.target.value = ''
8 | emit('archive:view', link)
9 | }
10 | }
11 | return html`
`
17 | }
18 |
--------------------------------------------------------------------------------
/client/js/elements/loader-icon/index.js:
--------------------------------------------------------------------------------
1 | const html = require('choo/html')
2 | const css = require('sheetify')
3 |
4 | var prefix = css`
5 | :host {
6 | width: 4rem;
7 | #p1, #p2 {
8 | animation:
9 | size cubic-bezier(0.165, 0.84, 0.44, 1) 1.8s,
10 | opacity cubic-bezier(0.3, 0.61, 0.355, 1) 1.8s;
11 | animation-iteration-count: infinite;
12 | transform-origin: 50% 50%;
13 | stroke-opacity: 1;
14 | }
15 | #p2 {
16 | animation-delay: -.9s;
17 | }
18 | }
19 | @keyframes size {
20 | 0% { transform: scale(0); }
21 | 100% { transform: scale(1); }
22 | }
23 | @keyframes opacity {
24 | 0% { stroke-opacity: 1; }
25 | 100% { stroke-opacity: 0; }
26 | }
27 | `
28 |
29 | module.exports = function () {
30 | return html`
31 |
38 | `
39 | }
40 |
--------------------------------------------------------------------------------
/client/js/elements/loading/index.js:
--------------------------------------------------------------------------------
1 | const html = require('choo/html')
2 | const loaderIcon = require('../loader-icon')
3 |
4 | module.exports = function () {
5 | return html`
6 |
7 |
8 | ${loaderIcon()}
9 |
10 |
Loading…
11 |
12 | `
13 | }
14 |
--------------------------------------------------------------------------------
/client/js/elements/message/index.js:
--------------------------------------------------------------------------------
1 | const html = require('choo/html')
2 | const css = require('sheetify')
3 |
4 | var messageStyles = css`
5 | :host {
6 | position: fixed;
7 | top: 0;
8 | padding: 1rem 1.5rem;
9 | width: 100%;
10 | text-align: center;
11 | z-index: 999;
12 | color: var(--color-white);
13 | &.success {
14 | background-color: var(--color-blue);
15 | }
16 | &.error {
17 | background-color: var(--color-red);
18 | }
19 | &.warning {
20 | background-color: var(--color-yellow-hover);
21 | }
22 | }
23 | `
24 | module.exports = function (props) {
25 | if (!props || !props.message) return html``
26 | return html`
27 | ${props.message}
28 |
`
29 | }
30 |
--------------------------------------------------------------------------------
/client/js/models/archive.js:
--------------------------------------------------------------------------------
1 | const xtend = require('xtend')
2 | const http = require('nets')
3 | const api = require('../api')()
4 |
5 | module.exports = function (state, emitter) {
6 | emitter.on('archive:update', function (data) {
7 | state.archive = xtend(state.archive, data)
8 | emitter.emit('render')
9 | })
10 |
11 | emitter.on('archive:directory', function (root) {
12 | var path = root === '.' ? '' : `/contents/${root}`
13 | emitter.emit('pushState', `/dat://${state.archive.key}${path}`)
14 | emitter.emit('archive:update', {root: root})
15 | })
16 |
17 | emitter.on('archive:health', function (data) {
18 | http({url: `/health/${state.archive.key}`, method: 'GET', json: true}, function (err, resp, json) {
19 | if (err) console.error(err)
20 | emitter.emit('archive:update', {health: json})
21 | })
22 | })
23 |
24 | emitter.on('archive:getMetadata', function (data) {
25 | if (!state.archive.key || state.archive.fetching) return
26 | state.archive.fetching = true
27 | http({url: `/metadata/${state.archive.key}?timeout=${data.timeout}`, method: 'GET', json: true}, function (err, resp, json) {
28 | json.fetching = false
29 | json.retries = state.archive.retries + 1
30 | if (err) return emitter.emit('archive:update', xtend({error: {message: err.message}}, json))
31 | if (json.error) return emitter.emit('archive:update', json)
32 | if (json.entries) json.error = null
33 | emitter.emit('archive:update', json)
34 | })
35 | })
36 | emitter.on('archive:delete', function (data) {
37 | api.dats.delete({id: data.id}, function (err, resp, json) {
38 | if (err) return emitter.emit('archive:update', {error: {message: err.message}})
39 | emitter.emit('pushState', '/profile')
40 | })
41 | })
42 | emitter.on('archive:view', function (link) {
43 | window.location.href = '/view?query=' + link
44 | })
45 | }
46 |
--------------------------------------------------------------------------------
/client/js/models/defaults.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | error: undefined,
3 | explore: {
4 | data: [],
5 | limit: 10,
6 | offset: 0
7 | },
8 | profile: {
9 | dats: [],
10 | id: null,
11 | email: null,
12 | name: null,
13 | username: null,
14 | description: null
15 | },
16 | township: {
17 | username: null,
18 | profile: {},
19 | key: null,
20 | email: null,
21 | whoami: false,
22 | token: null,
23 | register: 'hidden',
24 | sidePanel: 'hidden',
25 | passwordResetResponse: null,
26 | passwordResetConfirmResponse: null
27 | },
28 | preview: {
29 | isPanelOpen: false,
30 | isLoading: false,
31 | entry: null,
32 | error: false
33 | },
34 | message: {
35 | message: ''
36 | },
37 | archive: {
38 | health: null,
39 | id: null,
40 | updatedAt: false,
41 | key: null,
42 | retries: 0,
43 | peers: 0,
44 | error: null,
45 | root: '',
46 | metadata: {},
47 | fetching: false,
48 | entries: []
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/client/js/models/message.js:
--------------------------------------------------------------------------------
1 | module.exports = function (state, emitter) {
2 | emitter.on('DOMContentLoaded', function () {
3 | emitter.emit('message:clear')
4 | })
5 | emitter.on('message:update', function (data) {
6 | state.message = {message: data.message, type: data.type}
7 | emitter.emit('render')
8 | })
9 | emitter.on('message:clear', function (data) {
10 | state.message = {message: '', type: ''}
11 | emitter.emit('render')
12 | })
13 |
14 | emitter.on('message:new', function (data) {
15 | emitter.emit('message:update', data)
16 | setTimeout(function () {
17 | emitter.emit('message:clear')
18 | }, 4000)
19 | })
20 |
21 | emitter.on('message:success', function (message) {
22 | emitter.emit('message:new', {message: message, type: 'success'})
23 | })
24 | emitter.on('message:error', function (message) {
25 | emitter.emit('message:new', {message: message, type: 'error'})
26 | })
27 | emitter.on('message:warning', function (message) {
28 | emitter.emit('message:new', {message: message, type: 'warning'})
29 | })
30 | }
31 |
--------------------------------------------------------------------------------
/client/js/models/preview.js:
--------------------------------------------------------------------------------
1 | var xtend = require('xtend')
2 | var defaults = require('./defaults')
3 |
4 | module.exports = function (state, emitter) {
5 | emitter.on('preview:update', function (data) {
6 | state.preview = xtend(state.preview, data)
7 | emitter.emit('render')
8 | })
9 |
10 | emitter.on('preview:openPanel', function (data) {
11 | emitter.emit('preview:update', {isPanelOpen: true, error: null})
12 | })
13 |
14 | emitter.on('preview:file', function (data) {
15 | data.error = false
16 | emitter.emit('preview:update', data)
17 | emitter.emit('pushState', `/dat://${data.entry.archiveKey}/contents/${data.entry.name}`)
18 | emitter.emit('preview:openPanel')
19 | })
20 |
21 | emitter.on('preview:closePanel', function (data) {
22 | var arr = state.preview.entry.name.split('/')
23 | var path = arr.splice(0, arr.length - 1)
24 | emitter.emit('pushState', `/dat://${state.preview.entry.archiveKey}/contents/${path}`)
25 | emitter.emit('preview:update', defaults.preview)
26 | })
27 | }
28 |
--------------------------------------------------------------------------------
/client/js/models/profile.js:
--------------------------------------------------------------------------------
1 | const api = require('../api')()
2 | const xtend = require('xtend')
3 | const defaults = require('./defaults')
4 |
5 | module.exports = function (state, emitter) {
6 | emitter.on('profile:update', function (data) {
7 | state.profile = xtend(state.profile, data)
8 | emitter.emit('render')
9 | })
10 |
11 | emitter.on('profile:delete', function (data) {
12 | api.users.delete(data, function (err, resp, json) {
13 | if (err) return emitter.emit('township:update', {error: err.message})
14 | emitter.emit('profile:update', {profile: defaults.profile})
15 | emitter.emit('township:logout')
16 | window.location.href = '/'
17 | })
18 | })
19 |
20 | emitter.on('profile:edit', function (data) {
21 | api.users.update(data, function (err, resp, json) {
22 | if (err) return emitter.emit('township:update', {error: err.message})
23 | emitter.emit('township:update', {profile: data})
24 | emitter.emit('message:success', 'Profile edited successfully!')
25 | })
26 | })
27 |
28 | emitter.on('profile:get', function (data) {
29 | api.users.get(data, function (err, resp, json) {
30 | if (err) return emitter.emit('message:error', err.message)
31 | if (!json.length) return emitter.emit('message:error', 'User not found.')
32 | var user = json[0]
33 | api.dats.get({user_id: user.id}, function (err, resp, results) {
34 | if (err) return emitter.emit('message:error', err.message)
35 | user.dats = results
36 | emitter.emit('profile:update', user)
37 | })
38 | })
39 | })
40 | }
41 |
--------------------------------------------------------------------------------
/client/js/models/township.js:
--------------------------------------------------------------------------------
1 | const Api = require('../api')
2 | const xtend = require('xtend')
3 | const defaults = require('./defaults')
4 |
5 | module.exports = function (state, emitter) {
6 | emitter.on('DOMContentLoaded', function () {
7 | emitter.emit('township:whoami', {})
8 | })
9 | var api = Api()
10 |
11 | emitter.on('township:update', function (data) {
12 | state.township = xtend(state.township, data)
13 | state.township.whoami = true
14 | emitter.emit('render')
15 | })
16 |
17 | emitter.on('township:sidePanel', function (data) {
18 | state.township.sidePanel = state.township.sidePanel === 'hidden' ? '' : 'hidden'
19 | emitter.emit('render')
20 | })
21 |
22 | emitter.on('township:whoami', function (data) {
23 | const user = api.whoami()
24 | if (user.username) {
25 | api.users.get({username: user.username}, function (err, resp, results) {
26 | if (err && err.message === 'jwt expired') {
27 | emitter.emit('township:logout', data)
28 | return done()
29 | }
30 | if (!results.length) return done()
31 | var newState = user
32 | newState.profile = results[0]
33 | api.dats.get({user_id: user.id}, function (err, resp, results) {
34 | if (err && err.message === 'jwt expired') {
35 | emitter.emit('township:logout', data)
36 | return done()
37 | }
38 | newState.dats = results
39 | newState.whoami = true
40 | emitter.emit('township:update', newState)
41 | })
42 | })
43 | } else done()
44 | function done () {
45 | emitter.emit('township:update', {})
46 | }
47 | })
48 |
49 | emitter.on('township:logout', function (data) {
50 | api.logout(data, function (err, resp, data) {
51 | if (err) return emitter.emit('township:update', {error: err.message})
52 | emitter.emit('township:update', defaults.township)
53 | emitter.emit('message:success', 'Successfully Logged Out')
54 | })
55 | })
56 |
57 | emitter.on('township:login', function (data) {
58 | api.login(data, function (err, resp, data) {
59 | if (err) return emitter.emit('township:update', {error: err.message})
60 | data.login = 'hidden'
61 | emitter.emit('township:update', data)
62 | window.location.href = '/' + data.username
63 | })
64 | })
65 |
66 | emitter.on('township:register', function (data) {
67 | api.register(data, function (err, resp, data) {
68 | if (err) return emitter.emit('township:error', {error: err.message})
69 | data.register = 'hidden'
70 | emitter.emit('township:update', data)
71 | window.location.href = '/profile/edit'
72 | })
73 | })
74 |
75 | emitter.on('township:resetPassword', function (data) {
76 | var email = data || state.township.account.auth.basic.email
77 | api.users.resetPassword({email}, function (err, res, body) {
78 | if (err) return emitter.emit('township:error', err.message)
79 | emitter.emit('township:update', {passwordResetResponse: body.message})
80 | })
81 | })
82 |
83 | emitter.on('township:resetPasswordConfirmation', function (data) {
84 | api.users.resetPasswordConfirmation(data, function (err, res, body) {
85 | if (err) return emitter.emit('township:error', err.message)
86 | emitter.emit('township:update', {passwordResetConfirmResponse: body.message, passwordResetResponse: null})
87 | })
88 | })
89 |
90 | emitter.on('township:error', function (err) {
91 | state.township.error = err.error
92 | emitter.emit('render')
93 | })
94 | }
95 |
--------------------------------------------------------------------------------
/client/js/pages/archive/index.js:
--------------------------------------------------------------------------------
1 | const html = require('choo/html')
2 | const wrapper = require('../wrapper')
3 | const hyperdrive = require('../../components/hyperdrive')
4 | const hyperhealth = require('../../components/health')
5 | const copyButton = require('../../components/copy-button')
6 | const preview = require('../../components/preview')
7 | const fourohfour = require('../../elements/404')
8 | const error = require('../../elements/error')
9 | const hyperdriveStats = require('../../elements/hyperdrive-stats')
10 | const css = require('sheetify')
11 |
12 | var ARCHIVE_ERRORS = {
13 | 'Invalid key': {header: 'No dat here.', body: 'Looks like the key is invalid. Are you sure it\'s correct?'},
14 | '/ could not be found': {header: 'Looking for sources…', icon: 'loader', body: 'Is the address correct?'},
15 | 'timed out': {header: 'Looking for sources…', icon: 'loader', body: 'Is the address correct?'},
16 | 'Username not found.': {header: 'That user does not exist.'},
17 | 'Dat with that name not found.': {header: 'That user does not have a dat with that name.'},
18 | 'too many retries': {header: 'Could not find that dat.', body: 'Is the address correct? Try refreshing your browser.'}
19 | }
20 |
21 | const archivePage = (state, emit) => {
22 | var err = state.archive.error
23 | if (!module.parent && state.archive.retries < 3) emit('archive:getMetadata', {timeout: 3000})
24 | if (err) {
25 | emit(state.events.DOMTITLECHANGE, 'Error Loading Dat | datBase')
26 | if (err.message === 'Block not downloaded') err.message = 'timed out'
27 | if (!state.archive.entries.length) {
28 | if (state.archive.retries >= 3) err = {message: 'too many retries'}
29 | var props = ARCHIVE_ERRORS[err.message]
30 | if (props) {
31 | return html`
32 |
33 | ${fourohfour(props)}
34 |
35 | `
36 | }
37 | }
38 | }
39 | // var owner = (meta && state.township) && meta.username === state.township.username
40 | var meta = state.archive.metadata
41 | var title = meta && meta.title || meta.shortname || state.archive.key
42 | var description = meta && meta.description
43 |
44 | emit(state.events.DOMTITLECHANGE, `${title} | DatBase`)
45 |
46 | var styles = css`
47 | :host {
48 | .dat-header {
49 | padding-top: 1.25rem;
50 | padding-bottom: .75rem;
51 | border-bottom: 1px solid var(--color-neutral-10);
52 | background-color: var(--color-neutral-04);
53 | font-size: .8125rem;
54 |
55 | .dat-header-actions-wrapper {
56 | @media only screen and (min-width: 40rem) {
57 | float: right;
58 | margin-left: 2rem;
59 | }
60 | }
61 | }
62 |
63 | .title {
64 | word-wrap: break-word;
65 | font-size: 1.15rem;
66 | }
67 |
68 | .dat-header-action {
69 | display: inline-block;
70 | margin-left: 1rem;
71 | padding-top: .4rem;
72 | border: 0;
73 | font-size: .875rem;
74 | line-height: 1.25;
75 | background-color: transparent;
76 | color: var(--color-neutral-80);
77 | &:not([disabled]):hover, &:not([disabled]):focus {
78 | color: var(--color-neutral);
79 | }
80 | &:first-child {
81 | margin-left: 0;
82 | padding-left: 0;
83 | }
84 | &:disabled {
85 | opacity: 0.5;
86 | }
87 | svg,
88 | .btn__icon-img {
89 | width: 1rem;
90 | max-width: 1.25rem;
91 | max-height: 1rem;
92 | }
93 | }
94 | }
95 | `
96 |
97 | // TODO: add delete button with confirm modal.
98 | // const deleteButton = require('../../elements/delete-button')
99 | // function remove () {
100 | // emit('archive:delete', meta.id)
101 | // }
102 | // ${owner ? deleteButton(remove) : html``}
103 |
104 | return html`
105 |
106 |
123 |
124 |
125 | ${hyperdrive(state, emit)}
126 |
127 |
128 |
129 |
130 |
131 | ${(state.archive.downloadTotal || state.archive.uploadTotal)
132 | ? html`
133 | Total: ${hyperdriveStats({ downloaded: state.archive.downloadTotal, uploaded: state.archive.uploadTotal })}
134 |
`
135 | : ''}
136 |
137 |
138 | ${preview(state, emit)}
139 |
`
140 | }
141 |
142 | module.exports = wrapper(archivePage)
143 |
--------------------------------------------------------------------------------
/client/js/pages/archive/webrtc.js:
--------------------------------------------------------------------------------
1 | const html = require('choo/html')
2 | const importQueue = require('../../components/import-queue')
3 | const hyperdrive = require('../../components/hyperdrive')
4 | const copyButton = require('../../components/copy-button')
5 | const header = require('../../components/header')
6 | const preview = require('../../components/preview')
7 | const permissions = require('../../elements/permissions')
8 | const fourohfour = require('../../elements/404')
9 | const addFiles = require('../../elements/add-files')
10 | const error = require('../../elements/error')
11 | const hyperdriveStats = require('../../elements/hyperdrive-stats')
12 | const prettyBytes = require('pretty-bytes')
13 |
14 | const archivePage = (state, emit) => {
15 | // XXX: have an error enum?
16 | if (state.archive.error && state.archive.error.message === 'Invalid key') {
17 | var props = {
18 | header: 'No dat here.'
19 | }
20 | return html`
21 |
22 | ${header(state, emit)}
23 | ${fourohfour(props)}
24 |
25 | `
26 | }
27 | var archive = state.archive.instance
28 | var health = state.archive.health
29 | var swarm = state.archive.swarm // webrtc swarm
30 | var webrtcPeers = swarm ? swarm.connections : 0
31 | var sources = webrtcPeers + health.connected
32 | var bytes = archive && archive.content ? archive.content.bytes
33 | : health ? health.bytes : 0
34 | var size = prettyBytes(bytes)
35 | var downloadBtnDisabled = webrtcPeers > 0 ? '' : 'display:none;'
36 |
37 | return html`
38 |
39 | ${header(state, emit)}
40 |
73 |
74 |
75 |
${archive && archive.owner ? addFiles({ onfiles: (files) => emit('archive:importFiles', {files}) }) : ''}
76 | ${importQueue(state, emit)}
77 | ${hyperdrive(state, emit)}
78 |
79 |
80 |
81 |
82 |
83 | ${(state.archive.downloadTotal || state.archive.uploadTotal)
84 | ? html`
85 | Total: ${hyperdriveStats({ downloaded: state.archive.downloadTotal, uploaded: state.archive.uploadTotal })}
86 |
`
87 | : ''}
88 |
89 |
90 | ${preview(state, emit)}
91 |
`
92 | }
93 |
94 | module.exports = archivePage
95 |
--------------------------------------------------------------------------------
/client/js/pages/auth/delete-account.js:
--------------------------------------------------------------------------------
1 | const html = require('choo/html')
2 | const header = require('./../../components/header')
3 | const form = require('get-form-data')
4 |
5 | module.exports = (state, emit) => {
6 | function onsubmitConfirm (e) {
7 | e.preventDefault()
8 | var data = form(e.target)
9 | if (data.email !== state.township.email) emit('message:error', 'Incorrect email.')
10 | else emit('profile:delete', {id: state.township.profile.id})
11 | return false
12 | }
13 |
14 | return html`
15 |
16 | ${header(state, emit)}
17 |
18 |
Delete your account
19 |
20 |
This action CANNOT be undone.
21 |
This will permanently delete the ${state.township.profile.username} user account, profile information, and dats.
22 |
23 |
If you want to delete your account, please enter the email associated with your account below.
24 |
25 |
37 |
38 |
`
39 | }
40 |
--------------------------------------------------------------------------------
/client/js/pages/auth/edit-profile.js:
--------------------------------------------------------------------------------
1 | const html = require('choo/html')
2 | const css = require('sheetify')
3 | const form = require('get-form-data')
4 | const header = require('./../../components/header')
5 |
6 | var prefix = css`
7 | :host {
8 | textarea {
9 | min-height: 7rem;
10 | }
11 | }
12 | `
13 |
14 | module.exports = (state, emit) => {
15 | emit(state.events.DOMTITLECHANGE, 'Edit Profile | datBase')
16 |
17 | function onSubmit (e) {
18 | const data = form(e.target)
19 | data.id = state.township.profile.id
20 | emit('profile:edit', data)
21 | return false
22 | }
23 |
24 | var profile = state.township.profile
25 | return html`
26 |
27 | ${header(state, emit)}
28 |
29 |
Edit your Profile
30 |
70 |
71 |
`
72 | }
73 |
--------------------------------------------------------------------------------
/client/js/pages/auth/login.js:
--------------------------------------------------------------------------------
1 | const html = require('choo/html')
2 | const header = require('./../../components/header')
3 | const login = require('./../../components/auth/login')
4 |
5 | module.exports = (state, emit) => {
6 | emit(state.events.DOMTITLECHANGE, 'Login | datBase')
7 | return html`
8 |
9 | ${header(state, emit)}
10 | ${login(state, emit)}
11 |
`
12 | }
13 |
--------------------------------------------------------------------------------
/client/js/pages/auth/profile.js:
--------------------------------------------------------------------------------
1 | const html = require('choo/html')
2 | const fourohfour = require('../../elements/404')
3 | const css = require('sheetify')
4 | const gravatar = require('./../../elements/gravatar')
5 | const header = require('./../../components/header')
6 | const list = require('./../../components/list')
7 |
8 | var profileStyles = css`
9 | :host {
10 | min-height: calc(100vh - 4rem);
11 | }
12 | `
13 |
14 | var avatarStyles = css`
15 | :host {
16 | display: block;
17 | margin: 0 auto;
18 | height: auto;
19 | box-shadow: 0 0 1.5rem rgba(0,0,0,.15);
20 | }
21 | `
22 |
23 | function placeholder () {
24 | return html`
25 |
26 |
27 |
28 |
Welcome to Dat!
29 |
You have not published any dats.
30 |
31 |
32 |
33 |
34 | `
35 | }
36 |
37 | module.exports = (state, emit) => {
38 | if (state.error) {
39 | emit(state.events.DOMTITLECHANGE, 'Error | datBase')
40 | return html`
41 |
42 | ${header(state, emit)}
43 | ${fourohfour()}
44 |
45 | `
46 | }
47 | emit(state.events.DOMTITLECHANGE, `${state.profile.username} | datBase`)
48 |
49 | var username = state.profile.username
50 | var email = state.profile.email
51 | var name = state.profile.name
52 | var numDats = state.profile.dats.length
53 | var description = state.profile.description
54 | var pic = gravatar({email}, {}, avatarStyles)
55 | var owner = state.township.profile.username === state.profile.username
56 | var showPlaceholder = numDats === 0 && owner
57 |
58 | state.profile.dats.map(function (dat) {
59 | dat.shortname = `${state.profile.username}/${dat.name}`
60 | return dat
61 | })
62 | return html`
63 |
64 | ${header(state, emit)}
65 |
66 |
67 |
68 |
${name}
69 | ${username}
70 | ${pic}
71 |
72 |
73 | ${description}
74 |
75 |
76 |
77 |
78 | ${showPlaceholder ? placeholder() : html`
${username} has published ${numDats} dats
`}
79 |
80 | ${list(state.profile.dats, emit)}
81 |
82 |
83 |
84 | `
85 | }
86 |
--------------------------------------------------------------------------------
/client/js/pages/auth/register.js:
--------------------------------------------------------------------------------
1 | const html = require('choo/html')
2 | const register = require('./../../components/auth/register')
3 | const header = require('./../../components/header')
4 |
5 | module.exports = (state, emit) => {
6 | emit(state.events.DOMTITLECHANGE, 'Register | datBase')
7 | return html`
8 |
9 | ${header(state, emit)}
10 | ${register(state, emit)}
11 |
`
12 | }
13 |
--------------------------------------------------------------------------------
/client/js/pages/auth/reset-password.js:
--------------------------------------------------------------------------------
1 | const html = require('choo/html')
2 | const url = require('url')
3 | const querystring = require('querystring')
4 | const header = require('./../../components/header')
5 | const form = require('get-form-data')
6 |
7 | function body (state, emit) {
8 | emit(state.events.DOMTITLECHANGE, 'Reset Password | datBase')
9 | // const authenticated = state.township.username
10 | var query = {}
11 | if (!module.parent) query = url.parse(window.location.href).query
12 | const {accountKey, resetToken, email} = querystring.parse(query)
13 | function onsubmitConfirm (e) {
14 | e.preventDefault()
15 | var data = form(e.target)
16 | if (data.newPassword !== data.otherPassword) return emit('message:error', 'Passwords do not match.')
17 | data.accountKey = accountKey
18 | data.resetToken = resetToken
19 | data.email = email
20 | emit('township:resetPasswordConfirmation', data)
21 | }
22 |
23 | function onsubmitEmail (e) {
24 | e.preventDefault()
25 | var data = form(e.target)
26 | emit('township:resetPassword', data.email)
27 | }
28 |
29 | if (accountKey && resetToken) {
30 | if (state.township.passwordResetConfirmResponse) {
31 | return html`
32 |
33 |
34 |
Reset Your Password
35 |
36 | ${state.township.passwordResetConfirmResponse}
37 |
38 |
39 |
`
40 | } else {
41 | return html`
42 |
`
64 | }
65 | } else {
66 | if (state.township.passwordResetResponse) {
67 | return html`
68 |
69 |
70 |
Reset Your Password
71 |
72 | ${state.township.passwordResetResponse}
73 |
74 |
75 |
`
76 | } else {
77 | return html`
78 |
`
94 | }
95 | }
96 | }
97 |
98 | module.exports = (state, emit) => {
99 | return html`
100 |
101 | ${header(state, emit)}
102 | ${body(state, emit)}
103 |
`
104 | }
105 |
--------------------------------------------------------------------------------
/client/js/pages/download.js:
--------------------------------------------------------------------------------
1 | const html = require('choo/html')
2 | const wrapper = require('./wrapper')
3 |
4 | module.exports = wrapper(function (state, emit) {
5 | return html`
6 |
7 |
8 |
25 |
26 |
Using the Terminal
27 |
28 |
2. Use npm to install the dat command line tool.
29 |
$ npm install -g dat
30 |
31 |
3. Then download the archive.
32 |
$ dat clone ${state.archive.key}
33 |
34 | Having trouble installing dat? Try our troubleshooting checklist or ask questions in our public chatroom.
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | `
46 | })
47 |
--------------------------------------------------------------------------------
/client/js/pages/fourohfour.js:
--------------------------------------------------------------------------------
1 | const html = require('choo/html')
2 | const wrapper = require('./wrapper')
3 | const four = require('../elements/404')
4 |
5 | module.exports = wrapper(function fourohfour (state, emit) {
6 | emit(state.events.DOMTITLECHANGE, '404 - Page Not Found')
7 | return html`
8 |
9 | ${four()}
10 |
11 | `
12 | })
13 |
--------------------------------------------------------------------------------
/client/js/pages/landing/index.js:
--------------------------------------------------------------------------------
1 | const html = require('choo/html')
2 | const css = require('sheetify')
3 | const footer = require('../../elements/footer')
4 | const datIcon = require('../../elements/icon')
5 | const importButton = require('../../elements/import-button')
6 | const homeSection = require('../../elements/home-section')
7 | const panel = require('../../components/auth/user-panel')
8 | const loginButton = require('../../components/login-button')
9 | const message = require('../../elements/message')
10 |
11 | module.exports = function (state, emit) {
12 | const splash = css`
13 | :host {
14 | background-repeat: no-repeat;
15 | background-position: center -50px;
16 | background-size:100%;
17 |
18 | @media screen and (min-width: 30em) {
19 | /* ns - not small breakpoint from tachyons */
20 | background-position: center -200px;
21 | }
22 | }
23 | `
24 | const backgroundImageUrl = '/public/img/bg-landing-page.svg'
25 | const hex = datIcon('hexagon-up', {class: 'color-green-disabled'})
26 | return html`
27 |
28 |
29 | ${message(state.message)}
30 |
31 | ${importButton(emit)}
32 |
33 |
34 | ${loginButton(state, emit)}
35 | ${panel(state, emit)}
36 |
37 |
38 |
39 |
40 |
41 |
42 | ${hex}
43 | datBase
44 |
45 |
46 | Open data powered by Dat
47 | Future-friendly apps for your research data pipeline
48 |
49 |
50 |
51 | ${homeSection({
52 | 'title': 'Coming to a computer near you...',
53 | 'subtitle': 'Using the Dat Protocol, we are enhancing research & publishing tools with a data-first infrastructure. Coming soon.',
54 | 'sections': [
55 | {
56 | 'title': 'Try Dat',
57 | 'text': `
58 | See what it is like to use Dat today!
59 | The Try Dat workshop will walk you through sharing and downloading data.
60 | It showcases unique properties of the Dat Protocol.
61 |
62 |
63 | Try Dat Now!
64 |
65 | `
66 | },
67 | {
68 | 'title': 'Dat in the Lab',
69 | 'text': `
70 | A collaboration with the California Digitial Library, funded by the Moore Foundation.
71 | Learn more about our pilot to integrate Dat into research data workflows.
72 |
73 |
74 | Follow on our Blog
75 |
76 | `
77 | },
78 | {
79 | 'title': 'About Dat Project',
80 | 'text': `
81 | Dat is a nonprofit-backed community & open protocol for building apps of the future.
82 | We set out to improve access to public data and created the Dat Protocol along the way.
83 |
84 |
85 | Learn About Dat
86 |
87 | `
88 | }
89 | ]
90 | })}
91 |
92 |
93 |
94 |
95 |
96 |
113 |
114 |
115 |
116 |
117 |
118 | ${footer()}
119 |
120 | `
121 | }
122 |
--------------------------------------------------------------------------------
/client/js/pages/publish/index.js:
--------------------------------------------------------------------------------
1 | const html = require('choo/html')
2 | const wrapper = require('../wrapper')
3 |
4 | const publish = (state, emit) => {
5 | emit(state.events.DOMTITLECHANGE, 'Publish Data | datBase')
6 | return html`
7 |
8 |
9 |
10 |
11 |
Publish a Dat
12 |
Dats are not visible until their link is shared. You can publish a dataset
13 | here to make your dat public.
14 |
$ npm install -g dat
15 | $ cd /path/to/my/data
16 | $ dat login
17 | $ dat create
18 | $ dat publish
19 |
Install dat
20 | Learn more
21 |
22 |
23 |
`
24 | }
25 |
26 | module.exports = wrapper(publish)
27 |
--------------------------------------------------------------------------------
/client/js/pages/wrapper.js:
--------------------------------------------------------------------------------
1 | const html = require('choo/html')
2 | const header = require('../components/header')
3 | const footer = require('../elements/footer')
4 |
5 | module.exports = function (view, opts) {
6 | return function (state, emit) {
7 | return html`
8 |
9 | ${header(state, emit)}
10 | ${view(state, emit)}
11 | ${footer()}
12 |
13 | `
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/client/js/plugins/analytics.js:
--------------------------------------------------------------------------------
1 |
2 | module.exports = function (state, emitter) {
3 | var ga = null
4 |
5 | emitter.on(state.events.DOMCONTENTLOADED, trackView)
6 | emitter.on(state.events.NAVIGATE, trackView)
7 |
8 | function trackView () {
9 | const GAnalytics = require('ganalytics')
10 | if (!ga) ga = new GAnalytics('UA-49664853-1', { aid: 1, an: 'datbase' })
11 | ga.send('pageview')
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/client/scss/app.scss:
--------------------------------------------------------------------------------
1 | /**
2 | * Use awesome syntax of `node-sass-magic-importer` to import scss modules
3 | * See import `dat-design` rule below for example.
4 | * If there's no file path specified after module name, it resolves to the file
5 | * defined by the imported package.json's `style` property
6 | *
7 | * More magic importer features:
8 | * https://github.com/maoberlehner/node-sass-magic-importer
9 | */
10 |
11 | @import "~dat-design/scss/base.scss";
12 |
13 | @import "../bundle.css";
14 |
15 | @import "imports/fonts";
16 | @import "imports/variables";
17 | @import "imports/base";
18 | @import "imports/layout";
19 | @import "imports/grid";
20 | @import "imports/sections";
21 | @import "imports/horizontal-rule";
22 | @import "imports/block-create";
23 | @import "imports/status-bar";
24 | @import "imports/file-system";
25 | @import "imports/buttons";
26 | @import "imports/panel"; //
27 | @import "imports/error-page";
28 |
29 | :root {
30 | --site-header-height: 4rem;
31 | }
32 |
33 | h2, h3 {
34 | font-weight: 300;
35 | }
36 |
37 | pre {
38 | padding: .5em;
39 | font-size: .875rem;
40 | background-color: $color-neutral;
41 | color: $color-neutral-10;
42 | border: none;
43 | box-shadow: inset 0 1px 3px rgba(0,0,0,.5);
44 | }
45 |
46 | .hex-title-icon {
47 | width: 2.3rem;
48 | margin-right: 4px;
49 | margin-bottom: 2px;
50 | }
51 |
52 | // Comment out for footer, does it break anything else
53 | // ul {
54 | // list-style: initial;
55 | // padding-left: 1rem;
56 | // }
57 |
58 | .content-title {
59 | font-size: 2rem;
60 | font-weight: 600;
61 | margin-bottom: 0;
62 | }
63 | .content-subtitle {
64 | font-size: 1.25rem;
65 | color: $color-neutral-70;
66 | padding-bottom: 15px;
67 | }
68 |
69 | .bg-neutral {
70 | .content-subtitle,
71 | .content-card-subtitle {
72 | color: $color-neutral-40;
73 | }
74 | }
75 | .content-code-intro {
76 | text-align: left;
77 | margin-bottom: 60px;
78 | }
79 | .content-card {
80 | position: relative;
81 | background-color: $color-white;
82 | display: inline-block;
83 | vertical-align: top;
84 | margin-right: 30px;
85 | padding: 2rem;
86 | margin-bottom: 3rem;
87 | text-align: left;
88 | }
89 | .content-card-title {
90 | font-weight: 700;
91 | font-size: 1.5rem;
92 | line-height: 1.25;
93 | }
94 | .content-card-subtitle {
95 | color: $color-neutral-70;
96 | line-height: 1.75;
97 | }
98 | .copy-link,
99 | .open-desktop {
100 | display: inline-block;
101 | white-space: nowrap;
102 | svg {
103 | max-width: 1em;
104 | height: 1em;
105 | fill: currentColor;
106 | vertical-align: -9%;
107 | }
108 | }
109 |
110 | // Border colors
111 | // TODO: https://github.com/Kriesse/dat-colors/pull/5
112 |
113 | .b--white { border-color: #FFFFFF }
114 | .b--black { border-color: #070B14 }
115 | .b--neutral { border-color: #293648 }
116 | .b--neutral-90 { border-color: #394B5B }
117 | .b--neutral-80 { border-color: #505F6D }
118 | .b--neutral-70 { border-color: #65737F }
119 | .b--neutral-60 { border-color: #7C8792 }
120 | .b--neutral-50 { border-color: #919BA4 }
121 | .b--neutral-40 { border-color: #A7AFB6 }
122 | .b--neutral-30 { border-color: #C3C9CD }
123 | .b--neutral-20 { border-color: #D3D7DB }
124 | .b--neutral-14 { border-color: #E0E3E5 }
125 | .b--neutral-10 { border-color: #E9EBEC }
126 | .b--neutral-04 { border-color: #F6F7F8 }
127 | .b--green { border-color: #2ACA4B }
128 | .b--green-hover { border-color: #199E33 }
129 | .b--green-darker { border-color: #006607 }
130 | .b--green-disabled { border-color: #94E4A5 }
131 | .b--blue { border-color: #007FFF }
132 | .b--blue-hover { border-color: #0066CC }
133 | .b--blue-darker { border-color: #003E83 }
134 | .b--blue-disabled { border-color: #7FBFFF }
135 | .b--mint { border-color: #159F84 }
136 | .b--mint-hover { border-color: #0B856D }
137 | .b--mint-darker { border-color: #045943 }
138 | .b--mint-disabled { border-color: #8ACFC1 }
139 | .b--yellow { border-color: #F2CD02 }
140 | .b--yellow-hover { border-color: #C4A500 }
141 | .b--yellow-darker { border-color: #9F7D07 }
142 | .b--yellow-disabled { border-color: #FBF0B3 }
143 | .b--red { border-color: #D8524E }
144 | .b--red-hover { border-color: #B33C38 }
145 | .b--red-darker { border-color: #A52724 }
146 | .b--red-disabled { border-color: #EBA8A6 }
147 | .b--pink { border-color: #F9A5E4 }
148 |
--------------------------------------------------------------------------------
/client/scss/imports/_base.scss:
--------------------------------------------------------------------------------
1 | $padding-site-main: 3rem;
2 |
3 | html {
4 | font-size: 87.5%;
5 | @media screen and (min-width: $md1) {
6 | font-size: 100%;
7 | }
8 | }
9 |
10 | body, button, input, optgroup, select, textarea {
11 | font-family: 'Source Sans Pro', 'PT Sans', 'Calibri', sans-serif;
12 | }
13 |
14 | body {
15 | margin: 0;
16 | padding: 0;
17 | font-size: 100%;
18 | background-color: $color-white;
19 | color: $color-neutral;
20 | -webkit-font-smoothing: antialiased;
21 | }
22 |
23 | body {
24 | position: relative; // this is to position the bottom bar
25 | &.panel-open {
26 | overflow-y: hidden;
27 | }
28 | }
29 |
30 | .hidden {
31 | display: none !important;
32 | }
33 |
34 | a, button, input[type="submit"] {
35 | cursor: pointer;
36 | &:focus {
37 | outline: none;
38 | }
39 | &:disabled {
40 | cursor: default;
41 | }
42 | }
43 |
44 | a {
45 | &:hover, &:focus {
46 | outline: none;
47 | }
48 | }
49 |
50 | // Comment out for footer, does it break anything else
51 | // ul,
52 | // li {
53 | // @include list-plain;
54 | // }
55 |
56 | svg {
57 | fill: currentColor;
58 | }
59 |
60 | .site-main {
61 | position: relative;
62 | padding-top: $padding-site-main;
63 | padding-bottom: $padding-site-main;
64 | }
65 |
66 | #add-files {
67 | float: right;
68 | }
69 |
70 | .danger-block {
71 | background-color: $color-red;
72 | text-align: center;
73 | color: $color-white;
74 | h1, h2, h3, h4, h5 {
75 | color: $color-white;
76 | }
77 | a {
78 | text-decoration: underline;
79 | color: $color-white;
80 | }
81 | }
82 |
83 | .clipboard:hover {
84 | cursor: pointer;
85 | }
86 |
--------------------------------------------------------------------------------
/client/scss/imports/_block-create.scss:
--------------------------------------------------------------------------------
1 | .block-create {
2 | text-align: center;
3 | display: flex;
4 | justify-content: space-between;
5 | align-items: center;
6 | flex-direction: column;
7 | position: relative;
8 | .dat-input {
9 | border: 4px solid $color-green-hover;
10 | }
11 | }
12 |
13 | .block-create__icon {
14 | display: block;
15 | fill: $color-white;
16 | width: 4rem;
17 | height: 4rem;
18 | margin-bottom: 2rem;
19 | }
20 |
21 | .block-create--import {
22 | flex-direction: row;
23 | svg {
24 | margin-bottom: 0;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/client/scss/imports/_buttons.scss:
--------------------------------------------------------------------------------
1 | .btn__icon-wrapper {
2 | display: flex;
3 | flex-wrap: nowrap;
4 | flex-direction: row;
5 | }
6 |
7 | .btn__icon-img {
8 | opacity: .5;
9 | display: block;
10 | }
11 |
12 | .btn__icon-text {
13 | margin-left: .5em;
14 | white-space: nowrap;
15 | }
16 |
17 | .btn__reveal-text {
18 | .btn__icon-text {
19 | transition-property: width, margin, opacity;
20 | transition-duration: $transition-duration;
21 | transition-timing-function: $transition-timing-function;
22 | transition-timing-function: 1s;
23 | }
24 | &:not(:hover) {
25 | .btn__icon-text {
26 | width: 0;
27 | margin-left: 0;
28 | opacity: 0;
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/client/scss/imports/_error-page.scss:
--------------------------------------------------------------------------------
1 | .error-page {
2 | clear: both;
3 | min-height: calc(75vh - var(--site-header-height));
4 | display: flex;
5 | flex-direction: column;
6 | align-items: center;
7 | justify-content: center;
8 | }
9 |
--------------------------------------------------------------------------------
/client/scss/imports/_file-system.scss:
--------------------------------------------------------------------------------
1 | /* archive ui */
2 |
3 | #hyperdrive-ui {
4 | clear: both;
5 | }
6 |
7 | #file-widget,
8 | #import-queue {
9 | width: 100%;
10 | border-collapse: collapse;
11 | tr {
12 | font-size: 14px;
13 | color: $color-neutral;
14 | text-decoration: none;
15 | &:hover {
16 | background-color: $color-neutral-04;
17 | }
18 | }
19 | td {
20 | padding: 10px;
21 | border-bottom: 1px solid $color-neutral-20;
22 | }
23 | .name {
24 | max-width: 300px;
25 | white-space: nowrap;
26 | overflow: hidden;
27 | text-overflow: ellipsis;
28 | }
29 | .progress {
30 | min-width: 100px;
31 | }
32 | .size, .modified {
33 | text-align: right;
34 | }
35 | .directory .size {
36 | text-indent: -9999px; // hide file size until we can display a correct value
37 | overflow: hidden;
38 | text-align: left
39 | }
40 | }
41 |
42 | #file-widget {
43 | tr {
44 | cursor: pointer;
45 | }
46 | td:first-child {
47 | padding-left: 35px;
48 | }
49 | .file td:first-child {
50 | background-size: 15px;
51 | background-repeat: no-repeat;
52 | background-position: 10px 11px;
53 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEwAACxMBAJqcGAAABQZJREFUeJzt3M9rnEUcx/HPzPNskk3TTTZNVFQKIkIOEnr3pBQv2osiuKWpJ1Ei4kF7EPwnPIiIt5jmLPQkpV1wS089ay6ihJCy+dHEza/dZ/PMeEilYos2mZmdZ/f7eR1DmOfL5s3u7LOzAYiIiIhIHBVy8fqdOxeM0a9bixkoNQFlk5DXi2F9Z+uny5cufR97jtMKEsCtxt33APsVoGdDrF8kyyt/2Gpl7KN+jcBrAI1Go5ohXQDwts91i2x55XcAqm8j0L4WqtfvTXVs+jME/fEfsWq7tffd0o0bH8ae5KS8BFCv19OjtPujUnjVx3r9qT8j8BKASUe+0MBrPtbqb/0XgXMAN2/eG4cxX/oYZjD0VwTOAaiR7H1oXfExzODonwjcAzDqLR+DDJ7+iMB9D6DNBQ9zDKjiR+AcgDF4zscgg6vYETgHoLUu+RhksBU3Am83guj/FDMCBtBTxYuAAfRcsSJgAFEUJwIGEE0xImAAUcWPgAFEFzcCBlAI8SJgAIURJwIGUCi9j4ABONLK90PY2wgYgKNSmgZYtXcRMABH5aHhQCv3JgIG4Ojs6JmAq4ePgAE4OlMuY7g0FPAKYSNgAB48Wz0X+ArhImAAHoyOjGBqvBr4KmEiYACeTI1P4NzZ8cBX8R8BA/BoujqJ56emkeiQX4L2GwED8KwyOoaXX3gRz0xUMRRsc+gvghB3McTTSmOyMoHJygSO8hzZURd5bgBYn5dRsPgagNM3khlAYGmSIE2CvSSUXRfgS4BwDEA4BiBc9D1A62Av9ghRVUbHol4/egBrmxuxR4iqcj5uAHwJEI4BCMcAhGMAwkXfBM6cfyn2CKLxGUA4BiAcAxCOAQjHAIRjAMIxAOEYgHAMQLjodwJ5HoDnAWKPEBXPA1BUDEA4BiAcAxAu+iaQ5wHi4jOAcAxAOAYgHAMQjgEIxwCEYwDCMQDhGIBw0e8E8jwAzwPEHiEqngegqBiAcAxAOAYgXPRNIM8DxMVnAOEYgHAMQDgGIBwDEI4BCMcAhGMAwjEA4aLfCeR5AJ4HiD1CVDwPQFExAOEYgHAMQLjom0CeB4iLzwDCMQDhGIBwDEA4BiAcAxCOAQjHAIRjAMIxAOEYgHAMQDgGIBwDEI4BCOccgLXWxxx0Gh4ee+cAcpM7D0Gn083dH3vnANqdzHkIOp125v7YOwew1z50HoJOZ7+977yGcwCtgz0Ya5wHoZMxxqC1X4AAjDFma2fHeRA6mc3WNo5y902Aj7eB21u7f+Kg0/awFD2N/XYbD3Z3oYEHrmv5CGAZAFY3ml42JfTf2lmGtc3m8VtArX91Xc89AIvbwPFr0sr6fbQO3V+X6MlaB3tYaa4hN3/vuewt1zWdA9DK/gDAAMcRrG2sY3WzicOs47o0PdTOOljdaGJtcwPm0c0fo4BF17WV6wIA8M3i4hKUrv3750NpCaPlMobTEhKd+LmYABYWuTHIul3sdw6RdbuP/Y6xWPj0yuUPXK/l5athibWf59ZchNbT//x5dtRFtvv48OTImGZizTUfS3n5MOjjubn7Csm7BuBbgdCMOUSavDN/9eq6j+W8fRo4P1drJEa9CWNk/8uPkIxpIk0uflKr3fW1pNePg+fnag07PDRrYZfwcGNIXhhjsaCsmfX5xwc8bQKf5Nvr11/JrbqiLN4wwIzWmATPHzwVY0wO6G2t8Yu1uJ0navGzWu23ENf6C/l/AOrWkOSOAAAAAElFTkSuQmCC);
54 | }
55 | .directory td:first-child {
56 | background-size: 15px;
57 | background-repeat: no-repeat;
58 | background-position: 10px 11px;
59 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEwAACxMBAJqcGAAABTpJREFUeJzt2s9rHHUYx/HP88zO7OZX2cQareLBU7IVLyJ4EaJFS6ogIlKK+A948FDEi1DTFQUV1CJ49iBCFSse1DYR61EQqfbQNlFPFvzRkh9tkibZ3fk+HrRp2sb00GS+230+r1tmhvAJeXd3OhuAiIiIiIiIiIiIOp/c6IKhsYm7TO1eDeEOQLMiRt0sQQgGnUtKdubUgdGzsfe0s3UDGHrriz691P0iEPZB5P6iR22mYOFXEf2wZfkHv9WfuBh7T7u5LoChsWPPidh7Ah2MMWirGMI5GF6YrO/5PPaWdqJrv6iNjb+pIh932i8fAAQ6KKJHhg+OH4i9pZ2svgLUxiZegdgbMccUxcz2T9ZHD8Xe0Q4EAIbGjj6iwHGI3vCmsBMEhJYYHpqs7zkRe0tsAjOp1Sd+BPBA7DG0aQIszAKYNJHvYMlHk/XHf1nvQqmNHX0Mot8UPJCKZMEg8kkwfWmqvvuPtacUkKdj7aKCiAog+1TCyeH6xMjaU2qQh2PtoqLJdrH82NoIVMTujjmJiqYVsfDZ8OtHdwCAwuy22JOoaLJdmvIOAEjt4LjFnkNRBKgM642vow6lCPY8A3DMYLsYgGMCDJdij4itVOmNPWHTGQywgNBqwvLWBlfKgO8ARFCudtwHn1cJeROtSwtoXroIWH7taXX9FqDJLfEHTjdFkxRZXz+6B+9Z99XOdwBZOfaEwogoytVBZH0DVx13HUBa6Yk9oXBpTxVZb//q124D0LQMzbpiz4gi7e1H8t/P7jQAQbnP9xPwbNu/bwUuA8h6+6FZJfaMqLRURpJ1+Qsg66ki7a3GntEWkkoP3DwHEE1Q3rYdicMbv/+TZJXOD0BLGUpdfSh190HE3QvehjRJNw5A0wxJ2gUppRBVQG6NPxoWA6AJklIKaBJ7TvsSWT+ApNKDrLcKLfl5UOLV1QFIgkr1diTl7khzqGirAYgqKgM7oKXOfz5OV6zeFZX77+Qv3yEFgLS3iiT1/WDEKxVRpD18MOKVJpUe/v/YMeWTMd80SXnj55mKdvzTYNoA3/ydYwDOMQDnGIBzDMA5BuAcA3COATjHAJxjAM4xAOcYgHMMwDkG4BwDcI4BOMcAnGMAzjEA5xiAcwzAOQbgHANwjgE4xwCcYwDOMQDnGIBzDMA5BuAcA3COATjHAJxjAM4xAOcYgHMMwDkG4BwDcI4BOMcAnGMAzjEA5xiAcwzAOQbgHANwjgE4xwCcYwDOMQDnGIBzDMA5BuAcA3COATjHAJxjAM4xAOcUZrE3UCxm0JA3Y8+gSELegIbGSuwdFElorEBbKwuxd1AkzeVFaL6yhNBqxN5CBQvNFYTGEjRYaDYuTsfeQ4UyrMxPIwANBfSvvLGE5sJs7FVUkMbCLEJjGTD8qSL20+WDzcULsbfRFmsszqG5MAcAUNgJhclXqyfnp7Eydw4WQrSBtDUs5Fie+xvN+Zm1h7/UFvLDsLD6T7+1vICl87+jMT/Dm8MOEFoNNOansXT+LPLlxSvHgRlB+VMBgNrB8ZcBvL3eNxBNoKUUIglMihlNmyDkyFtNIOTrnjaz/ZP10UMlADiz88K7tVPbnoTIyHUXhhx5Y/1vQremYPbtFL5/H7j8YdDevbkAzwSEn6MuoyKcqKD8LOr1AKz5NPB0fXQmNxuB4Ui8bbS17LBYNnKy/ujc5SPrvqvvHJt4yiQ/AOiDxY2jrWLAD2JSP1Pf/fW15za8rbvvtYlaHmyXAMMABoIh2bKVtGkEaIlgFrDTQe341Kt7pmJvIiIiIqI28g8P/1ywWiSczgAAAABJRU5ErkJggg==)
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/client/scss/imports/_fonts.scss:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: 'Source Code Pro';
3 | font-style: normal;
4 | font-weight: 400;
5 | src: url(/public/fonts/Source+Code+Pro_400_normal.eot); /* {{embedded-opentype-gf-url}} */
6 | src: local('☺'),
7 | url(/public/fonts/Source+Code+Pro_400_normal.eot?#iefix) format('embedded-opentype'), /* {{embedded-opentype-gf-url}} */
8 | url(/public/fonts/Source+Code+Pro_400_normal.woff) format('woff'), /* http://fonts.gstatic.com/s/sourcecodepro/v6/mrl8jkM18OlOQN8JLgasDxM0YzuT7MdOe03otPbuUS0.woff */
9 | url(/public/fonts/Source+Code+Pro_400_normal.ttf) format('truetype'), /* http://fonts.gstatic.com/s/sourcecodepro/v6/mrl8jkM18OlOQN8JLgasD0Y6Fu39Tt9XkmtSosaMoEA.ttf */
10 | url(/public/fonts/Source+Code+Pro_400_normal.svg#Source+Code+Pro_400_normal) format('svg'); /* http://fonts.gstatic.com/l/font?kit=mrl8jkM18OlOQN8JLgasDwDX0QHsyBtvFGhCuXSMYhM&skey=d94d317bc78fd84e&v=v6#SourceCodePro */
11 | }
12 | @font-face {
13 | font-family: 'Source Code Pro';
14 | font-style: normal;
15 | font-weight: 500;
16 | src: url(/public/fonts/Source+Code+Pro_500_normal.eot); /* {{embedded-opentype-gf-url}} */
17 | src: local('☺'),
18 | url(/public/fonts/Source+Code+Pro_500_normal.eot?#iefix) format('embedded-opentype'), /* {{embedded-opentype-gf-url}} */
19 | url(/public/fonts/Source+Code+Pro_500_normal.woff) format('woff'), /* http://fonts.gstatic.com/s/sourcecodepro/v6/leqv3v-yTsJNC7nFznSMqe_puumMoxhiakHfBfLgHWs.woff */
20 | url(/public/fonts/Source+Code+Pro_500_normal.ttf) format('truetype'), /* http://fonts.gstatic.com/s/sourcecodepro/v6/leqv3v-yTsJNC7nFznSMqSupjbsDx8xYeOMUfKCxdHk.ttf */
21 | url(/public/fonts/Source+Code+Pro_500_normal.svg#Source+Code+Pro_500_normal) format('svg'); /* {{svg-gf-url}} */
22 | }
23 | @font-face {
24 | font-family: 'Source Sans Pro';
25 | font-style: normal;
26 | font-weight: 400;
27 | src: url(/public/fonts/Source+Sans+Pro_400_normal.eot); /* {{embedded-opentype-gf-url}} */
28 | src: local('☺'),
29 | url(/public/fonts/Source+Sans+Pro_400_normal.eot?#iefix) format('embedded-opentype'), /* {{embedded-opentype-gf-url}} */
30 | url(/public/fonts/Source+Sans+Pro_400_normal.woff) format('woff'), /* http://fonts.gstatic.com/s/sourcesanspro/v10/ODelI1aHBYDBqgeIAH2zlBM0YzuT7MdOe03otPbuUS0.woff */
31 | url(/public/fonts/Source+Sans+Pro_400_normal.ttf) format('truetype'), /* http://fonts.gstatic.com/s/sourcesanspro/v10/ODelI1aHBYDBqgeIAH2zlEY6Fu39Tt9XkmtSosaMoEA.ttf */
32 | url(/public/fonts/Source+Sans+Pro_400_normal.svg#Source+Sans+Pro_400_normal) format('svg'); /* http://fonts.gstatic.com/l/font?kit=ODelI1aHBYDBqgeIAH2zlADX0QHsyBtvFGhCuXSMYhM&skey=1e026b1c27170b9b&v=v10#SourceSansPro */
33 | }
34 | @font-face {
35 | font-family: 'Source Sans Pro';
36 | font-style: normal;
37 | font-weight: 700;
38 | src: url(/public/fonts/Source+Sans+Pro_700_normal.eot); /* {{embedded-opentype-gf-url}} */
39 | src: local('☺'),
40 | url(/public/fonts/Source+Sans+Pro_700_normal.eot?#iefix) format('embedded-opentype'), /* {{embedded-opentype-gf-url}} */
41 | url(/public/fonts/Source+Sans+Pro_700_normal.woff) format('woff'), /* http://fonts.gstatic.com/s/sourcesanspro/v10/toadOcfmlt9b38dHJxOBGFkQc6VGVFSmCnC_l7QZG60.woff */
42 | url(/public/fonts/Source+Sans+Pro_700_normal.ttf) format('truetype'), /* http://fonts.gstatic.com/s/sourcesanspro/v10/toadOcfmlt9b38dHJxOBGLlcMrNrsnL9dgADnXgYJjs.ttf */
43 | url(/public/fonts/Source+Sans+Pro_700_normal.svg#Source+Sans+Pro_700_normal) format('svg'); /* {{svg-gf-url}} */
44 | }
45 |
--------------------------------------------------------------------------------
/client/scss/imports/_horizontal-rule.scss:
--------------------------------------------------------------------------------
1 | .horizontal-rule {
2 | &:after {
3 | content: '';
4 | display: block;
5 | width: 4rem;
6 | height: 5px;
7 | margin-top: 1.25rem;
8 | margin-bottom: 1rem;
9 | background-color: $color-pink;
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/client/scss/imports/_layout.scss:
--------------------------------------------------------------------------------
1 | .container {
2 | padding-right: 1rem;
3 | padding-left: 1rem;
4 | margin-right: auto;
5 | margin-left: auto;
6 | max-width: 64rem; // 1024px
7 | @media screen and (min-width: $sm) {
8 | padding-right: 1.5rem;
9 | padding-left: 1.5rem;
10 | }
11 | @media screen and (min-width: $md1) {
12 | padding-right: 2rem;
13 | padding-left: 2rem;
14 | }
15 | }
16 |
17 | .container--top-bar {
18 | width: auto;
19 | max-width: 100%;
20 | padding-right: .5rem;
21 | padding-left: .5rem;
22 | @media screen and (min-width: $sm) {
23 | padding-right: .5rem;
24 | padding-left: .5rem;
25 | }
26 | @media screen and (min-width: $md1) {
27 | padding-right: 1rem;
28 | padding-left: 1rem;
29 | }
30 | @media screen and (min-width: $lg) {
31 | padding-right: 2rem;
32 | padding-left: 2rem;
33 | }
34 | }
35 |
36 | .container--narrow {
37 | max-width: 48rem; // 768px
38 | }
39 |
40 | .container--wide {
41 | max-width: 80rem; // 1280px
42 | }
43 |
--------------------------------------------------------------------------------
/client/scss/imports/_panel.scss:
--------------------------------------------------------------------------------
1 | .panel {
2 | position: fixed;
3 | z-index:1000;
4 | top: 0;
5 | right: 0;
6 | width: 100%;
7 | height: 100%;
8 | overflow-y:scroll;
9 | transition: opacity, transform;
10 | transition-duration: 250ms;
11 | transition-timing-function: $transition-timing-function;
12 | opacity: 0;
13 | transform: scale(.8);
14 | pointer-events: none;
15 | &.open {
16 | opacity: 1;
17 | transform: none;
18 | pointer-events: all;
19 | }
20 | }
21 |
22 | .panel-header {
23 | position: relative;
24 | z-index:1;
25 | top:0;
26 | width: 100%;
27 | height: var(--site-header-height);
28 | border-bottom: 1px solid $color-neutral-10;
29 | background-color: $color-neutral-04;
30 | font-size: .8125rem;
31 | display: flex;
32 | flex-direction: row;
33 | align-items: center;
34 | }
35 |
36 | .panel-header__close-button {
37 | width: var(--site-header-height);
38 | height: var(--site-header-height);
39 | flex-basis: var(--site-header-height);
40 | flex-shrink: 0;
41 | border: none;
42 | margin-right: 1rem;
43 | position: relative;
44 | overflow: hidden;
45 | background-color: $color-green;
46 | color: $color-white;
47 | &:hover, &:focus {
48 | background-color: $color-green-hover;
49 | color: $color-white;
50 | }
51 | &:before,
52 | &:after {
53 | content: '';
54 | position: absolute;
55 | top: calc(50% - 2px);
56 | left: calc(50% - #{var(--site-header-height)} / 4);
57 | width: var(--site-header-height) / 2;
58 | height: 2px;
59 | transform: rotate(-45deg);
60 | background-color: currentColor;
61 | }
62 | &:after {
63 | transform: rotate(45deg);
64 | }
65 | }
66 |
67 | .panel-header__title-group {
68 | flex-grow: 1;
69 | .dat-details {
70 | padding-top: 0;
71 | padding-bottom: 0;
72 | }
73 | }
74 |
75 | .panel-header__action-group {
76 | padding-right: 1rem;
77 | }
78 |
79 | .panel-title {
80 | max-width: calc(100vw - 25rem);
81 | font-size: 1.375rem;
82 | margin-right: 1rem;
83 | }
84 |
--------------------------------------------------------------------------------
/client/scss/imports/_sections.scss:
--------------------------------------------------------------------------------
1 | .section {
2 | padding-top: 2rem;
3 | padding-bottom: 2rem;
4 | @media screen and (min-width: $md1) {
5 | padding-top: 3rem;
6 | padding-bottom: 3rem;
7 | }
8 | @media screen and (min-width: $lg) {
9 | padding-top: 4rem;
10 | padding-bottom: 4rem;
11 | }
12 | }
13 |
14 | .bg-splash {
15 | background-image: url('/public/img/bg-landing-page.svg');
16 | background-repeat: no-repeat;
17 | background-position: 50% 46%;
18 | background-size: 240%, 100%;
19 | @media screen and (min-width: $md1) {
20 | background-position: 50% 64%;
21 | background-size: 160%, 100%;
22 | }
23 | @media screen and (min-width: $lg) {
24 | background-position: 50% 64%;
25 | background-size: 95%, 100%;
26 | }
27 | @media screen and (min-width: $xxl) {
28 | background-position: 50% 64%;
29 | background-size: 90%, 100%;
30 | }
31 | @media screen and (min-width: $xxxl) {
32 | background-position: 50% 64%;
33 | background-size: 90%, 100%;
34 | }
35 | }
36 |
37 | .bg-splash-02 {
38 | background-image: url('/public/img/bg-landing-page.svg');
39 | background-repeat: no-repeat;
40 | background-position: 50vw 46%;
41 | background-size: 200%;
42 | @media screen and (min-width: $md1) {
43 | background-size: 100%;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/client/scss/imports/_status-bar.scss:
--------------------------------------------------------------------------------
1 | .status-bar {
2 | position: fixed;
3 | bottom: 0;
4 | width: 100%;
5 | padding-top: .25rem;
6 | padding-bottom: .25rem;
7 | border-top: 1px solid $color-neutral-10;
8 | background-color: $color-neutral-04;
9 | font-size: .6875rem;
10 | text-align: center;
11 | .container {
12 | overflow: hidden;
13 | }
14 | }
15 | .status-bar-status {
16 | float: left;
17 | }
18 | .status-bar-stats {
19 | float: right;
20 | }
21 |
--------------------------------------------------------------------------------
/client/scss/imports/_variables.scss:
--------------------------------------------------------------------------------
1 | // Breakpoint definitions
2 | $xs: 20rem; // 320px;
3 | $sm: 30rem; // 480px
4 | $md1: 40rem; // 640px
5 | $md2: 48rem; // 768px;
6 | $lg: 64rem; // 1024px
7 | $xl: 80rem; // 1280px;
8 | $xxl: 90rem; // 1440px
9 | $xxxl: 110rem; // 1600px
10 |
11 | // Transitions
12 | $transition-duration: .05s;
13 | $transition-timing-function: ease-out;
14 |
--------------------------------------------------------------------------------
/config/config.production.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 |
3 | module.exports = {
4 | data: '',
5 | mixpanel: process.env.MIXPANEL || path.join('secrets', 'mixpanel'),
6 | township: {
7 | secret: process.env.TOWNSHIP_SECRET,
8 | db: 'datland-township.db',
9 | publicKey: path.join('secrets', 'ecdsa-p521-public.pem'),
10 | privateKey: path.join('secrets', 'ecdsa-p521-private.pem'),
11 | algorithm: 'ES512'
12 | },
13 | email: {
14 | from: 'noreply@datproject.org',
15 | smtpConfig: {
16 | host: 'smtp.postmarkapp.com',
17 | port: 2525,
18 | auth: {
19 | user: process.env.POSTMARK_KEY,
20 | pass: process.env.POSTMARK_KEY
21 | }
22 | }
23 | },
24 | db: {
25 | dialect: 'sqlite3',
26 | connection: {
27 | filename: 'datland-production.db'
28 | },
29 | useNullAsDefault: true
30 | },
31 | whitelist: false
32 | }
33 |
--------------------------------------------------------------------------------
/config/config.test.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 |
3 | module.exports = {
4 | data: path.join(__dirname, '..', 'tests'),
5 | db: {
6 | dialect: 'sqlite3',
7 | connection: {
8 | filename: 'test-api.sqlite'
9 | },
10 | useNullAsDefault: true
11 | },
12 | whitelist: false,
13 | port: process.env.PORT || 8888
14 | }
15 |
--------------------------------------------------------------------------------
/config/default.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | data: 'data',
3 | mixpanel: process.env.MIXPANEL || 'notakey',
4 | township: {
5 | secret: process.env.TOWNSHIP_SECRET || 'very very not secret',
6 | db: 'township.db'
7 | },
8 | email: {
9 | from: 'hi@example.com',
10 | smtpConfig: undefined
11 | },
12 | db: {
13 | dialect: 'sqlite3',
14 | connection: {
15 | filename: 'sqlite.db'
16 | },
17 | useNullAsDefault: true
18 | },
19 | whitelist: false,
20 | archiver: 'archiver'
21 | }
22 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '2'
2 | services:
3 | datbase:
4 | build: .
5 | image: datproject/datbase:${TAG}
6 | ports:
7 | - 25:25
8 | expose:
9 | - 80
10 | environment:
11 | - NODE_ENV=${NODE_ENV}
12 | volumes:
13 | - ./data:/data
14 | newdat:
15 | build: .
16 | image: datproject/datbase:${TAG}
17 | command: sh -c 'npm i dat-node && node tests/new-dat.js'
18 | volumes:
19 | - ./tests:/usr/src/app/tests
20 | chromedriver:
21 | image: blueimp/chromedriver
22 | environment:
23 | - VNC_ENABLED=true
24 | - EXPOSE_X11=true
25 | ports:
26 | - 5900:5900
27 | nightwatch:
28 | image: blueimp/nightwatch:0.9
29 | depends_on:
30 | - chromedriver
31 | - datbase
32 | - newdat
33 | environment:
34 | - PORT=80
35 | - TEST_SERVER=http://datbase
36 | - WAIT_FOR_HOSTS=datbase:80 chromedriver:4444 chromedriver:6060
37 | volumes:
38 | - ./tests:/home/node
39 |
--------------------------------------------------------------------------------
/docs/api.md:
--------------------------------------------------------------------------------
1 | ## API
2 |
3 | ##### ```GET /api/v1/:model```
4 |
5 | Can pass query parameters like `?username='martha'` to filter results.
6 |
7 | Additional options:
8 |
9 | * `limit`: 100 (default)
10 | * `offset`: 0 (default)
11 |
12 | ##### ```POST /api/v1/:model```
13 |
14 | Success returns model with `id` field added.
15 |
16 | ##### ```PUT /api/v1/:model?id=```
17 |
18 | `id` required
19 |
20 | Success returns number of rows modified.
21 | ```
22 | {updated: 1}
23 | ```
24 |
25 | ##### ```DELETE /api/v1/:model?id=```
26 |
27 | `id` required
28 |
29 | Success returns number of rows deleted.
30 | ```
31 | {deleted: 1}
32 | ```
33 |
34 | ##### users model: ```/api/v1/users```
35 |
36 | - `id` (required)
37 | - `email` (required)
38 | - `username`
39 | - `description`
40 | - `created_at`
41 | - `updated_at`
42 |
43 | ##### dats model: ```/api/v1/dats```
44 |
45 | - `id`
46 | - `user_id`
47 | - `name`
48 | - `title`
49 | - `hash`
50 | - `description`
51 | - `created_at`
52 | - `updated_at`
53 |
54 | ##### ```/api/v1/register```
55 | ##### ```/api/v1/login```
56 |
--------------------------------------------------------------------------------
/docs/contributing.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | Install dependencies.
4 |
5 | ```
6 | npm install
7 | ```
8 |
9 | Initialize the database. You only have to do this once:
10 |
11 | ```
12 | node server/database/init.js
13 | ```
14 |
15 | Watch assets and start server in one command:
16 |
17 | ```
18 | npm start
19 | ```
20 |
21 | ## Creating example user and dat
22 |
23 | Run the following command to create a user with the given email address. The
24 | user will have the password `dogsandcats.`
25 |
26 | ```
27 | node scripts/user-and-dat.js
28 | ```
29 |
30 | To create just dats for a given user that already exists, use
31 | ```
32 | node scripts/add-dats-for-user.js
33 | ```
34 |
35 | ## Running end-to-end tests
36 |
37 | Docker and `docker-compose` are required.
38 |
39 | ```
40 | docker-compose build
41 | docker-compose up -d newdat && sleep 10
42 | docker-compose run --rm nightwatch
43 | ```
44 |
45 | You may also see the test browser in action with [VNC](https://github.com/blueimp/nightwatch).
46 |
--------------------------------------------------------------------------------
/docs/deployment.md:
--------------------------------------------------------------------------------
1 | ## Build for production
2 | ```
3 | npm run build
4 | npm run minify
5 | npm run version
6 | ```
7 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "datbase.org",
3 | "version": "1.0.0",
4 | "description": "Open data powered by Dat",
5 | "license": "MIT",
6 | "repository": {
7 | "type": "git",
8 | "url": "https://github.com/datproject/datbase.git"
9 | },
10 | "bugs": {
11 | "url": "https://github.com/datproject/datbase/issues"
12 | },
13 | "main": "server/index.js",
14 | "bin": "server/cli.js",
15 | "browser": {
16 | "server/**/**/*": false
17 | },
18 | "browserify": {
19 | "transform": [
20 | [
21 | "sheetify/transform",
22 | {
23 | "use": "sheetify-nested"
24 | }
25 | ]
26 | ]
27 | },
28 | "scripts": {
29 | "server": "node ./server/cli",
30 | "lint": "standard",
31 | "database": "dat-registry-api",
32 | "clean-test": "rm -rf tests/archiver && rm -rf tests/township.db && rm -rf tests/*.sqlite",
33 | "test": "./tests/test.sh",
34 | "start": "npm run watch-js & npm run watch-css & npm run watch-server",
35 | "render": "node bin/render-static.js",
36 | "new-dat": "node tests/new-dat",
37 | "watch-render": "nodemon --watch public -i public/rendered -e html bin/render-static",
38 | "watch-css": "nodemon --watch client -e scss -x \"npm run build-css\"",
39 | "watch-js": "mkdir -p public/js && watchify --fast --verbose -t brfs -p [ css-extract -o bundle.css ] client/js/app.js -o public/js/app.js",
40 | "watch-server": "nodemon server/cli --watch server --watch client",
41 | "build": "npm run build-js && npm run build-css",
42 | "build-css": "node-sass --importer node_modules/node-sass-magic-importer client/scss/app.scss public/css/app.css",
43 | "build-js": "mkdir -p public/js && browserify -t brfs -p [ css-extract -o bundle.css ] client/js/app.js > public/js/app.js",
44 | "build-js-prod": "mkdir -p public/js && NODE_ENV=production browserify -t brfs -t envify -p [ css-extract -o bundle.css ] client/js/app.js > public/js/app.js",
45 | "minify": "npm run minify-css && npm run minify-js",
46 | "minify-css": "minify --output public/css/app.min.css public/css/app.css",
47 | "minify-js": "./node_modules/uglify-es/bin/uglifyjs public/js/app.js -o public/js/app.min.js",
48 | "version": "./bin/version-assets.js"
49 | },
50 | "standard": {
51 | "ignore": [
52 | "public/**"
53 | ]
54 | },
55 | "devDependencies": {
56 | "dat-node": "^3.5.0",
57 | "marked": "^0.3.6",
58 | "nodemon": "^1.10.0",
59 | "request": "^2.79.0",
60 | "rimraf": "^2.5.4",
61 | "standard": "^7.1.2",
62 | "tap-spec": "^4.1.1",
63 | "tape": "^4.6.3",
64 | "watchify": "~3.6.0"
65 | },
66 | "dependencies": {
67 | "babel-preset-es2015": "^6.18.0",
68 | "body-parser": "^1.15.2",
69 | "bole": "^3.0.1",
70 | "brfs": "^1.4.3",
71 | "browserify": "^13.0.0",
72 | "choo": "^6.0.0",
73 | "choo-log": "^7.2.1",
74 | "choo-persist": "^3.0.2",
75 | "clipboard": "^1.6.1",
76 | "collect-stream": "^1.1.1",
77 | "compression": "^1.6.2",
78 | "css-extract": "^1.2.0",
79 | "dat-colors": "^3.4.0",
80 | "dat-design": "^5.0.0",
81 | "dat-icons": "^2.5.0",
82 | "dat-registry": "^3.0.2",
83 | "dat-registry-api": "^6.0.8",
84 | "dat-swarm-defaults": "^1.0.0",
85 | "debug": "^2.6.8",
86 | "discovery-swarm": "^4.2.0",
87 | "drag-drop": "^2.11.0",
88 | "envify": "^4.0.0",
89 | "es6-promise": "^3.3.1",
90 | "express": "^4.14.0",
91 | "express-simple-redirect": "^1.0.1",
92 | "extend": "^3.0.0",
93 | "file-saver": "^1.3.2",
94 | "from2": "^2.3.0",
95 | "ganalytics": "^2.0.1",
96 | "get-form-data": "^1.2.5",
97 | "gravatar": "^1.6.0",
98 | "mime": "^1.3.4",
99 | "minifier": "^0.8.0",
100 | "mixpanel": "^0.7.0",
101 | "nets": "^3.2.0",
102 | "node-sass": "^3.8.0",
103 | "node-sass-magic-importer": "^0.1.4",
104 | "node-version-assets": "^1.1.0",
105 | "pretty-bytes": "^3.0.1",
106 | "pump": "^1.0.2",
107 | "range-parser": "^1.2.0",
108 | "relative-date": "^1.1.3",
109 | "render-data": "^2.2.0",
110 | "serialize-javascript": "^1.3.0",
111 | "sheetify": "^6.0.1",
112 | "sheetify-nested": "^1.0.2",
113 | "tachyons": "^4.8.1",
114 | "township-client": "^1.2.2",
115 | "uglify-es": "github:mishoo/UglifyJS2#harmony",
116 | "uparams": "^1.3.1",
117 | "xhr": "^2.4.0",
118 | "xtend": "^4.0.1",
119 | "yo-fs": "^3.0.1"
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/public/dat-paper.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dat-ecosystem-archive/datBase/b8caec355683c537444d25c4e48fb8b6fcf3e0c9/public/dat-paper.pdf
--------------------------------------------------------------------------------
/public/fonts/Source+Code+Pro_400_normal.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dat-ecosystem-archive/datBase/b8caec355683c537444d25c4e48fb8b6fcf3e0c9/public/fonts/Source+Code+Pro_400_normal.ttf
--------------------------------------------------------------------------------
/public/fonts/Source+Code+Pro_400_normal.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dat-ecosystem-archive/datBase/b8caec355683c537444d25c4e48fb8b6fcf3e0c9/public/fonts/Source+Code+Pro_400_normal.woff
--------------------------------------------------------------------------------
/public/fonts/Source+Code+Pro_500_normal.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dat-ecosystem-archive/datBase/b8caec355683c537444d25c4e48fb8b6fcf3e0c9/public/fonts/Source+Code+Pro_500_normal.ttf
--------------------------------------------------------------------------------
/public/fonts/Source+Code+Pro_500_normal.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dat-ecosystem-archive/datBase/b8caec355683c537444d25c4e48fb8b6fcf3e0c9/public/fonts/Source+Code+Pro_500_normal.woff
--------------------------------------------------------------------------------
/public/fonts/Source+Sans+Pro_400_normal.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dat-ecosystem-archive/datBase/b8caec355683c537444d25c4e48fb8b6fcf3e0c9/public/fonts/Source+Sans+Pro_400_normal.ttf
--------------------------------------------------------------------------------
/public/fonts/Source+Sans+Pro_400_normal.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dat-ecosystem-archive/datBase/b8caec355683c537444d25c4e48fb8b6fcf3e0c9/public/fonts/Source+Sans+Pro_400_normal.woff
--------------------------------------------------------------------------------
/public/fonts/Source+Sans+Pro_700_normal.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dat-ecosystem-archive/datBase/b8caec355683c537444d25c4e48fb8b6fcf3e0c9/public/fonts/Source+Sans+Pro_700_normal.ttf
--------------------------------------------------------------------------------
/public/fonts/Source+Sans+Pro_700_normal.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dat-ecosystem-archive/datBase/b8caec355683c537444d25c4e48fb8b6fcf3e0c9/public/fonts/Source+Sans+Pro_700_normal.woff
--------------------------------------------------------------------------------
/public/img/bg-landing-page.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/img/blog/preview-file.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dat-ecosystem-archive/datBase/b8caec355683c537444d25c4e48fb8b6fcf3e0c9/public/img/blog/preview-file.png
--------------------------------------------------------------------------------
/public/img/blog/preview-filelist.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dat-ecosystem-archive/datBase/b8caec355683c537444d25c4e48fb8b6fcf3e0c9/public/img/blog/preview-filelist.png
--------------------------------------------------------------------------------
/public/img/clipboard.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/public/img/codeforscience.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dat-ecosystem-archive/datBase/b8caec355683c537444d25c4e48fb8b6fcf3e0c9/public/img/codeforscience.png
--------------------------------------------------------------------------------
/public/img/create-new-dat.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/public/img/dat-data-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dat-ecosystem-archive/datBase/b8caec355683c537444d25c4e48fb8b6fcf3e0c9/public/img/dat-data-logo.png
--------------------------------------------------------------------------------
/public/img/dat-data-logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/img/dat-gif.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dat-ecosystem-archive/datBase/b8caec355683c537444d25c4e48fb8b6fcf3e0c9/public/img/dat-gif.gif
--------------------------------------------------------------------------------
/public/img/dat-hexagon.svg:
--------------------------------------------------------------------------------
1 |
2 |
6 |
--------------------------------------------------------------------------------
/public/img/dat-terminal.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/img/download.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/public/img/eop.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dat-ecosystem-archive/datBase/b8caec355683c537444d25c4e48fb8b6fcf3e0c9/public/img/eop.png
--------------------------------------------------------------------------------
/public/img/example-california.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/public/img/example-densho.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dat-ecosystem-archive/datBase/b8caec355683c537444d25c4e48fb8b6fcf3e0c9/public/img/example-densho.png
--------------------------------------------------------------------------------
/public/img/example-osm-2.svg:
--------------------------------------------------------------------------------
1 |
36 |
--------------------------------------------------------------------------------
/public/img/example-osm.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/img/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dat-ecosystem-archive/datBase/b8caec355683c537444d25c4e48fb8b6fcf3e0c9/public/img/favicon.ico
--------------------------------------------------------------------------------
/public/img/knight.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dat-ecosystem-archive/datBase/b8caec355683c537444d25c4e48fb8b6fcf3e0c9/public/img/knight.png
--------------------------------------------------------------------------------
/public/img/link.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/public/img/logo-dat-desktop-dark.svg:
--------------------------------------------------------------------------------
1 |
33 |
--------------------------------------------------------------------------------
/public/img/moore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dat-ecosystem-archive/datBase/b8caec355683c537444d25c4e48fb8b6fcf3e0c9/public/img/moore.png
--------------------------------------------------------------------------------
/public/img/nasa-logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/img/open-in-desktop.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/img/question.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/public/img/screenshot-dat-desktop-empty.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dat-ecosystem-archive/datBase/b8caec355683c537444d25c4e48fb8b6fcf3e0c9/public/img/screenshot-dat-desktop-empty.png
--------------------------------------------------------------------------------
/public/img/screenshot-dat-desktop-import-dat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dat-ecosystem-archive/datBase/b8caec355683c537444d25c4e48fb8b6fcf3e0c9/public/img/screenshot-dat-desktop-import-dat.png
--------------------------------------------------------------------------------
/public/img/screenshot-dat-desktop.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dat-ecosystem-archive/datBase/b8caec355683c537444d25c4e48fb8b6fcf3e0c9/public/img/screenshot-dat-desktop.png
--------------------------------------------------------------------------------
/public/img/sloan.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dat-ecosystem-archive/datBase/b8caec355683c537444d25c4e48fb8b6fcf3e0c9/public/img/sloan.png
--------------------------------------------------------------------------------
/public/img/terminal-icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/scripts/add-dats-for-user.js:
--------------------------------------------------------------------------------
1 | const Api = require('dat-registry')
2 | const hyperdrive = require('hyperdrive')
3 | const ram = require('random-access-memory')
4 |
5 | var rootUrl = 'http://localhost:8080'
6 | var email = process.argv[2]
7 | var password = process.argv[3]
8 | var api = Api({server: rootUrl, apiPath: '/api/v1'})
9 |
10 | function createDat (dat) {
11 | var archive = hyperdrive(ram)
12 | archive.ready(function () {
13 | dat.url = 'dat://' + archive.key.toString('hex')
14 | api.dats.create(dat, function (err, resp, json) {
15 | if (err) console.error(err.toString())
16 | console.log('json')
17 | })
18 | })
19 | }
20 |
21 | api.login({email, password}, function (err, resp, json) {
22 | if (err) console.error('err', err.toString())
23 | createDat({name: 'cats', description: 'meow these are cats', keywords: 'meow, cats, grass, mice, scratching'})
24 | createDat({name: 'dogs', description: 'yo these are dogs', keywords: 'dogs, love, cuddles, running, eating'})
25 | })
26 |
--------------------------------------------------------------------------------
/scripts/user-and-dat.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | var TownshipClient = require('township-client')
4 |
5 | var email = process.argv.splice(2)[0]
6 | var password = 'dogsandcats'
7 | if (!email) {
8 | console.error('populate.js ')
9 | process.exit(1)
10 | }
11 |
12 | var dat = {
13 | name: 'more-tweets-more-votes',
14 | title: 'More Tweets More Votes',
15 | description: "Is social media a valid indicator of political behavior? There is considerable debate about the validity of data extracted from social media for studying offline behavior. To address this issue, we show that there is a statistically significant association between tweets that mention a candidate for the U.S. House of Representatives and his or her subsequent electoral performance. We demonstrate this result with an analysis of 542,969 tweets mentioning candidates selected from a random sample of 3,570,054,618, as well as Federal Election Commission data from 795 competitive races in the 2010 and 2012 U.S. congressional elections. This finding persists even when controlling for incumbency, district partisanship, media coverage of the race, time, and demographic variables such as the district's racial and gender composition. Our findings show that reliable data about political behavior can be extracted from social media",
16 | url: 'dat://587db7de5a030b9b91ddcb1882cf0e4b67b4609568997eee0d4dfe74ce31d198',
17 | keywords: 'hi, bye'
18 | }
19 |
20 | var township = TownshipClient({
21 | server: 'http://localhost:8080/api/v1'
22 | })
23 |
24 | township.register({username: 'admin', password: password, email: email}, function (err, resp, json) {
25 | if (err && err.message.indexOf('exists') === -1) throw new Error(err.message)
26 | console.log('logging in', email, password)
27 | township.login({email: email, password: password}, function (err, resp, json) {
28 | if (err) throw new Error(err.message)
29 | township.secureRequest({url: '/dats', method: 'POST', body: dat, json: true}, function (err, resp, json) {
30 | if (err) throw new Error(err.message)
31 | console.log('created dat', dat)
32 | })
33 | })
34 | })
35 |
--------------------------------------------------------------------------------
/server/cli.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | const bole = require('bole')
3 | const db = require('dat-registry-api/database/init')
4 | const Server = require('./')
5 | const Config = require('./config')
6 |
7 | bole.output({
8 | level: 'info',
9 | stream: process.stdout
10 | })
11 |
12 | const config = Config()
13 | const PORT = process.env.PORT || process.env.DATLAND_PORT || 8080
14 | config.log = bole(__filename)
15 | const server = Server(config)
16 | db(config.db, function (err, db) {
17 | if (err) throw err
18 | db.knex.destroy(function () {
19 | server.listen(PORT, function () {
20 | config.log.info({
21 | message: 'listening',
22 | port: server.address().port,
23 | env: process.env.NODE || 'undefined'
24 | })
25 | })
26 |
27 | process.on('SIGINT', function () {
28 | server.close()
29 | })
30 |
31 | process.once('uncaughtException', function (err) {
32 | config.log.error({message: 'error', error: err.message, stack: err.stack})
33 | console.error(err.stack)
34 | process.exit(1)
35 | })
36 | })
37 | })
38 |
--------------------------------------------------------------------------------
/server/config.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 | const debug = require('debug')('dat-registry')
3 | const path = require('path')
4 | const xtend = require('xtend')
5 |
6 | var defaultCfg = require('../config/default')
7 |
8 | var env = process.env.NODE_ENV || 'development'
9 |
10 | module.exports = function () {
11 | const input = require(path.join(__dirname, '..', 'config', 'config.' + env + '.js'))
12 | var config = xtend(defaultCfg, input)
13 | var datadir = config.data || process.env.DATADIR || path.join(__dirname, '..', 'data')
14 | if (config.township) {
15 | var ship = config.township
16 | ship.db = path.join(datadir, config.township.db)
17 | if (ship.publicKey) ship.publicKey = fs.readFileSync(path.join(datadir, ship.publicKey)).toString()
18 | if (ship.privateKey) ship.privateKey = fs.readFileSync(path.join(datadir, ship.privateKey)).toString()
19 | }
20 | if (config.db.dialect === 'sqlite3') config.db.connection.filename = path.join(datadir, config.db.connection.filename)
21 | if (config.archiver === 'archiver') config.archiver = path.join(datadir, config.archiver)
22 | debug(JSON.stringify(config))
23 | return config
24 | }
25 |
--------------------------------------------------------------------------------
/server/index.js:
--------------------------------------------------------------------------------
1 | const http = require('http')
2 | const createRouter = require('./router')
3 |
4 | module.exports = function (config) {
5 | const router = createRouter(config)
6 |
7 | var server = http.createServer(function (req, res) {
8 | router(req, res)
9 | })
10 |
11 | server.on('close', function () {
12 | router.api.close(function () {
13 | })
14 | })
15 |
16 | return server
17 | }
18 |
--------------------------------------------------------------------------------
/server/page.js:
--------------------------------------------------------------------------------
1 | const xtend = require('extend')
2 | const datIcons = require('dat-icons/raw')
3 | const serializeJS = require('serialize-javascript')
4 |
5 | function page (url, contents, appState) {
6 | var dehydratedAppState = serializeJS(appState)
7 | var defaultMetadata = {
8 | title: 'datBase - Open data powered by Dat',
9 | author: 'Dat Project',
10 | description: 'Future-friendly apps for your research data pipeline.',
11 | image: 'https://datbase.org/public/img/dat-hexagon.png',
12 | twitterImage: 'https://datbase.org/public/img/dat-data-logo.png',
13 | twitterSite: '@dat_project'
14 | }
15 |
16 | function renderMetaTags () {
17 | var md = appState.archive && appState.archive.metadata ? appState.archive.metadata : {}
18 | md = xtend(defaultMetadata, md)
19 | return `
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | `
28 | }
29 |
30 | return `
31 |
32 |
33 | ${defaultMetadata.title}
34 |
35 |
36 |
37 | ${renderMetaTags()}
38 |
39 |
40 |
41 |
42 |
43 |
44 | ${contents}
45 |
46 |
47 |
48 | ${datIcons()}
49 |
52 |
53 | `
54 | }
55 |
56 | module.exports = page
57 |
--------------------------------------------------------------------------------
/server/router.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 | const range = require('range-parser')
3 | const debug = require('debug')('dat-registry')
4 | const mime = require('mime')
5 | const pump = require('pump')
6 | const xtend = require('xtend')
7 | const path = require('path')
8 | const compression = require('compression')
9 | const bodyParser = require('body-parser')
10 | const UrlParams = require('uparams')
11 | const express = require('express')
12 | const Mixpanel = require('mixpanel')
13 | const app = require('../client/js/app')
14 | const page = require('./page')
15 | const Api = require('dat-registry-api')
16 |
17 | module.exports = function (config) {
18 | config = config || {}
19 |
20 | const mx = Mixpanel.init(config.mixpanel)
21 | const api = Api(config)
22 | const db = api.db
23 | const archiver = api.archiver
24 |
25 | var router = express()
26 | router.use(compression())
27 | router.use('/public', express.static(path.join(__dirname, '..', 'public')))
28 | router.use(bodyParser.json()) // support json encoded bodies
29 |
30 | router.post('/api/v1/users', api.users.post)
31 | router.get('/api/v1/users', api.users.get)
32 | router.put('/api/v1/users', api.users.put)
33 | router.delete('/api/v1/users', api.users.delete)
34 |
35 | router.get('/api/v1/dats', api.dats.get)
36 | router.post('/api/v1/dats', api.dats.post)
37 | router.put('/api/v1/dats', api.dats.put)
38 | router.delete('/api/v1/dats', api.dats.delete)
39 |
40 | router.post('/api/v1/register', api.auth.register)
41 | router.post('/api/v1/login', api.auth.login)
42 | router.post('/api/v1/password-reset', api.auth.passwordReset)
43 | router.post('/api/v1/password-reset-confirm', api.auth.passwordResetConfirm)
44 |
45 | router.get('/api/v1/dats/search', function (req, res) {
46 | api.db.dats.search(req.query, function (err, resp) {
47 | if (err) return onerror(err, res)
48 | res.json(resp)
49 | })
50 | })
51 | router.get('/api/v1/:username/:dataset', function (req, res) {
52 | db.dats.getByShortname(req.params, function (err, dat) {
53 | if (err) return onerror(err, res)
54 | res.json(dat)
55 | })
56 | })
57 |
58 | function send (req, res) {
59 | var state = getDefaultAppState()
60 | sendSPA(req, res, state)
61 | }
62 |
63 | // landing page
64 | router.get('/', send)
65 | router.get('/register', send)
66 | router.get('/login', send)
67 | router.get('/reset-password', send)
68 | router.get('/profile/delete', send)
69 | router.get('/browser', send)
70 |
71 | router.get('/view', function (req, res) {
72 | archiveRoute(req.query.query, function (state) {
73 | if (state.archive.error && state.archive.error.message === 'Invalid key') {
74 | // var url = '/' + req._parsedUrl.search
75 | // TODO: this needs to show an error message or something
76 | return res.redirect(301, '/')
77 | }
78 | return sendSPA(req, res, state)
79 | })
80 | })
81 |
82 | // TODO: move a lot of this junk below to some other api file so it can be more easily read
83 |
84 | function onfile (archive, name, req, res) {
85 | archive.stat(name, function (err, st) {
86 | if (err) return onerror(err, res)
87 | debug('file requested', st.size)
88 | mx.track('file requested', {size: st.size})
89 |
90 | if (st.isDirectory()) {
91 | res.statusCode = 302
92 | res.setHeader('Location', name + '/')
93 | return
94 | }
95 |
96 | var r = req.headers.range && range(st.size, req.headers.range)[0]
97 | res.setHeader('Accept-Ranges', 'bytes')
98 | res.setHeader('Content-Type', mime.lookup(name))
99 |
100 | if (r) {
101 | res.statusCode = 206
102 | res.setHeader('Content-Range', 'bytes ' + r.start + '-' + r.end + '/' + st.size)
103 | res.setHeader('Content-Length', r.end - r.start + 1)
104 | } else {
105 | res.setHeader('Content-Length', st.size)
106 | }
107 |
108 | if (req.method === 'HEAD') return res.end()
109 | pump(archive.createReadStream(name, r), res)
110 | })
111 | }
112 |
113 | router.get('/download/:archiveKey/*', function (req, res) {
114 | debug('getting file contents', req.params)
115 | archiver.get(req.params.archiveKey, function (err, archive) {
116 | if (err) return onerror(err, res)
117 | var filename = req.params[0]
118 | return onfile(archive, filename, req, res)
119 | })
120 | })
121 |
122 | router.get('/health/:archiveKey', function (req, res) {
123 | archiver.get(req.params.archiveKey, function (err, archive, key) {
124 | if (err) return onerror(err, res)
125 | archive.ready(function () {
126 | return res.status(200).json(archiver.health(archive))
127 | })
128 | })
129 | })
130 |
131 | router.get('/metadata/:archiveKey', function (req, res) {
132 | var timeout = parseInt(req.query.timeout, 10) || 1000
133 | debug('requesting metadata for key', req.params.archiveKey)
134 | archiver.get(req.params.archiveKey, {timeout}, function (err, archive) {
135 | if (err) return onerror(err, res)
136 | archiver.metadata(archive, {timeout}, function (err, info) {
137 | if (err) info.error = {message: err.message}
138 | info.health = archiver.health(archive)
139 | return res.status(200).json(info)
140 | })
141 | })
142 | })
143 |
144 | router.get('/profile/edit', function (req, res) {
145 | var state = getDefaultAppState()
146 | return sendSPA(req, res, state)
147 | })
148 |
149 | router.get('/:username', function (req, res) {
150 | var state = getDefaultAppState()
151 | debug('looking for user', req.params.username)
152 | db.users.get({username: req.params.username}, function (err, results) {
153 | if (err) return onerror(err, res)
154 | if (!results.length) {
155 | debug('user not found')
156 | return res.redirect(301, '/dat://' + req.params.username)
157 | }
158 | var user = results[0]
159 | debug('profile views', user)
160 | mx.track('profile viewed', {distinct_id: user.email})
161 | state.profile = {
162 | username: user.username,
163 | role: user.role,
164 | name: user.name,
165 | data: user.data,
166 | description: user.description,
167 | created_at: user.created_at,
168 | email: user.email,
169 | id: user.id
170 | }
171 | debug('getting dats')
172 | db.dats.get({user_id: user.id}, function (err, results) {
173 | if (err) return onerror(err, res)
174 | state.profile.dats = results
175 | debug('sending profile', state.profile)
176 | return sendSPA(req, res, state)
177 | })
178 | })
179 | })
180 |
181 | router.get('/:username/:dataset', function (req, res) {
182 | debug('requesting username/dataset', req.params)
183 | mx.track('shortname viewed', req.params)
184 | db.dats.getByShortname(req.params, function (err, dat) {
185 | if (err) {
186 | var state = getDefaultAppState()
187 | state.archive.error = {message: err.message}
188 | debug('could not get dat with ' + req.params, err)
189 | return sendSPA(req, res, state)
190 | }
191 | res.setHeader('Hyperdrive-Key', dat.url)
192 | var contentType = req.accepts(['html', 'json'])
193 | if (contentType === 'json') {
194 | if (err) return onerror(err, res)
195 | return res.status(200).json(dat)
196 | }
197 | archiveRoute(dat.url, function (state) {
198 | state.archive.id = dat.id
199 | dat.username = req.params.username
200 | dat.shortname = req.params.username + '/' + req.params.dataset
201 | state.archive.metadata = dat
202 | return sendSPA(req, res, state)
203 | })
204 | })
205 | })
206 |
207 | router.get('/dat://:archiveKey', function (req, res) {
208 | archiveRoute(req.params.archiveKey, function (state) {
209 | return sendSPA(req, res, state)
210 | })
211 | })
212 |
213 | router.get('/dat://:archiveKey/contents', function (req, res) {
214 | archiveRoute(req.params.archiveKey, function (state) {
215 | return sendSPA(req, res, state)
216 | })
217 | })
218 |
219 | router.get('/dat://:archiveKey/contents/*', function (req, res) {
220 | debug('getting file contents', req.params)
221 | var filename = req.params[0]
222 | archiveRoute(req.params.archiveKey, function (state) {
223 | archiver.get(req.params.archiveKey, function (err, archive) {
224 | if (err) return onerror(err, res)
225 | archive.stat(filename, function (err, entry) {
226 | if (err) {
227 | state.preview.error = {message: err.message}
228 | entry = {name: filename}
229 | }
230 | entry.name = filename
231 | entry.archiveKey = req.params.archiveKey
232 | entry.type = entry.isDirectory
233 | ? entry.isDirectory() ? 'directory' : 'file'
234 | : 'file'
235 | if (entry.type === 'directory') {
236 | state.archive.root = entry.name
237 | return sendSPA(req, res, state)
238 | }
239 | if (entry.type === 'file') {
240 | var arr = entry.name.split('/')
241 | if (arr.length > 1) state.archive.root = arr.splice(0, arr.length - 1).join('/')
242 | }
243 | state.preview.entry = entry
244 | return sendSPA(req, res, state)
245 | })
246 | })
247 | })
248 | })
249 |
250 | function onerror (err, res) {
251 | console.trace(err)
252 | return res.status(400).json({statusCode: 400, message: err.message})
253 | }
254 |
255 | function archiveRoute (key, cb) {
256 | debug('finding', key)
257 | var cancelled = false
258 |
259 | function onerror (err) {
260 | debug(key, err)
261 | if (cancelled) return true
262 | cancelled = true
263 | state.archive.error = {message: err.message}
264 | return cb(state)
265 | }
266 |
267 | var timeout = setTimeout(function () {
268 | var msg = 'timed out'
269 | if (cancelled) return
270 | cancelled = true
271 | return onerror(new Error(msg))
272 | }, 1000)
273 |
274 | var state = getDefaultAppState()
275 | mx.track('archive viewed', {key: key})
276 |
277 | archiver.get(key, function (err, archive, key) {
278 | if (err) return onerror(err)
279 | archive.ready(function () {
280 | debug('got archive key', key)
281 | state.archive.health = archiver.health(archive)
282 | clearTimeout(timeout)
283 | if (cancelled) return
284 | cancelled = true
285 |
286 | archiver.metadata(archive, {timeout: 1000}, function (err, info) {
287 | if (err) state.archive.error = {message: err.message}
288 | state.archive = xtend(state.archive, info)
289 | state.archive.key = key
290 | cb(state)
291 | })
292 | })
293 | })
294 | }
295 |
296 | router.archiver = archiver
297 | router.api = api
298 | return router
299 |
300 | /* helpers */
301 | function getDefaultAppState () {
302 | var state = {}
303 | for (var key in app.defaults) {
304 | state[key] = app.defaults[key]
305 | }
306 | return JSON.parse(JSON.stringify(state))
307 | }
308 |
309 | function sendSPA (req, res, state) {
310 | var route = req.url
311 | if (!state) state = {}
312 | const frozenState = Object.freeze(state)
313 | const contents = app.toString(route, frozenState)
314 | const urlParams = new UrlParams(req.url)
315 | res.setHeader('Content-Type', 'text/html')
316 | var url = req.headers.host + route
317 | if (urlParams.debug) {
318 | return fs.access('./server/page-debug.js', function (err) {
319 | if (err) {
320 | return res.end('Please run the bin/version-assets script via `npm run version` to debug with un-minified assets.')
321 | }
322 | return res.end(require('./page-debug')(url, contents, frozenState))
323 | })
324 | }
325 | return res.end(page(url, contents, frozenState))
326 | }
327 | }
328 |
--------------------------------------------------------------------------------
/tests/e2e-webrtc/connect.js:
--------------------------------------------------------------------------------
1 | var process = require('process')
2 | var net = require('net')
3 | var path = require('path')
4 | var browser2
5 |
6 | function makeServer () {
7 | var server = net.createServer((c) => {
8 | browser2 = c
9 | }).on('error', (err) => {
10 | throw err
11 | })
12 | server.listen({path: '/tmp/nightwatch-ipc.sock'}, () => {
13 | })
14 | return server
15 | }
16 |
17 | function makeClient () {
18 | var socket = net.connect({path: '/tmp/nightwatch-ipc.sock'}, () => {
19 | socket.on('data', (data) => {
20 | })
21 | }).on('error', () => {
22 | console.error('not connected')
23 | })
24 | return socket
25 | }
26 |
27 | module.exports = new function () {
28 | var ipcServer, ipcClient
29 | var firstClient = process.env.__NIGHTWATCH_ENV_KEY.match(/_1$/)
30 | var testServer = process.env.TEST_SERVER || 'https://datbase.org'
31 | if (firstClient) {
32 | ipcServer = makeServer()
33 | } else {
34 | ipcClient = makeClient()
35 | }
36 | var testCases = this
37 |
38 | if (firstClient) {
39 | testCases.beforeEach = (browser, done) => {
40 | const check = () => {
41 | if (browser2) return done()
42 | setTimeout(check, 1000)
43 | }
44 | check()
45 | }
46 |
47 | testCases['opening the browser and navigating to the url'] = (client) => {
48 | client
49 | .url(testServer + 'browser')
50 | .click('.share-button').pause(1000)
51 | .expect.element('#title').text.matches(/^(.+)$/).before(10000)
52 |
53 | client.getText('#title', (result) => {
54 | console.log('sending', result.value)
55 | browser2.write(result.value)
56 | })
57 | }
58 | } else {
59 | testCases['opening the browser and navigating to the url'] = (client) => {
60 | client.pause(5000)
61 | ipcClient.once('data', (data) => {
62 | const datlink = data.toString()
63 | console.info('using datlink', datlink)
64 | client
65 | .url(testServer + 'dat://' + datlink)
66 | .waitForElementVisible('body', 10000)
67 | })
68 | }
69 | }
70 |
71 | testCases['found the other peer'] = (client) => {
72 | client
73 | .expect.element('#peers').text.matches(/1 Source\(s\)/).before(30000)
74 | }
75 |
76 | if (firstClient) {
77 | testCases['upload file'] = (client) => {
78 | client.setValue('#add-files input[type=file]', path.join(__dirname, '..', 'fixtures', 'dat.json'))
79 | .expect.element('#fs').text.to.contain('dat.json').before(10000)
80 | }
81 | testCases['archive size updates'] = (client) => {
82 | client.expect.element('#hyperdrive-size').text.to.contain('48 B').before(3000)
83 | }
84 | } else {
85 | testCases['file synced'] = (client) => {
86 | client
87 | .expect.element('#fs').text.to.contain('dat.json').before(10000)
88 | }
89 | testCases['archive size updates'] = (client) => {
90 | client.expect.element('#hyperdrive-size').text.to.contain('48 B').before(3000)
91 | }
92 | }
93 |
94 | if (firstClient) {
95 | testCases['upload file 2'] = (client) => {
96 | client.setValue('#add-files input[type=file]', path.join(__dirname, '..', 'fixtures', 'hello.csv'))
97 | .expect.element('#fs').text.to.contain('hello.csv').before(10000)
98 | client.expect.element('#hyperdrive-size').text.to.contain('64 B').before(3000)
99 | }
100 | } else {
101 | testCases['file 2 synced'] = (client) => {
102 | client
103 | .expect.element('#fs').text.to.contain('hello.csv').before(10000)
104 | }
105 | }
106 |
107 | testCases.suspend = (client) => {
108 | client.pause(1000)
109 | }
110 |
111 | testCases['metadata rendered'] = client => {
112 | client.expect.element('#title').text.to.contain('hello world').before(1000)
113 | client.expect.element('#author').text.to.contain('joe bob').before(1000)
114 | }
115 |
116 | testCases['file display opens'] = (client) => {
117 | client.click('.entry.file').pause(3000)
118 | .expect.element('#preview').to.have.attribute('class').which.contains('open').before(1000)
119 | client.expect.element('.dat-detail.size').text.to.contain('48 B').before(1000)
120 | client.frame(0).expect.element('pre').text.to.contain('hello world')
121 | client.frame(null)
122 | client.click('.panel-header__close-button').pause(1000)
123 | .expect.element('#preview').to.have.attribute('class').not.to.contain('open').before(1000)
124 | }
125 |
126 | if (firstClient) {
127 | testCases['create new button properly resets view'] = (client) => {
128 | client.click('.new-dat').pause(1000)
129 | client
130 | .click('.share-button').pause(1000)
131 | .expect.element('#fs').text.not.to.contain('dat.json').before(1000)
132 | client
133 | .expect.element('#author').text.not.to.contain('datapackage.json').before(1000)
134 | client
135 | .expect.element('#peers').text.matches(/0 Source\(s\)/).before(1000)
136 | }
137 | }
138 |
139 | testCases.after = (client, done) => {
140 | if (ipcServer) {
141 | ipcServer.close()
142 | }
143 | if (ipcClient) {
144 | ipcClient.end()
145 | }
146 | client.end().perform(function () { done() })
147 | }
148 | }()
149 |
--------------------------------------------------------------------------------
/tests/e2e/preview.js:
--------------------------------------------------------------------------------
1 | var fs = require('fs')
2 | var path = require('path')
3 | var testServer = process.env.TEST_SERVER || 'https://datbase.org'
4 |
5 | module.exports = new function () {
6 | var key = fs.readFileSync(path.join(__dirname, '..', 'key.txt')).toString()
7 | var testCases = this
8 | testCases['Preview file when clicking on it'] = (client) => {
9 | client
10 | .url(testServer + '/install')
11 | .setValue("input[name='import-dat']", key)
12 | client.keys(client.Keys.ENTER, function (done) {
13 | client
14 | .expect.element('#fs').text.to.contain('dat.json').before(5000)
15 | client.click('.entry.file')
16 | client.pause(2000)
17 | client.expect.element('.panel-title.truncate').text.to.contain('dat.json')
18 | client.expect.element('.dat-detail.size').text.to.contain('48 B')
19 | })
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/tests/e2e/server.js:
--------------------------------------------------------------------------------
1 | var fs = require('fs')
2 | var path = require('path')
3 | var testServer = process.env.TEST_SERVER || 'https://datbase.org'
4 | const os = require('os')
5 | const modificatorKey = (os.type().toLowerCase() === 'darwin')
6 | ? 'COMMAND'
7 | : 'CONTROL'
8 |
9 | module.exports = new function () {
10 | var key = fs.readFileSync(path.join(__dirname, '..', 'key.txt')).toString()
11 |
12 | var testCases = this
13 | testCases['opening the browser and navigating to the url'] = (client) => {
14 | client
15 | .url(testServer)
16 | .assert.containsText('body', 'dat')
17 | }
18 | testCases['viewing a dat that doesnt exist gives explore page'] = (client) => {
19 | client
20 | .url(testServer + '/install')
21 | .expect.element("input[name='import-dat']").value.to.equal('').before(2000)
22 | client
23 | .setValue("input[name='import-dat']", 'hello')
24 | .keys(client.Keys.ENTER)
25 | .expect.element('body').text.to.contain('Shared with Dat').before(2000)
26 | }
27 | testCases['viewing a dat with a key that doesnt exist gives 404'] = (client) => {
28 | // this is just a valid hash but doesn't resolve to a dat
29 | var notDat = 'e333a491c886867f9550afb6addf3bdad4204928af650412b988988d4c3b5fbc'
30 | client
31 | .url(testServer + '/install')
32 | .expect.element("input[name='import-dat']").value.to.equal('').before(2000)
33 | client
34 | .setValue("input[name='import-dat']", notDat)
35 | .keys(client.Keys.ENTER)
36 | .expect.element('body').text.to.contain('Looking for sources…').before(7000)
37 | }
38 | testCases['viewing a dat that exists with file list works'] = (client) => {
39 | client
40 | .setValue("input[name='import-dat']", key)
41 | client.keys(client.Keys.ENTER, function (done) {
42 | client.pause(2000)
43 | client
44 | .expect.element('#fs').text.to.contain('dat.json').before(5000)
45 | // client
46 | // .expect.element('#peers').text.to.contain('1').before(5000)
47 | client.click('.directory')
48 | client.expect.element('#fs').text.to.contain('hello.txt').before(1000)
49 | client.expect.element('#title').text.to.contain('hello world').before(5000)
50 | })
51 | }
52 | testCases['copying link works'] = (client) => {
53 | client.click('a.clipboard')
54 | client.expect.element('.success').text.to.contain('Link copied to clipboard').before(1000)
55 | client.expect.element("input[name='import-dat']").value.to.equal('')
56 | }
57 | testCases['pasting link works'] = (client) => {
58 | // somehow click to focus doesn't work unless we re-open browser
59 | client
60 | .url(testServer + '/install')
61 | .click("input[name='import-dat']")
62 | .keys([client.Keys[modificatorKey], 'v'])
63 | .expect.element("input[name='import-dat']").value.to.equal('dat://' + key).before(3000)
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/tests/e2e/user.js:
--------------------------------------------------------------------------------
1 | var testServer = process.env.TEST_SERVER || 'https://datbase.org'
2 |
3 | module.exports = new function () {
4 | var testCases = this
5 | testCases['Registration works'] = (client) => {
6 | client
7 | .url(testServer + '/register')
8 | .assert.containsText('body', 'Create a New Account')
9 |
10 | client
11 | .setValue(".register form input[name='username']", 'testuser')
12 | .setValue(".register form input[name='email']", 'hi@pam.com')
13 | .setValue(".register form input[name='password']", 'fnordfoobar')
14 | .submitForm('.register form')
15 |
16 | client.end()
17 | }
18 |
19 | // testCases['duplicated registration should fail'] = (client) => {
20 | // client
21 | // .url(testServer + '/register')
22 | // .assert.containsText('body', 'Create a New Account')
23 | //
24 | // client
25 | // .setValue(".register form input[name='username']", 'testuser')
26 | // .setValue(".register form input[name='email']", 'hi@pam.com')
27 | // .setValue(".register form input[name='password']", 'fnordfoobar')
28 | // .submitForm('.register form')
29 | // .expect.element('.register form .error').text.matches(/Account with that email already exists./).before(5000)
30 | //
31 | // client.end()
32 | // }
33 |
34 | testCases['view profile should work'] = (client) => {
35 | client
36 | .url(testServer + '/testuser')
37 | .pause(5000)
38 | .assert.containsText('body', 'testuser has published 0 dats')
39 |
40 | client.end()
41 | }
42 |
43 | testCases['view profile of a user that does not exist should give 404'] = (client) => {
44 | client
45 | .url(testServer + '/doesnotexist')
46 | .pause(5000)
47 | .assert.containsText('body', 'Looks like the key is invalid')
48 |
49 | client.end()
50 | }
51 |
52 | testCases['login with bad password displays error message'] = (client) => {
53 | client
54 | .url(testServer + '/login')
55 |
56 | client
57 | .pause(5000)
58 | .setValue(".login form input[name='email']", 'hi@pam.com')
59 | .setValue(".login form input[name='password']", 'badpassword')
60 | .submitForm('.login form')
61 | .expect.element('.login form .error').text.matches(/Password incorrect./).before(5000)
62 | }
63 |
64 | testCases['login should work'] = (client) => {
65 | client
66 | .url(testServer + '/login')
67 |
68 | client
69 | .pause(5000)
70 | .setValue(".login form input[name='email']", 'hi@pam.com')
71 | .setValue(".login form input[name='password']", 'fnordfoobar')
72 | .submitForm('.login form')
73 |
74 | client.end()
75 | }
76 |
77 | testCases['edit profile should work'] = (client) => {
78 | client
79 | .url(testServer + '/profile/edit')
80 | .assert.containsText('body', 'Edit your Profile')
81 |
82 | client
83 | .pause(5000)
84 | .setValue(".edit-profile form textarea[name='description']", 'testing description')
85 | .submitForm('.edit-profile form')
86 |
87 | client.end()
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/tests/fixtures/dat.json:
--------------------------------------------------------------------------------
1 | { "title" : "hello world", "author": "joe bob"}
2 |
--------------------------------------------------------------------------------
/tests/fixtures/hello.csv:
--------------------------------------------------------------------------------
1 | hello,world
2 | 1,2
3 |
--------------------------------------------------------------------------------
/tests/fixtures/testingfolder/hello.txt:
--------------------------------------------------------------------------------
1 | world
2 |
--------------------------------------------------------------------------------
/tests/fixtures/whitelist.txt:
--------------------------------------------------------------------------------
1 | bilbo@baggins.co.nz
2 | hi@joe.com
3 | hi@bob.com
4 | hi@pam.com
5 |
6 | # nazgul@mordornet.ru
7 |
--------------------------------------------------------------------------------
/tests/globals.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | module.exports = {
4 | beforeEach: function (browser, done) {
5 | // require('nightwatch-video-recorder').start(browser, done)
6 | },
7 | afterEach: function (browser, done) {
8 | // require('nightwatch-video-recorder').stop(browser, done)
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/tests/helpers.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 | const rimraf = require('rimraf')
3 | const Server = require('../server')
4 |
5 | module.exports = {
6 | server: function (config, cb) {
7 | const server = Server(config)
8 | server.listen(config.port, function () {
9 | cb(server.close.bind(server))
10 | })
11 | },
12 | tearDown: function (config, close) {
13 | rimraf(config.township.db, function () {
14 | fs.unlink(config.db.connection.filename, function () {
15 | close()
16 | })
17 | })
18 | },
19 | users: {
20 | joe: {name: 'joe schmo', username: 'joe', password: 'very secret', email: 'hi@joe.com', description: 'hello i am a description', token: null, role: 'user'},
21 | bob: {name: 'bob smob', username: 'bob', password: 'so secret', email: 'hi@bob.com', description: 'i like it', token: null, role: 'user'},
22 | admin: {name: 'pam spam', username: 'pam', password: 'secret123', email: 'hi@pam.com', description: 'i dont eat it', token: null, role: 'admin'}
23 | },
24 | dats: {
25 | cats: {name: 'cats', title: 'all of the cats', description: 'live on the corner of washington and 7th', keywords: 'furry, fluffy'},
26 | penguins: {name: 'penguins', title: 'all of the penguins', description: 'lives in your house', keywords: 'sloppy, loud'},
27 | dogs: {name: 'dogs', title: 'all of the dogs', description: 'lives in your house', keywords: 'sloppy, loud'}
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/tests/new-dat.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | const fs = require('fs')
3 | const Dat = require('dat-node')
4 | const path = require('path')
5 | const rimraf = require('rimraf')
6 |
7 | module.exports = newDat
8 |
9 | function newDat (loc, cb) {
10 | rimraf(path.join(loc, '.dat'), function () {
11 | Dat(loc, function (err, dat) {
12 | if (err) return cb(err)
13 | dat.importFiles()
14 | dat.joinNetwork()
15 | fs.writeFile(path.join(__dirname, 'key.txt'), dat.key.toString('hex'))
16 | cb(null, dat)
17 | })
18 | })
19 | }
20 |
21 | if (!module.parent) {
22 | newDat(path.join(__dirname, 'fixtures'), function (err, dat) {
23 | if (err) throw err
24 | console.log('listening to', dat.key.toString('hex'))
25 | })
26 | }
27 |
--------------------------------------------------------------------------------
/tests/nightwatch.json:
--------------------------------------------------------------------------------
1 | {
2 | "globals_path": "globals.js",
3 | "output_folder": false,
4 | "src_folders": ["e2e"],
5 | "test_settings": {
6 | "default": {
7 | "screenshots": {
8 | "enabled": true,
9 | "on_failure": true,
10 | "path": "screenshots"
11 | },
12 | "videos": {
13 | "enabled": true,
14 | "delete_on_success": true,
15 | "path": "videos"
16 | },
17 | "launch_url": "http://app",
18 | "selenium_host": "chromedriver"
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/tests/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "blueimp-nightwatch-example",
3 | "private": true,
4 | "version": "0.0.0",
5 | "main": "globals.js",
6 | "dependencies": {
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/tests/test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | standard
3 | node tests/new-dat &
4 | npm run clean-test
5 | NODE_ENV=test npm run database tests/test-api.sqlite
6 | NODE_ENV=test tape tests/unit/*.js | tap-spec
7 |
8 |
--------------------------------------------------------------------------------