diff options
Diffstat (limited to 'src')
60 files changed, 3177 insertions, 124 deletions
diff --git a/src/components/auth/Login.js b/src/components/auth/Login.js index eea5a09bf..e58016e25 100644 --- a/src/components/auth/Login.js +++ b/src/components/auth/Login.js | |||
@@ -1,11 +1,13 @@ | |||
1 | /* eslint jsx-a11y/anchor-is-valid: 0 */ | ||
1 | import React, { Component } from 'react'; | 2 | import React, { Component } from 'react'; |
2 | import PropTypes from 'prop-types'; | 3 | import PropTypes from 'prop-types'; |
3 | import { observer } from 'mobx-react'; | 4 | import { observer, inject } from 'mobx-react'; |
4 | import { defineMessages, intlShape } from 'react-intl'; | 5 | import { defineMessages, intlShape } from 'react-intl'; |
5 | 6 | ||
6 | import { isDevMode, useLiveAPI } from '../../environment'; | 7 | import { isDevMode, useLiveAPI } from '../../environment'; |
7 | import Form from '../../lib/Form'; | 8 | import Form from '../../lib/Form'; |
8 | import { required, email } from '../../helpers/validation-helpers'; | 9 | import { required, email } from '../../helpers/validation-helpers'; |
10 | import serverlessLogin from '../../helpers/serverless-helpers'; | ||
9 | import Input from '../ui/Input'; | 11 | import Input from '../ui/Input'; |
10 | import Button from '../ui/Button'; | 12 | import Button from '../ui/Button'; |
11 | import Link from '../ui/Link'; | 13 | import Link from '../ui/Link'; |
@@ -54,13 +56,21 @@ const messages = defineMessages({ | |||
54 | id: 'login.link.signup', | 56 | id: 'login.link.signup', |
55 | defaultMessage: '!!!Create a free account', | 57 | defaultMessage: '!!!Create a free account', |
56 | }, | 58 | }, |
59 | changeServer: { | ||
60 | id: 'login.changeServer', | ||
61 | defaultMessage: '!!!Change server', | ||
62 | }, | ||
63 | serverless: { | ||
64 | id: 'services.serverless', | ||
65 | defaultMessage: '!!!Use Ferdi without an Account', | ||
66 | }, | ||
57 | passwordLink: { | 67 | passwordLink: { |
58 | id: 'login.link.password', | 68 | id: 'login.link.password', |
59 | defaultMessage: '!!!Forgot password', | 69 | defaultMessage: '!!!Forgot password', |
60 | }, | 70 | }, |
61 | }); | 71 | }); |
62 | 72 | ||
63 | export default @observer class Login extends Component { | 73 | export default @observer @inject('actions') class Login extends Component { |
64 | static propTypes = { | 74 | static propTypes = { |
65 | onSubmit: PropTypes.func.isRequired, | 75 | onSubmit: PropTypes.func.isRequired, |
66 | isSubmitting: PropTypes.bool.isRequired, | 76 | isSubmitting: PropTypes.bool.isRequired, |
@@ -69,6 +79,7 @@ export default @observer class Login extends Component { | |||
69 | signupRoute: PropTypes.string.isRequired, | 79 | signupRoute: PropTypes.string.isRequired, |
70 | passwordRoute: PropTypes.string.isRequired, | 80 | passwordRoute: PropTypes.string.isRequired, |
71 | error: globalErrorPropType.isRequired, | 81 | error: globalErrorPropType.isRequired, |
82 | actions: PropTypes.object.isRequired, | ||
72 | }; | 83 | }; |
73 | 84 | ||
74 | static contextTypes = { | 85 | static contextTypes = { |
@@ -103,6 +114,10 @@ export default @observer class Login extends Component { | |||
103 | }); | 114 | }); |
104 | } | 115 | } |
105 | 116 | ||
117 | useLocalServer() { | ||
118 | serverlessLogin(this.props.actions); | ||
119 | } | ||
120 | |||
106 | render() { | 121 | render() { |
107 | const { form } = this; | 122 | const { form } = this; |
108 | const { intl } = this.context; | 123 | const { intl } = this.context; |
@@ -179,7 +194,8 @@ export default @observer class Login extends Component { | |||
179 | )} | 194 | )} |
180 | </form> | 195 | </form> |
181 | <div className="auth__links"> | 196 | <div className="auth__links"> |
182 | <Link to="/settings/app">Change server</Link> | 197 | <Link to="/settings/app">{intl.formatMessage(messages.changeServer)}</Link> |
198 | <a onClick={this.useLocalServer.bind(this)}>{intl.formatMessage(messages.serverless)}</a> | ||
183 | <Link to={signupRoute}>{intl.formatMessage(messages.signupLink)}</Link> | 199 | <Link to={signupRoute}>{intl.formatMessage(messages.signupLink)}</Link> |
184 | <Link to={passwordRoute}>{intl.formatMessage(messages.passwordLink)}</Link> | 200 | <Link to={passwordRoute}>{intl.formatMessage(messages.passwordLink)}</Link> |
185 | </div> | 201 | </div> |
diff --git a/src/components/auth/Signup.js b/src/components/auth/Signup.js index b36e71ce1..47e9daf18 100644 --- a/src/components/auth/Signup.js +++ b/src/components/auth/Signup.js | |||
@@ -1,11 +1,13 @@ | |||
1 | /* eslint jsx-a11y/anchor-is-valid: 0 */ | ||
1 | import React, { Component } from 'react'; | 2 | import React, { Component } from 'react'; |
2 | import PropTypes from 'prop-types'; | 3 | import PropTypes from 'prop-types'; |
3 | import { observer } from 'mobx-react'; | 4 | import { observer, inject } from 'mobx-react'; |
4 | import { defineMessages, intlShape } from 'react-intl'; | 5 | import { defineMessages, intlShape } from 'react-intl'; |
5 | 6 | ||
6 | import { isDevMode, useLiveAPI } from '../../environment'; | 7 | import { isDevMode, useLiveAPI } from '../../environment'; |
7 | import Form from '../../lib/Form'; | 8 | import Form from '../../lib/Form'; |
8 | import { required, email, minLength } from '../../helpers/validation-helpers'; | 9 | import { required, email, minLength } from '../../helpers/validation-helpers'; |
10 | import serverlessLogin from '../../helpers/serverless-helpers'; | ||
9 | import Input from '../ui/Input'; | 11 | import Input from '../ui/Input'; |
10 | import Button from '../ui/Button'; | 12 | import Button from '../ui/Button'; |
11 | import Link from '../ui/Link'; | 13 | import Link from '../ui/Link'; |
@@ -58,18 +60,27 @@ const messages = defineMessages({ | |||
58 | id: 'signup.link.login', | 60 | id: 'signup.link.login', |
59 | defaultMessage: '!!!Already have an account, sign in?', | 61 | defaultMessage: '!!!Already have an account, sign in?', |
60 | }, | 62 | }, |
63 | changeServer: { | ||
64 | id: 'login.changeServer', | ||
65 | defaultMessage: '!!!Change server', | ||
66 | }, | ||
67 | serverless: { | ||
68 | id: 'services.serverless', | ||
69 | defaultMessage: '!!!Use Ferdi without an Account', | ||
70 | }, | ||
61 | emailDuplicate: { | 71 | emailDuplicate: { |
62 | id: 'signup.emailDuplicate', | 72 | id: 'signup.emailDuplicate', |
63 | defaultMessage: '!!!A user with that email address already exists', | 73 | defaultMessage: '!!!A user with that email address already exists', |
64 | }, | 74 | }, |
65 | }); | 75 | }); |
66 | 76 | ||
67 | export default @observer class Signup extends Component { | 77 | export default @observer @inject('actions') class Signup extends Component { |
68 | static propTypes = { | 78 | static propTypes = { |
69 | onSubmit: PropTypes.func.isRequired, | 79 | onSubmit: PropTypes.func.isRequired, |
70 | isSubmitting: PropTypes.bool.isRequired, | 80 | isSubmitting: PropTypes.bool.isRequired, |
71 | loginRoute: PropTypes.string.isRequired, | 81 | loginRoute: PropTypes.string.isRequired, |
72 | error: globalErrorPropType.isRequired, | 82 | error: globalErrorPropType.isRequired, |
83 | actions: PropTypes.object.isRequired, | ||
73 | }; | 84 | }; |
74 | 85 | ||
75 | static contextTypes = { | 86 | static contextTypes = { |
@@ -112,6 +123,10 @@ export default @observer class Signup extends Component { | |||
112 | }); | 123 | }); |
113 | } | 124 | } |
114 | 125 | ||
126 | useLocalServer() { | ||
127 | serverlessLogin(this.props.actions); | ||
128 | } | ||
129 | |||
115 | render() { | 130 | render() { |
116 | const { form } = this; | 131 | const { form } = this; |
117 | const { intl } = this.context; | 132 | const { intl } = this.context; |
@@ -183,7 +198,8 @@ export default @observer class Signup extends Component { | |||
183 | </p> | 198 | </p> |
184 | </form> | 199 | </form> |
185 | <div className="auth__links"> | 200 | <div className="auth__links"> |
186 | <Link to="/settings/app">Change server</Link> | 201 | <Link to="/settings/app">{intl.formatMessage(messages.changeServer)}</Link> |
202 | <a onClick={this.useLocalServer.bind(this)}>{intl.formatMessage(messages.serverless)}</a> | ||
187 | <Link to={loginRoute}>{intl.formatMessage(messages.loginLink)}</Link> | 203 | <Link to={loginRoute}>{intl.formatMessage(messages.loginLink)}</Link> |
188 | </div> | 204 | </div> |
189 | </div> | 205 | </div> |
diff --git a/src/components/auth/Welcome.js b/src/components/auth/Welcome.js index ef917e336..2ca8b430f 100644 --- a/src/components/auth/Welcome.js +++ b/src/components/auth/Welcome.js | |||
@@ -1,7 +1,9 @@ | |||
1 | /* eslint jsx-a11y/anchor-is-valid: 0 */ | ||
1 | import React, { Component } from 'react'; | 2 | import React, { Component } from 'react'; |
2 | import PropTypes from 'prop-types'; | 3 | import PropTypes from 'prop-types'; |
3 | import { observer, PropTypes as MobxPropTypes } from 'mobx-react'; | 4 | import { observer, PropTypes as MobxPropTypes, inject } from 'mobx-react'; |
4 | import { defineMessages, intlShape } from 'react-intl'; | 5 | import { defineMessages, intlShape } from 'react-intl'; |
6 | import serverlessLogin from '../../helpers/serverless-helpers'; | ||
5 | 7 | ||
6 | import Link from '../ui/Link'; | 8 | import Link from '../ui/Link'; |
7 | 9 | ||
@@ -14,19 +16,28 @@ const messages = defineMessages({ | |||
14 | id: 'welcome.loginButton', | 16 | id: 'welcome.loginButton', |
15 | defaultMessage: '!!!Login to your account', | 17 | defaultMessage: '!!!Login to your account', |
16 | }, | 18 | }, |
19 | serverless: { | ||
20 | id: 'services.serverless', | ||
21 | defaultMessage: '!!!Use Ferdi without an Account', | ||
22 | }, | ||
17 | }); | 23 | }); |
18 | 24 | ||
19 | export default @observer class Login extends Component { | 25 | export default @observer @inject('actions') class Login extends Component { |
20 | static propTypes = { | 26 | static propTypes = { |
21 | loginRoute: PropTypes.string.isRequired, | 27 | loginRoute: PropTypes.string.isRequired, |
22 | signupRoute: PropTypes.string.isRequired, | 28 | signupRoute: PropTypes.string.isRequired, |
23 | recipes: MobxPropTypes.arrayOrObservableArray.isRequired, | 29 | recipes: MobxPropTypes.arrayOrObservableArray.isRequired, |
30 | actions: PropTypes.object.isRequired, | ||
24 | }; | 31 | }; |
25 | 32 | ||
26 | static contextTypes = { | 33 | static contextTypes = { |
27 | intl: intlShape, | 34 | intl: intlShape, |
28 | }; | 35 | }; |
29 | 36 | ||
37 | useLocalServer() { | ||
38 | serverlessLogin(this.props.actions); | ||
39 | } | ||
40 | |||
30 | render() { | 41 | render() { |
31 | const { intl } = this.context; | 42 | const { intl } = this.context; |
32 | const { | 43 | const { |
@@ -53,6 +64,12 @@ export default @observer class Login extends Component { | |||
53 | </Link> | 64 | </Link> |
54 | <br /> | 65 | <br /> |
55 | <br /> | 66 | <br /> |
67 | <a className="button" onClick={this.useLocalServer.bind(this)}> | ||
68 | {intl.formatMessage(messages.serverless)} | ||
69 | </a> | ||
70 | <br /> | ||
71 | <br /> | ||
72 | |||
56 | 73 | ||
57 | <Link to="settings/app"> | 74 | <Link to="settings/app"> |
58 | <span style={{ | 75 | <span style={{ |
diff --git a/src/components/services/content/Services.js b/src/components/services/content/Services.js index caa3f3b23..80f17d8f2 100644 --- a/src/components/services/content/Services.js +++ b/src/components/services/content/Services.js | |||
@@ -1,6 +1,6 @@ | |||
1 | import React, { Component } from 'react'; | 1 | import React, { Component } from 'react'; |
2 | import PropTypes from 'prop-types'; | 2 | import PropTypes from 'prop-types'; |
3 | import { observer, PropTypes as MobxPropTypes } from 'mobx-react'; | 3 | import { observer, PropTypes as MobxPropTypes, inject } from 'mobx-react'; |
4 | import { Link } from 'react-router'; | 4 | import { Link } from 'react-router'; |
5 | import { defineMessages, intlShape } from 'react-intl'; | 5 | import { defineMessages, intlShape } from 'react-intl'; |
6 | import Confetti from 'react-confetti'; | 6 | import Confetti from 'react-confetti'; |
@@ -9,6 +9,7 @@ import injectSheet from 'react-jss'; | |||
9 | 9 | ||
10 | import ServiceView from './ServiceView'; | 10 | import ServiceView from './ServiceView'; |
11 | import Appear from '../../ui/effects/Appear'; | 11 | import Appear from '../../ui/effects/Appear'; |
12 | import serverlessLogin from '../../../helpers/serverless-helpers'; | ||
12 | 13 | ||
13 | const messages = defineMessages({ | 14 | const messages = defineMessages({ |
14 | welcome: { | 15 | welcome: { |
@@ -23,6 +24,10 @@ const messages = defineMessages({ | |||
23 | id: 'services.login', | 24 | id: 'services.login', |
24 | defaultMessage: '!!!Please login to use Ferdi.', | 25 | defaultMessage: '!!!Please login to use Ferdi.', |
25 | }, | 26 | }, |
27 | serverless: { | ||
28 | id: 'services.serverless', | ||
29 | defaultMessage: '!!!Use Ferdi without an Account', | ||
30 | }, | ||
26 | serverInfo: { | 31 | serverInfo: { |
27 | id: 'services.serverInfo', | 32 | id: 'services.serverInfo', |
28 | defaultMessage: '!!!Optionally, you can change your Ferdi server by clicking the cog in the bottom left corner.', | 33 | defaultMessage: '!!!Optionally, you can change your Ferdi server by clicking the cog in the bottom left corner.', |
@@ -39,7 +44,7 @@ const styles = { | |||
39 | }, | 44 | }, |
40 | }; | 45 | }; |
41 | 46 | ||
42 | export default @injectSheet(styles) @observer class Services extends Component { | 47 | export default @injectSheet(styles) @inject('actions') @observer class Services extends Component { |
43 | static propTypes = { | 48 | static propTypes = { |
44 | services: MobxPropTypes.arrayOrObservableArray, | 49 | services: MobxPropTypes.arrayOrObservableArray, |
45 | setWebviewReference: PropTypes.func.isRequired, | 50 | setWebviewReference: PropTypes.func.isRequired, |
@@ -52,6 +57,7 @@ export default @injectSheet(styles) @observer class Services extends Component { | |||
52 | userHasCompletedSignup: PropTypes.bool.isRequired, | 57 | userHasCompletedSignup: PropTypes.bool.isRequired, |
53 | hasActivatedTrial: PropTypes.bool.isRequired, | 58 | hasActivatedTrial: PropTypes.bool.isRequired, |
54 | classes: PropTypes.object.isRequired, | 59 | classes: PropTypes.object.isRequired, |
60 | actions: PropTypes.object.isRequired, | ||
55 | }; | 61 | }; |
56 | 62 | ||
57 | static defaultProps = { | 63 | static defaultProps = { |
@@ -68,6 +74,12 @@ export default @injectSheet(styles) @observer class Services extends Component { | |||
68 | 74 | ||
69 | _confettiTimeout = null; | 75 | _confettiTimeout = null; |
70 | 76 | ||
77 | constructor(props) { | ||
78 | super(props); | ||
79 | |||
80 | this.useLocalServer = this.useLocalServer.bind(this); | ||
81 | } | ||
82 | |||
71 | componentDidMount() { | 83 | componentDidMount() { |
72 | this._confettiTimeout = window.setTimeout(() => { | 84 | this._confettiTimeout = window.setTimeout(() => { |
73 | this.setState({ | 85 | this.setState({ |
@@ -82,6 +94,10 @@ export default @injectSheet(styles) @observer class Services extends Component { | |||
82 | } | 94 | } |
83 | } | 95 | } |
84 | 96 | ||
97 | useLocalServer() { | ||
98 | serverlessLogin(this.props.actions); | ||
99 | } | ||
100 | |||
85 | render() { | 101 | render() { |
86 | const { | 102 | const { |
87 | services, | 103 | services, |
@@ -136,6 +152,18 @@ export default @injectSheet(styles) @observer class Services extends Component { | |||
136 | <Link to={isLoggedIn ? '/settings/services' : '/auth/welcome'} className="button"> | 152 | <Link to={isLoggedIn ? '/settings/services' : '/auth/welcome'} className="button"> |
137 | { isLoggedIn ? intl.formatMessage(messages.getStarted) : 'Login' } | 153 | { isLoggedIn ? intl.formatMessage(messages.getStarted) : 'Login' } |
138 | </Link> | 154 | </Link> |
155 | {!isLoggedIn && ( | ||
156 | <button | ||
157 | type="button" | ||
158 | className="button" | ||
159 | style={{ | ||
160 | marginLeft: 10, | ||
161 | }} | ||
162 | onClick={this.useLocalServer} | ||
163 | > | ||
164 | {intl.formatMessage(messages.serverless)} | ||
165 | </button> | ||
166 | )} | ||
139 | </Appear> | 167 | </Appear> |
140 | </div> | 168 | </div> |
141 | </Appear> | 169 | </Appear> |
diff --git a/src/helpers/serverless-helpers.js b/src/helpers/serverless-helpers.js new file mode 100644 index 000000000..741bce7f9 --- /dev/null +++ b/src/helpers/serverless-helpers.js | |||
@@ -0,0 +1,16 @@ | |||
1 | export default function useLocalServer(actions) { | ||
2 | // Use local server for user | ||
3 | actions.settings.update({ | ||
4 | type: 'app', | ||
5 | data: { | ||
6 | server: 'http://localhost:45569', | ||
7 | }, | ||
8 | }); | ||
9 | |||
10 | // Log into local server | ||
11 | // Credentials are ignored by the server but the client requires them | ||
12 | actions.user.login({ | ||
13 | email: 'ferdi@localhost', | ||
14 | password: 'FERDI_', | ||
15 | }); | ||
16 | } | ||
diff --git a/src/i18n/locales/defaultMessages.json b/src/i18n/locales/defaultMessages.json index 6f380af3b..66b41ac21 100644 --- a/src/i18n/locales/defaultMessages.json +++ b/src/i18n/locales/defaultMessages.json | |||
@@ -272,143 +272,169 @@ | |||
272 | "defaultMessage": "!!!Sign in", | 272 | "defaultMessage": "!!!Sign in", |
273 | "end": { | 273 | "end": { |
274 | "column": 3, | 274 | "column": 3, |
275 | "line": 20 | 275 | "line": 22 |
276 | }, | 276 | }, |
277 | "file": "src/components/auth/Login.js", | 277 | "file": "src/components/auth/Login.js", |
278 | "id": "login.headline", | 278 | "id": "login.headline", |
279 | "start": { | 279 | "start": { |
280 | "column": 12, | 280 | "column": 12, |
281 | "line": 17 | 281 | "line": 19 |
282 | } | 282 | } |
283 | }, | 283 | }, |
284 | { | 284 | { |
285 | "defaultMessage": "!!!Email address", | 285 | "defaultMessage": "!!!Email address", |
286 | "end": { | 286 | "end": { |
287 | "column": 3, | 287 | "column": 3, |
288 | "line": 24 | 288 | "line": 26 |
289 | }, | 289 | }, |
290 | "file": "src/components/auth/Login.js", | 290 | "file": "src/components/auth/Login.js", |
291 | "id": "login.email.label", | 291 | "id": "login.email.label", |
292 | "start": { | 292 | "start": { |
293 | "column": 14, | 293 | "column": 14, |
294 | "line": 21 | 294 | "line": 23 |
295 | } | 295 | } |
296 | }, | 296 | }, |
297 | { | 297 | { |
298 | "defaultMessage": "!!!Password", | 298 | "defaultMessage": "!!!Password", |
299 | "end": { | 299 | "end": { |
300 | "column": 3, | 300 | "column": 3, |
301 | "line": 28 | 301 | "line": 30 |
302 | }, | 302 | }, |
303 | "file": "src/components/auth/Login.js", | 303 | "file": "src/components/auth/Login.js", |
304 | "id": "login.password.label", | 304 | "id": "login.password.label", |
305 | "start": { | 305 | "start": { |
306 | "column": 17, | 306 | "column": 17, |
307 | "line": 25 | 307 | "line": 27 |
308 | } | 308 | } |
309 | }, | 309 | }, |
310 | { | 310 | { |
311 | "defaultMessage": "!!!Sign in", | 311 | "defaultMessage": "!!!Sign in", |
312 | "end": { | 312 | "end": { |
313 | "column": 3, | 313 | "column": 3, |
314 | "line": 32 | 314 | "line": 34 |
315 | }, | 315 | }, |
316 | "file": "src/components/auth/Login.js", | 316 | "file": "src/components/auth/Login.js", |
317 | "id": "login.submit.label", | 317 | "id": "login.submit.label", |
318 | "start": { | 318 | "start": { |
319 | "column": 21, | 319 | "column": 21, |
320 | "line": 29 | 320 | "line": 31 |
321 | } | 321 | } |
322 | }, | 322 | }, |
323 | { | 323 | { |
324 | "defaultMessage": "!!!Email or password not valid", | 324 | "defaultMessage": "!!!Email or password not valid", |
325 | "end": { | 325 | "end": { |
326 | "column": 3, | 326 | "column": 3, |
327 | "line": 36 | 327 | "line": 38 |
328 | }, | 328 | }, |
329 | "file": "src/components/auth/Login.js", | 329 | "file": "src/components/auth/Login.js", |
330 | "id": "login.invalidCredentials", | 330 | "id": "login.invalidCredentials", |
331 | "start": { | 331 | "start": { |
332 | "column": 22, | 332 | "column": 22, |
333 | "line": 33 | 333 | "line": 35 |
334 | } | 334 | } |
335 | }, | 335 | }, |
336 | { | 336 | { |
337 | "defaultMessage": "!!!Using a Franz account to log in?", | 337 | "defaultMessage": "!!!Using a Franz account to log in?", |
338 | "end": { | 338 | "end": { |
339 | "column": 3, | 339 | "column": 3, |
340 | "line": 40 | 340 | "line": 42 |
341 | }, | 341 | }, |
342 | "file": "src/components/auth/Login.js", | 342 | "file": "src/components/auth/Login.js", |
343 | "id": "login.customServerQuestion", | 343 | "id": "login.customServerQuestion", |
344 | "start": { | 344 | "start": { |
345 | "column": 24, | 345 | "column": 24, |
346 | "line": 37 | 346 | "line": 39 |
347 | } | 347 | } |
348 | }, | 348 | }, |
349 | { | 349 | { |
350 | "defaultMessage": "!!!Try importing your Franz account into Ferdi", | 350 | "defaultMessage": "!!!Try importing your Franz account into Ferdi", |
351 | "end": { | 351 | "end": { |
352 | "column": 3, | 352 | "column": 3, |
353 | "line": 44 | 353 | "line": 46 |
354 | }, | 354 | }, |
355 | "file": "src/components/auth/Login.js", | 355 | "file": "src/components/auth/Login.js", |
356 | "id": "login.customServerSuggestion", | 356 | "id": "login.customServerSuggestion", |
357 | "start": { | 357 | "start": { |
358 | "column": 26, | 358 | "column": 26, |
359 | "line": 41 | 359 | "line": 43 |
360 | } | 360 | } |
361 | }, | 361 | }, |
362 | { | 362 | { |
363 | "defaultMessage": "!!!Your session expired, please login again.", | 363 | "defaultMessage": "!!!Your session expired, please login again.", |
364 | "end": { | 364 | "end": { |
365 | "column": 3, | 365 | "column": 3, |
366 | "line": 48 | 366 | "line": 50 |
367 | }, | 367 | }, |
368 | "file": "src/components/auth/Login.js", | 368 | "file": "src/components/auth/Login.js", |
369 | "id": "login.tokenExpired", | 369 | "id": "login.tokenExpired", |
370 | "start": { | 370 | "start": { |
371 | "column": 16, | 371 | "column": 16, |
372 | "line": 45 | 372 | "line": 47 |
373 | } | 373 | } |
374 | }, | 374 | }, |
375 | { | 375 | { |
376 | "defaultMessage": "!!!Your session expired, please login again.", | 376 | "defaultMessage": "!!!Your session expired, please login again.", |
377 | "end": { | 377 | "end": { |
378 | "column": 3, | 378 | "column": 3, |
379 | "line": 52 | 379 | "line": 54 |
380 | }, | 380 | }, |
381 | "file": "src/components/auth/Login.js", | 381 | "file": "src/components/auth/Login.js", |
382 | "id": "login.serverLogout", | 382 | "id": "login.serverLogout", |
383 | "start": { | 383 | "start": { |
384 | "column": 16, | 384 | "column": 16, |
385 | "line": 49 | 385 | "line": 51 |
386 | } | 386 | } |
387 | }, | 387 | }, |
388 | { | 388 | { |
389 | "defaultMessage": "!!!Create a free account", | 389 | "defaultMessage": "!!!Create a free account", |
390 | "end": { | 390 | "end": { |
391 | "column": 3, | 391 | "column": 3, |
392 | "line": 56 | 392 | "line": 58 |
393 | }, | 393 | }, |
394 | "file": "src/components/auth/Login.js", | 394 | "file": "src/components/auth/Login.js", |
395 | "id": "login.link.signup", | 395 | "id": "login.link.signup", |
396 | "start": { | 396 | "start": { |
397 | "column": 14, | 397 | "column": 14, |
398 | "line": 53 | 398 | "line": 55 |
399 | } | ||
400 | }, | ||
401 | { | ||
402 | "defaultMessage": "!!!Change server", | ||
403 | "end": { | ||
404 | "column": 3, | ||
405 | "line": 62 | ||
406 | }, | ||
407 | "file": "src/components/auth/Login.js", | ||
408 | "id": "login.changeServer", | ||
409 | "start": { | ||
410 | "column": 16, | ||
411 | "line": 59 | ||
412 | } | ||
413 | }, | ||
414 | { | ||
415 | "defaultMessage": "!!!Use Ferdi without an Account", | ||
416 | "end": { | ||
417 | "column": 3, | ||
418 | "line": 66 | ||
419 | }, | ||
420 | "file": "src/components/auth/Login.js", | ||
421 | "id": "services.serverless", | ||
422 | "start": { | ||
423 | "column": 14, | ||
424 | "line": 63 | ||
399 | } | 425 | } |
400 | }, | 426 | }, |
401 | { | 427 | { |
402 | "defaultMessage": "!!!Forgot password", | 428 | "defaultMessage": "!!!Forgot password", |
403 | "end": { | 429 | "end": { |
404 | "column": 3, | 430 | "column": 3, |
405 | "line": 60 | 431 | "line": 70 |
406 | }, | 432 | }, |
407 | "file": "src/components/auth/Login.js", | 433 | "file": "src/components/auth/Login.js", |
408 | "id": "login.link.password", | 434 | "id": "login.link.password", |
409 | "start": { | 435 | "start": { |
410 | "column": 16, | 436 | "column": 16, |
411 | "line": 57 | 437 | "line": 67 |
412 | } | 438 | } |
413 | } | 439 | } |
414 | ], | 440 | ], |
@@ -638,143 +664,169 @@ | |||
638 | "defaultMessage": "!!!Sign up", | 664 | "defaultMessage": "!!!Sign up", |
639 | "end": { | 665 | "end": { |
640 | "column": 3, | 666 | "column": 3, |
641 | "line": 20 | 667 | "line": 22 |
642 | }, | 668 | }, |
643 | "file": "src/components/auth/Signup.js", | 669 | "file": "src/components/auth/Signup.js", |
644 | "id": "signup.headline", | 670 | "id": "signup.headline", |
645 | "start": { | 671 | "start": { |
646 | "column": 12, | 672 | "column": 12, |
647 | "line": 17 | 673 | "line": 19 |
648 | } | 674 | } |
649 | }, | 675 | }, |
650 | { | 676 | { |
651 | "defaultMessage": "!!!Firstname", | 677 | "defaultMessage": "!!!Firstname", |
652 | "end": { | 678 | "end": { |
653 | "column": 3, | 679 | "column": 3, |
654 | "line": 24 | 680 | "line": 26 |
655 | }, | 681 | }, |
656 | "file": "src/components/auth/Signup.js", | 682 | "file": "src/components/auth/Signup.js", |
657 | "id": "signup.firstname.label", | 683 | "id": "signup.firstname.label", |
658 | "start": { | 684 | "start": { |
659 | "column": 18, | 685 | "column": 18, |
660 | "line": 21 | 686 | "line": 23 |
661 | } | 687 | } |
662 | }, | 688 | }, |
663 | { | 689 | { |
664 | "defaultMessage": "!!!Lastname", | 690 | "defaultMessage": "!!!Lastname", |
665 | "end": { | 691 | "end": { |
666 | "column": 3, | 692 | "column": 3, |
667 | "line": 28 | 693 | "line": 30 |
668 | }, | 694 | }, |
669 | "file": "src/components/auth/Signup.js", | 695 | "file": "src/components/auth/Signup.js", |
670 | "id": "signup.lastname.label", | 696 | "id": "signup.lastname.label", |
671 | "start": { | 697 | "start": { |
672 | "column": 17, | 698 | "column": 17, |
673 | "line": 25 | 699 | "line": 27 |
674 | } | 700 | } |
675 | }, | 701 | }, |
676 | { | 702 | { |
677 | "defaultMessage": "!!!Email address", | 703 | "defaultMessage": "!!!Email address", |
678 | "end": { | 704 | "end": { |
679 | "column": 3, | 705 | "column": 3, |
680 | "line": 32 | 706 | "line": 34 |
681 | }, | 707 | }, |
682 | "file": "src/components/auth/Signup.js", | 708 | "file": "src/components/auth/Signup.js", |
683 | "id": "signup.email.label", | 709 | "id": "signup.email.label", |
684 | "start": { | 710 | "start": { |
685 | "column": 14, | 711 | "column": 14, |
686 | "line": 29 | 712 | "line": 31 |
687 | } | 713 | } |
688 | }, | 714 | }, |
689 | { | 715 | { |
690 | "defaultMessage": "!!!Password", | 716 | "defaultMessage": "!!!Password", |
691 | "end": { | 717 | "end": { |
692 | "column": 3, | 718 | "column": 3, |
693 | "line": 40 | 719 | "line": 42 |
694 | }, | 720 | }, |
695 | "file": "src/components/auth/Signup.js", | 721 | "file": "src/components/auth/Signup.js", |
696 | "id": "signup.password.label", | 722 | "id": "signup.password.label", |
697 | "start": { | 723 | "start": { |
698 | "column": 17, | 724 | "column": 17, |
699 | "line": 37 | 725 | "line": 39 |
700 | } | 726 | } |
701 | }, | 727 | }, |
702 | { | 728 | { |
703 | "defaultMessage": "!!!By creating a Ferdi account you accept the", | 729 | "defaultMessage": "!!!By creating a Ferdi account you accept the", |
704 | "end": { | 730 | "end": { |
705 | "column": 3, | 731 | "column": 3, |
706 | "line": 44 | 732 | "line": 46 |
707 | }, | 733 | }, |
708 | "file": "src/components/auth/Signup.js", | 734 | "file": "src/components/auth/Signup.js", |
709 | "id": "signup.legal.info", | 735 | "id": "signup.legal.info", |
710 | "start": { | 736 | "start": { |
711 | "column": 13, | 737 | "column": 13, |
712 | "line": 41 | 738 | "line": 43 |
713 | } | 739 | } |
714 | }, | 740 | }, |
715 | { | 741 | { |
716 | "defaultMessage": "!!!Terms of service", | 742 | "defaultMessage": "!!!Terms of service", |
717 | "end": { | 743 | "end": { |
718 | "column": 3, | 744 | "column": 3, |
719 | "line": 48 | 745 | "line": 50 |
720 | }, | 746 | }, |
721 | "file": "src/components/auth/Signup.js", | 747 | "file": "src/components/auth/Signup.js", |
722 | "id": "signup.legal.terms", | 748 | "id": "signup.legal.terms", |
723 | "start": { | 749 | "start": { |
724 | "column": 9, | 750 | "column": 9, |
725 | "line": 45 | 751 | "line": 47 |
726 | } | 752 | } |
727 | }, | 753 | }, |
728 | { | 754 | { |
729 | "defaultMessage": "!!!Privacy Statement", | 755 | "defaultMessage": "!!!Privacy Statement", |
730 | "end": { | 756 | "end": { |
731 | "column": 3, | 757 | "column": 3, |
732 | "line": 52 | 758 | "line": 54 |
733 | }, | 759 | }, |
734 | "file": "src/components/auth/Signup.js", | 760 | "file": "src/components/auth/Signup.js", |
735 | "id": "signup.legal.privacy", | 761 | "id": "signup.legal.privacy", |
736 | "start": { | 762 | "start": { |
737 | "column": 11, | 763 | "column": 11, |
738 | "line": 49 | 764 | "line": 51 |
739 | } | 765 | } |
740 | }, | 766 | }, |
741 | { | 767 | { |
742 | "defaultMessage": "!!!Create account", | 768 | "defaultMessage": "!!!Create account", |
743 | "end": { | 769 | "end": { |
744 | "column": 3, | 770 | "column": 3, |
745 | "line": 56 | 771 | "line": 58 |
746 | }, | 772 | }, |
747 | "file": "src/components/auth/Signup.js", | 773 | "file": "src/components/auth/Signup.js", |
748 | "id": "signup.submit.label", | 774 | "id": "signup.submit.label", |
749 | "start": { | 775 | "start": { |
750 | "column": 21, | 776 | "column": 21, |
751 | "line": 53 | 777 | "line": 55 |
752 | } | 778 | } |
753 | }, | 779 | }, |
754 | { | 780 | { |
755 | "defaultMessage": "!!!Already have an account, sign in?", | 781 | "defaultMessage": "!!!Already have an account, sign in?", |
756 | "end": { | 782 | "end": { |
757 | "column": 3, | 783 | "column": 3, |
758 | "line": 60 | 784 | "line": 62 |
759 | }, | 785 | }, |
760 | "file": "src/components/auth/Signup.js", | 786 | "file": "src/components/auth/Signup.js", |
761 | "id": "signup.link.login", | 787 | "id": "signup.link.login", |
762 | "start": { | 788 | "start": { |
763 | "column": 13, | 789 | "column": 13, |
764 | "line": 57 | 790 | "line": 59 |
791 | } | ||
792 | }, | ||
793 | { | ||
794 | "defaultMessage": "!!!Change server", | ||
795 | "end": { | ||
796 | "column": 3, | ||
797 | "line": 66 | ||
798 | }, | ||
799 | "file": "src/components/auth/Signup.js", | ||
800 | "id": "login.changeServer", | ||
801 | "start": { | ||
802 | "column": 16, | ||
803 | "line": 63 | ||
804 | } | ||
805 | }, | ||
806 | { | ||
807 | "defaultMessage": "!!!Use Ferdi without an Account", | ||
808 | "end": { | ||
809 | "column": 3, | ||
810 | "line": 70 | ||
811 | }, | ||
812 | "file": "src/components/auth/Signup.js", | ||
813 | "id": "services.serverless", | ||
814 | "start": { | ||
815 | "column": 14, | ||
816 | "line": 67 | ||
765 | } | 817 | } |
766 | }, | 818 | }, |
767 | { | 819 | { |
768 | "defaultMessage": "!!!A user with that email address already exists", | 820 | "defaultMessage": "!!!A user with that email address already exists", |
769 | "end": { | 821 | "end": { |
770 | "column": 3, | 822 | "column": 3, |
771 | "line": 64 | 823 | "line": 74 |
772 | }, | 824 | }, |
773 | "file": "src/components/auth/Signup.js", | 825 | "file": "src/components/auth/Signup.js", |
774 | "id": "signup.emailDuplicate", | 826 | "id": "signup.emailDuplicate", |
775 | "start": { | 827 | "start": { |
776 | "column": 18, | 828 | "column": 18, |
777 | "line": 61 | 829 | "line": 71 |
778 | } | 830 | } |
779 | } | 831 | } |
780 | ], | 832 | ], |
@@ -786,26 +838,39 @@ | |||
786 | "defaultMessage": "!!!Create a free account", | 838 | "defaultMessage": "!!!Create a free account", |
787 | "end": { | 839 | "end": { |
788 | "column": 3, | 840 | "column": 3, |
789 | "line": 12 | 841 | "line": 14 |
790 | }, | 842 | }, |
791 | "file": "src/components/auth/Welcome.js", | 843 | "file": "src/components/auth/Welcome.js", |
792 | "id": "welcome.signupButton", | 844 | "id": "welcome.signupButton", |
793 | "start": { | 845 | "start": { |
794 | "column": 16, | 846 | "column": 16, |
795 | "line": 9 | 847 | "line": 11 |
796 | } | 848 | } |
797 | }, | 849 | }, |
798 | { | 850 | { |
799 | "defaultMessage": "!!!Login to your account", | 851 | "defaultMessage": "!!!Login to your account", |
800 | "end": { | 852 | "end": { |
801 | "column": 3, | 853 | "column": 3, |
802 | "line": 16 | 854 | "line": 18 |
803 | }, | 855 | }, |
804 | "file": "src/components/auth/Welcome.js", | 856 | "file": "src/components/auth/Welcome.js", |
805 | "id": "welcome.loginButton", | 857 | "id": "welcome.loginButton", |
806 | "start": { | 858 | "start": { |
807 | "column": 15, | 859 | "column": 15, |
808 | "line": 13 | 860 | "line": 15 |
861 | } | ||
862 | }, | ||
863 | { | ||
864 | "defaultMessage": "!!!Use Ferdi without an Account", | ||
865 | "end": { | ||
866 | "column": 3, | ||
867 | "line": 22 | ||
868 | }, | ||
869 | "file": "src/components/auth/Welcome.js", | ||
870 | "id": "services.serverless", | ||
871 | "start": { | ||
872 | "column": 14, | ||
873 | "line": 19 | ||
809 | } | 874 | } |
810 | } | 875 | } |
811 | ], | 876 | ], |
@@ -1167,52 +1232,65 @@ | |||
1167 | "defaultMessage": "!!!Welcome to Ferdi", | 1232 | "defaultMessage": "!!!Welcome to Ferdi", |
1168 | "end": { | 1233 | "end": { |
1169 | "column": 3, | 1234 | "column": 3, |
1170 | "line": 17 | 1235 | "line": 18 |
1171 | }, | 1236 | }, |
1172 | "file": "src/components/services/content/Services.js", | 1237 | "file": "src/components/services/content/Services.js", |
1173 | "id": "services.welcome", | 1238 | "id": "services.welcome", |
1174 | "start": { | 1239 | "start": { |
1175 | "column": 11, | 1240 | "column": 11, |
1176 | "line": 14 | 1241 | "line": 15 |
1177 | } | 1242 | } |
1178 | }, | 1243 | }, |
1179 | { | 1244 | { |
1180 | "defaultMessage": "!!!Get started", | 1245 | "defaultMessage": "!!!Get started", |
1181 | "end": { | 1246 | "end": { |
1182 | "column": 3, | 1247 | "column": 3, |
1183 | "line": 21 | 1248 | "line": 22 |
1184 | }, | 1249 | }, |
1185 | "file": "src/components/services/content/Services.js", | 1250 | "file": "src/components/services/content/Services.js", |
1186 | "id": "services.getStarted", | 1251 | "id": "services.getStarted", |
1187 | "start": { | 1252 | "start": { |
1188 | "column": 14, | 1253 | "column": 14, |
1189 | "line": 18 | 1254 | "line": 19 |
1190 | } | 1255 | } |
1191 | }, | 1256 | }, |
1192 | { | 1257 | { |
1193 | "defaultMessage": "!!!Please login to use Ferdi.", | 1258 | "defaultMessage": "!!!Please login to use Ferdi.", |
1194 | "end": { | 1259 | "end": { |
1195 | "column": 3, | 1260 | "column": 3, |
1196 | "line": 25 | 1261 | "line": 26 |
1197 | }, | 1262 | }, |
1198 | "file": "src/components/services/content/Services.js", | 1263 | "file": "src/components/services/content/Services.js", |
1199 | "id": "services.login", | 1264 | "id": "services.login", |
1200 | "start": { | 1265 | "start": { |
1201 | "column": 9, | 1266 | "column": 9, |
1202 | "line": 22 | 1267 | "line": 23 |
1268 | } | ||
1269 | }, | ||
1270 | { | ||
1271 | "defaultMessage": "!!!Use Ferdi without an Account", | ||
1272 | "end": { | ||
1273 | "column": 3, | ||
1274 | "line": 30 | ||
1275 | }, | ||
1276 | "file": "src/components/services/content/Services.js", | ||
1277 | "id": "services.serverless", | ||
1278 | "start": { | ||
1279 | "column": 14, | ||
1280 | "line": 27 | ||
1203 | } | 1281 | } |
1204 | }, | 1282 | }, |
1205 | { | 1283 | { |
1206 | "defaultMessage": "!!!Optionally, you can change your Ferdi server by clicking the cog in the bottom left corner.", | 1284 | "defaultMessage": "!!!Optionally, you can change your Ferdi server by clicking the cog in the bottom left corner.", |
1207 | "end": { | 1285 | "end": { |
1208 | "column": 3, | 1286 | "column": 3, |
1209 | "line": 29 | 1287 | "line": 34 |
1210 | }, | 1288 | }, |
1211 | "file": "src/components/services/content/Services.js", | 1289 | "file": "src/components/services/content/Services.js", |
1212 | "id": "services.serverInfo", | 1290 | "id": "services.serverInfo", |
1213 | "start": { | 1291 | "start": { |
1214 | "column": 14, | 1292 | "column": 14, |
1215 | "line": 26 | 1293 | "line": 31 |
1216 | } | 1294 | } |
1217 | } | 1295 | } |
1218 | ], | 1296 | ], |
diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json index e1ee6f824..6f28e2bfc 100644 --- a/src/i18n/locales/en-US.json +++ b/src/i18n/locales/en-US.json | |||
@@ -54,6 +54,7 @@ | |||
54 | "locked.invalidCredentials": "Password invalid", | 54 | "locked.invalidCredentials": "Password invalid", |
55 | "locked.password.label": "Password", | 55 | "locked.password.label": "Password", |
56 | "locked.submit.label": "Unlock", | 56 | "locked.submit.label": "Unlock", |
57 | "login.changeServer": "Change server", | ||
57 | "login.customServerQuestion": "Using a Franz account to log in?", | 58 | "login.customServerQuestion": "Using a Franz account to log in?", |
58 | "login.customServerSuggestion": "Try importing your Franz account into Ferdi", | 59 | "login.customServerSuggestion": "Try importing your Franz account into Ferdi", |
59 | "login.email.label": "Email address", | 60 | "login.email.label": "Email address", |
@@ -186,6 +187,7 @@ | |||
186 | "services.getStarted": "Get started", | 187 | "services.getStarted": "Get started", |
187 | "services.login": "Please login to use Ferdi.", | 188 | "services.login": "Please login to use Ferdi.", |
188 | "services.serverInfo": "Optionally, you can change your Ferdi server by clicking the cog in the bottom left corner.", | 189 | "services.serverInfo": "Optionally, you can change your Ferdi server by clicking the cog in the bottom left corner.", |
190 | "services.serverless": "Use Ferdi without an Account", | ||
189 | "services.welcome": "Welcome to Ferdi", | 191 | "services.welcome": "Welcome to Ferdi", |
190 | "settings.account.account.editButton": "Edit account", | 192 | "settings.account.account.editButton": "Edit account", |
191 | "settings.account.accountType.basic": "Basic Account", | 193 | "settings.account.accountType.basic": "Basic Account", |
diff --git a/src/i18n/messages/src/components/auth/Login.json b/src/i18n/messages/src/components/auth/Login.json index 7e4b32294..c3b4ee4eb 100644 --- a/src/i18n/messages/src/components/auth/Login.json +++ b/src/i18n/messages/src/components/auth/Login.json | |||
@@ -4,11 +4,11 @@ | |||
4 | "defaultMessage": "!!!Sign in", | 4 | "defaultMessage": "!!!Sign in", |
5 | "file": "src/components/auth/Login.js", | 5 | "file": "src/components/auth/Login.js", |
6 | "start": { | 6 | "start": { |
7 | "line": 17, | 7 | "line": 19, |
8 | "column": 12 | 8 | "column": 12 |
9 | }, | 9 | }, |
10 | "end": { | 10 | "end": { |
11 | "line": 20, | 11 | "line": 22, |
12 | "column": 3 | 12 | "column": 3 |
13 | } | 13 | } |
14 | }, | 14 | }, |
@@ -17,11 +17,11 @@ | |||
17 | "defaultMessage": "!!!Email address", | 17 | "defaultMessage": "!!!Email address", |
18 | "file": "src/components/auth/Login.js", | 18 | "file": "src/components/auth/Login.js", |
19 | "start": { | 19 | "start": { |
20 | "line": 21, | 20 | "line": 23, |
21 | "column": 14 | 21 | "column": 14 |
22 | }, | 22 | }, |
23 | "end": { | 23 | "end": { |
24 | "line": 24, | 24 | "line": 26, |
25 | "column": 3 | 25 | "column": 3 |
26 | } | 26 | } |
27 | }, | 27 | }, |
@@ -30,11 +30,11 @@ | |||
30 | "defaultMessage": "!!!Password", | 30 | "defaultMessage": "!!!Password", |
31 | "file": "src/components/auth/Login.js", | 31 | "file": "src/components/auth/Login.js", |
32 | "start": { | 32 | "start": { |
33 | "line": 25, | 33 | "line": 27, |
34 | "column": 17 | 34 | "column": 17 |
35 | }, | 35 | }, |
36 | "end": { | 36 | "end": { |
37 | "line": 28, | 37 | "line": 30, |
38 | "column": 3 | 38 | "column": 3 |
39 | } | 39 | } |
40 | }, | 40 | }, |
@@ -43,11 +43,11 @@ | |||
43 | "defaultMessage": "!!!Sign in", | 43 | "defaultMessage": "!!!Sign in", |
44 | "file": "src/components/auth/Login.js", | 44 | "file": "src/components/auth/Login.js", |
45 | "start": { | 45 | "start": { |
46 | "line": 29, | 46 | "line": 31, |
47 | "column": 21 | 47 | "column": 21 |
48 | }, | 48 | }, |
49 | "end": { | 49 | "end": { |
50 | "line": 32, | 50 | "line": 34, |
51 | "column": 3 | 51 | "column": 3 |
52 | } | 52 | } |
53 | }, | 53 | }, |
@@ -56,11 +56,11 @@ | |||
56 | "defaultMessage": "!!!Email or password not valid", | 56 | "defaultMessage": "!!!Email or password not valid", |
57 | "file": "src/components/auth/Login.js", | 57 | "file": "src/components/auth/Login.js", |
58 | "start": { | 58 | "start": { |
59 | "line": 33, | 59 | "line": 35, |
60 | "column": 22 | 60 | "column": 22 |
61 | }, | 61 | }, |
62 | "end": { | 62 | "end": { |
63 | "line": 36, | 63 | "line": 38, |
64 | "column": 3 | 64 | "column": 3 |
65 | } | 65 | } |
66 | }, | 66 | }, |
@@ -69,11 +69,11 @@ | |||
69 | "defaultMessage": "!!!Using a Franz account to log in?", | 69 | "defaultMessage": "!!!Using a Franz account to log in?", |
70 | "file": "src/components/auth/Login.js", | 70 | "file": "src/components/auth/Login.js", |
71 | "start": { | 71 | "start": { |
72 | "line": 37, | 72 | "line": 39, |
73 | "column": 24 | 73 | "column": 24 |
74 | }, | 74 | }, |
75 | "end": { | 75 | "end": { |
76 | "line": 40, | 76 | "line": 42, |
77 | "column": 3 | 77 | "column": 3 |
78 | } | 78 | } |
79 | }, | 79 | }, |
@@ -82,11 +82,11 @@ | |||
82 | "defaultMessage": "!!!Try importing your Franz account into Ferdi", | 82 | "defaultMessage": "!!!Try importing your Franz account into Ferdi", |
83 | "file": "src/components/auth/Login.js", | 83 | "file": "src/components/auth/Login.js", |
84 | "start": { | 84 | "start": { |
85 | "line": 41, | 85 | "line": 43, |
86 | "column": 26 | 86 | "column": 26 |
87 | }, | 87 | }, |
88 | "end": { | 88 | "end": { |
89 | "line": 44, | 89 | "line": 46, |
90 | "column": 3 | 90 | "column": 3 |
91 | } | 91 | } |
92 | }, | 92 | }, |
@@ -95,11 +95,11 @@ | |||
95 | "defaultMessage": "!!!Your session expired, please login again.", | 95 | "defaultMessage": "!!!Your session expired, please login again.", |
96 | "file": "src/components/auth/Login.js", | 96 | "file": "src/components/auth/Login.js", |
97 | "start": { | 97 | "start": { |
98 | "line": 45, | 98 | "line": 47, |
99 | "column": 16 | 99 | "column": 16 |
100 | }, | 100 | }, |
101 | "end": { | 101 | "end": { |
102 | "line": 48, | 102 | "line": 50, |
103 | "column": 3 | 103 | "column": 3 |
104 | } | 104 | } |
105 | }, | 105 | }, |
@@ -108,11 +108,11 @@ | |||
108 | "defaultMessage": "!!!Your session expired, please login again.", | 108 | "defaultMessage": "!!!Your session expired, please login again.", |
109 | "file": "src/components/auth/Login.js", | 109 | "file": "src/components/auth/Login.js", |
110 | "start": { | 110 | "start": { |
111 | "line": 49, | 111 | "line": 51, |
112 | "column": 16 | 112 | "column": 16 |
113 | }, | 113 | }, |
114 | "end": { | 114 | "end": { |
115 | "line": 52, | 115 | "line": 54, |
116 | "column": 3 | 116 | "column": 3 |
117 | } | 117 | } |
118 | }, | 118 | }, |
@@ -121,11 +121,37 @@ | |||
121 | "defaultMessage": "!!!Create a free account", | 121 | "defaultMessage": "!!!Create a free account", |
122 | "file": "src/components/auth/Login.js", | 122 | "file": "src/components/auth/Login.js", |
123 | "start": { | 123 | "start": { |
124 | "line": 53, | 124 | "line": 55, |
125 | "column": 14 | 125 | "column": 14 |
126 | }, | 126 | }, |
127 | "end": { | 127 | "end": { |
128 | "line": 56, | 128 | "line": 58, |
129 | "column": 3 | ||
130 | } | ||
131 | }, | ||
132 | { | ||
133 | "id": "login.changeServer", | ||
134 | "defaultMessage": "!!!Change server", | ||
135 | "file": "src/components/auth/Login.js", | ||
136 | "start": { | ||
137 | "line": 59, | ||
138 | "column": 16 | ||
139 | }, | ||
140 | "end": { | ||
141 | "line": 62, | ||
142 | "column": 3 | ||
143 | } | ||
144 | }, | ||
145 | { | ||
146 | "id": "services.serverless", | ||
147 | "defaultMessage": "!!!Use Ferdi without an Account", | ||
148 | "file": "src/components/auth/Login.js", | ||
149 | "start": { | ||
150 | "line": 63, | ||
151 | "column": 14 | ||
152 | }, | ||
153 | "end": { | ||
154 | "line": 66, | ||
129 | "column": 3 | 155 | "column": 3 |
130 | } | 156 | } |
131 | }, | 157 | }, |
@@ -134,11 +160,11 @@ | |||
134 | "defaultMessage": "!!!Forgot password", | 160 | "defaultMessage": "!!!Forgot password", |
135 | "file": "src/components/auth/Login.js", | 161 | "file": "src/components/auth/Login.js", |
136 | "start": { | 162 | "start": { |
137 | "line": 57, | 163 | "line": 67, |
138 | "column": 16 | 164 | "column": 16 |
139 | }, | 165 | }, |
140 | "end": { | 166 | "end": { |
141 | "line": 60, | 167 | "line": 70, |
142 | "column": 3 | 168 | "column": 3 |
143 | } | 169 | } |
144 | } | 170 | } |
diff --git a/src/i18n/messages/src/components/auth/Signup.json b/src/i18n/messages/src/components/auth/Signup.json index 9aa7b25ab..2628c9aa3 100644 --- a/src/i18n/messages/src/components/auth/Signup.json +++ b/src/i18n/messages/src/components/auth/Signup.json | |||
@@ -4,11 +4,11 @@ | |||
4 | "defaultMessage": "!!!Sign up", | 4 | "defaultMessage": "!!!Sign up", |
5 | "file": "src/components/auth/Signup.js", | 5 | "file": "src/components/auth/Signup.js", |
6 | "start": { | 6 | "start": { |
7 | "line": 17, | 7 | "line": 19, |
8 | "column": 12 | 8 | "column": 12 |
9 | }, | 9 | }, |
10 | "end": { | 10 | "end": { |
11 | "line": 20, | 11 | "line": 22, |
12 | "column": 3 | 12 | "column": 3 |
13 | } | 13 | } |
14 | }, | 14 | }, |
@@ -17,11 +17,11 @@ | |||
17 | "defaultMessage": "!!!Firstname", | 17 | "defaultMessage": "!!!Firstname", |
18 | "file": "src/components/auth/Signup.js", | 18 | "file": "src/components/auth/Signup.js", |
19 | "start": { | 19 | "start": { |
20 | "line": 21, | 20 | "line": 23, |
21 | "column": 18 | 21 | "column": 18 |
22 | }, | 22 | }, |
23 | "end": { | 23 | "end": { |
24 | "line": 24, | 24 | "line": 26, |
25 | "column": 3 | 25 | "column": 3 |
26 | } | 26 | } |
27 | }, | 27 | }, |
@@ -30,11 +30,11 @@ | |||
30 | "defaultMessage": "!!!Lastname", | 30 | "defaultMessage": "!!!Lastname", |
31 | "file": "src/components/auth/Signup.js", | 31 | "file": "src/components/auth/Signup.js", |
32 | "start": { | 32 | "start": { |
33 | "line": 25, | 33 | "line": 27, |
34 | "column": 17 | 34 | "column": 17 |
35 | }, | 35 | }, |
36 | "end": { | 36 | "end": { |
37 | "line": 28, | 37 | "line": 30, |
38 | "column": 3 | 38 | "column": 3 |
39 | } | 39 | } |
40 | }, | 40 | }, |
@@ -43,11 +43,11 @@ | |||
43 | "defaultMessage": "!!!Email address", | 43 | "defaultMessage": "!!!Email address", |
44 | "file": "src/components/auth/Signup.js", | 44 | "file": "src/components/auth/Signup.js", |
45 | "start": { | 45 | "start": { |
46 | "line": 29, | 46 | "line": 31, |
47 | "column": 14 | 47 | "column": 14 |
48 | }, | 48 | }, |
49 | "end": { | 49 | "end": { |
50 | "line": 32, | 50 | "line": 34, |
51 | "column": 3 | 51 | "column": 3 |
52 | } | 52 | } |
53 | }, | 53 | }, |
@@ -56,11 +56,11 @@ | |||
56 | "defaultMessage": "!!!Password", | 56 | "defaultMessage": "!!!Password", |
57 | "file": "src/components/auth/Signup.js", | 57 | "file": "src/components/auth/Signup.js", |
58 | "start": { | 58 | "start": { |
59 | "line": 37, | 59 | "line": 39, |
60 | "column": 17 | 60 | "column": 17 |
61 | }, | 61 | }, |
62 | "end": { | 62 | "end": { |
63 | "line": 40, | 63 | "line": 42, |
64 | "column": 3 | 64 | "column": 3 |
65 | } | 65 | } |
66 | }, | 66 | }, |
@@ -69,11 +69,11 @@ | |||
69 | "defaultMessage": "!!!By creating a Ferdi account you accept the", | 69 | "defaultMessage": "!!!By creating a Ferdi account you accept the", |
70 | "file": "src/components/auth/Signup.js", | 70 | "file": "src/components/auth/Signup.js", |
71 | "start": { | 71 | "start": { |
72 | "line": 41, | 72 | "line": 43, |
73 | "column": 13 | 73 | "column": 13 |
74 | }, | 74 | }, |
75 | "end": { | 75 | "end": { |
76 | "line": 44, | 76 | "line": 46, |
77 | "column": 3 | 77 | "column": 3 |
78 | } | 78 | } |
79 | }, | 79 | }, |
@@ -82,11 +82,11 @@ | |||
82 | "defaultMessage": "!!!Terms of service", | 82 | "defaultMessage": "!!!Terms of service", |
83 | "file": "src/components/auth/Signup.js", | 83 | "file": "src/components/auth/Signup.js", |
84 | "start": { | 84 | "start": { |
85 | "line": 45, | 85 | "line": 47, |
86 | "column": 9 | 86 | "column": 9 |
87 | }, | 87 | }, |
88 | "end": { | 88 | "end": { |
89 | "line": 48, | 89 | "line": 50, |
90 | "column": 3 | 90 | "column": 3 |
91 | } | 91 | } |
92 | }, | 92 | }, |
@@ -95,11 +95,11 @@ | |||
95 | "defaultMessage": "!!!Privacy Statement", | 95 | "defaultMessage": "!!!Privacy Statement", |
96 | "file": "src/components/auth/Signup.js", | 96 | "file": "src/components/auth/Signup.js", |
97 | "start": { | 97 | "start": { |
98 | "line": 49, | 98 | "line": 51, |
99 | "column": 11 | 99 | "column": 11 |
100 | }, | 100 | }, |
101 | "end": { | 101 | "end": { |
102 | "line": 52, | 102 | "line": 54, |
103 | "column": 3 | 103 | "column": 3 |
104 | } | 104 | } |
105 | }, | 105 | }, |
@@ -108,11 +108,11 @@ | |||
108 | "defaultMessage": "!!!Create account", | 108 | "defaultMessage": "!!!Create account", |
109 | "file": "src/components/auth/Signup.js", | 109 | "file": "src/components/auth/Signup.js", |
110 | "start": { | 110 | "start": { |
111 | "line": 53, | 111 | "line": 55, |
112 | "column": 21 | 112 | "column": 21 |
113 | }, | 113 | }, |
114 | "end": { | 114 | "end": { |
115 | "line": 56, | 115 | "line": 58, |
116 | "column": 3 | 116 | "column": 3 |
117 | } | 117 | } |
118 | }, | 118 | }, |
@@ -121,11 +121,37 @@ | |||
121 | "defaultMessage": "!!!Already have an account, sign in?", | 121 | "defaultMessage": "!!!Already have an account, sign in?", |
122 | "file": "src/components/auth/Signup.js", | 122 | "file": "src/components/auth/Signup.js", |
123 | "start": { | 123 | "start": { |
124 | "line": 57, | 124 | "line": 59, |
125 | "column": 13 | 125 | "column": 13 |
126 | }, | 126 | }, |
127 | "end": { | 127 | "end": { |
128 | "line": 60, | 128 | "line": 62, |
129 | "column": 3 | ||
130 | } | ||
131 | }, | ||
132 | { | ||
133 | "id": "login.changeServer", | ||
134 | "defaultMessage": "!!!Change server", | ||
135 | "file": "src/components/auth/Signup.js", | ||
136 | "start": { | ||
137 | "line": 63, | ||
138 | "column": 16 | ||
139 | }, | ||
140 | "end": { | ||
141 | "line": 66, | ||
142 | "column": 3 | ||
143 | } | ||
144 | }, | ||
145 | { | ||
146 | "id": "services.serverless", | ||
147 | "defaultMessage": "!!!Use Ferdi without an Account", | ||
148 | "file": "src/components/auth/Signup.js", | ||
149 | "start": { | ||
150 | "line": 67, | ||
151 | "column": 14 | ||
152 | }, | ||
153 | "end": { | ||
154 | "line": 70, | ||
129 | "column": 3 | 155 | "column": 3 |
130 | } | 156 | } |
131 | }, | 157 | }, |
@@ -134,11 +160,11 @@ | |||
134 | "defaultMessage": "!!!A user with that email address already exists", | 160 | "defaultMessage": "!!!A user with that email address already exists", |
135 | "file": "src/components/auth/Signup.js", | 161 | "file": "src/components/auth/Signup.js", |
136 | "start": { | 162 | "start": { |
137 | "line": 61, | 163 | "line": 71, |
138 | "column": 18 | 164 | "column": 18 |
139 | }, | 165 | }, |
140 | "end": { | 166 | "end": { |
141 | "line": 64, | 167 | "line": 74, |
142 | "column": 3 | 168 | "column": 3 |
143 | } | 169 | } |
144 | } | 170 | } |
diff --git a/src/i18n/messages/src/components/auth/Welcome.json b/src/i18n/messages/src/components/auth/Welcome.json index b4d2ce689..3f0c1e5c2 100644 --- a/src/i18n/messages/src/components/auth/Welcome.json +++ b/src/i18n/messages/src/components/auth/Welcome.json | |||
@@ -4,11 +4,11 @@ | |||
4 | "defaultMessage": "!!!Create a free account", | 4 | "defaultMessage": "!!!Create a free account", |
5 | "file": "src/components/auth/Welcome.js", | 5 | "file": "src/components/auth/Welcome.js", |
6 | "start": { | 6 | "start": { |
7 | "line": 9, | 7 | "line": 11, |
8 | "column": 16 | 8 | "column": 16 |
9 | }, | 9 | }, |
10 | "end": { | 10 | "end": { |
11 | "line": 12, | 11 | "line": 14, |
12 | "column": 3 | 12 | "column": 3 |
13 | } | 13 | } |
14 | }, | 14 | }, |
@@ -17,11 +17,24 @@ | |||
17 | "defaultMessage": "!!!Login to your account", | 17 | "defaultMessage": "!!!Login to your account", |
18 | "file": "src/components/auth/Welcome.js", | 18 | "file": "src/components/auth/Welcome.js", |
19 | "start": { | 19 | "start": { |
20 | "line": 13, | 20 | "line": 15, |
21 | "column": 15 | 21 | "column": 15 |
22 | }, | 22 | }, |
23 | "end": { | 23 | "end": { |
24 | "line": 16, | 24 | "line": 18, |
25 | "column": 3 | ||
26 | } | ||
27 | }, | ||
28 | { | ||
29 | "id": "services.serverless", | ||
30 | "defaultMessage": "!!!Use Ferdi without an Account", | ||
31 | "file": "src/components/auth/Welcome.js", | ||
32 | "start": { | ||
33 | "line": 19, | ||
34 | "column": 14 | ||
35 | }, | ||
36 | "end": { | ||
37 | "line": 22, | ||
25 | "column": 3 | 38 | "column": 3 |
26 | } | 39 | } |
27 | } | 40 | } |
diff --git a/src/i18n/messages/src/components/services/content/Services.json b/src/i18n/messages/src/components/services/content/Services.json index c2e57b8b5..6a5eb052e 100644 --- a/src/i18n/messages/src/components/services/content/Services.json +++ b/src/i18n/messages/src/components/services/content/Services.json | |||
@@ -4,11 +4,11 @@ | |||
4 | "defaultMessage": "!!!Welcome to Ferdi", | 4 | "defaultMessage": "!!!Welcome to Ferdi", |
5 | "file": "src/components/services/content/Services.js", | 5 | "file": "src/components/services/content/Services.js", |
6 | "start": { | 6 | "start": { |
7 | "line": 14, | 7 | "line": 15, |
8 | "column": 11 | 8 | "column": 11 |
9 | }, | 9 | }, |
10 | "end": { | 10 | "end": { |
11 | "line": 17, | 11 | "line": 18, |
12 | "column": 3 | 12 | "column": 3 |
13 | } | 13 | } |
14 | }, | 14 | }, |
@@ -17,11 +17,11 @@ | |||
17 | "defaultMessage": "!!!Get started", | 17 | "defaultMessage": "!!!Get started", |
18 | "file": "src/components/services/content/Services.js", | 18 | "file": "src/components/services/content/Services.js", |
19 | "start": { | 19 | "start": { |
20 | "line": 18, | 20 | "line": 19, |
21 | "column": 14 | 21 | "column": 14 |
22 | }, | 22 | }, |
23 | "end": { | 23 | "end": { |
24 | "line": 21, | 24 | "line": 22, |
25 | "column": 3 | 25 | "column": 3 |
26 | } | 26 | } |
27 | }, | 27 | }, |
@@ -30,11 +30,24 @@ | |||
30 | "defaultMessage": "!!!Please login to use Ferdi.", | 30 | "defaultMessage": "!!!Please login to use Ferdi.", |
31 | "file": "src/components/services/content/Services.js", | 31 | "file": "src/components/services/content/Services.js", |
32 | "start": { | 32 | "start": { |
33 | "line": 22, | 33 | "line": 23, |
34 | "column": 9 | 34 | "column": 9 |
35 | }, | 35 | }, |
36 | "end": { | 36 | "end": { |
37 | "line": 25, | 37 | "line": 26, |
38 | "column": 3 | ||
39 | } | ||
40 | }, | ||
41 | { | ||
42 | "id": "services.serverless", | ||
43 | "defaultMessage": "!!!Use Ferdi without an Account", | ||
44 | "file": "src/components/services/content/Services.js", | ||
45 | "start": { | ||
46 | "line": 27, | ||
47 | "column": 14 | ||
48 | }, | ||
49 | "end": { | ||
50 | "line": 30, | ||
38 | "column": 3 | 51 | "column": 3 |
39 | } | 52 | } |
40 | }, | 53 | }, |
@@ -43,11 +56,11 @@ | |||
43 | "defaultMessage": "!!!Optionally, you can change your Ferdi server by clicking the cog in the bottom left corner.", | 56 | "defaultMessage": "!!!Optionally, you can change your Ferdi server by clicking the cog in the bottom left corner.", |
44 | "file": "src/components/services/content/Services.js", | 57 | "file": "src/components/services/content/Services.js", |
45 | "start": { | 58 | "start": { |
46 | "line": 26, | 59 | "line": 31, |
47 | "column": 14 | 60 | "column": 14 |
48 | }, | 61 | }, |
49 | "end": { | 62 | "end": { |
50 | "line": 29, | 63 | "line": 34, |
51 | "column": 3 | 64 | "column": 3 |
52 | } | 65 | } |
53 | } | 66 | } |
diff --git a/src/index.js b/src/index.js index 4d7215d5e..7a0e89285 100644 --- a/src/index.js +++ b/src/index.js | |||
@@ -34,6 +34,9 @@ import { isPositionValid } from './electron/windowUtils'; | |||
34 | import { appId } from './package.json'; // eslint-disable-line import/no-unresolved | 34 | import { appId } from './package.json'; // eslint-disable-line import/no-unresolved |
35 | import './electron/exception'; | 35 | import './electron/exception'; |
36 | 36 | ||
37 | // Start internal server | ||
38 | import './server/start'; | ||
39 | |||
37 | import { | 40 | import { |
38 | DEFAULT_APP_SETTINGS, | 41 | DEFAULT_APP_SETTINGS, |
39 | DEFAULT_WINDOW_OPTIONS, | 42 | DEFAULT_WINDOW_OPTIONS, |
diff --git a/src/server/.editorconfig b/src/server/.editorconfig new file mode 100644 index 000000000..914223976 --- /dev/null +++ b/src/server/.editorconfig | |||
@@ -0,0 +1,13 @@ | |||
1 | # editorconfig.org | ||
2 | root = true | ||
3 | |||
4 | [*] | ||
5 | indent_size = 2 | ||
6 | indent_style = space | ||
7 | end_of_line = lf | ||
8 | charset = utf-8 | ||
9 | trim_trailing_whitespace = true | ||
10 | insert_final_newline = true | ||
11 | |||
12 | [*.md] | ||
13 | trim_trailing_whitespace = false | ||
diff --git a/src/server/.eslintrc.js b/src/server/.eslintrc.js new file mode 100644 index 000000000..d02f4890d --- /dev/null +++ b/src/server/.eslintrc.js | |||
@@ -0,0 +1,22 @@ | |||
1 | module.exports = { | ||
2 | env: { | ||
3 | commonjs: true, | ||
4 | es6: true, | ||
5 | node: true, | ||
6 | }, | ||
7 | extends: [ | ||
8 | 'airbnb-base', | ||
9 | ], | ||
10 | globals: { | ||
11 | Atomics: 'readonly', | ||
12 | SharedArrayBuffer: 'readonly', | ||
13 | use: 'readonly' | ||
14 | }, | ||
15 | parserOptions: { | ||
16 | ecmaVersion: 2018, | ||
17 | }, | ||
18 | rules: { | ||
19 | "class-methods-use-this": 'off', | ||
20 | "no-restricted-syntax": 'off', | ||
21 | }, | ||
22 | }; | ||
diff --git a/src/server/.gitattributes b/src/server/.gitattributes new file mode 100644 index 000000000..dfe077042 --- /dev/null +++ b/src/server/.gitattributes | |||
@@ -0,0 +1,2 @@ | |||
1 | # Auto detect text files and perform LF normalization | ||
2 | * text=auto | ||
diff --git a/src/server/.gitignore b/src/server/.gitignore new file mode 100644 index 000000000..d84ffadd4 --- /dev/null +++ b/src/server/.gitignore | |||
@@ -0,0 +1,19 @@ | |||
1 | # Node modules | ||
2 | node_modules | ||
3 | |||
4 | # Adonis directory for storing tmp files | ||
5 | tmp | ||
6 | |||
7 | # Environment variables, never commit this file | ||
8 | .env | ||
9 | |||
10 | # The development sqlite file | ||
11 | database/development.sqlite | ||
12 | database/adonis.sqlite | ||
13 | |||
14 | # Uploaded recipes | ||
15 | recipes/ | ||
16 | |||
17 | .DS_Store | ||
18 | public/terms.html | ||
19 | public/privacy.html | ||
diff --git a/src/server/README.md b/src/server/README.md new file mode 100644 index 000000000..0074f2314 --- /dev/null +++ b/src/server/README.md | |||
@@ -0,0 +1,17 @@ | |||
1 | <p align="center"> | ||
2 | <img src="./logo.png" alt="" width="300"/> | ||
3 | </p> | ||
4 | |||
5 | # ferdi-internal-server | ||
6 | Internal Ferdi Server used for storing settings without logging into an external server. | ||
7 | |||
8 | ## Configuration | ||
9 | franz-server's configuration is saved inside the `env.ini` file. Besides AdonisJS's settings, ferdi-internal-server has the following custom settings: | ||
10 | - `CONNECT_WITH_FRANZ` (`true` or `false`, default: `true`): Whether to enable connections to the Franz server. By enabling this option, ferdi-server can: | ||
11 | - Show the full Franz recipe library instead of only custom recipes | ||
12 | - Import Franz accounts | ||
13 | |||
14 | ## Importing your Franz account | ||
15 | ferdi-server allows you to import your full Franz account, including all its settings. | ||
16 | |||
17 | To import your Franz account, open `http://localhost:45569/import` in your browser and login using your Franz account details. ferdi-server will create a new user with the same credentials and copy your Franz settings, services and workspaces. | ||
diff --git a/src/server/ace b/src/server/ace new file mode 100644 index 000000000..42f8f10d1 --- /dev/null +++ b/src/server/ace | |||
@@ -0,0 +1,21 @@ | |||
1 | 'use strict' | ||
2 | |||
3 | /* | ||
4 | |-------------------------------------------------------------------------- | ||
5 | | Ace Commands | ||
6 | |-------------------------------------------------------------------------- | ||
7 | | | ||
8 | | The ace file is just a regular Javascript file but with no extension. You | ||
9 | | can call `node ace` followed by the command name and it just works. | ||
10 | | | ||
11 | | Also you can use `adonis` followed by the command name, since the adonis | ||
12 | | global proxies all the ace commands. | ||
13 | | | ||
14 | */ | ||
15 | |||
16 | const { Ignitor } = require('@adonisjs/ignitor') | ||
17 | |||
18 | new Ignitor(require('@adonisjs/fold')) | ||
19 | .appRoot(__dirname) | ||
20 | .fireAce() | ||
21 | .catch(console.error) | ||
diff --git a/src/server/app/Controllers/Http/RecipeController.js b/src/server/app/Controllers/Http/RecipeController.js new file mode 100644 index 000000000..71ac12c0f --- /dev/null +++ b/src/server/app/Controllers/Http/RecipeController.js | |||
@@ -0,0 +1,119 @@ | |||
1 | |||
2 | const Recipe = use('App/Models/Recipe'); | ||
3 | const Drive = use('Drive'); | ||
4 | const { | ||
5 | validateAll, | ||
6 | } = use('Validator'); | ||
7 | const Env = use('Env'); | ||
8 | |||
9 | const fetch = require('node-fetch'); | ||
10 | |||
11 | class RecipeController { | ||
12 | // List official and custom recipes | ||
13 | async list({ | ||
14 | response, | ||
15 | }) { | ||
16 | const officialRecipes = JSON.parse(await (await fetch('https://api.getferdi.com/v1/recipes')).text()); | ||
17 | const customRecipesArray = (await Recipe.all()).rows; | ||
18 | const customRecipes = customRecipesArray.map(recipe => ({ | ||
19 | id: recipe.recipeId, | ||
20 | name: recipe.name, | ||
21 | ...JSON.parse(recipe.data), | ||
22 | })); | ||
23 | |||
24 | const recipes = [ | ||
25 | ...officialRecipes, | ||
26 | ...customRecipes, | ||
27 | ]; | ||
28 | |||
29 | return response.send(recipes); | ||
30 | } | ||
31 | |||
32 | // Search official and custom recipes | ||
33 | async search({ | ||
34 | request, | ||
35 | response, | ||
36 | }) { | ||
37 | // Validate user input | ||
38 | const validation = await validateAll(request.all(), { | ||
39 | needle: 'required', | ||
40 | }); | ||
41 | if (validation.fails()) { | ||
42 | return response.status(401).send({ | ||
43 | message: 'Please provide a needle', | ||
44 | messages: validation.messages(), | ||
45 | status: 401, | ||
46 | }); | ||
47 | } | ||
48 | |||
49 | const needle = request.input('needle'); | ||
50 | |||
51 | // Get results | ||
52 | let results; | ||
53 | |||
54 | if (needle === 'ferdi:custom') { | ||
55 | const dbResults = (await Recipe.all()).toJSON(); | ||
56 | results = dbResults.map(recipe => ({ | ||
57 | id: recipe.recipeId, | ||
58 | name: recipe.name, | ||
59 | ...JSON.parse(recipe.data), | ||
60 | })); | ||
61 | } else { | ||
62 | let remoteResults = []; | ||
63 | if (Env.get('CONNECT_WITH_FRANZ') == 'true') { // eslint-disable-line eqeqeq | ||
64 | remoteResults = JSON.parse(await (await fetch(`https://api.getferdi.com/v1/recipes/search?needle=${encodeURIComponent(needle)}`)).text()); | ||
65 | } | ||
66 | const localResultsArray = (await Recipe.query().where('name', 'LIKE', `%${needle}%`).fetch()).toJSON(); | ||
67 | const localResults = localResultsArray.map(recipe => ({ | ||
68 | id: recipe.recipeId, | ||
69 | name: recipe.name, | ||
70 | ...JSON.parse(recipe.data), | ||
71 | })); | ||
72 | |||
73 | results = [ | ||
74 | ...localResults, | ||
75 | ...remoteResults || [], | ||
76 | ]; | ||
77 | } | ||
78 | |||
79 | return response.send(results); | ||
80 | } | ||
81 | |||
82 | // Download a recipe | ||
83 | async download({ | ||
84 | response, | ||
85 | params, | ||
86 | }) { | ||
87 | // Validate user input | ||
88 | const validation = await validateAll(params, { | ||
89 | recipe: 'required|accepted', | ||
90 | }); | ||
91 | if (validation.fails()) { | ||
92 | return response.status(401).send({ | ||
93 | message: 'Please provide a recipe ID', | ||
94 | messages: validation.messages(), | ||
95 | status: 401, | ||
96 | }); | ||
97 | } | ||
98 | |||
99 | const service = params.recipe; | ||
100 | |||
101 | // Check for invalid characters | ||
102 | if (/\.{1,}/.test(service) || /\/{1,}/.test(service)) { | ||
103 | return response.send('Invalid recipe name'); | ||
104 | } | ||
105 | |||
106 | // Check if recipe exists in recipes folder | ||
107 | if (await Drive.exists(`${service}.tar.gz`)) { | ||
108 | return response.send(await Drive.get(`${service}.tar.gz`)); | ||
109 | } if (Env.get('CONNECT_WITH_FRANZ') == 'true') { // eslint-disable-line eqeqeq | ||
110 | return response.redirect(`https://api.getferdi.com/v1/recipes/download/${service}`); | ||
111 | } | ||
112 | return response.status(400).send({ | ||
113 | message: 'Recipe not found', | ||
114 | code: 'recipe-not-found', | ||
115 | }); | ||
116 | } | ||
117 | } | ||
118 | |||
119 | module.exports = RecipeController; | ||
diff --git a/src/server/app/Controllers/Http/ServiceController.js b/src/server/app/Controllers/Http/ServiceController.js new file mode 100644 index 000000000..ea7035ca1 --- /dev/null +++ b/src/server/app/Controllers/Http/ServiceController.js | |||
@@ -0,0 +1,211 @@ | |||
1 | const Service = use('App/Models/Service'); | ||
2 | const { | ||
3 | validateAll, | ||
4 | } = use('Validator'); | ||
5 | |||
6 | const uuid = require('uuid/v4'); | ||
7 | |||
8 | class ServiceController { | ||
9 | // Create a new service for user | ||
10 | async create({ | ||
11 | request, | ||
12 | response, | ||
13 | }) { | ||
14 | // Validate user input | ||
15 | const validation = await validateAll(request.all(), { | ||
16 | name: 'required|string', | ||
17 | recipeId: 'required', | ||
18 | }); | ||
19 | if (validation.fails()) { | ||
20 | return response.status(401).send({ | ||
21 | message: 'Invalid POST arguments', | ||
22 | messages: validation.messages(), | ||
23 | status: 401, | ||
24 | }); | ||
25 | } | ||
26 | |||
27 | const data = request.all(); | ||
28 | |||
29 | // Get new, unused uuid | ||
30 | let serviceId; | ||
31 | do { | ||
32 | serviceId = uuid(); | ||
33 | } while ((await Service.query().where('serviceId', serviceId).fetch()).rows.length > 0); // eslint-disable-line no-await-in-loop | ||
34 | |||
35 | await Service.create({ | ||
36 | serviceId, | ||
37 | name: data.name, | ||
38 | recipeId: data.recipeId, | ||
39 | settings: JSON.stringify(data), | ||
40 | }); | ||
41 | |||
42 | return response.send({ | ||
43 | data: { | ||
44 | userId: 1, | ||
45 | id: serviceId, | ||
46 | isEnabled: true, | ||
47 | isNotificationEnabled: true, | ||
48 | isBadgeEnabled: true, | ||
49 | isMuted: false, | ||
50 | isDarkModeEnabled: '', | ||
51 | spellcheckerLanguage: '', | ||
52 | order: 1, | ||
53 | customRecipe: false, | ||
54 | hasCustomIcon: false, | ||
55 | workspaces: [], | ||
56 | iconUrl: null, | ||
57 | ...data, | ||
58 | }, | ||
59 | status: ['created'], | ||
60 | }); | ||
61 | } | ||
62 | |||
63 | // List all services a user has created | ||
64 | async list({ | ||
65 | response, | ||
66 | }) { | ||
67 | const services = (await Service.all()).rows; | ||
68 | // Convert to array with all data Franz wants | ||
69 | const servicesArray = services.map(service => ({ | ||
70 | customRecipe: false, | ||
71 | hasCustomIcon: false, | ||
72 | isBadgeEnabled: true, | ||
73 | isDarkModeEnabled: '', | ||
74 | isEnabled: true, | ||
75 | isMuted: false, | ||
76 | isNotificationEnabled: true, | ||
77 | order: 1, | ||
78 | spellcheckerLanguage: '', | ||
79 | workspaces: [], | ||
80 | iconUrl: null, | ||
81 | ...JSON.parse(service.settings), | ||
82 | id: service.serviceId, | ||
83 | name: service.name, | ||
84 | recipeId: service.recipeId, | ||
85 | userId: 1, | ||
86 | })); | ||
87 | |||
88 | return response.send(servicesArray); | ||
89 | } | ||
90 | |||
91 | async edit({ | ||
92 | request, | ||
93 | response, | ||
94 | params, | ||
95 | }) { | ||
96 | // Validate user input | ||
97 | const validation = await validateAll(request.all(), { | ||
98 | name: 'required', | ||
99 | }); | ||
100 | if (validation.fails()) { | ||
101 | return response.status(401).send({ | ||
102 | message: 'Invalid POST arguments', | ||
103 | messages: validation.messages(), | ||
104 | status: 401, | ||
105 | }); | ||
106 | } | ||
107 | |||
108 | const data = request.all(); | ||
109 | const { | ||
110 | id, | ||
111 | } = params; | ||
112 | |||
113 | // Get current settings from db | ||
114 | const serviceData = (await Service.query() | ||
115 | .where('serviceId', id).fetch()).rows[0]; | ||
116 | |||
117 | const settings = { | ||
118 | ...JSON.parse(serviceData.settings), | ||
119 | ...data, | ||
120 | }; | ||
121 | |||
122 | // Update data in database | ||
123 | await (Service.query() | ||
124 | .where('serviceId', id)).update({ | ||
125 | name: data.name, | ||
126 | settings: JSON.stringify(settings), | ||
127 | }); | ||
128 | |||
129 | // Get updated row | ||
130 | const service = (await Service.query() | ||
131 | .where('serviceId', id).fetch()).rows[0]; | ||
132 | |||
133 | return response.send({ | ||
134 | id: service.serviceId, | ||
135 | name: data.name, | ||
136 | ...settings, | ||
137 | userId: 1, | ||
138 | }); | ||
139 | } | ||
140 | |||
141 | async reorder({ | ||
142 | request, | ||
143 | response, | ||
144 | }) { | ||
145 | const data = request.all(); | ||
146 | |||
147 | for (const service of Object.keys(data)) { | ||
148 | // Get current settings from db | ||
149 | const serviceData = (await Service.query() // eslint-disable-line no-await-in-loop | ||
150 | .where('serviceId', service).fetch()).rows[0]; | ||
151 | |||
152 | const settings = { | ||
153 | ...JSON.parse(serviceData.settings), | ||
154 | order: data[service], | ||
155 | }; | ||
156 | |||
157 | // Update data in database | ||
158 | await (Service.query() // eslint-disable-line no-await-in-loop | ||
159 | .where('serviceId', service)) | ||
160 | .update({ | ||
161 | settings: JSON.stringify(settings), | ||
162 | }); | ||
163 | } | ||
164 | |||
165 | // Get new services | ||
166 | const services = (await Service.all()).rows; | ||
167 | // Convert to array with all data Franz wants | ||
168 | const servicesArray = services.map(service => ({ | ||
169 | customRecipe: false, | ||
170 | hasCustomIcon: false, | ||
171 | isBadgeEnabled: true, | ||
172 | isDarkModeEnabled: '', | ||
173 | isEnabled: true, | ||
174 | isMuted: false, | ||
175 | isNotificationEnabled: true, | ||
176 | order: 1, | ||
177 | spellcheckerLanguage: '', | ||
178 | workspaces: [], | ||
179 | iconUrl: null, | ||
180 | ...JSON.parse(service.settings), | ||
181 | id: service.serviceId, | ||
182 | name: service.name, | ||
183 | recipeId: service.recipeId, | ||
184 | userId: 1, | ||
185 | })); | ||
186 | |||
187 | return response.send(servicesArray); | ||
188 | } | ||
189 | |||
190 | update({ | ||
191 | response, | ||
192 | }) { | ||
193 | return response.send([]); | ||
194 | } | ||
195 | |||
196 | async delete({ | ||
197 | params, | ||
198 | response, | ||
199 | }) { | ||
200 | // Update data in database | ||
201 | await (Service.query() | ||
202 | .where('serviceId', params.id)).delete(); | ||
203 | |||
204 | return response.send({ | ||
205 | message: 'Sucessfully deleted service', | ||
206 | status: 200, | ||
207 | }); | ||
208 | } | ||
209 | } | ||
210 | |||
211 | module.exports = ServiceController; | ||
diff --git a/src/server/app/Controllers/Http/StaticController.js b/src/server/app/Controllers/Http/StaticController.js new file mode 100644 index 000000000..b16e6cb6d --- /dev/null +++ b/src/server/app/Controllers/Http/StaticController.js | |||
@@ -0,0 +1,224 @@ | |||
1 | |||
2 | /** | ||
3 | * Controller for routes with static responses | ||
4 | */ | ||
5 | |||
6 | class StaticController { | ||
7 | // Enable all features | ||
8 | features({ | ||
9 | response, | ||
10 | }) { | ||
11 | return response.send({ | ||
12 | needToWaitToProceed: false, | ||
13 | isSpellcheckerPremiumFeature: true, | ||
14 | isServiceProxyEnabled: true, | ||
15 | isServiceProxyPremiumFeature: true, | ||
16 | isWorkspacePremiumFeature: true, | ||
17 | isWorkspaceEnabled: true, | ||
18 | isAnnouncementsEnabled: true, | ||
19 | isSettingsWSEnabled: false, | ||
20 | isServiceLimitEnabled: false, | ||
21 | serviceLimitCount: 0, | ||
22 | isCommunityRecipesPremiumFeature: false, | ||
23 | }); | ||
24 | } | ||
25 | |||
26 | // Return an empty array | ||
27 | emptyArray({ | ||
28 | response, | ||
29 | }) { | ||
30 | return response.send([]); | ||
31 | } | ||
32 | |||
33 | // Payment plans availible | ||
34 | plans({ | ||
35 | response, | ||
36 | }) { | ||
37 | return response.send({ | ||
38 | month: { | ||
39 | id: 'franz-supporter-license', | ||
40 | price: 99, | ||
41 | }, | ||
42 | year: { | ||
43 | id: 'franz-supporter-license-year-2019', | ||
44 | price: 99, | ||
45 | }, | ||
46 | }); | ||
47 | } | ||
48 | |||
49 | // Return list of popular recipes (copy of the response Franz's API is returning) | ||
50 | popularRecipes({ | ||
51 | response, | ||
52 | }) { | ||
53 | return response.send([{ | ||
54 | author: 'Stefan Malzner <stefan@adlk.io>', | ||
55 | featured: false, | ||
56 | id: 'slack', | ||
57 | name: 'Slack', | ||
58 | version: '1.0.4', | ||
59 | icons: { | ||
60 | png: 'https://cdn.franzinfra.com/recipes/dist/slack/src/icon.png', | ||
61 | svg: 'https://cdn.franzinfra.com/recipes/dist/slack/src/icon.svg', | ||
62 | }, | ||
63 | }, { | ||
64 | author: 'Stefan Malzner <stefan@adlk.io>', | ||
65 | featured: false, | ||
66 | id: 'whatsapp', | ||
67 | name: 'WhatsApp', | ||
68 | version: '1.0.1', | ||
69 | icons: { | ||
70 | png: 'https://cdn.franzinfra.com/recipes/dist/whatsapp/src/icon.png', | ||
71 | svg: 'https://cdn.franzinfra.com/recipes/dist/whatsapp/src/icon.svg', | ||
72 | }, | ||
73 | }, { | ||
74 | author: 'Stefan Malzner <stefan@adlk.io>', | ||
75 | featured: false, | ||
76 | id: 'messenger', | ||
77 | name: 'Messenger', | ||
78 | version: '1.0.6', | ||
79 | icons: { | ||
80 | png: 'https://cdn.franzinfra.com/recipes/dist/messenger/src/icon.png', | ||
81 | svg: 'https://cdn.franzinfra.com/recipes/dist/messenger/src/icon.svg', | ||
82 | }, | ||
83 | }, { | ||
84 | author: 'Stefan Malzner <stefan@adlk.io>', | ||
85 | featured: false, | ||
86 | id: 'telegram', | ||
87 | name: 'Telegram', | ||
88 | version: '1.0.0', | ||
89 | icons: { | ||
90 | png: 'https://cdn.franzinfra.com/recipes/dist/telegram/src/icon.png', | ||
91 | svg: 'https://cdn.franzinfra.com/recipes/dist/telegram/src/icon.svg', | ||
92 | }, | ||
93 | }, { | ||
94 | author: 'Stefan Malzner <stefan@adlk.io>', | ||
95 | featured: false, | ||
96 | id: 'gmail', | ||
97 | name: 'Gmail', | ||
98 | version: '1.0.0', | ||
99 | icons: { | ||
100 | png: 'https://cdn.franzinfra.com/recipes/dist/gmail/src/icon.png', | ||
101 | svg: 'https://cdn.franzinfra.com/recipes/dist/gmail/src/icon.svg', | ||
102 | }, | ||
103 | }, { | ||
104 | author: 'Stefan Malzner <stefan@adlk.io>', | ||
105 | featured: false, | ||
106 | id: 'skype', | ||
107 | name: 'Skype', | ||
108 | version: '1.0.0', | ||
109 | icons: { | ||
110 | png: 'https://cdn.franzinfra.com/recipes/dist/skype/src/icon.png', | ||
111 | svg: 'https://cdn.franzinfra.com/recipes/dist/skype/src/icon.svg', | ||
112 | }, | ||
113 | }, { | ||
114 | author: 'Stefan Malzner <stefan@adlk.io>', | ||
115 | featured: false, | ||
116 | id: 'hangouts', | ||
117 | name: 'Hangouts', | ||
118 | version: '1.0.0', | ||
119 | icons: { | ||
120 | png: 'https://cdn.franzinfra.com/recipes/dist/hangouts/src/icon.png', | ||
121 | svg: 'https://cdn.franzinfra.com/recipes/dist/hangouts/src/icon.svg', | ||
122 | }, | ||
123 | }, { | ||
124 | author: 'Stefan Malzner <stefan@adlk.io>', | ||
125 | featured: false, | ||
126 | id: 'discord', | ||
127 | name: 'Discord', | ||
128 | version: '1.0.0', | ||
129 | icons: { | ||
130 | png: 'https://cdn.franzinfra.com/recipes/dist/discord/src/icon.png', | ||
131 | svg: 'https://cdn.franzinfra.com/recipes/dist/discord/src/icon.svg', | ||
132 | }, | ||
133 | }, { | ||
134 | author: 'Stefan Malzner <stefan@adlk.io>', | ||
135 | featured: false, | ||
136 | id: 'tweetdeck', | ||
137 | name: 'Tweetdeck', | ||
138 | version: '1.0.1', | ||
139 | icons: { | ||
140 | png: 'https://cdn.franzinfra.com/recipes/dist/tweetdeck/src/icon.png', | ||
141 | svg: 'https://cdn.franzinfra.com/recipes/dist/tweetdeck/src/icon.svg', | ||
142 | }, | ||
143 | }, { | ||
144 | author: 'Stefan Malzner <stefan@adlk.io>', | ||
145 | featured: false, | ||
146 | id: 'hipchat', | ||
147 | name: 'HipChat', | ||
148 | version: '1.0.1', | ||
149 | icons: { | ||
150 | png: 'https://cdn.franzinfra.com/recipes/dist/hipchat/src/icon.png', | ||
151 | svg: 'https://cdn.franzinfra.com/recipes/dist/hipchat/src/icon.svg', | ||
152 | }, | ||
153 | }, { | ||
154 | author: 'Stefan Malzner <stefan@adlk.io>', | ||
155 | featured: false, | ||
156 | id: 'gmailinbox', | ||
157 | name: 'Inbox by Gmail', | ||
158 | version: '1.0.0', | ||
159 | icons: { | ||
160 | png: 'https://cdn.franzinfra.com/recipes/dist/gmailinbox/src/icon.png', | ||
161 | svg: 'https://cdn.franzinfra.com/recipes/dist/gmailinbox/src/icon.svg', | ||
162 | }, | ||
163 | }, { | ||
164 | author: 'Stefan Malzner <stefan@adlk.io>', | ||
165 | featured: false, | ||
166 | id: 'rocketchat', | ||
167 | name: 'Rocket.Chat', | ||
168 | version: '1.0.1', | ||
169 | icons: { | ||
170 | png: 'https://cdn.franzinfra.com/recipes/dist/rocketchat/src/icon.png', | ||
171 | svg: 'https://cdn.franzinfra.com/recipes/dist/rocketchat/src/icon.svg', | ||
172 | }, | ||
173 | }, { | ||
174 | author: 'Brian Gilbert <brian@briangilbert.net>', | ||
175 | featured: false, | ||
176 | id: 'gitter', | ||
177 | name: 'Gitter', | ||
178 | version: '1.0.0', | ||
179 | icons: { | ||
180 | png: 'https://cdn.franzinfra.com/recipes/dist/gitter/src/icon.png', | ||
181 | svg: 'https://cdn.franzinfra.com/recipes/dist/gitter/src/icon.svg', | ||
182 | }, | ||
183 | }, { | ||
184 | author: 'Stefan Malzner <stefan@adlk.io>', | ||
185 | featured: false, | ||
186 | id: 'mattermost', | ||
187 | name: 'Mattermost', | ||
188 | version: '1.0.0', | ||
189 | icons: { | ||
190 | png: 'https://cdn.franzinfra.com/recipes/dist/mattermost/src/icon.png', | ||
191 | svg: 'https://cdn.franzinfra.com/recipes/dist/mattermost/src/icon.svg', | ||
192 | }, | ||
193 | }, { | ||
194 | author: 'Franz <recipe@meetfranz.com>', | ||
195 | featured: false, | ||
196 | id: 'toggl', | ||
197 | name: 'toggl', | ||
198 | version: '1.0.0', | ||
199 | icons: { | ||
200 | png: 'https://cdn.franzinfra.com/recipes/dist/toggl/src/icon.png', | ||
201 | svg: 'https://cdn.franzinfra.com/recipes/dist/toggl/src/icon.svg', | ||
202 | }, | ||
203 | }, { | ||
204 | author: 'Stuart Clark <stuart@realityloop.com>', | ||
205 | featured: false, | ||
206 | id: 'twist', | ||
207 | name: 'twist', | ||
208 | version: '1.0.0', | ||
209 | icons: { | ||
210 | png: 'https://cdn.franzinfra.com/recipes/dist/twist/src/icon.png', | ||
211 | svg: 'https://cdn.franzinfra.com/recipes/dist/twist/src/icon.svg', | ||
212 | }, | ||
213 | }]); | ||
214 | } | ||
215 | |||
216 | // Show announcements | ||
217 | announcement({ | ||
218 | response, | ||
219 | }) { | ||
220 | return response.send('No announcement found.'); | ||
221 | } | ||
222 | } | ||
223 | |||
224 | module.exports = StaticController; | ||
diff --git a/src/server/app/Controllers/Http/UserController.js b/src/server/app/Controllers/Http/UserController.js new file mode 100644 index 000000000..ee6a82702 --- /dev/null +++ b/src/server/app/Controllers/Http/UserController.js | |||
@@ -0,0 +1,231 @@ | |||
1 | const Service = use('App/Models/Service'); | ||
2 | const Workspace = use('App/Models/Workspace'); | ||
3 | const { | ||
4 | validateAll, | ||
5 | } = use('Validator'); | ||
6 | const Env = use('Env'); | ||
7 | |||
8 | const btoa = require('btoa'); | ||
9 | const fetch = require('node-fetch'); | ||
10 | const uuid = require('uuid/v4'); | ||
11 | const crypto = require('crypto'); | ||
12 | |||
13 | const franzRequest = (route, method, auth) => new Promise((resolve, reject) => { | ||
14 | const base = 'https://api.franzinfra.com/v1/'; | ||
15 | const user = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Ferdi/5.3.0-beta.1 Chrome/69.0.3497.128 Electron/4.2.4 Safari/537.36'; | ||
16 | |||
17 | try { | ||
18 | fetch(base + route, { | ||
19 | method, | ||
20 | headers: { | ||
21 | Authorization: `Bearer ${auth}`, | ||
22 | 'User-Agent': user, | ||
23 | }, | ||
24 | }) | ||
25 | .then(data => data.json()) | ||
26 | .then(json => resolve(json)); | ||
27 | } catch (e) { | ||
28 | reject(); | ||
29 | } | ||
30 | }); | ||
31 | |||
32 | class UserController { | ||
33 | // Register a new user | ||
34 | async signup({ | ||
35 | request, | ||
36 | response, | ||
37 | }) { | ||
38 | // Validate user input | ||
39 | const validation = await validateAll(request.all(), { | ||
40 | firstname: 'required', | ||
41 | email: 'required|email', | ||
42 | password: 'required', | ||
43 | }); | ||
44 | if (validation.fails()) { | ||
45 | return response.status(401).send({ | ||
46 | message: 'Invalid POST arguments', | ||
47 | messages: validation.messages(), | ||
48 | status: 401, | ||
49 | }); | ||
50 | } | ||
51 | |||
52 | return response.send({ | ||
53 | message: 'Successfully created account', | ||
54 | token: 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJGZXJkaSBJbnRlcm5hbCBTZXJ2ZXIiLCJpYXQiOjE1NzEwNDAyMTUsImV4cCI6MjUzMzk1NDE3ODQ0LCJhdWQiOiJnZXRmZXJkaS5jb20iLCJzdWIiOiJmZXJkaUBsb2NhbGhvc3QiLCJ1c2VySWQiOiIxIn0.9_TWFGp6HROv8Yg82Rt6i1-95jqWym40a-HmgrdMC6M', | ||
55 | }); | ||
56 | } | ||
57 | |||
58 | // Login using an existing user | ||
59 | async login({ | ||
60 | request, | ||
61 | response, | ||
62 | }) { | ||
63 | if (!request.header('Authorization')) { | ||
64 | return response.status(401).send({ | ||
65 | message: 'Please provide authorization', | ||
66 | status: 401, | ||
67 | }); | ||
68 | } | ||
69 | |||
70 | return response.send({ | ||
71 | message: 'Successfully logged in', | ||
72 | token: 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJGZXJkaSBJbnRlcm5hbCBTZXJ2ZXIiLCJpYXQiOjE1NzEwNDAyMTUsImV4cCI6MjUzMzk1NDE3ODQ0LCJhdWQiOiJnZXRmZXJkaS5jb20iLCJzdWIiOiJmZXJkaUBsb2NhbGhvc3QiLCJ1c2VySWQiOiIxIn0.9_TWFGp6HROv8Yg82Rt6i1-95jqWym40a-HmgrdMC6M', | ||
73 | }); | ||
74 | } | ||
75 | |||
76 | // Return information about the current user | ||
77 | async me({ | ||
78 | response, | ||
79 | }) { | ||
80 | return response.send({ | ||
81 | accountType: 'individual', | ||
82 | beta: false, | ||
83 | donor: {}, | ||
84 | email: '', | ||
85 | emailValidated: true, | ||
86 | features: {}, | ||
87 | firstname: 'Ferdi', | ||
88 | id: '82c1cf9d-ab58-4da2-b55e-aaa41d2142d8', | ||
89 | isPremium: true, | ||
90 | isSubscriptionOwner: true, | ||
91 | lastname: 'Application', | ||
92 | locale: 'en-US', | ||
93 | }); | ||
94 | } | ||
95 | |||
96 | |||
97 | async import({ | ||
98 | request, | ||
99 | response, | ||
100 | }) { | ||
101 | // Validate user input | ||
102 | const validation = await validateAll(request.all(), { | ||
103 | email: 'required|email', | ||
104 | password: 'required', | ||
105 | }); | ||
106 | if (validation.fails()) { | ||
107 | let errorMessage = 'There was an error while trying to import your account:\n'; | ||
108 | for (const message of validation.messages()) { | ||
109 | if (message.validation === 'required') { | ||
110 | errorMessage += `- Please make sure to supply your ${message.field}\n`; | ||
111 | } else if (message.validation === 'unique') { | ||
112 | errorMessage += '- There is already a user with this email.\n'; | ||
113 | } else { | ||
114 | errorMessage += `${message.message}\n`; | ||
115 | } | ||
116 | } | ||
117 | return response.status(401).send(errorMessage); | ||
118 | } | ||
119 | |||
120 | const { | ||
121 | email, | ||
122 | password, | ||
123 | } = request.all(); | ||
124 | |||
125 | const hashedPassword = crypto.createHash('sha256').update(password).digest('base64'); | ||
126 | |||
127 | if (Env.get('CONNECT_WITH_FRANZ') == 'false') { // eslint-disable-line eqeqeq | ||
128 | return response.send('Your account has been created but due to this server\'s configuration, we could not import your Franz account data.\n\nIf you are the server owner, please set CONNECT_WITH_FRANZ to true to enable account imports.'); | ||
129 | } | ||
130 | |||
131 | const base = 'https://api.franzinfra.com/v1/'; | ||
132 | const userAgent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Ferdi/5.3.0-beta.1 Chrome/69.0.3497.128 Electron/4.2.4 Safari/537.36'; | ||
133 | |||
134 | // Try to get an authentication token | ||
135 | let token; | ||
136 | try { | ||
137 | const basicToken = btoa(`${email}:${hashedPassword}`); | ||
138 | |||
139 | const rawResponse = await fetch(`${base}auth/login`, { | ||
140 | method: 'POST', | ||
141 | headers: { | ||
142 | Authorization: `Basic ${basicToken}`, | ||
143 | 'User-Agent': userAgent, | ||
144 | }, | ||
145 | }); | ||
146 | const content = await rawResponse.json(); | ||
147 | |||
148 | if (!content.message || content.message !== 'Successfully logged in') { | ||
149 | const errorMessage = 'Could not login into Franz with your supplied credentials. Please check and try again'; | ||
150 | return response.status(401).send(errorMessage); | ||
151 | } | ||
152 | |||
153 | // eslint-disable-next-line prefer-destructuring | ||
154 | token = content.token; | ||
155 | } catch (e) { | ||
156 | return response.status(401).send({ | ||
157 | message: 'Cannot login to Franz', | ||
158 | error: e, | ||
159 | }); | ||
160 | } | ||
161 | |||
162 | // Get user information | ||
163 | let userInf = false; | ||
164 | try { | ||
165 | userInf = await franzRequest('me', 'GET', token); | ||
166 | } catch (e) { | ||
167 | const errorMessage = `Could not get your user info from Franz. Please check your credentials or try again later.\nError: ${e}`; | ||
168 | return response.status(401).send(errorMessage); | ||
169 | } | ||
170 | if (!userInf) { | ||
171 | const errorMessage = 'Could not get your user info from Franz. Please check your credentials or try again later'; | ||
172 | return response.status(401).send(errorMessage); | ||
173 | } | ||
174 | |||
175 | const serviceIdTranslation = {}; | ||
176 | |||
177 | // Import services | ||
178 | try { | ||
179 | const services = await franzRequest('me/services', 'GET', token); | ||
180 | |||
181 | for (const service of services) { | ||
182 | // Get new, unused uuid | ||
183 | let serviceId; | ||
184 | do { | ||
185 | serviceId = uuid(); | ||
186 | } while ((await Service.query().where('serviceId', serviceId).fetch()).rows.length > 0); // eslint-disable-line no-await-in-loop | ||
187 | |||
188 | await Service.create({ // eslint-disable-line no-await-in-loop | ||
189 | serviceId, | ||
190 | name: service.name, | ||
191 | recipeId: service.recipeId, | ||
192 | settings: JSON.stringify(service), | ||
193 | }); | ||
194 | |||
195 | serviceIdTranslation[service.id] = serviceId; | ||
196 | } | ||
197 | } catch (e) { | ||
198 | const errorMessage = `Could not import your services into our system.\nError: ${e}`; | ||
199 | return response.status(401).send(errorMessage); | ||
200 | } | ||
201 | |||
202 | // Import workspaces | ||
203 | try { | ||
204 | const workspaces = await franzRequest('workspace', 'GET', token); | ||
205 | |||
206 | for (const workspace of workspaces) { | ||
207 | let workspaceId; | ||
208 | do { | ||
209 | workspaceId = uuid(); | ||
210 | } while ((await Workspace.query().where('workspaceId', workspaceId).fetch()).rows.length > 0); // eslint-disable-line no-await-in-loop | ||
211 | |||
212 | const services = workspace.services.map(service => serviceIdTranslation[service]); | ||
213 | |||
214 | await Workspace.create({ // eslint-disable-line no-await-in-loop | ||
215 | workspaceId, | ||
216 | name: workspace.name, | ||
217 | order: workspace.order, | ||
218 | services: JSON.stringify(services), | ||
219 | data: JSON.stringify({}), | ||
220 | }); | ||
221 | } | ||
222 | } catch (e) { | ||
223 | const errorMessage = `Could not import your workspaces into our system.\nError: ${e}`; | ||
224 | return response.status(401).send(errorMessage); | ||
225 | } | ||
226 | |||
227 | return response.send('Your account has been imported. You can now use your Franz account in Ferdi.'); | ||
228 | } | ||
229 | } | ||
230 | |||
231 | module.exports = UserController; | ||
diff --git a/src/server/app/Controllers/Http/WorkspaceController.js b/src/server/app/Controllers/Http/WorkspaceController.js new file mode 100644 index 000000000..7990b8434 --- /dev/null +++ b/src/server/app/Controllers/Http/WorkspaceController.js | |||
@@ -0,0 +1,148 @@ | |||
1 | const Workspace = use('App/Models/Workspace'); | ||
2 | const { | ||
3 | validateAll, | ||
4 | } = use('Validator'); | ||
5 | |||
6 | const uuid = require('uuid/v4'); | ||
7 | |||
8 | class WorkspaceController { | ||
9 | // Create a new workspace for user | ||
10 | async create({ | ||
11 | request, | ||
12 | response, | ||
13 | }) { | ||
14 | // Validate user input | ||
15 | const validation = await validateAll(request.all(), { | ||
16 | name: 'required|alpha', | ||
17 | }); | ||
18 | if (validation.fails()) { | ||
19 | return response.status(401).send({ | ||
20 | message: 'Invalid POST arguments', | ||
21 | messages: validation.messages(), | ||
22 | status: 401, | ||
23 | }); | ||
24 | } | ||
25 | |||
26 | const data = request.all(); | ||
27 | |||
28 | // Get new, unused uuid | ||
29 | let workspaceId; | ||
30 | do { | ||
31 | workspaceId = uuid(); | ||
32 | } while ((await Workspace.query().where('workspaceId', workspaceId).fetch()).rows.length > 0); // eslint-disable-line no-await-in-loop | ||
33 | |||
34 | const order = (await Workspace.all()).rows.length; | ||
35 | |||
36 | await Workspace.create({ | ||
37 | workspaceId, | ||
38 | name: data.name, | ||
39 | order, | ||
40 | services: JSON.stringify([]), | ||
41 | data: JSON.stringify(data), | ||
42 | }); | ||
43 | |||
44 | return response.send({ | ||
45 | userId: 1, | ||
46 | name: data.name, | ||
47 | id: workspaceId, | ||
48 | order, | ||
49 | workspaces: [], | ||
50 | }); | ||
51 | } | ||
52 | |||
53 | async edit({ | ||
54 | request, | ||
55 | response, | ||
56 | params, | ||
57 | }) { | ||
58 | // Validate user input | ||
59 | const validation = await validateAll(request.all(), { | ||
60 | name: 'required|alpha', | ||
61 | services: 'required|array', | ||
62 | }); | ||
63 | if (validation.fails()) { | ||
64 | return response.status(401).send({ | ||
65 | message: 'Invalid POST arguments', | ||
66 | messages: validation.messages(), | ||
67 | status: 401, | ||
68 | }); | ||
69 | } | ||
70 | |||
71 | const data = request.all(); | ||
72 | const { | ||
73 | id, | ||
74 | } = params; | ||
75 | |||
76 | // Update data in database | ||
77 | await (Workspace.query() | ||
78 | .where('workspaceId', id)).update({ | ||
79 | name: data.name, | ||
80 | services: JSON.stringify(data.services), | ||
81 | }); | ||
82 | |||
83 | // Get updated row | ||
84 | const workspace = (await Workspace.query() | ||
85 | .where('workspaceId', id).fetch()).rows[0]; | ||
86 | |||
87 | return response.send({ | ||
88 | id: workspace.workspaceId, | ||
89 | name: data.name, | ||
90 | order: workspace.order, | ||
91 | services: data.services, | ||
92 | userId: 1, | ||
93 | }); | ||
94 | } | ||
95 | |||
96 | async delete({ | ||
97 | request, | ||
98 | response, | ||
99 | params, | ||
100 | }) { | ||
101 | // Validate user input | ||
102 | const validation = await validateAll(request.all(), { | ||
103 | id: 'required', | ||
104 | }); | ||
105 | if (validation.fails()) { | ||
106 | return response.status(401).send({ | ||
107 | message: 'Invalid POST arguments', | ||
108 | messages: validation.messages(), | ||
109 | status: 401, | ||
110 | }); | ||
111 | } | ||
112 | |||
113 | const { | ||
114 | id, | ||
115 | } = params; | ||
116 | |||
117 | // Update data in database | ||
118 | await (Workspace.query() | ||
119 | .where('workspaceId', id)).delete(); | ||
120 | |||
121 | return response.send({ | ||
122 | message: 'Successfully deleted workspace', | ||
123 | }); | ||
124 | } | ||
125 | |||
126 | // List all workspaces a user has created | ||
127 | async list({ | ||
128 | response, | ||
129 | }) { | ||
130 | const workspaces = (await Workspace.all()).rows; | ||
131 | // Convert to array with all data Franz wants | ||
132 | let workspacesArray = []; | ||
133 | if (workspaces) { | ||
134 | workspacesArray = workspaces.map(workspace => ({ | ||
135 | id: workspace.workspaceId, | ||
136 | name: workspace.name, | ||
137 | order: workspace.order, | ||
138 | services: JSON.parse(workspace.services), | ||
139 | userId: 1, | ||
140 | })); | ||
141 | } | ||
142 | |||
143 | |||
144 | return response.send(workspacesArray); | ||
145 | } | ||
146 | } | ||
147 | |||
148 | module.exports = WorkspaceController; | ||
diff --git a/src/server/app/Exceptions/Handler.js b/src/server/app/Exceptions/Handler.js new file mode 100644 index 000000000..e8d2d2ee2 --- /dev/null +++ b/src/server/app/Exceptions/Handler.js | |||
@@ -0,0 +1,45 @@ | |||
1 | |||
2 | const BaseExceptionHandler = use('BaseExceptionHandler'); | ||
3 | |||
4 | /** | ||
5 | * This class handles all exceptions thrown during | ||
6 | * the HTTP request lifecycle. | ||
7 | * | ||
8 | * @class ExceptionHandler | ||
9 | */ | ||
10 | class ExceptionHandler extends BaseExceptionHandler { | ||
11 | /** | ||
12 | * Handle exception thrown during the HTTP lifecycle | ||
13 | * | ||
14 | * @method handle | ||
15 | * | ||
16 | * @param {Object} error | ||
17 | * @param {Object} options.request | ||
18 | * @param {Object} options.response | ||
19 | * | ||
20 | * @return {void} | ||
21 | */ | ||
22 | async handle(error, { response }) { | ||
23 | if (error.name === 'ValidationException') { | ||
24 | return response.status(400).send('Invalid arguments'); | ||
25 | } | ||
26 | |||
27 | return response.status(error.status).send(error.message); | ||
28 | } | ||
29 | |||
30 | /** | ||
31 | * Report exception for logging or debugging. | ||
32 | * | ||
33 | * @method report | ||
34 | * | ||
35 | * @param {Object} error | ||
36 | * @param {Object} options.request | ||
37 | * | ||
38 | * @return {void} | ||
39 | */ | ||
40 | async report() { | ||
41 | return true; | ||
42 | } | ||
43 | } | ||
44 | |||
45 | module.exports = ExceptionHandler; | ||
diff --git a/src/server/app/Middleware/ConvertEmptyStringsToNull.js b/src/server/app/Middleware/ConvertEmptyStringsToNull.js new file mode 100644 index 000000000..bc3079a7f --- /dev/null +++ b/src/server/app/Middleware/ConvertEmptyStringsToNull.js | |||
@@ -0,0 +1,16 @@ | |||
1 | |||
2 | class ConvertEmptyStringsToNull { | ||
3 | async handle({ request }, next) { | ||
4 | if (Object.keys(request.body).length) { | ||
5 | request.body = Object.assign( | ||
6 | ...Object.keys(request.body).map(key => ({ | ||
7 | [key]: request.body[key] !== '' ? request.body[key] : null, | ||
8 | })), | ||
9 | ); | ||
10 | } | ||
11 | |||
12 | await next(); | ||
13 | } | ||
14 | } | ||
15 | |||
16 | module.exports = ConvertEmptyStringsToNull; | ||
diff --git a/src/server/app/Models/Recipe.js b/src/server/app/Models/Recipe.js new file mode 100644 index 000000000..da3618bf7 --- /dev/null +++ b/src/server/app/Models/Recipe.js | |||
@@ -0,0 +1,8 @@ | |||
1 | |||
2 | /** @type {typeof import('@adonisjs/lucid/src/Lucid/Model')} */ | ||
3 | const Model = use('Model'); | ||
4 | |||
5 | class Recipe extends Model { | ||
6 | } | ||
7 | |||
8 | module.exports = Recipe; | ||
diff --git a/src/server/app/Models/Service.js b/src/server/app/Models/Service.js new file mode 100644 index 000000000..20679feb1 --- /dev/null +++ b/src/server/app/Models/Service.js | |||
@@ -0,0 +1,8 @@ | |||
1 | |||
2 | /** @type {typeof import('@adonisjs/lucid/src/Lucid/Model')} */ | ||
3 | const Model = use('Model'); | ||
4 | |||
5 | class Service extends Model { | ||
6 | } | ||
7 | |||
8 | module.exports = Service; | ||
diff --git a/src/server/app/Models/Token.js b/src/server/app/Models/Token.js new file mode 100644 index 000000000..f6bec0852 --- /dev/null +++ b/src/server/app/Models/Token.js | |||
@@ -0,0 +1,8 @@ | |||
1 | |||
2 | /** @type {typeof import('@adonisjs/lucid/src/Lucid/Model')} */ | ||
3 | const Model = use('Model'); | ||
4 | |||
5 | class Token extends Model { | ||
6 | } | ||
7 | |||
8 | module.exports = Token; | ||
diff --git a/src/server/app/Models/Traits/NoTimestamp.js b/src/server/app/Models/Traits/NoTimestamp.js new file mode 100644 index 000000000..c647428b3 --- /dev/null +++ b/src/server/app/Models/Traits/NoTimestamp.js | |||
@@ -0,0 +1,15 @@ | |||
1 | |||
2 | class NoTimestamp { | ||
3 | register(Model) { | ||
4 | Object.defineProperties(Model, { | ||
5 | createdAtColumn: { | ||
6 | get: () => null, | ||
7 | }, | ||
8 | updatedAtColumn: { | ||
9 | get: () => null, | ||
10 | }, | ||
11 | }); | ||
12 | } | ||
13 | } | ||
14 | |||
15 | module.exports = NoTimestamp; | ||
diff --git a/src/server/app/Models/User.js b/src/server/app/Models/User.js new file mode 100644 index 000000000..907710d8d --- /dev/null +++ b/src/server/app/Models/User.js | |||
@@ -0,0 +1,8 @@ | |||
1 | // File is required by AdonisJS but not used by the server | ||
2 | /** @type {typeof import('@adonisjs/lucid/src/Lucid/Model')} */ | ||
3 | const Model = use('Model'); | ||
4 | |||
5 | class User extends Model { | ||
6 | } | ||
7 | |||
8 | module.exports = User; | ||
diff --git a/src/server/app/Models/Workspace.js b/src/server/app/Models/Workspace.js new file mode 100644 index 000000000..3b73cbf33 --- /dev/null +++ b/src/server/app/Models/Workspace.js | |||
@@ -0,0 +1,8 @@ | |||
1 | |||
2 | /** @type {typeof import('@adonisjs/lucid/src/Lucid/Model')} */ | ||
3 | const Model = use('Model'); | ||
4 | |||
5 | class Workspace extends Model { | ||
6 | } | ||
7 | |||
8 | module.exports = Workspace; | ||
diff --git a/src/server/config/app.js b/src/server/config/app.js new file mode 100644 index 000000000..7938b81df --- /dev/null +++ b/src/server/config/app.js | |||
@@ -0,0 +1,242 @@ | |||
1 | |||
2 | /** @type {import('@adonisjs/framework/src/Env')} */ | ||
3 | const Env = use('Env'); | ||
4 | |||
5 | module.exports = { | ||
6 | |||
7 | /* | ||
8 | |-------------------------------------------------------------------------- | ||
9 | | Application Name | ||
10 | |-------------------------------------------------------------------------- | ||
11 | | | ||
12 | | This value is the name of your application and can used when you | ||
13 | | need to place the application's name in a email, view or | ||
14 | | other location. | ||
15 | | | ||
16 | */ | ||
17 | |||
18 | name: Env.get('APP_NAME', 'Ferdi Internal Server'), | ||
19 | |||
20 | /* | ||
21 | |-------------------------------------------------------------------------- | ||
22 | | App Key | ||
23 | |-------------------------------------------------------------------------- | ||
24 | | | ||
25 | | App key is a randomly generated 16 or 32 characters long string required | ||
26 | | to encrypt cookies, sessions and other sensitive data. | ||
27 | | | ||
28 | */ | ||
29 | appKey: Env.getOrFail('APP_KEY'), | ||
30 | |||
31 | http: { | ||
32 | /* | ||
33 | |-------------------------------------------------------------------------- | ||
34 | | Allow Method Spoofing | ||
35 | |-------------------------------------------------------------------------- | ||
36 | | | ||
37 | | Method spoofing allows to make requests by spoofing the http verb. | ||
38 | | Which means you can make a GET request but instruct the server to | ||
39 | | treat as a POST or PUT request. If you want this feature, set the | ||
40 | | below value to true. | ||
41 | | | ||
42 | */ | ||
43 | allowMethodSpoofing: true, | ||
44 | |||
45 | /* | ||
46 | |-------------------------------------------------------------------------- | ||
47 | | Trust Proxy | ||
48 | |-------------------------------------------------------------------------- | ||
49 | | | ||
50 | | Trust proxy defines whether X-Forwarded-* headers should be trusted or not. | ||
51 | | When your application is behind a proxy server like nginx, these values | ||
52 | | are set automatically and should be trusted. Apart from setting it | ||
53 | | to true or false Adonis supports handful or ways to allow proxy | ||
54 | | values. Read documentation for that. | ||
55 | | | ||
56 | */ | ||
57 | trustProxy: false, | ||
58 | |||
59 | /* | ||
60 | |-------------------------------------------------------------------------- | ||
61 | | Subdomains | ||
62 | |-------------------------------------------------------------------------- | ||
63 | | | ||
64 | | Offset to be used for returning subdomains for a given request.For | ||
65 | | majority of applications it will be 2, until you have nested | ||
66 | | sudomains. | ||
67 | | cheatsheet.adonisjs.com - offset - 2 | ||
68 | | virk.cheatsheet.adonisjs.com - offset - 3 | ||
69 | | | ||
70 | */ | ||
71 | subdomainOffset: 2, | ||
72 | |||
73 | /* | ||
74 | |-------------------------------------------------------------------------- | ||
75 | | JSONP Callback | ||
76 | |-------------------------------------------------------------------------- | ||
77 | | | ||
78 | | Default jsonp callback to be used when callback query string is missing | ||
79 | | in request url. | ||
80 | | | ||
81 | */ | ||
82 | jsonpCallback: 'callback', | ||
83 | |||
84 | |||
85 | /* | ||
86 | |-------------------------------------------------------------------------- | ||
87 | | Etag | ||
88 | |-------------------------------------------------------------------------- | ||
89 | | | ||
90 | | Set etag on all HTTP response. In order to disable for selected routes, | ||
91 | | you can call the `response.send` with an options object as follows. | ||
92 | | | ||
93 | | response.send('Hello', { ignoreEtag: true }) | ||
94 | | | ||
95 | */ | ||
96 | etag: false, | ||
97 | }, | ||
98 | |||
99 | views: { | ||
100 | /* | ||
101 | |-------------------------------------------------------------------------- | ||
102 | | Cache Views | ||
103 | |-------------------------------------------------------------------------- | ||
104 | | | ||
105 | | Define whether or not to cache the compiled view. Set it to true in | ||
106 | | production to optimize view loading time. | ||
107 | | | ||
108 | */ | ||
109 | cache: Env.get('CACHE_VIEWS', true), | ||
110 | }, | ||
111 | |||
112 | static: { | ||
113 | /* | ||
114 | |-------------------------------------------------------------------------- | ||
115 | | Dot Files | ||
116 | |-------------------------------------------------------------------------- | ||
117 | | | ||
118 | | Define how to treat dot files when trying to server static resources. | ||
119 | | By default it is set to ignore, which will pretend that dotfiles | ||
120 | | does not exists. | ||
121 | | | ||
122 | | Can be one of the following | ||
123 | | ignore, deny, allow | ||
124 | | | ||
125 | */ | ||
126 | dotfiles: 'ignore', | ||
127 | |||
128 | /* | ||
129 | |-------------------------------------------------------------------------- | ||
130 | | ETag | ||
131 | |-------------------------------------------------------------------------- | ||
132 | | | ||
133 | | Enable or disable etag generation | ||
134 | | | ||
135 | */ | ||
136 | etag: true, | ||
137 | |||
138 | /* | ||
139 | |-------------------------------------------------------------------------- | ||
140 | | Extensions | ||
141 | |-------------------------------------------------------------------------- | ||
142 | | | ||
143 | | Set file extension fallbacks. When set, if a file is not found, the given | ||
144 | | extensions will be added to the file name and search for. The first | ||
145 | | that exists will be served. Example: ['html', 'htm']. | ||
146 | | | ||
147 | */ | ||
148 | extensions: false, | ||
149 | }, | ||
150 | |||
151 | locales: { | ||
152 | /* | ||
153 | |-------------------------------------------------------------------------- | ||
154 | | Loader | ||
155 | |-------------------------------------------------------------------------- | ||
156 | | | ||
157 | | The loader to be used for fetching and updating locales. Below is the | ||
158 | | list of available options. | ||
159 | | | ||
160 | | file, database | ||
161 | | | ||
162 | */ | ||
163 | loader: 'file', | ||
164 | |||
165 | /* | ||
166 | |-------------------------------------------------------------------------- | ||
167 | | Default Locale | ||
168 | |-------------------------------------------------------------------------- | ||
169 | | | ||
170 | | Default locale to be used by Antl provider. You can always switch drivers | ||
171 | | in runtime or use the official Antl middleware to detect the driver | ||
172 | | based on HTTP headers/query string. | ||
173 | | | ||
174 | */ | ||
175 | locale: 'en', | ||
176 | }, | ||
177 | |||
178 | logger: { | ||
179 | /* | ||
180 | |-------------------------------------------------------------------------- | ||
181 | | Transport | ||
182 | |-------------------------------------------------------------------------- | ||
183 | | | ||
184 | | Transport to be used for logging messages. You can have multiple | ||
185 | | transports using same driver. | ||
186 | | | ||
187 | | Available drivers are: `file` and `console`. | ||
188 | | | ||
189 | */ | ||
190 | transport: 'console', | ||
191 | |||
192 | /* | ||
193 | |-------------------------------------------------------------------------- | ||
194 | | Console Transport | ||
195 | |-------------------------------------------------------------------------- | ||
196 | | | ||
197 | | Using `console` driver for logging. This driver writes to `stdout` | ||
198 | | and `stderr` | ||
199 | | | ||
200 | */ | ||
201 | console: { | ||
202 | driver: 'console', | ||
203 | name: 'adonis-app', | ||
204 | level: 'info', | ||
205 | }, | ||
206 | |||
207 | /* | ||
208 | |-------------------------------------------------------------------------- | ||
209 | | File Transport | ||
210 | |-------------------------------------------------------------------------- | ||
211 | | | ||
212 | | File transport uses file driver and writes log messages for a given | ||
213 | | file inside `tmp` directory for your app. | ||
214 | | | ||
215 | | For a different directory, set an absolute path for the filename. | ||
216 | | | ||
217 | */ | ||
218 | file: { | ||
219 | driver: 'file', | ||
220 | name: 'adonis-app', | ||
221 | filename: 'adonis.log', | ||
222 | level: 'info', | ||
223 | }, | ||
224 | }, | ||
225 | |||
226 | /* | ||
227 | |-------------------------------------------------------------------------- | ||
228 | | Generic Cookie Options | ||
229 | |-------------------------------------------------------------------------- | ||
230 | | | ||
231 | | The following cookie options are generic settings used by AdonisJs to create | ||
232 | | cookies. However, some parts of the application like `sessions` can have | ||
233 | | separate settings for cookies inside `config/session.js`. | ||
234 | | | ||
235 | */ | ||
236 | cookie: { | ||
237 | httpOnly: true, | ||
238 | sameSite: false, | ||
239 | path: '/', | ||
240 | maxAge: 7200, | ||
241 | }, | ||
242 | }; | ||
diff --git a/src/server/config/auth.js b/src/server/config/auth.js new file mode 100644 index 000000000..b831b06c6 --- /dev/null +++ b/src/server/config/auth.js | |||
@@ -0,0 +1,93 @@ | |||
1 | |||
2 | /** @type {import('@adonisjs/framework/src/Env')} */ | ||
3 | const Env = use('Env'); | ||
4 | |||
5 | module.exports = { | ||
6 | /* | ||
7 | |-------------------------------------------------------------------------- | ||
8 | | Authenticator | ||
9 | |-------------------------------------------------------------------------- | ||
10 | | | ||
11 | | Authentication is a combination of serializer and scheme with extra | ||
12 | | config to define on how to authenticate a user. | ||
13 | | | ||
14 | | Available Schemes - basic, session, jwt, api | ||
15 | | Available Serializers - lucid, database | ||
16 | | | ||
17 | */ | ||
18 | authenticator: 'jwt', | ||
19 | |||
20 | /* | ||
21 | |-------------------------------------------------------------------------- | ||
22 | | Session | ||
23 | |-------------------------------------------------------------------------- | ||
24 | | | ||
25 | | Session authenticator makes use of sessions to authenticate a user. | ||
26 | | Session authentication is always persistent. | ||
27 | | | ||
28 | */ | ||
29 | session: { | ||
30 | serializer: 'lucid', | ||
31 | model: 'App/Models/User', | ||
32 | scheme: 'session', | ||
33 | uid: 'email', | ||
34 | password: 'password', | ||
35 | }, | ||
36 | |||
37 | /* | ||
38 | |-------------------------------------------------------------------------- | ||
39 | | Basic Auth | ||
40 | |-------------------------------------------------------------------------- | ||
41 | | | ||
42 | | The basic auth authenticator uses basic auth header to authenticate a | ||
43 | | user. | ||
44 | | | ||
45 | | NOTE: | ||
46 | | This scheme is not persistent and users are supposed to pass | ||
47 | | login credentials on each request. | ||
48 | | | ||
49 | */ | ||
50 | basic: { | ||
51 | serializer: 'lucid', | ||
52 | model: 'App/Models/User', | ||
53 | scheme: 'basic', | ||
54 | uid: 'email', | ||
55 | password: 'password', | ||
56 | }, | ||
57 | |||
58 | /* | ||
59 | |-------------------------------------------------------------------------- | ||
60 | | Jwt | ||
61 | |-------------------------------------------------------------------------- | ||
62 | | | ||
63 | | The jwt authenticator works by passing a jwt token on each HTTP request | ||
64 | | via HTTP `Authorization` header. | ||
65 | | | ||
66 | */ | ||
67 | jwt: { | ||
68 | serializer: 'lucid', | ||
69 | model: 'App/Models/User', | ||
70 | scheme: 'jwt', | ||
71 | uid: 'email', | ||
72 | password: 'password', | ||
73 | options: { | ||
74 | secret: Env.get('APP_KEY'), | ||
75 | }, | ||
76 | }, | ||
77 | |||
78 | /* | ||
79 | |-------------------------------------------------------------------------- | ||
80 | | Api | ||
81 | |-------------------------------------------------------------------------- | ||
82 | | | ||
83 | | The Api scheme makes use of API personal tokens to authenticate a user. | ||
84 | | | ||
85 | */ | ||
86 | api: { | ||
87 | serializer: 'lucid', | ||
88 | model: 'App/Models/User', | ||
89 | scheme: 'api', | ||
90 | uid: 'email', | ||
91 | password: 'password', | ||
92 | }, | ||
93 | }; | ||
diff --git a/src/server/config/bodyParser.js b/src/server/config/bodyParser.js new file mode 100644 index 000000000..c336e67d2 --- /dev/null +++ b/src/server/config/bodyParser.js | |||
@@ -0,0 +1,156 @@ | |||
1 | |||
2 | module.exports = { | ||
3 | /* | ||
4 | |-------------------------------------------------------------------------- | ||
5 | | JSON Parser | ||
6 | |-------------------------------------------------------------------------- | ||
7 | | | ||
8 | | Below settings are applied when the request body contains a JSON payload. | ||
9 | | If you want body parser to ignore JSON payloads, then simply set `types` | ||
10 | | to an empty array. | ||
11 | */ | ||
12 | json: { | ||
13 | /* | ||
14 | |-------------------------------------------------------------------------- | ||
15 | | limit | ||
16 | |-------------------------------------------------------------------------- | ||
17 | | | ||
18 | | Defines the limit of JSON that can be sent by the client. If payload | ||
19 | | is over 1mb it will not be processed. | ||
20 | | | ||
21 | */ | ||
22 | limit: '50mb', | ||
23 | |||
24 | /* | ||
25 | |-------------------------------------------------------------------------- | ||
26 | | strict | ||
27 | |-------------------------------------------------------------------------- | ||
28 | | | ||
29 | | When `strict` is set to true, body parser will only parse Arrays and | ||
30 | | Object. Otherwise everything parseable by `JSON.parse` is parsed. | ||
31 | | | ||
32 | */ | ||
33 | strict: true, | ||
34 | |||
35 | /* | ||
36 | |-------------------------------------------------------------------------- | ||
37 | | types | ||
38 | |-------------------------------------------------------------------------- | ||
39 | | | ||
40 | | Which content types are processed as JSON payloads. You are free to | ||
41 | | add your own types here, but the request body should be parseable | ||
42 | | by `JSON.parse` method. | ||
43 | | | ||
44 | */ | ||
45 | types: [ | ||
46 | 'application/json', | ||
47 | 'application/json-patch+json', | ||
48 | 'application/vnd.api+json', | ||
49 | 'application/csp-report', | ||
50 | ], | ||
51 | }, | ||
52 | |||
53 | /* | ||
54 | |-------------------------------------------------------------------------- | ||
55 | | Raw Parser | ||
56 | |-------------------------------------------------------------------------- | ||
57 | | | ||
58 | | | ||
59 | | | ||
60 | */ | ||
61 | raw: { | ||
62 | types: [ | ||
63 | 'text/*', | ||
64 | ], | ||
65 | }, | ||
66 | |||
67 | /* | ||
68 | |-------------------------------------------------------------------------- | ||
69 | | Form Parser | ||
70 | |-------------------------------------------------------------------------- | ||
71 | | | ||
72 | | | ||
73 | | | ||
74 | */ | ||
75 | form: { | ||
76 | types: [ | ||
77 | 'application/x-www-form-urlencoded', | ||
78 | ], | ||
79 | }, | ||
80 | |||
81 | /* | ||
82 | |-------------------------------------------------------------------------- | ||
83 | | Files Parser | ||
84 | |-------------------------------------------------------------------------- | ||
85 | | | ||
86 | | | ||
87 | | | ||
88 | */ | ||
89 | files: { | ||
90 | types: [ | ||
91 | 'multipart/form-data', | ||
92 | ], | ||
93 | |||
94 | /* | ||
95 | |-------------------------------------------------------------------------- | ||
96 | | Max Size | ||
97 | |-------------------------------------------------------------------------- | ||
98 | | | ||
99 | | Below value is the max size of all the files uploaded to the server. It | ||
100 | | is validated even before files have been processed and hard exception | ||
101 | | is thrown. | ||
102 | | | ||
103 | | Consider setting a reasonable value here, otherwise people may upload GB's | ||
104 | | of files which will keep your server busy. | ||
105 | | | ||
106 | | Also this value is considered when `autoProcess` is set to true. | ||
107 | | | ||
108 | */ | ||
109 | maxSize: '20mb', | ||
110 | |||
111 | /* | ||
112 | |-------------------------------------------------------------------------- | ||
113 | | Auto Process | ||
114 | |-------------------------------------------------------------------------- | ||
115 | | | ||
116 | | Whether or not to auto-process files. Since HTTP servers handle files via | ||
117 | | couple of specific endpoints. It is better to set this value off and | ||
118 | | manually process the files when required. | ||
119 | | | ||
120 | | This value can contain a boolean or an array of route patterns | ||
121 | | to be autoprocessed. | ||
122 | */ | ||
123 | autoProcess: true, | ||
124 | |||
125 | /* | ||
126 | |-------------------------------------------------------------------------- | ||
127 | | Process Manually | ||
128 | |-------------------------------------------------------------------------- | ||
129 | | | ||
130 | | The list of routes that should not process files and instead rely on | ||
131 | | manual process. This list should only contain routes when autoProcess | ||
132 | | is to true. Otherwise everything is processed manually. | ||
133 | | | ||
134 | */ | ||
135 | processManually: [], | ||
136 | |||
137 | /* | ||
138 | |-------------------------------------------------------------------------- | ||
139 | | Temporary file name | ||
140 | |-------------------------------------------------------------------------- | ||
141 | | | ||
142 | | Define a function, which should return a string to be used as the | ||
143 | | tmp file name. | ||
144 | | | ||
145 | | If not defined, Bodyparser will use `uuid` as the tmp file name. | ||
146 | | | ||
147 | | To be defined as. If you are defining the function, then do make sure | ||
148 | | to return a value from it. | ||
149 | | | ||
150 | | tmpFileName () { | ||
151 | | return 'some-unique-value' | ||
152 | | } | ||
153 | | | ||
154 | */ | ||
155 | }, | ||
156 | }; | ||
diff --git a/src/server/config/cors.js b/src/server/config/cors.js new file mode 100644 index 000000000..7ebbe3ffa --- /dev/null +++ b/src/server/config/cors.js | |||
@@ -0,0 +1,86 @@ | |||
1 | |||
2 | module.exports = { | ||
3 | /* | ||
4 | |-------------------------------------------------------------------------- | ||
5 | | Origin | ||
6 | |-------------------------------------------------------------------------- | ||
7 | | | ||
8 | | Set a list of origins to be allowed. The value can be one of the following | ||
9 | | | ||
10 | | Boolean: true - Allow current request origin | ||
11 | | Boolean: false - Disallow all | ||
12 | | String - Comma separated list of allowed origins | ||
13 | | Array - An array of allowed origins | ||
14 | | String: * - A wildcard to allow current request origin | ||
15 | | Function - Receives the current origin and should return one of the above values. | ||
16 | | | ||
17 | */ | ||
18 | origin: false, | ||
19 | |||
20 | /* | ||
21 | |-------------------------------------------------------------------------- | ||
22 | | Methods | ||
23 | |-------------------------------------------------------------------------- | ||
24 | | | ||
25 | | HTTP methods to be allowed. The value can be one of the following | ||
26 | | | ||
27 | | String - Comma separated list of allowed methods | ||
28 | | Array - An array of allowed methods | ||
29 | | | ||
30 | */ | ||
31 | methods: ['GET', 'PUT', 'PATCH', 'POST', 'DELETE'], | ||
32 | |||
33 | /* | ||
34 | |-------------------------------------------------------------------------- | ||
35 | | Headers | ||
36 | |-------------------------------------------------------------------------- | ||
37 | | | ||
38 | | List of headers to be allowed via Access-Control-Request-Headers header. | ||
39 | | The value can be one of the following. | ||
40 | | | ||
41 | | Boolean: true - Allow current request headers | ||
42 | | Boolean: false - Disallow all | ||
43 | | String - Comma separated list of allowed headers | ||
44 | | Array - An array of allowed headers | ||
45 | | String: * - A wildcard to allow current request headers | ||
46 | | Function - Receives the current header and should return one of the above values. | ||
47 | | | ||
48 | */ | ||
49 | headers: true, | ||
50 | |||
51 | /* | ||
52 | |-------------------------------------------------------------------------- | ||
53 | | Expose Headers | ||
54 | |-------------------------------------------------------------------------- | ||
55 | | | ||
56 | | A list of headers to be exposed via `Access-Control-Expose-Headers` | ||
57 | | header. The value can be one of the following. | ||
58 | | | ||
59 | | Boolean: false - Disallow all | ||
60 | | String: Comma separated list of allowed headers | ||
61 | | Array - An array of allowed headers | ||
62 | | | ||
63 | */ | ||
64 | exposeHeaders: false, | ||
65 | |||
66 | /* | ||
67 | |-------------------------------------------------------------------------- | ||
68 | | Credentials | ||
69 | |-------------------------------------------------------------------------- | ||
70 | | | ||
71 | | Define Access-Control-Allow-Credentials header. It should always be a | ||
72 | | boolean. | ||
73 | | | ||
74 | */ | ||
75 | credentials: false, | ||
76 | |||
77 | /* | ||
78 | |-------------------------------------------------------------------------- | ||
79 | | MaxAge | ||
80 | |-------------------------------------------------------------------------- | ||
81 | | | ||
82 | | Define Access-Control-Allow-Max-Age | ||
83 | | | ||
84 | */ | ||
85 | maxAge: 90, | ||
86 | }; | ||
diff --git a/src/server/config/database.js b/src/server/config/database.js new file mode 100644 index 000000000..86f18dac5 --- /dev/null +++ b/src/server/config/database.js | |||
@@ -0,0 +1,87 @@ | |||
1 | |||
2 | /** @type {import('@adonisjs/framework/src/Env')} */ | ||
3 | const Env = use('Env'); | ||
4 | |||
5 | // eslint-disable-next-line import/no-extraneous-dependencies | ||
6 | const { app } = require('electron'); | ||
7 | const path = require('path'); | ||
8 | |||
9 | const dbPath = path.join(app.getPath('userData'), 'server.sqlite'); | ||
10 | |||
11 | module.exports = { | ||
12 | /* | ||
13 | |-------------------------------------------------------------------------- | ||
14 | | Default Connection | ||
15 | |-------------------------------------------------------------------------- | ||
16 | | | ||
17 | | Connection defines the default connection settings to be used while | ||
18 | | interacting with SQL databases. | ||
19 | | | ||
20 | */ | ||
21 | connection: Env.get('DB_CONNECTION', 'sqlite'), | ||
22 | |||
23 | /* | ||
24 | |-------------------------------------------------------------------------- | ||
25 | | Sqlite | ||
26 | |-------------------------------------------------------------------------- | ||
27 | | | ||
28 | | Sqlite is a flat file database and can be a good choice for a development | ||
29 | | environment. | ||
30 | | | ||
31 | | npm i --save sqlite3 | ||
32 | | | ||
33 | */ | ||
34 | sqlite: { | ||
35 | client: 'sqlite3', | ||
36 | connection: { | ||
37 | // filename: Helpers.databasePath(`${Env.get('DB_DATABASE', 'development')}.sqlite`), | ||
38 | filename: dbPath, | ||
39 | }, | ||
40 | useNullAsDefault: true, | ||
41 | debug: Env.get('DB_DEBUG', false), | ||
42 | }, | ||
43 | |||
44 | /* | ||
45 | |-------------------------------------------------------------------------- | ||
46 | | MySQL | ||
47 | |-------------------------------------------------------------------------- | ||
48 | | | ||
49 | | Here we define connection settings for MySQL database. | ||
50 | | | ||
51 | | npm i --save mysql | ||
52 | | | ||
53 | */ | ||
54 | mysql: { | ||
55 | client: 'mysql', | ||
56 | connection: { | ||
57 | host: Env.get('DB_HOST', 'localhost'), | ||
58 | port: Env.get('DB_PORT', ''), | ||
59 | user: Env.get('DB_USER', 'root'), | ||
60 | password: Env.get('DB_PASSWORD', ''), | ||
61 | database: Env.get('DB_DATABASE', 'adonis'), | ||
62 | }, | ||
63 | debug: Env.get('DB_DEBUG', false), | ||
64 | }, | ||
65 | |||
66 | /* | ||
67 | |-------------------------------------------------------------------------- | ||
68 | | PostgreSQL | ||
69 | |-------------------------------------------------------------------------- | ||
70 | | | ||
71 | | Here we define connection settings for PostgreSQL database. | ||
72 | | | ||
73 | | npm i --save pg | ||
74 | | | ||
75 | */ | ||
76 | pg: { | ||
77 | client: 'pg', | ||
78 | connection: { | ||
79 | host: Env.get('DB_HOST', 'localhost'), | ||
80 | port: Env.get('DB_PORT', ''), | ||
81 | user: Env.get('DB_USER', 'root'), | ||
82 | password: Env.get('DB_PASSWORD', ''), | ||
83 | database: Env.get('DB_DATABASE', 'adonis'), | ||
84 | }, | ||
85 | debug: Env.get('DB_DEBUG', false), | ||
86 | }, | ||
87 | }; | ||
diff --git a/src/server/config/drive.js b/src/server/config/drive.js new file mode 100644 index 000000000..617ce470a --- /dev/null +++ b/src/server/config/drive.js | |||
@@ -0,0 +1,45 @@ | |||
1 | const Env = use('Env'); | ||
2 | |||
3 | module.exports = { | ||
4 | /* | ||
5 | |-------------------------------------------------------------------------- | ||
6 | | Default disk | ||
7 | |-------------------------------------------------------------------------- | ||
8 | | | ||
9 | | The default disk is used when you interact with the file system without | ||
10 | | defining a disk name | ||
11 | | | ||
12 | */ | ||
13 | default: 'local', | ||
14 | |||
15 | disks: { | ||
16 | /* | ||
17 | |-------------------------------------------------------------------------- | ||
18 | | Local | ||
19 | |-------------------------------------------------------------------------- | ||
20 | | | ||
21 | | Local disk interacts with the a local folder inside your application | ||
22 | | | ||
23 | */ | ||
24 | local: { | ||
25 | root: `${__dirname}/../recipes`, | ||
26 | driver: 'local', | ||
27 | }, | ||
28 | |||
29 | /* | ||
30 | |-------------------------------------------------------------------------- | ||
31 | | S3 | ||
32 | |-------------------------------------------------------------------------- | ||
33 | | | ||
34 | | S3 disk interacts with a bucket on aws s3 | ||
35 | | | ||
36 | */ | ||
37 | s3: { | ||
38 | driver: 's3', | ||
39 | key: Env.get('S3_KEY'), | ||
40 | secret: Env.get('S3_SECRET'), | ||
41 | bucket: Env.get('S3_BUCKET'), | ||
42 | region: Env.get('S3_REGION'), | ||
43 | }, | ||
44 | }, | ||
45 | }; | ||
diff --git a/src/server/config/hash.js b/src/server/config/hash.js new file mode 100644 index 000000000..297c977fc --- /dev/null +++ b/src/server/config/hash.js | |||
@@ -0,0 +1,48 @@ | |||
1 | |||
2 | /** @type {import('@adonisjs/framework/src/Env')} */ | ||
3 | const Env = use('Env'); | ||
4 | |||
5 | module.exports = { | ||
6 | /* | ||
7 | |-------------------------------------------------------------------------- | ||
8 | | Driver | ||
9 | |-------------------------------------------------------------------------- | ||
10 | | | ||
11 | | Driver to be used for hashing values. The same driver is used by the | ||
12 | | auth module too. | ||
13 | | | ||
14 | */ | ||
15 | driver: Env.get('HASH_DRIVER', 'bcrypt'), | ||
16 | |||
17 | /* | ||
18 | |-------------------------------------------------------------------------- | ||
19 | | Bcrypt | ||
20 | |-------------------------------------------------------------------------- | ||
21 | | | ||
22 | | Config related to bcrypt hashing. https://www.npmjs.com/package/bcrypt | ||
23 | | package is used internally. | ||
24 | | | ||
25 | */ | ||
26 | bcrypt: { | ||
27 | rounds: 10, | ||
28 | }, | ||
29 | |||
30 | /* | ||
31 | |-------------------------------------------------------------------------- | ||
32 | | Argon | ||
33 | |-------------------------------------------------------------------------- | ||
34 | | | ||
35 | | Config related to argon. https://www.npmjs.com/package/argon2 package is | ||
36 | | used internally. | ||
37 | | | ||
38 | | Since argon is optional, you will have to install the dependency yourself | ||
39 | | | ||
40 | |============================================================================ | ||
41 | | npm i argon2 | ||
42 | |============================================================================ | ||
43 | | | ||
44 | */ | ||
45 | argon: { | ||
46 | type: 1, | ||
47 | }, | ||
48 | }; | ||
diff --git a/src/server/config/session.js b/src/server/config/session.js new file mode 100644 index 000000000..bce28bdd9 --- /dev/null +++ b/src/server/config/session.js | |||
@@ -0,0 +1,98 @@ | |||
1 | |||
2 | const Env = use('Env'); | ||
3 | |||
4 | module.exports = { | ||
5 | /* | ||
6 | |-------------------------------------------------------------------------- | ||
7 | | Session Driver | ||
8 | |-------------------------------------------------------------------------- | ||
9 | | | ||
10 | | The session driver to be used for storing session values. It can be | ||
11 | | cookie, file or redis. | ||
12 | | | ||
13 | | For `redis` driver, make sure to install and register `@adonisjs/redis` | ||
14 | | | ||
15 | */ | ||
16 | driver: Env.get('SESSION_DRIVER', 'cookie'), | ||
17 | |||
18 | /* | ||
19 | |-------------------------------------------------------------------------- | ||
20 | | Cookie Name | ||
21 | |-------------------------------------------------------------------------- | ||
22 | | | ||
23 | | The name of the cookie to be used for saving session id. Session ids | ||
24 | | are signed and encrypted. | ||
25 | | | ||
26 | */ | ||
27 | cookieName: 'adonis-session', | ||
28 | |||
29 | /* | ||
30 | |-------------------------------------------------------------------------- | ||
31 | | Clear session when browser closes | ||
32 | |-------------------------------------------------------------------------- | ||
33 | | | ||
34 | | If this value is true, the session cookie will be temporary and will be | ||
35 | | removed when browser closes. | ||
36 | | | ||
37 | */ | ||
38 | clearWithBrowser: true, | ||
39 | |||
40 | /* | ||
41 | |-------------------------------------------------------------------------- | ||
42 | | Session age | ||
43 | |-------------------------------------------------------------------------- | ||
44 | | | ||
45 | | This value is only used when `clearWithBrowser` is set to false. The | ||
46 | | age must be a valid https://npmjs.org/package/ms string or should | ||
47 | | be in milliseconds. | ||
48 | | | ||
49 | | Valid values are: | ||
50 | | '2h', '10d', '5y', '2.5 hrs' | ||
51 | | | ||
52 | */ | ||
53 | age: '2h', | ||
54 | |||
55 | /* | ||
56 | |-------------------------------------------------------------------------- | ||
57 | | Cookie options | ||
58 | |-------------------------------------------------------------------------- | ||
59 | | | ||
60 | | Cookie options defines the options to be used for setting up session | ||
61 | | cookie | ||
62 | | | ||
63 | */ | ||
64 | cookie: { | ||
65 | httpOnly: true, | ||
66 | path: '/', | ||
67 | sameSite: false, | ||
68 | }, | ||
69 | |||
70 | /* | ||
71 | |-------------------------------------------------------------------------- | ||
72 | | Sessions location | ||
73 | |-------------------------------------------------------------------------- | ||
74 | | | ||
75 | | If driver is set to file, we need to define the relative location from | ||
76 | | the temporary path or absolute url to any location. | ||
77 | | | ||
78 | */ | ||
79 | file: { | ||
80 | location: 'sessions', | ||
81 | }, | ||
82 | |||
83 | /* | ||
84 | |-------------------------------------------------------------------------- | ||
85 | | Redis config | ||
86 | |-------------------------------------------------------------------------- | ||
87 | | | ||
88 | | The configuration for the redis driver. | ||
89 | | | ||
90 | */ | ||
91 | redis: { | ||
92 | host: '127.0.0.1', | ||
93 | port: 6379, | ||
94 | password: null, | ||
95 | db: 0, | ||
96 | keyPrefix: '', | ||
97 | }, | ||
98 | }; | ||
diff --git a/src/server/config/shield.js b/src/server/config/shield.js new file mode 100644 index 000000000..5c1c5cd73 --- /dev/null +++ b/src/server/config/shield.js | |||
@@ -0,0 +1,144 @@ | |||
1 | |||
2 | module.exports = { | ||
3 | /* | ||
4 | |-------------------------------------------------------------------------- | ||
5 | | Content Security Policy | ||
6 | |-------------------------------------------------------------------------- | ||
7 | | | ||
8 | | Content security policy filters out the origins not allowed to execute | ||
9 | | and load resources like scripts, styles and fonts. There are wide | ||
10 | | variety of options to choose from. | ||
11 | */ | ||
12 | csp: { | ||
13 | /* | ||
14 | |-------------------------------------------------------------------------- | ||
15 | | Directives | ||
16 | |-------------------------------------------------------------------------- | ||
17 | | | ||
18 | | All directives are defined in camelCase and here is the list of | ||
19 | | available directives and their possible values. | ||
20 | | | ||
21 | | https://content-security-policy.com | ||
22 | | | ||
23 | | @example | ||
24 | | directives: { | ||
25 | | defaultSrc: ['self', '@nonce', 'cdnjs.cloudflare.com'] | ||
26 | | } | ||
27 | | | ||
28 | */ | ||
29 | directives: { | ||
30 | }, | ||
31 | /* | ||
32 | |-------------------------------------------------------------------------- | ||
33 | | Report only | ||
34 | |-------------------------------------------------------------------------- | ||
35 | | | ||
36 | | Setting `reportOnly=true` will not block the scripts from running and | ||
37 | | instead report them to a URL. | ||
38 | | | ||
39 | */ | ||
40 | reportOnly: false, | ||
41 | /* | ||
42 | |-------------------------------------------------------------------------- | ||
43 | | Set all headers | ||
44 | |-------------------------------------------------------------------------- | ||
45 | | | ||
46 | | Headers staring with `X` have been depreciated, since all major browsers | ||
47 | | supports the standard CSP header. So its better to disable deperciated | ||
48 | | headers, unless you want them to be set. | ||
49 | | | ||
50 | */ | ||
51 | setAllHeaders: false, | ||
52 | |||
53 | /* | ||
54 | |-------------------------------------------------------------------------- | ||
55 | | Disable on android | ||
56 | |-------------------------------------------------------------------------- | ||
57 | | | ||
58 | | Certain versions of android are buggy with CSP policy. So you can set | ||
59 | | this value to true, to disable it for Android versions with buggy | ||
60 | | behavior. | ||
61 | | | ||
62 | | Here is an issue reported on a different package, but helpful to read | ||
63 | | if you want to know the behavior. https://github.com/helmetjs/helmet/pull/82 | ||
64 | | | ||
65 | */ | ||
66 | disableAndroid: true, | ||
67 | }, | ||
68 | |||
69 | /* | ||
70 | |-------------------------------------------------------------------------- | ||
71 | | X-XSS-Protection | ||
72 | |-------------------------------------------------------------------------- | ||
73 | | | ||
74 | | X-XSS Protection saves from applications from XSS attacks. It is adopted | ||
75 | | by IE and later followed by some other browsers. | ||
76 | | | ||
77 | | Learn more at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection | ||
78 | | | ||
79 | */ | ||
80 | xss: { | ||
81 | enabled: true, | ||
82 | enableOnOldIE: false, | ||
83 | }, | ||
84 | |||
85 | /* | ||
86 | |-------------------------------------------------------------------------- | ||
87 | | Iframe Options | ||
88 | |-------------------------------------------------------------------------- | ||
89 | | | ||
90 | | xframe defines whether or not your website can be embedded inside an | ||
91 | | iframe. Choose from one of the following options. | ||
92 | | @available options | ||
93 | | DENY, SAMEORIGIN, ALLOW-FROM http://example.com | ||
94 | | | ||
95 | | Learn more at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options | ||
96 | */ | ||
97 | xframe: 'DENY', | ||
98 | |||
99 | /* | ||
100 | |-------------------------------------------------------------------------- | ||
101 | | No Sniff | ||
102 | |-------------------------------------------------------------------------- | ||
103 | | | ||
104 | | Browsers have a habit of sniffing content-type of a response. Which means | ||
105 | | files with .txt extension containing Javascript code will be executed as | ||
106 | | Javascript. You can disable this behavior by setting nosniff to false. | ||
107 | | | ||
108 | | Learn more at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options | ||
109 | | | ||
110 | */ | ||
111 | nosniff: true, | ||
112 | |||
113 | /* | ||
114 | |-------------------------------------------------------------------------- | ||
115 | | No Open | ||
116 | |-------------------------------------------------------------------------- | ||
117 | | | ||
118 | | IE users can execute webpages in the context of your website, which is | ||
119 | | a serious security risk. Below option will manage this for you. | ||
120 | | | ||
121 | */ | ||
122 | noopen: true, | ||
123 | |||
124 | /* | ||
125 | |-------------------------------------------------------------------------- | ||
126 | | CSRF Protection | ||
127 | |-------------------------------------------------------------------------- | ||
128 | | | ||
129 | | CSRF Protection adds another layer of security by making sure, actionable | ||
130 | | routes does have a valid token to execute an action. | ||
131 | | | ||
132 | */ | ||
133 | csrf: { | ||
134 | enable: true, | ||
135 | methods: ['POST', 'PUT', 'DELETE'], | ||
136 | filterUris: [], | ||
137 | cookieOptions: { | ||
138 | httpOnly: false, | ||
139 | sameSite: true, | ||
140 | path: '/', | ||
141 | maxAge: 7200, | ||
142 | }, | ||
143 | }, | ||
144 | }; | ||
diff --git a/src/server/database/factory.js b/src/server/database/factory.js new file mode 100644 index 000000000..550c5e6ab --- /dev/null +++ b/src/server/database/factory.js | |||
@@ -0,0 +1,20 @@ | |||
1 | |||
2 | /* | ||
3 | |-------------------------------------------------------------------------- | ||
4 | | Factory | ||
5 | |-------------------------------------------------------------------------- | ||
6 | | | ||
7 | | Factories are used to define blueprints for database tables or Lucid | ||
8 | | models. Later you can use these blueprints to seed your database | ||
9 | | with dummy data. | ||
10 | | | ||
11 | */ | ||
12 | |||
13 | /** @type {import('@adonisjs/lucid/src/Factory')} */ | ||
14 | // const Factory = use('Factory') | ||
15 | |||
16 | // Factory.blueprint('App/Models/User', (faker) => { | ||
17 | // return { | ||
18 | // username: faker.username() | ||
19 | // } | ||
20 | // }) | ||
diff --git a/src/server/database/migrations/1566385379883_service_schema.js b/src/server/database/migrations/1566385379883_service_schema.js new file mode 100644 index 000000000..1db95c19d --- /dev/null +++ b/src/server/database/migrations/1566385379883_service_schema.js | |||
@@ -0,0 +1,22 @@ | |||
1 | |||
2 | /** @type {import('@adonisjs/lucid/src/Schema')} */ | ||
3 | const Schema = use('Schema'); | ||
4 | |||
5 | class ServiceSchema extends Schema { | ||
6 | up() { | ||
7 | this.create('services', (table) => { | ||
8 | table.increments(); | ||
9 | table.string('serviceId', 80).notNullable(); | ||
10 | table.string('name', 80).notNullable(); | ||
11 | table.string('recipeId', 254).notNullable(); | ||
12 | table.json('settings'); | ||
13 | table.timestamps(); | ||
14 | }); | ||
15 | } | ||
16 | |||
17 | down() { | ||
18 | this.drop('services'); | ||
19 | } | ||
20 | } | ||
21 | |||
22 | module.exports = ServiceSchema; | ||
diff --git a/src/server/database/migrations/1566554231482_recipe_schema.js b/src/server/database/migrations/1566554231482_recipe_schema.js new file mode 100644 index 000000000..14fcb82e5 --- /dev/null +++ b/src/server/database/migrations/1566554231482_recipe_schema.js | |||
@@ -0,0 +1,21 @@ | |||
1 | |||
2 | /** @type {import('@adonisjs/lucid/src/Schema')} */ | ||
3 | const Schema = use('Schema'); | ||
4 | |||
5 | class RecipeSchema extends Schema { | ||
6 | up() { | ||
7 | this.create('recipes', (table) => { | ||
8 | table.increments(); | ||
9 | table.string('name', 80).notNullable(); | ||
10 | table.string('recipeId', 254).notNullable().unique(); | ||
11 | table.json('data'); | ||
12 | table.timestamps(); | ||
13 | }); | ||
14 | } | ||
15 | |||
16 | down() { | ||
17 | this.drop('recipes'); | ||
18 | } | ||
19 | } | ||
20 | |||
21 | module.exports = RecipeSchema; | ||
diff --git a/src/server/database/migrations/1566554359294_workspace_schema.js b/src/server/database/migrations/1566554359294_workspace_schema.js new file mode 100644 index 000000000..b53bbe656 --- /dev/null +++ b/src/server/database/migrations/1566554359294_workspace_schema.js | |||
@@ -0,0 +1,23 @@ | |||
1 | |||
2 | /** @type {import('@adonisjs/lucid/src/Schema')} */ | ||
3 | const Schema = use('Schema'); | ||
4 | |||
5 | class WorkspaceSchema extends Schema { | ||
6 | up() { | ||
7 | this.create('workspaces', (table) => { | ||
8 | table.increments(); | ||
9 | table.string('workspaceId', 80).notNullable().unique(); | ||
10 | table.string('name', 80).notNullable(); | ||
11 | table.integer('order'); | ||
12 | table.json('services'); | ||
13 | table.json('data'); | ||
14 | table.timestamps(); | ||
15 | }); | ||
16 | } | ||
17 | |||
18 | down() { | ||
19 | this.drop('workspaces'); | ||
20 | } | ||
21 | } | ||
22 | |||
23 | module.exports = WorkspaceSchema; | ||
diff --git a/src/server/database/template.sqlite b/src/server/database/template.sqlite new file mode 100644 index 000000000..db5425ee6 --- /dev/null +++ b/src/server/database/template.sqlite | |||
Binary files differ | |||
diff --git a/src/server/env.ini b/src/server/env.ini new file mode 100644 index 000000000..902e8e4c8 --- /dev/null +++ b/src/server/env.ini | |||
@@ -0,0 +1,16 @@ | |||
1 | HOST=127.0.0.1 | ||
2 | PORT=45569 | ||
3 | NODE_ENV=development | ||
4 | APP_NAME=Ferdi Internal Server | ||
5 | APP_URL=http://${HOST}:${PORT} | ||
6 | CACHE_VIEWS=false | ||
7 | APP_KEY=FERDIINTERNALSERVER | ||
8 | DB_CONNECTION=sqlite | ||
9 | DB_HOST=127.0.0.1 | ||
10 | DB_PORT=3306 | ||
11 | DB_USER=root | ||
12 | DB_PASSWORD= | ||
13 | DB_DATABASE=ferdi | ||
14 | HASH_DRIVER=bcrypt | ||
15 | IS_CREATION_ENABLED=true | ||
16 | CONNECT_WITH_FRANZ=true \ No newline at end of file | ||
diff --git a/src/server/logo.png b/src/server/logo.png new file mode 100644 index 000000000..4145a077a --- /dev/null +++ b/src/server/logo.png | |||
Binary files differ | |||
diff --git a/src/server/package.json b/src/server/package.json new file mode 100644 index 000000000..60dd58e93 --- /dev/null +++ b/src/server/package.json | |||
@@ -0,0 +1,49 @@ | |||
1 | { | ||
2 | "name": "ferdi-internal-server", | ||
3 | "version": "1.0.0", | ||
4 | "adonis-version": "4.1.0", | ||
5 | "description": "Internal server used by the Ferdi application.", | ||
6 | "main": "index.js", | ||
7 | "scripts": { | ||
8 | "start": "node server.js", | ||
9 | "test": "node ace test", | ||
10 | "lint": "eslint --fix ./" | ||
11 | }, | ||
12 | "keywords": [ | ||
13 | ], | ||
14 | "author": "", | ||
15 | "license": "MIT License", | ||
16 | "private": true, | ||
17 | "dependencies": { | ||
18 | "@adonisjs/ace": "^5.0.8", | ||
19 | "@adonisjs/auth": "^3.0.7", | ||
20 | "@adonisjs/bodyparser": "^2.0.5", | ||
21 | "@adonisjs/cors": "^1.0.7", | ||
22 | "@adonisjs/drive": "^1.0.4", | ||
23 | "@adonisjs/fold": "^4.0.9", | ||
24 | "@adonisjs/framework": "^5.0.9", | ||
25 | "@adonisjs/ignitor": "^2.0.8", | ||
26 | "@adonisjs/lucid": "^6.1.3", | ||
27 | "@adonisjs/session": "^1.0.29", | ||
28 | "@adonisjs/shield": "^1.0.8", | ||
29 | "@adonisjs/validator": "^5.0.6", | ||
30 | "atob": "^2.1.2", | ||
31 | "btoa": "^1.2.1", | ||
32 | "fs-extra": "^8.1.0", | ||
33 | "node-fetch": "^2.6.0", | ||
34 | "sqlite3": "^4.1.0", | ||
35 | "uuid": "^3.3.3" | ||
36 | }, | ||
37 | "devDependencies": { | ||
38 | "eslint": "^6.3.0", | ||
39 | "eslint-config-airbnb": "^18.0.1", | ||
40 | "eslint-config-airbnb-base": "^14.0.0", | ||
41 | "eslint-plugin-import": "^2.18.2", | ||
42 | "eslint-plugin-jsx-a11y": "^6.2.3", | ||
43 | "eslint-plugin-react": "^7.14.3", | ||
44 | "eslint-plugin-react-hooks": "^1.7.0" | ||
45 | }, | ||
46 | "autoload": { | ||
47 | "App": "./app" | ||
48 | } | ||
49 | } | ||
diff --git a/src/server/public/css/main.css b/src/server/public/css/main.css new file mode 100644 index 000000000..a1c5653d7 --- /dev/null +++ b/src/server/public/css/main.css | |||
@@ -0,0 +1,69 @@ | |||
1 | input { | ||
2 | margin-bottom: 1rem; | ||
3 | width: 100%; | ||
4 | padding: 0.5rem; | ||
5 | } | ||
6 | |||
7 | button, .button { | ||
8 | display: flex; | ||
9 | overflow: hidden; | ||
10 | padding: 12px 12px; | ||
11 | cursor: pointer; | ||
12 | width: 100%; | ||
13 | -webkit-user-select: none; | ||
14 | -moz-user-select: none; | ||
15 | -ms-user-select: none; | ||
16 | user-select: none; | ||
17 | transition: all 150ms linear; | ||
18 | text-align: center; | ||
19 | white-space: nowrap; | ||
20 | text-decoration: none !important; | ||
21 | text-transform: none; | ||
22 | text-transform: capitalize; | ||
23 | color: #fff !important; | ||
24 | border: 0 none; | ||
25 | border-radius: 4px; | ||
26 | font-size: 13px; | ||
27 | font-weight: 500; | ||
28 | line-height: 1.3; | ||
29 | -webkit-appearance: none; | ||
30 | -moz-appearance: none; | ||
31 | appearance: none; | ||
32 | justify-content: center; | ||
33 | align-items: center; | ||
34 | flex: 0 0 160px; | ||
35 | box-shadow: 2px 5px 10px #e4e4e4; | ||
36 | color: #FFFFFF; | ||
37 | background: #161616; | ||
38 | } | ||
39 | |||
40 | #dropzone { | ||
41 | width: 100%; | ||
42 | height: 30vh; | ||
43 | background-color: #ebebeb; | ||
44 | |||
45 | display: flex; | ||
46 | align-items: center; | ||
47 | justify-content: center; | ||
48 | text-align: center; | ||
49 | |||
50 | cursor: pointer; | ||
51 | } | ||
52 | |||
53 | #dropzone p { | ||
54 | font-size: 0.85rem; | ||
55 | } | ||
56 | |||
57 | #files { | ||
58 | display: none; | ||
59 | } | ||
60 | |||
61 | .alert { | ||
62 | background-color: #e7a8a6; | ||
63 | padding: 0.8rem; | ||
64 | margin-bottom: 1rem; | ||
65 | } | ||
66 | |||
67 | td { | ||
68 | word-break: break-all; | ||
69 | } \ No newline at end of file | ||
diff --git a/src/server/public/css/vanilla.css b/src/server/public/css/vanilla.css new file mode 100644 index 000000000..37bc051a2 --- /dev/null +++ b/src/server/public/css/vanilla.css | |||
@@ -0,0 +1,138 @@ | |||
1 | /* Reset */ | ||
2 | html, body, div, span, applet, object, iframe, | ||
3 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, | ||
4 | a, abbr, acronym, address, big, cite, code, | ||
5 | del, dfn, em, img, ins, kbd, q, s, samp, | ||
6 | small, strike, strong, sub, sup, tt, var, | ||
7 | b, u, i, center, | ||
8 | dl, dt, dd, ol, ul, li, | ||
9 | fieldset, form, label, legend, | ||
10 | table, caption, tbody, tfoot, thead, tr, th, td, | ||
11 | article, aside, canvas, details, embed, | ||
12 | figure, figcaption, footer, header, hgroup, | ||
13 | menu, nav, output, ruby, section, summary, | ||
14 | time, mark, audio, video { | ||
15 | margin: 0; | ||
16 | padding: 0; | ||
17 | border: 0; | ||
18 | font-size: 100%; | ||
19 | font: inherit; | ||
20 | vertical-align: baseline; | ||
21 | } | ||
22 | * { | ||
23 | box-sizing: border-box; | ||
24 | } | ||
25 | |||
26 | |||
27 | |||
28 | /* Variables */ | ||
29 | :root { | ||
30 | --desktop-font-size: 1.3rem/1.5; | ||
31 | --mobile-font-size: 1.1rem/1.4; | ||
32 | --text-color: #2d2d2d; | ||
33 | --link-color: blue; | ||
34 | --primary-color: lightsteelblue; | ||
35 | --secondary-color: aliceblue; | ||
36 | --tertiary-color: whitesmoke; | ||
37 | } | ||
38 | |||
39 | |||
40 | |||
41 | |||
42 | /* Typography */ | ||
43 | body { | ||
44 | color: var(--text-color); | ||
45 | padding: 3rem; | ||
46 | font: var(--desktop-font-size) -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto, Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji", "Segoe UI Symbol"; | ||
47 | } | ||
48 | |||
49 | h1,h2,h3,h4,h5,h6,p,blockquote,dl,img,figure { | ||
50 | margin: 2rem 0; | ||
51 | } | ||
52 | |||
53 | h1,h2,h3,h4,h5,h6 { font-weight: bold; } | ||
54 | h1 { font-size: 200%; } | ||
55 | h2 { font-size: 150%; } | ||
56 | h3 { font-size: 120%; } | ||
57 | h4,h5,h6 { font-size: 100%; } | ||
58 | h5, h6 { text-transform: uppercase; } | ||
59 | |||
60 | header h1 { border-bottom: 1px solid; } | ||
61 | |||
62 | p { margin: 2rem 0; } | ||
63 | |||
64 | a,a:visited { color: var(--link-color); } | ||
65 | |||
66 | strong, time, b { font-weight: bold; } | ||
67 | em, dfn, i { font-style: italic; } | ||
68 | sub { font-size: 60%; vertical-align: bottom; } | ||
69 | small { font-size: 80%; } | ||
70 | |||
71 | blockquote, q { | ||
72 | background: var(--secondary-color); | ||
73 | border-left: 10px solid var(--primary-color); | ||
74 | font-family: "Georgia", serif; | ||
75 | padding: 1rem; | ||
76 | } | ||
77 | blockquote p:first-child { margin-top: 0; } | ||
78 | cite { | ||
79 | font-family: "Georgia", serif; | ||
80 | font-style: italic; | ||
81 | font-weight: bold; | ||
82 | } | ||
83 | |||
84 | kbd,code,samp,pre,var { font-family: monospace; font-weight: bold; } | ||
85 | code, pre { | ||
86 | background: var(--tertiary-color); | ||
87 | padding: 0.5rem 1rem; | ||
88 | } | ||
89 | code pre , pre code { padding: 0; } | ||
90 | |||
91 | |||
92 | |||
93 | /* Elements */ | ||
94 | hr { | ||
95 | background: var(--text-color); | ||
96 | border: 0; | ||
97 | height: 1px; | ||
98 | margin: 4rem 0; | ||
99 | } | ||
100 | |||
101 | img { max-width: 100%; } | ||
102 | |||
103 | figure { | ||
104 | border: 1px solid var(--primary-color); | ||
105 | display: inline-block; | ||
106 | padding: 1rem; | ||
107 | width: auto; | ||
108 | } | ||
109 | figure img { margin: 0; } | ||
110 | figure figcaption { font-size: 80%; } | ||
111 | |||
112 | ul, ol { margin: 2rem 0; padding: 0 0 0 4rem; } | ||
113 | |||
114 | dl dd { padding-left: 2rem; } | ||
115 | |||
116 | table { | ||
117 | border: 1px solid var(--primary-color); | ||
118 | border-collapse: collapse; | ||
119 | table-layout: fixed; | ||
120 | width: 100%; | ||
121 | } | ||
122 | table caption { margin: 2rem 0; } | ||
123 | table thead { text-align: center; } | ||
124 | table tbody { text-align: right; } | ||
125 | table tr { border-bottom: 1px solid var(--primary-color); } | ||
126 | table tbody tr:nth-child(even) { background: var(--tertiary-color); } | ||
127 | table th { background: var(--secondary-color); font-weight: bold; } | ||
128 | table th, table td { padding: 1rem; } | ||
129 | table th:not(last-of-type), table td:not(last-of-type) { border-right: 1px solid var(--primary-color); } | ||
130 | |||
131 | |||
132 | |||
133 | /* Mobile Styling */ | ||
134 | @media screen and (max-width: 50rem) { | ||
135 | body { | ||
136 | font: var(--mobile-font-size) -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto, Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji", "Segoe UI Symbol" | ||
137 | } | ||
138 | } \ No newline at end of file | ||
diff --git a/src/server/resources/views/import.edge b/src/server/resources/views/import.edge new file mode 100644 index 000000000..f7b52b179 --- /dev/null +++ b/src/server/resources/views/import.edge | |||
@@ -0,0 +1,15 @@ | |||
1 | @layout('layouts.main') | ||
2 | |||
3 | @section('content') | ||
4 | <h1>Import a Franz account</h1> | ||
5 | <p>Please login using your Franz account. We will import your services and workspaces.</p> | ||
6 | <form action="import" method="post"> | ||
7 | <label for="email">E-Mail address</label><br /> | ||
8 | <input type="email" name="email" placeholder="joe@example.com" required><br /> | ||
9 | |||
10 | <label for="password">Password</label><br /> | ||
11 | <input type="password" name="password" placeholder="********" required><br /> | ||
12 | |||
13 | <button type="submit" id="submitbutton">Import Franz account</button> | ||
14 | </form> | ||
15 | @endsection | ||
diff --git a/src/server/resources/views/index.edge b/src/server/resources/views/index.edge new file mode 100644 index 000000000..3e0198a09 --- /dev/null +++ b/src/server/resources/views/index.edge | |||
@@ -0,0 +1,32 @@ | |||
1 | @layout('layouts.main') | ||
2 | |||
3 | @section('content') | ||
4 | <style> | ||
5 | ol, | ||
6 | p { | ||
7 | margin: 0.5rem 0; | ||
8 | } | ||
9 | |||
10 | </style> | ||
11 | <h1>Internal Ferdi Server</h1> | ||
12 | <p>You are accessing the local server instance of your Ferdi application. This server is used to enable Ferdi's "Use without an Account" feature.</p> | ||
13 | <p> | ||
14 | To use this server in your Ferdi client, <a href="ferdi://settings/app">open Ferdi's settings</a> and as the | ||
15 | <code>server</code>, enter <code id="server"></code> | ||
16 | </p> | ||
17 | <p> | ||
18 | Alternatively, you can <a href="/import">import your Franz account</a>. | ||
19 | </p> | ||
20 | |||
21 | <script> | ||
22 | // Get server URL for current location | ||
23 | let server = location.href.replace('/index.html', ''); | ||
24 | if (server[server.length - 1] == '/') { | ||
25 | server = server.substr(0, server.length - 1) | ||
26 | } | ||
27 | |||
28 | // Show on page | ||
29 | document.getElementById('server').innerText = server; | ||
30 | |||
31 | </script> | ||
32 | @endsection | ||
diff --git a/src/server/resources/views/layouts/main.edge b/src/server/resources/views/layouts/main.edge new file mode 100644 index 000000000..77af30327 --- /dev/null +++ b/src/server/resources/views/layouts/main.edge | |||
@@ -0,0 +1,18 @@ | |||
1 | <!DOCTYPE html> | ||
2 | <html lang="en"> | ||
3 | |||
4 | <head> | ||
5 | <meta charset="UTF-8"> | ||
6 | <meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||
7 | <meta http-equiv="X-UA-Compatible" content="ie=edge"> | ||
8 | <title>ferdi-internal-server</title> | ||
9 | |||
10 | {{ style('css/vanilla') }} | ||
11 | {{ style('css/main') }} | ||
12 | </head> | ||
13 | |||
14 | <body> | ||
15 | @!section('content') | ||
16 | </body> | ||
17 | |||
18 | </html> | ||
diff --git a/src/server/start.js b/src/server/start.js new file mode 100644 index 000000000..8a8711a78 --- /dev/null +++ b/src/server/start.js | |||
@@ -0,0 +1,40 @@ | |||
1 | |||
2 | /* | ||
3 | |-------------------------------------------------------------------------- | ||
4 | | Http server | ||
5 | |-------------------------------------------------------------------------- | ||
6 | | | ||
7 | | This file bootstraps Adonisjs to start the HTTP server. You are free to | ||
8 | | customize the process of booting the http server. | ||
9 | | | ||
10 | | """ Loading ace commands """ | ||
11 | | At times you may want to load ace commands when starting the HTTP server. | ||
12 | | Same can be done by chaining `loadCommands()` method after | ||
13 | | | ||
14 | | """ Preloading files """ | ||
15 | | Also you can preload files by calling `preLoad('path/to/file')` method. | ||
16 | | Make sure to pass a relative path from the project root. | ||
17 | */ | ||
18 | const path = require('path'); | ||
19 | const fs = require('fs-extra'); | ||
20 | // eslint-disable-next-line import/no-extraneous-dependencies | ||
21 | const { app } = require('electron'); | ||
22 | |||
23 | process.env.ENV_PATH = path.join(__dirname, 'env.ini'); | ||
24 | |||
25 | // Make sure local database exists | ||
26 | const dbPath = path.join(app.getPath('userData'), 'server.sqlite'); | ||
27 | if (!fs.existsSync(dbPath)) { | ||
28 | fs.copySync( | ||
29 | path.join(__dirname, 'database', 'template.sqlite'), | ||
30 | dbPath, | ||
31 | ); | ||
32 | } | ||
33 | |||
34 | const { Ignitor } = require('@adonisjs/ignitor'); | ||
35 | const fold = require('@adonisjs/fold'); | ||
36 | |||
37 | new Ignitor(fold) | ||
38 | .appRoot(__dirname) | ||
39 | .fireHttpServer() | ||
40 | .catch(console.error); // eslint-disable-line no-console | ||
diff --git a/src/server/start/app.js b/src/server/start/app.js new file mode 100644 index 000000000..a29ca6594 --- /dev/null +++ b/src/server/start/app.js | |||
@@ -0,0 +1,62 @@ | |||
1 | |||
2 | /* | ||
3 | |-------------------------------------------------------------------------- | ||
4 | | Providers | ||
5 | |-------------------------------------------------------------------------- | ||
6 | | | ||
7 | | Providers are building blocks for your Adonis app. Anytime you install | ||
8 | | a new Adonis specific package, chances are you will register the | ||
9 | | provider here. | ||
10 | | | ||
11 | */ | ||
12 | const providers = [ | ||
13 | '@adonisjs/framework/providers/AppProvider', | ||
14 | '@adonisjs/bodyparser/providers/BodyParserProvider', | ||
15 | '@adonisjs/cors/providers/CorsProvider', | ||
16 | '@adonisjs/lucid/providers/LucidProvider', | ||
17 | '@adonisjs/drive/providers/DriveProvider', | ||
18 | '@adonisjs/validator/providers/ValidatorProvider', | ||
19 | '@adonisjs/framework/providers/ViewProvider', | ||
20 | '@adonisjs/shield/providers/ShieldProvider', | ||
21 | ]; | ||
22 | |||
23 | /* | ||
24 | |-------------------------------------------------------------------------- | ||
25 | | Ace Providers | ||
26 | |-------------------------------------------------------------------------- | ||
27 | | | ||
28 | | Ace providers are required only when running ace commands. For example | ||
29 | | Providers for migrations, tests etc. | ||
30 | | | ||
31 | */ | ||
32 | const aceProviders = [ | ||
33 | '@adonisjs/lucid/providers/MigrationsProvider', | ||
34 | ]; | ||
35 | |||
36 | /* | ||
37 | |-------------------------------------------------------------------------- | ||
38 | | Aliases | ||
39 | |-------------------------------------------------------------------------- | ||
40 | | | ||
41 | | Aliases are short unique names for IoC container bindings. You are free | ||
42 | | to create your own aliases. | ||
43 | | | ||
44 | | For example: | ||
45 | | { Route: 'Adonis/Src/Route' } | ||
46 | | | ||
47 | */ | ||
48 | const aliases = {}; | ||
49 | |||
50 | /* | ||
51 | |-------------------------------------------------------------------------- | ||
52 | | Commands | ||
53 | |-------------------------------------------------------------------------- | ||
54 | | | ||
55 | | Here you store ace commands for your package | ||
56 | | | ||
57 | */ | ||
58 | const commands = []; | ||
59 | |||
60 | module.exports = { | ||
61 | providers, aceProviders, aliases, commands, | ||
62 | }; | ||
diff --git a/src/server/start/kernel.js b/src/server/start/kernel.js new file mode 100644 index 000000000..54fe1f35d --- /dev/null +++ b/src/server/start/kernel.js | |||
@@ -0,0 +1,56 @@ | |||
1 | |||
2 | /** @type {import('@adonisjs/framework/src/Server')} */ | ||
3 | const Server = use('Server'); | ||
4 | |||
5 | /* | ||
6 | |-------------------------------------------------------------------------- | ||
7 | | Global Middleware | ||
8 | |-------------------------------------------------------------------------- | ||
9 | | | ||
10 | | Global middleware are executed on each http request only when the routes | ||
11 | | match. | ||
12 | | | ||
13 | */ | ||
14 | const globalMiddleware = [ | ||
15 | 'Adonis/Middleware/BodyParser', | ||
16 | 'App/Middleware/ConvertEmptyStringsToNull', | ||
17 | ]; | ||
18 | |||
19 | /* | ||
20 | |-------------------------------------------------------------------------- | ||
21 | | Named Middleware | ||
22 | |-------------------------------------------------------------------------- | ||
23 | | | ||
24 | | Named middleware is key/value object to conditionally add middleware on | ||
25 | | specific routes or group of routes. | ||
26 | | | ||
27 | | // define | ||
28 | | { | ||
29 | | auth: 'Adonis/Middleware/Auth' | ||
30 | | } | ||
31 | | | ||
32 | | // use | ||
33 | | Route.get().middleware('auth') | ||
34 | | | ||
35 | */ | ||
36 | const namedMiddleware = { | ||
37 | }; | ||
38 | |||
39 | /* | ||
40 | |-------------------------------------------------------------------------- | ||
41 | | Server Middleware | ||
42 | |-------------------------------------------------------------------------- | ||
43 | | | ||
44 | | Server level middleware are executed even when route for a given URL is | ||
45 | | not registered. Features like `static assets` and `cors` needs better | ||
46 | | control over request lifecycle. | ||
47 | | | ||
48 | */ | ||
49 | const serverMiddleware = [ | ||
50 | 'Adonis/Middleware/Static', | ||
51 | ]; | ||
52 | |||
53 | Server | ||
54 | .registerGlobal(globalMiddleware) | ||
55 | .registerNamed(namedMiddleware) | ||
56 | .use(serverMiddleware); | ||
diff --git a/src/server/start/routes.js b/src/server/start/routes.js new file mode 100644 index 000000000..333a5ba06 --- /dev/null +++ b/src/server/start/routes.js | |||
@@ -0,0 +1,74 @@ | |||
1 | |||
2 | /* | ||
3 | |-------------------------------------------------------------------------- | ||
4 | | Routes | ||
5 | |-------------------------------------------------------------------------- | ||
6 | | | ||
7 | */ | ||
8 | |||
9 | /** @type {typeof import('@adonisjs/framework/src/Route/Manager')} */ | ||
10 | const Route = use('Route'); | ||
11 | |||
12 | const OnlyAllowFerdi = async ({ request, response }, next) => { | ||
13 | const user = request.header('User-Agent'); | ||
14 | if (!/Ferdi\/\d(\.\d){2}/g.test(user)) { | ||
15 | return response.status(403).redirect('/'); | ||
16 | } | ||
17 | |||
18 | await next(); | ||
19 | return true; | ||
20 | }; | ||
21 | |||
22 | // Health: Returning if all systems function correctly | ||
23 | Route.get('health', ({ | ||
24 | response, | ||
25 | }) => response.send({ | ||
26 | api: 'success', | ||
27 | db: 'success', | ||
28 | })).middleware(OnlyAllowFerdi); | ||
29 | |||
30 | // API is grouped under '/v1/' route | ||
31 | Route.group(() => { | ||
32 | // User authentification | ||
33 | Route.post('auth/signup', 'UserController.signup'); | ||
34 | Route.post('auth/login', 'UserController.login'); | ||
35 | |||
36 | // User info | ||
37 | Route.get('me', 'UserController.me'); | ||
38 | |||
39 | // Service info | ||
40 | Route.post('service', 'ServiceController.create'); | ||
41 | Route.put('service/:id', 'ServiceController.edit'); | ||
42 | Route.delete('service/:id', 'ServiceController.delete'); | ||
43 | Route.get('me/services', 'ServiceController.list'); | ||
44 | Route.put('service/reorder', 'ServiceController.reorder'); | ||
45 | Route.get('recipe', 'ServiceController.list'); | ||
46 | Route.post('recipes/update', 'ServiceController.update'); | ||
47 | |||
48 | // Recipe store | ||
49 | Route.get('recipes', 'RecipeController.list'); | ||
50 | Route.get('recipes/download/:recipe', 'RecipeController.download'); | ||
51 | Route.get('recipes/search', 'RecipeController.search'); | ||
52 | Route.get('recipes/popular', 'StaticController.popularRecipes'); | ||
53 | Route.get('recipes/update', 'StaticController.emptyArray'); | ||
54 | |||
55 | // Workspaces | ||
56 | Route.put('workspace/:id', 'WorkspaceController.edit'); | ||
57 | Route.delete('workspace/:id', 'WorkspaceController.delete'); | ||
58 | Route.post('workspace', 'WorkspaceController.create'); | ||
59 | Route.get('workspace', 'WorkspaceController.list'); | ||
60 | |||
61 | // Static responses | ||
62 | Route.get('features', 'StaticController.features'); | ||
63 | Route.get('services', 'StaticController.emptyArray'); | ||
64 | Route.get('news', 'StaticController.emptyArray'); | ||
65 | Route.get('payment/plans', 'StaticController.plans'); | ||
66 | Route.get('announcements/:version', 'StaticController.announcement'); | ||
67 | }).prefix('v1').middleware(OnlyAllowFerdi); | ||
68 | |||
69 | // Franz account import | ||
70 | Route.post('import', 'UserController.import'); | ||
71 | Route.get('import', ({ view }) => view.render('import')); | ||
72 | |||
73 | // Index | ||
74 | Route.get('/', ({ view }) => view.render('index')); | ||
diff --git a/src/styles/services.scss b/src/styles/services.scss index 5acf92d2c..ef1097791 100644 --- a/src/styles/services.scss +++ b/src/styles/services.scss | |||
@@ -64,7 +64,7 @@ | |||
64 | margin: 25px 0 40px; | 64 | margin: 25px 0 40px; |
65 | } | 65 | } |
66 | 66 | ||
67 | a.button, | 67 | .button, |
68 | button { margin: 40px 0 20px; } | 68 | button { margin: 40px 0 20px; } |
69 | } | 69 | } |
70 | 70 | ||
diff --git a/src/styles/type.scss b/src/styles/type.scss index e0b27fe34..636b8fd36 100644 --- a/src/styles/type.scss +++ b/src/styles/type.scss | |||
@@ -33,7 +33,7 @@ p { | |||
33 | 33 | ||
34 | strong { font-weight: bold; } | 34 | strong { font-weight: bold; } |
35 | 35 | ||
36 | a { | 36 | a, button { |
37 | color: $theme-text-color; | 37 | color: $theme-text-color; |
38 | text-decoration: none; | 38 | text-decoration: none; |
39 | 39 | ||
@@ -49,6 +49,7 @@ a { | |||
49 | position: relative; | 49 | position: relative; |
50 | text-align: center; | 50 | text-align: center; |
51 | transition: background .5s, color .5s; | 51 | transition: background .5s, color .5s; |
52 | cursor: pointer; | ||
52 | 53 | ||
53 | &:hover { | 54 | &:hover { |
54 | background: darken($theme-brand-primary, 5%); | 55 | background: darken($theme-brand-primary, 5%); |
diff --git a/src/styles/welcome.scss b/src/styles/welcome.scss index b517431f0..c1f85391e 100644 --- a/src/styles/welcome.scss +++ b/src/styles/welcome.scss | |||
@@ -48,6 +48,7 @@ | |||
48 | .button { | 48 | .button { |
49 | border-color: #FFF; | 49 | border-color: #FFF; |
50 | color: #FFF; | 50 | color: #FFF; |
51 | cursor: pointer; | ||
51 | 52 | ||
52 | &:hover { | 53 | &:hover { |
53 | background: #FFF; | 54 | background: #FFF; |