Compare commits

..

154 commits

Author SHA1 Message Date
jessopb 363c079433
Merge pull request #1073 from lbryio/staging
cuts release from staging
2020-06-11 12:45:13 -04:00
jessopb 8ca76a6de1
Merge pull request #1072 from lbryio/master
redirect fixes
2020-06-11 12:31:04 -04:00
jessopb 99c7b087d7
Merge pull request #1071 from lbryio/redirect2
prevent server side redirect
2020-06-11 12:30:31 -04:00
jessop 67e198db2f prevent server side redirect 2020-06-11 12:29:26 -04:00
jessopb 50805ad4ea
Merge pull request #1070 from lbryio/master
cut staging from master
2020-06-10 17:14:01 -04:00
jessopb b410eb8c94
Merge pull request #1069 from lbryio/redirectTv
redirect content to lbry.tv
2020-06-10 17:12:46 -04:00
jessop efc38529c7 redirect content to lbry.tv 2020-06-10 17:01:44 -04:00
Thomas Zarebczan dfa6db6276
fix broken claims?
https://spee.ch/@Deterrence-Dispensed:2/AR22LR-MagazineKit-1.1
2020-06-05 16:46:39 -04:00
jessopb 5215bfbc4e
Merge pull request #1068 from lbryio/staging
cut release
2020-06-05 16:30:13 -04:00
jessopb 2b4a379638
Merge pull request #1067 from lbryio/master
cut staging
2020-06-05 16:27:37 -04:00
Thomas Zarebczan 9df4d29dda
Use only streams for channel
Maybe fix the repost bug where a channel won't load for now.
2020-06-04 21:14:42 -04:00
jessopb 68b6f484e4
Merge pull request #1066 from lbryio/staging
Staging
2020-05-16 11:08:51 -04:00
jessopb 7584d0c7e3
Merge pull request #1065 from lbryio/master
Merge pull request #1064 from lbryio/updateMessaging
2020-05-15 14:11:35 -04:00
jessopb 7940c61b17
Merge pull request #1064 from lbryio/updateMessaging
update messaging, disable publish and channels
2020-05-15 14:11:00 -04:00
jessopb 3d36f136f1
Merge pull request #1063 from lbryio/updateMessaging
Update messaging
2020-05-15 14:10:08 -04:00
jessop 5ed72b0e14 update messaging, disable publish and channels 2020-04-22 20:24:23 -04:00
Jeremy Kauffman 922a53cc30
Merge pull request #1055 from ykris45/patch-5
Update LICENSE
2020-02-03 15:00:34 -05:00
YULIUS KURNIAWAN KRISTIANTO d11ba6225e
Update LICENSE
update year
2020-02-03 04:41:19 +07:00
jessopb a8678f5d1f
Merge pull request #1054 from lbryio/staging
cuts release from master
2020-01-12 19:49:57 -05:00
jessopb 423c0c9311
Merge pull request #1053 from lbryio/master
cuts staging from master
2020-01-12 19:43:08 -05:00
jessopb 3d23635bf5
Merge pull request #1052 from lbryio/fixesJan20
pagination (thanks Tom)
2020-01-12 19:39:50 -05:00
jessop 707e144eff pagination (thanks Tom) 2020-01-12 19:34:16 -05:00
Thomas Zarebczan ad87e2b8a7
Update README.md 2019-12-08 09:44:22 -05:00
jessopb 01ae90fc15
fix customize link 2019-12-07 11:15:24 -05:00
Thomas Zarebczan 1e6cb2a7c6
Merge pull request #1042 from StrikerRUS/patch-1
removed deprecated field in Travis
2019-10-15 11:53:56 -04:00
jessopb 2e7d4c5112
Merge pull request #1046 from lbryio/staging
Cuts Release from Staging
2019-10-14 11:16:23 -04:00
jessopb 15e4a97e6a
Merge pull request #1044 from lbryio/master
cuts staging from master
2019-10-10 22:20:57 -04:00
jessopb 5dc89f0a91
Merge pull request #1043 from lbryio/dmca-fix
Update report route
2019-10-10 18:11:57 -04:00
Thomas Zarebczan 0974e6d6b0
Update report route
+ pass claim id

Example: https://lbry.com/dmca/116d62d14d8efc311e1f8a9de92dcbc17deb259a
2019-10-10 13:42:29 -04:00
Nikita Titov 64d7afd278
removed deprecated field in Travis 2019-10-09 00:16:24 +03:00
jessopb 648639e6c6
Merge pull request #1041 from lbryio/cors2
applies open cors to publish routes
2019-10-08 09:45:53 -04:00
jessop 8ded28beb3 applies open cors to publish routes 2019-10-08 09:06:33 -04:00
jessopb b1a9d8ee74
Merge pull request #1040 from lbryio/master
cuts staging from master
2019-10-01 15:52:45 -04:00
jessopb 9e76553f0a
Merge pull request #1039 from lbryio/speechCors
moves whitelist to config and catches errors
2019-10-01 15:39:38 -04:00
jessop f4a50d0c85 moves whitelist to config and catches errors 2019-10-01 15:19:31 -04:00
jessopb 23953a32e8
Merge pull request #1037 from lbryio/cors
Cors
2019-09-29 15:06:43 -04:00
jessop aa4a43a1a5 cors 2019-09-29 14:54:17 -04:00
Thomas Zarebczan eba7fd596f
Merge pull request #1035 from lbryio/dependabot/npm_and_yarn/mixin-deep-1.3.2
Bump mixin-deep from 1.3.1 to 1.3.2
2019-09-18 11:45:54 -04:00
dependabot[bot] 9130b6f609
Bump mixin-deep from 1.3.1 to 1.3.2
Bumps [mixin-deep](https://github.com/jonschlinkert/mixin-deep) from 1.3.1 to 1.3.2.
- [Release notes](https://github.com/jonschlinkert/mixin-deep/releases)
- [Commits](https://github.com/jonschlinkert/mixin-deep/compare/1.3.1...1.3.2)

Signed-off-by: dependabot[bot] <support@github.com>
2019-08-27 22:24:31 +00:00
jessopb 94f5f6dc69
Merge pull request #1032 from lbryio/staging
cuts release from staging
2019-08-13 21:13:37 -04:00
jessopb 269a2c57a7
Merge pull request #1031 from lbryio/master
cuts staging from master
2019-08-09 10:04:19 -04:00
jessopb 1fd0ec1fc2
Merge pull request #1030 from lbryio/revert-1025-dependabot/npm_and_yarn/sequelize-5.3.0
Revert "Bump sequelize from 4.41.1 to 5.3.0"
2019-08-09 09:55:15 -04:00
jessopb 53e0d2a0db
Revert "Bump sequelize from 4.41.1 to 5.3.0" 2019-08-09 09:51:45 -04:00
Jeremy Kauffman aebaa4f954
Merge pull request #1029 from lbryio/master
cuts staging from master
2019-08-07 11:05:01 -04:00
jessopb 5b2108796c
Merge pull request #1025 from lbryio/dependabot/npm_and_yarn/sequelize-5.3.0
Bump sequelize from 4.41.1 to 5.3.0
2019-08-07 10:42:32 -04:00
jessopb 200903827a
Merge pull request #1022 from lbryio/dependabot/npm_and_yarn/lodash.mergewith-4.6.2
Bump lodash.mergewith from 4.6.1 to 4.6.2
2019-08-07 10:39:37 -04:00
jessopb 993b1d31e9
Merge pull request #1023 from lbryio/dependabot/npm_and_yarn/axios-0.18.1
Bump axios from 0.18.0 to 0.18.1
2019-08-07 10:39:22 -04:00
jessopb 8891f298ae
Merge pull request #1024 from lbryio/dependabot/npm_and_yarn/lodash-4.17.13
Bump lodash from 4.17.11 to 4.17.13
2019-08-07 10:39:11 -04:00
jessopb 9bd779775a
Merge pull request #1026 from lbryio/dependabot/npm_and_yarn/handlebars-4.1.2
Bump handlebars from 4.1.0 to 4.1.2
2019-08-07 10:38:27 -04:00
jessopb 322b8a147d
Merge pull request #1027 from lbryio/dependabot/npm_and_yarn/fstream-1.0.12
Bump fstream from 1.0.11 to 1.0.12
2019-08-07 10:38:14 -04:00
jessopb 424827ea15
Merge pull request #1028 from lbryio/dependabot/npm_and_yarn/js-yaml-3.13.1
Bump js-yaml from 3.13.0 to 3.13.1
2019-08-07 10:38:03 -04:00
dependabot[bot] 15e7456a88
Bump fstream from 1.0.11 to 1.0.12
Bumps [fstream](https://github.com/npm/fstream) from 1.0.11 to 1.0.12.
- [Release notes](https://github.com/npm/fstream/releases)
- [Commits](https://github.com/npm/fstream/compare/v1.0.11...v1.0.12)

Signed-off-by: dependabot[bot] <support@github.com>
2019-08-05 18:45:42 +00:00
dependabot[bot] d4e38db1ae
Bump js-yaml from 3.13.0 to 3.13.1
Bumps [js-yaml](https://github.com/nodeca/js-yaml) from 3.13.0 to 3.13.1.
- [Release notes](https://github.com/nodeca/js-yaml/releases)
- [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodeca/js-yaml/compare/3.13.0...3.13.1)

Signed-off-by: dependabot[bot] <support@github.com>
2019-08-05 18:45:42 +00:00
dependabot[bot] d3f4c0aefb
Bump handlebars from 4.1.0 to 4.1.2
Bumps [handlebars](https://github.com/wycats/handlebars.js) from 4.1.0 to 4.1.2.
- [Release notes](https://github.com/wycats/handlebars.js/releases)
- [Changelog](https://github.com/wycats/handlebars.js/blob/master/release-notes.md)
- [Commits](https://github.com/wycats/handlebars.js/compare/v4.1.0...v4.1.2)

Signed-off-by: dependabot[bot] <support@github.com>
2019-08-05 18:45:33 +00:00
dependabot[bot] ee004405a6
Bump sequelize from 4.41.1 to 5.3.0
Bumps [sequelize](https://github.com/sequelize/sequelize) from 4.41.1 to 5.3.0.
- [Release notes](https://github.com/sequelize/sequelize/releases)
- [Commits](https://github.com/sequelize/sequelize/compare/v4.41.1...v5.3.0)

Signed-off-by: dependabot[bot] <support@github.com>
2019-08-05 18:45:25 +00:00
dependabot[bot] 6313c7eeff
Bump lodash from 4.17.11 to 4.17.13
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.11 to 4.17.13.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.11...4.17.13)

Signed-off-by: dependabot[bot] <support@github.com>
2019-08-05 18:45:24 +00:00
dependabot[bot] ee1b5c68bc
Bump axios from 0.18.0 to 0.18.1
Bumps [axios](https://github.com/axios/axios) from 0.18.0 to 0.18.1.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v0.18.1/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v0.18.0...v0.18.1)

Signed-off-by: dependabot[bot] <support@github.com>
2019-08-05 18:45:20 +00:00
dependabot[bot] 962d07a6c9
Bump lodash.mergewith from 4.6.1 to 4.6.2
Bumps [lodash.mergewith](https://github.com/lodash/lodash) from 4.6.1 to 4.6.2.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/commits)

Signed-off-by: dependabot[bot] <support@github.com>
2019-08-05 18:45:11 +00:00
jessopb a27f6e2b21
Merge pull request #1020 from lbryio/staging
cuts release from staging
2019-07-03 18:44:31 -04:00
jessopb 848077b057
Merge pull request #1019 from lbryio/master
cuts staging from master
2019-07-03 18:30:56 -04:00
jessopb b9b4333b55
Merge pull request #1018 from lbryio/ipWhitelist
adds ip whitelist
2019-07-03 18:08:46 -04:00
jessop ac3e9d1e67 adds ip whitelist 2019-07-03 17:55:33 -04:00
Jeremy Kauffman bd0c3fd4c3
Merge pull request #1017 from AlessandroSpallina/master
Fixed broken link
2019-06-25 16:09:33 -04:00
AlessandroSpallina 85cc743f90 Fixed broken link 2019-06-25 21:46:00 +02:00
jessopb 0e357b7b56
Merge pull request #1016 from lbryio/staging
cuts release from staging
2019-06-21 17:53:54 -04:00
jessopb 2311e5685f
Merge pull request #1015 from lbryio/master
cuts staging from master
2019-06-21 16:42:06 -04:00
jessopb a66511caaf
Merge pull request #1014 from lbryio/lenientPublishLimits
changes max publishes to 20
2019-06-21 15:23:12 -04:00
jessop 39095c16a9 changes max publishes to 20 2019-06-21 14:40:07 -04:00
Jeremy Kauffman 3d4ad3d827
Merge pull request #1013 from lbryio/noMetrics
stops logging metrics to localdb
2019-06-05 11:02:09 -04:00
Jessop Breth 6992b87033 stops logging metrics to localdb 2019-06-05 10:08:13 -04:00
jessopb 75818e47fe
Merge pull request #1012 from lbryio/fix-abandon
fix: abandoning claims
2019-05-31 15:23:10 -04:00
jessopb 4cfb85fed3
Merge pull request #1011 from ProfessorDey/patch-1
Added Server Response Error Handling
2019-05-31 14:36:09 -04:00
ProfessorDey 8a6cd2db13
Tab Removal & Check Abstraction
Per Jessop's request: https://github.com/lbryio/spee.ch/pull/1011#pullrequestreview-243975520
Corrected tabs and added the clarified abstraction to the request state check.
2019-05-31 00:15:27 +01:00
Tom dee61b5b6f fix: abandoning claims
Confirmed working locally. File/claims table has outpoint. Claim was abandoned by the sdk.
2019-05-30 14:57:47 -04:00
ProfessorDey 82ab3b47b1
Added Server Response Error Handling
Presently the spee.ch client will error out if it received an unexpected response, resulting in hangs and a very vague JSON parsing error. This failsafe will catch the known issue of server request size limits causing 413 responses if misconfigured, making things easier to diagnose, as well as catching any other unexpected responses cleanly. Further specific behaviours can be added to ensure administrators spend less time debugging simple configuration issues.

The 413 error response should be fairly self explanatory, sufficient to not need further documentation, though adding an addendum to the README.md would aid other developers have a smooth experience.
2019-05-30 14:33:53 +01:00
jessopb 73667d4d9a
Merge pull request #1010 from lbryio/staging
cuts release from staging
2019-05-28 09:49:03 -04:00
jessopb 0b4eab9bb6
Merge pull request #1009 from lbryio/master
cuts staging from master
2019-05-28 09:18:18 -04:00
jessopb 68d2f303c1
Merge pull request #1008 from lbryio/0.37-fixes
Support for 0.37 SDK
2019-05-26 18:34:21 -04:00
Tom b98fffc386 fix: updates! 2019-05-25 18:00:20 -04:00
Tom adbafa4194 further
fixes!
2019-05-24 16:39:46 -04:00
Tom 1b069ba2a1 WIP - 0.37 SDK changes
Needs the metadata sub object removed, everything should be passed at the publish level - no need to set protobuf metadata directly.

Thumbnail > thumbnail_url, licenseURL > license_url and possibly a few others.
changes

fix: 1 of many
fix 2 of many
fix: 3 of many
fix: 4 of many
more fixes
fix license
fix thumb
remove tags
fix thumb?
remove log
fix
fix txid
2019-05-24 13:09:07 -04:00
jessopb 5f3f5c678a
Merge pull request #1006 from lbryio/staging
cuts release from staging 220419
2019-04-23 07:25:37 -04:00
jessopb 5ab338cfa5
Merge pull request #1005 from lbryio/master
cuts staging from master
2019-04-22 07:53:15 -04:00
jessopb ec9f78de16
Merge pull request #1004 from lbryio/loggingImprovements
improves quality and reduces noise in logs
2019-04-19 17:11:06 -04:00
jessop b2624026bb improves quality and reduces noise in logs 2019-04-19 12:14:03 -04:00
jessopb 2ec4f366b0
Merge pull request #991 from lbryio/staging
Cuts release from staging
2019-04-15 13:02:49 -04:00
jessopb b2e35c7e13
Merge pull request #989 from D1V3/master
Adding more indentation to markup
2019-04-03 17:47:19 -04:00
D1V3 bf26e7d8a9
Addind more indentation 2019-04-03 11:30:00 -04:00
D1V3 8279b92a46
Fixing squished text 2019-04-03 11:26:46 -04:00
jessopb e21761e278
Merge pull request #986 from lbryio/master
cuts staging from master
2019-04-01 14:38:58 -04:00
jessopb 1a9ceef1e0
Merge pull request #980 from jessopb/cssChange
security patch and css bug
2019-03-27 13:05:27 -04:00
jessop 87ca19d700 security patch and css bug 2019-03-27 10:32:49 -04:00
jessopb 13cee683c3
Merge pull request #974 from jessopb/fileAvailability
uses CQ-derived outpoint and file list to detect foreign file changes
2019-03-26 07:32:13 -04:00
jessop c97685cfb5 uses chainquery derived outpoint and file list to detect foreign server file changes 2019-03-25 15:41:04 -04:00
jessopb 792bb51134
Merge pull request #973 from lbryio/staging
cuts release from staging
2019-03-23 15:52:47 -04:00
jessopb e210773016
Merge pull request #972 from lbryio/master
cut staging from master
2019-03-22 19:58:37 -04:00
jessopb a4cc7460d5
Merge pull request #967 from jessopb/isbot
avoids downloading content for googlebot and provides fallback thumbs
2019-03-20 12:09:57 -04:00
jessopb 3507bd6bf2
Merge pull request #966 from jessopb/persistExpand
persist expand state using redux
2019-03-20 12:09:37 -04:00
jessopb cab6c6dc80
Merge pull request #965 from LavRadis/patch-4
small typo
2019-03-20 11:08:02 -04:00
jessopb 992eefd40c
Merge pull request #968 from jessopb/lbrydotcom
changes lbry.io to lbry.com
2019-03-20 11:07:13 -04:00
jessop 8799e0e320 changes lbry.io to lbry.com 2019-03-17 23:13:00 -04:00
jessop 302442385e avoids downloading content for googlebot and loads fallback images for missing thumbnails 2019-03-17 22:51:57 -04:00
jessop 4632ee6472 persist expand state using redux 2019-03-16 15:20:21 -04:00
Lav Radis cf221b5aba
small typo 2019-03-16 20:14:26 +01:00
jessopb 2b20900ae5
Merge pull request #963 from lbryio/staging
cuts release from staging
2019-03-15 12:12:03 -04:00
jessopb 626b3f8b5c
Merge pull request #962 from lbryio/master
cuts staging from master
2019-03-15 11:50:27 -04:00
jessopb 373ee83c13
Merge pull request #961 from jessopb/fixChannelSelect
fixes channelSelectDropdown
2019-03-15 11:44:13 -04:00
jessop 220d043ff6 fixes channelSelectDropdown 2019-03-15 11:23:56 -04:00
jessopb 423c2827bc
Merge pull request #956 from jessopb/previewCards
improves preview cards, no logic changes
2019-03-08 02:39:57 -05:00
jessop 3411cedb55 improves preview cards 2019-03-07 01:43:49 -05:00
jessopb eb5473bbb6
Update ubuntuinstall.md 2019-03-05 12:17:59 -05:00
jessopb 790943445f
Merge pull request #946 from lbryio/staging
cuts release from staging
2019-03-02 02:09:55 -05:00
jessopb 6b97b415ff
Update ubuntuinstall.md 2019-03-02 02:01:01 -05:00
jessopb e6274fe9d8
Merge pull request #945 from jessopb/sdk32compat
updates lbrynet download_dir
2019-03-01 10:57:26 -05:00
jessop a9627fbd96 updates lbrynet download_dir 2019-03-01 03:20:53 -05:00
jessopb da4a1d6c03
Update ubuntuinstall.md
adds imagemagick
2019-03-01 03:07:01 -05:00
jessopb 63a1be5287
Update ubuntuinstall.md 2019-03-01 03:03:53 -05:00
jessopb aa70c2854d
Update ubuntuinstall.md 2019-03-01 02:23:57 -05:00
jessopb c5739b1cb6
Merge pull request #944 from lbryio/master
cuts staging from master
2019-03-01 00:47:25 -05:00
jessopb 665b60d03b
Merge pull request #943 from jessopb/fileWaitFix
fixes swapped timeout interval values and makes ready-size more conservative
2019-03-01 00:34:19 -05:00
jessop 161b57fef5 fixes swapped timeout interval values 2019-03-01 00:29:45 -05:00
jessopb 629d133fd2
Merge pull request #942 from jessopb/lbrynetFileWait
checks if file is ready using sdk file_list and outpoint
2019-02-28 14:56:12 -05:00
jessop 357388d1e4 checks if file is ready using file_list 2019-02-27 19:00:05 -05:00
jessopb 5e66b6827a
Merge pull request #941 from jessopb/licenseUrlBugged
patch bug in licenseUrl
2019-02-26 23:49:35 -05:00
jessop 42fc707dba update bug in licenseUrl 2019-02-26 13:43:35 -05:00
jessopb f636023f80
Merge pull request #939 from lbryio/master
cuts staging from master
2019-02-26 11:58:26 -05:00
jessopb a5700be902
Merge pull request #935 from jessopb/licenseDev2
Adds License and LicenseUrl to AssetInfo and Publish/Edit Forms
2019-02-26 03:44:02 -05:00
jessopb b107346c28
Merge branch 'master' into licenseDev2 2019-02-26 03:32:34 -05:00
jessopb 117ffa68a5
Merge pull request #938 from jessopb/waitOnBroke
replaces wait-on
2019-02-26 03:28:55 -05:00
jessopb 3cd7b6080d
Merge pull request #933 from jessopb/croppingSafe
documents and validates image resize
2019-02-26 03:28:32 -05:00
jessop 7b7a0339b8 licenseUrl shows on edit publish 2019-02-26 03:12:11 -05:00
jessop 4c37a6311f documents and validates image resize 2019-02-26 03:03:31 -05:00
jessop f8662dddb1 replaces wait-on 2019-02-26 02:27:09 -05:00
jessopb 0d04659498
Update README.md 2019-02-25 20:59:46 -05:00
jessopb dfdb933527
Merge pull request #936 from lbryio/staging
cuts release from staging
2019-02-25 20:09:29 -05:00
jessop 7d5e41153e adds licenseUrl 2019-02-23 00:52:31 -05:00
jessopb a2c87af728
Merge pull request #932 from jessopb/handlbars
patch vulnerability
2019-02-22 16:32:03 -05:00
jessop 739127e44f patch vulnerability 2019-02-22 15:20:49 -05:00
jessopb c79573f4d2
Merge pull request #931 from lbryio/master
cuts staging from master
2019-02-22 14:59:12 -05:00
jessopb 8e9e195b8e
Merge pull request #930 from kcseb/master
Updated the download link to retain file extension.
2019-02-22 14:10:38 -05:00
jessopb 969988f5e8
Merge pull request #929 from lbryio/readmeSDKsettings
Update ubuntuinstall.md
2019-02-22 14:09:12 -05:00
Kenneth C 961700390a
Updated the download link to retain file extension.
Updated the download URL to retain the file extension. Before, when you would download the file it would not append the file extension, this will fix that.
2019-02-22 12:23:06 -05:00
jessopb 060e34090e
Update ubuntuinstall.md 2019-02-21 22:38:00 -05:00
jessopb aa381d5940
Update ubuntuinstall.md
Adds SDK settings to install process
2019-02-21 18:12:42 -05:00
jessopb ed9d037155
Merge pull request #928 from jessopb/xformFileOnServe
implements querystring image transformation
2019-02-21 17:56:59 -05:00
jessop a6cecf84e2 implements querystring transformation 2019-02-21 01:56:27 -05:00
jessopb 7a83d4c75f
Merge pull request #923 from jessopb/cssvars
better enables style changes from variables file
2019-02-19 01:08:56 -05:00
jessopb cf97b0d3df
Merge pull request #924 from jessopb/sdkUpdates
updates for sdk .32
2019-02-19 01:08:32 -05:00
jessopb ac28b0c43f
Update README.md 2019-02-18 21:55:56 -05:00
jessopb 287464e311
Update settings.md 2019-02-18 21:52:50 -05:00
jessop 7c157d0b18 updates for sdk .32 2019-02-18 20:03:37 -05:00
jessop b46f641937 better enables style changes from variables file 2019-02-18 13:57:00 -05:00
jessopb 48667623db
Merge pull request #919 from jessopb/siteConfigBug
siteConfig had dots in extensions
2019-02-16 18:35:23 -05:00
jessop f559b5aae1 siteConfig had dots in extensions 2019-02-16 17:16:36 -05:00
92 changed files with 1786 additions and 934 deletions

View file

@ -1,4 +1,3 @@
sudo: true
dist: xenial
#addons:
# apt:
@ -9,7 +8,7 @@ dist: xenial
# - mysql-client
language: node_js
node_js:
- "lts/*"
- "lts/dubnium"
cache:
directories:
- "node_modules"

View file

@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2017-2019 LBRY Inc.
Copyright (c) 2017-2020 LBRY Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish,

View file

@ -3,6 +3,8 @@
spee.ch provides a user-friendly, custom-designed, image and video hosting site backed by a decentralized network and
blockchain ([LBRY](https://lbry.tech/)). Via just a small set of config files, you can spin your an entire spee.ch site back up including assets.
**Please note: the spee.ch code base and setup instructions are no longer actively maintained now that we have lbry.tv. Proceed at your own caution. Setup will require dev ops skills.**
![App GIF](https://spee.ch/e/speechgif.gif)
For a completely open, unrestricted example of a spee.ch site, check out https://www.spee.ch.
@ -47,6 +49,7 @@ For a closed, custom-hosted and branded example, check out https://lbry.theantim
- `./lbrynet account_balance` gets your balance (initially 0.0)
- `./lbrynet address_list` gets addresses you can use to recieve LBC
- [FFmpeg](https://www.ffmpeg.org/download.html)
- [ImageMagick](https://packages.ubuntu.com/xenial/graphics/imagemagick)
- Spee.ch (below)
- pm2 (optional) process manager such as pm2 to run speech server.js
- http proxy server e.g. caddy, nginx, or traefik, to forward 80/443 to speech port 3000
@ -104,7 +107,7 @@ $ npm run start
#### Customize your app
Check out the [customization guide](https://github.com/lbryio/spee.ch/blob/readme-update/customize.md) to change your app's appearance and components
Check out the [customization guide](https://github.com/lbryio/spee.ch/blob/master/customize.md) to change your app's appearance and components
#### (optional) add custom components and update the styles
@ -118,7 +121,7 @@ Instructions are coming at [lbry-docker] to install your own chainquery instance
## Settings
There are a number of settings available for customizing the behavior of your installation.
_INSERT LINK TO SETTINGS.MD_
[Here](https://github.com/lbryio/spee.ch/blob/master/docs/settings.md) is some documentation on them.
## API
@ -260,9 +263,11 @@ Spee.ch has a few types of URL formats that return different assets from the LBR
- retrieve the controlling `LBRY` claim:
- https://spee.ch/`claim`
- https://spee.ch/`claim`.`ext` (serve)
- https://spee.ch/`claim`.`ext`&`querystring` (serve transformed)
- retrieve a specific `LBRY` claim:
- https://spee.ch/`claim_id`/`claim`
- https://spee.ch/`claim_id`/`claim`.`ext` (serve)
- https://spee.ch/`claim_id`/`claim`.`ext`&`querystring` (serve transformed)
- retrieve all contents for the controlling `LBRY` channel
- https://spee.ch/`@channel`
- a specific `LBRY` channel
@ -270,9 +275,15 @@ Spee.ch has a few types of URL formats that return different assets from the LBR
- retrieve a specific claim within the controlling `LBRY` channel
- https://spee.ch/`@channel`/`claim`
- https://spee.ch/`@channel`/`claim`.`ext` (serve)
- https://spee.ch/`@channel`/`claim`.`ext`&`querystring` (serve)
- retrieve a specific claim within a specific `LBRY` channel
- https://spee.ch/`@channel`:`channel_id`/`claim`
- https://spee.ch/`@channel`:`channel_id`/`claim`.`ext` (serve)
- https://spee.ch/`@channel`:`channel_id`/`claim`.`ext`&`querystring` (serve)
- `querystring` can include the following transformation values separated by `&`
- h=`number` (defines height)
- w=`number` (defines width)
- t=`crop` or `stretch` (defines transformation - missing implies constrained proportions)
### Dependencies
@ -291,8 +302,8 @@ This project is MIT licensed. For the full license, see [LICENSE](LICENSE).
## Security
We take security seriously. Please contact security@lbry.io regarding any security issues. [Our GPG key is here](https://lbry.io/faq/gpg-key) if you need it.
We take security seriously. Please contact security@lbry.com regarding any security issues. [Our GPG key is here](https://lbry.com/faq/gpg-key) if you need it.
## Contact
The primary contact for this project is [@jessopb](mailto:jessop@lbry.io).
The primary contact for this project is [@jessopb](mailto:jessop@lbry.com).

0
changelog.md Normal file
View file

View file

@ -188,8 +188,8 @@ inquirer
.post('http://localhost:5279', {
method: 'channel_new',
params: {
channel_name: thumbnailChannelDefault,
amount: channelBid,
name: thumbnailChannelDefault,
bid: channelBid,
},
})
.then(response => {
@ -197,7 +197,6 @@ inquirer
if (response.data.error) {
throw new Error(response.data.error.message);
}
thumbnailChannel = thumbnailChannelDefault;
thumbnailChannelId = response.data.result.claim_id;
siteConfig['publishing']['thumbnailChannel'] = thumbnailChannel;
@ -237,7 +236,9 @@ inquirer
'\nIt\'s a good idea to BACK UP YOUR MASTER PASSWORD \nin "/site/private/authConfig.json" so that you don\'t lose \ncontrol of your channel.'
);
console.log('\nNext step: run "npm run start" to build and start your server!');
console.log(
'\nNext step: run "npm run build" (or "npm run dev") to compiles, and "npm run start" to start your server!'
);
console.log(
'If you want to change any settings, you can edit the files in the "/site" folder.'
);

View file

@ -1,5 +1,5 @@
{
"host": "public.chainquery.lbry.io",
"host": "public.chainquery.lbry.com",
"port": "3306",
"timeout": 30,
"database": "chainquery",

View file

@ -18,12 +18,11 @@
"host": "https://www.example.com",
"description": "A decentralized hosting platform built on LBRY",
"twitter": false,
"blockListEndpoint": "https://api.lbry.io/file/list_blocked"
"blockListEndpoint": "https://api.lbry.com/file/list_blocked"
},
"publishing": {
"primaryClaimAddress": null,
"uploadDirectory": "/home/lbry/Uploads",
"lbrynetHome": "/CURRENTLYUNUSED",
"thumbnailChannel": null,
"thumbnailChannelId": null,
"additionalClaimAddresses": [],
@ -46,12 +45,13 @@
"customByContentType": {
"application/octet-stream": 50000000
}
},
"maxSizeImage": 10000000,
"maxSizeGif": 50000000,
"maxSizeVideo": 50000000
}
},
"serving": {
"dynamicFileSizing": {
"enabled": true,
"maxDimension": 2000
},
"markdownSettings": {
"skipHtmlMain": true,
"escapeHtmlMain": true,
@ -86,24 +86,21 @@
"code",
"html",
"parsedHtml"
],
"disallowedTypesMain": [],
"disallowedTypesDescriptions": ["image", "html"],
"disallowedTypesExample": ["image", "html"]
]
},
"customFileExtensions": {
"application/x-troff-man": ".man",
"application/x-troff-me": ".me",
"application/x-mif": ".mif",
"application/x-troff-ms": ".ms",
"application/x-troff": ".roff",
"application/x-python-code": ".pyc",
"text/x-python": ".py",
"application/x-pn-realaudio": ".ram",
"application/x-sgml": ".sgm",
"model/stl": ".stl",
"image/pict": ".pct",
"text/xul": ".xul",
"application/x-troff-man": "man",
"application/x-troff-me": "me",
"application/x-mif": "mif",
"application/x-troff-ms": "ms",
"application/x-troff": "roff",
"application/x-python-code": "pyc",
"text/x-python": "py",
"application/x-pn-realaudio": "ram",
"application/x-sgml": "sgm",
"model/stl": "stl",
"image/pict": "pct",
"text/xul": "xul",
"text/x-go": "go"
}
},

View file

@ -1,29 +1,88 @@
.asset-preview {
position: relative;
display: flex;
flex-direction: column;
background: $card-color;
padding: $thin-padding;
color: $text-color;
width: 240px;
border: $subtle-border;
height: 280px;
height: 256px;
&:hover {
border: 1px solid $highlight-border-color;
color: #000000;
border-color: $highlight-border-color;
color: $primary-color;
}
}
.asset-preview__image {
height : 180px;
width : 240px;
overflow: hidden;
object-fit: cover;
padding: 0;
margin : 0;
box-sizing: border-box;
}
.asset-preview__image-box {
width : 240px;
height : 180px;
padding: 0;
margin : 0;
box-sizing: border-box;
}
.asset-preview__label {
padding: $thin-padding;
display: flex;
flex-direction: column;
justify-content: space-between;
height: 7.3em;
}
.asset-preview__label-text {
height: 4.5em;
overflow: hidden;
display: flex;
flex-direction: column;
justify-content: space-around;
box-sizing: border-box;
font-size: $text-small;
font-weight: bold;
height: 54px;
}
.asset-preview__label-info {
width: 100%;
height: 15px;
display: flex;
flex-direction: row;
justify-content: space-between;
overflow: hidden;
text-overflow: ellipsis;
box-sizing: border-box;
align-items: center;
}
.asset-preview__label-info-datum {
display: flex;
flex-direction: row;
align-items: center;
overflow: hidden;
box-sizing: border-box;
font-size: $text-small;
max-width: 40%;
}
.asset-preview__label-info-datum svg{
height: 1.2em;
width: 1.2em;
padding: 0;
padding-right: $thin-padding;
margin: 0;
}
.asset-preview__label-info-datum .svg-icon{
padding: 0px;
margin: 0;
height: 15px;
}
.asset-preview__blocked {
@ -34,22 +93,3 @@
padding: $thin-padding;
margin-bottom: $thin-padding;
}
.asset-preview__image {
width : 240px;
height : 180px;
overflow: hidden;
object-fit: cover;
padding: 0;
margin : 0;
box-sizing: border-box;
}
h3.asset-preview__title {
margin: 0;
text-overflow: ellipsis;
word-wrap: break-word;
overflow: hidden;
max-height: 4em;
font-size: $text-large;
}

View file

@ -1,7 +1,7 @@
.channel-claims-display {
width: 100%;
display: grid;
grid-gap: $thin-padding;
grid-gap: $tertiary-padding;
align-content: space-around;
@media (min-width: $break-point-x-large) {
grid-template-columns: 1fr 1fr 1fr 1fr 1fr;

View file

@ -8,6 +8,7 @@ input {
border: 0;
background-color: $background-color;
display: inline-block;
color: $text-color
}
.input-slider {

View file

@ -9,7 +9,6 @@ a, a:visited {
.link--nav {
color: $text-color;
border-bottom: 2px solid white;
&:hover {
color: $primary-color;
}
@ -18,4 +17,4 @@ a, a:visited {
.link--nav-active {
border-bottom: 2px solid $primary-color;
}
}

View file

@ -1,15 +1,24 @@
.nav-bar {
box-sizing: border-box;
padding: $thin-padding $primary-padding;
background: $base-color;
background: $chrome-color;
flex: 0 1 auto;
width: 100%;
border-bottom: $subtle-border;
color: $primary-color;
@media (max-width: $break-point-mobile) {
margin-left: 15px;
margin-right: 15px;
}
input {
background: $chrome-color;
}
select {
background: $chrome-color;
color: $text-color;
}
}
.nav-bar-link {

View file

@ -1,6 +1,7 @@
select {
margin: 0;
display: inline-block;
background: $base-color;
background: $background-color;
border: 0;
color: $text-color;
}

View file

@ -1,7 +1,7 @@
//backgrounds
$base-color: white; //default white
$card-color: white; //default white
$chrome-color: lightgray; //default white (navbar)
$chrome-color: white; //default white (navbar)
$blockquote-background: #EEEEFF;
$background-color: $base-color;
@ -19,7 +19,7 @@ $blockquote-text: $text-color;
$grey: #9095A5;
$help-color: $grey;
$subtle-border-color: #DDD;
$highlight-border-color: #333;
$highlight-border-color: #777;
$shadow-color: rgba(169, 173, 186, 0.2);
$subtle-border: 1px dashed $subtle-border-color;
$grey-border: $subtle-border-color; //factor this out for all customers

View file

@ -1,13 +1,8 @@
import * as actions from '../constants/show_action_types';
import {
ASSET_DETAILS,
ASSET_LITE,
CHANNEL,
SPECIAL_ASSET,
} from '../constants/show_request_types';
import { ASSET_DETAILS, ASSET_LITE, CHANNEL, SPECIAL_ASSET } from '../constants/show_request_types';
// basic request parsing
export function onHandleShowPageUri (params, url) {
export function onHandleShowPageUri(params, url) {
return {
type: actions.HANDLE_SHOW_URI,
data: {
@ -17,7 +12,7 @@ export function onHandleShowPageUri (params, url) {
};
}
export function onHandleShowHomepage (params, url) {
export function onHandleShowHomepage(params, url) {
return {
type: actions.HANDLE_SHOW_HOMEPAGE,
data: {
@ -27,14 +22,14 @@ export function onHandleShowHomepage (params, url) {
};
}
export function onRequestError (error) {
export function onRequestError(error) {
return {
type: actions.REQUEST_ERROR,
data: error,
};
}
export function onNewChannelRequest (channelName, channelId) {
export function onNewChannelRequest(channelName, channelId) {
const requestType = CHANNEL;
const requestId = `cr#${channelName}#${channelId}`;
return {
@ -43,7 +38,7 @@ export function onNewChannelRequest (channelName, channelId) {
};
}
export function onNewSpecialAssetRequest (name) {
export function onNewSpecialAssetRequest(name) {
const requestType = SPECIAL_ASSET;
const requestId = `sar#${name}`;
return {
@ -52,7 +47,7 @@ export function onNewSpecialAssetRequest (name) {
};
}
export function onNewAssetRequest (name, id, channelName, channelId, extension) {
export function onNewAssetRequest(name, id, channelName, channelId, extension) {
const requestType = extension ? ASSET_LITE : ASSET_DETAILS;
const requestId = `ar#${name}#${id}#${channelName}#${channelId}`;
return {
@ -65,14 +60,14 @@ export function onNewAssetRequest (name, id, channelName, channelId, extension)
id,
channel: {
name: channelName,
id : channelId,
id: channelId,
},
},
},
};
}
export function onRequestUpdate (requestType, requestId) {
export function onRequestUpdate(requestType, requestId) {
return {
type: actions.REQUEST_UPDATE,
data: {
@ -82,7 +77,7 @@ export function onRequestUpdate (requestType, requestId) {
};
}
export function addRequestToRequestList (id, error, key) {
export function addRequestToRequestList(id, error, key) {
return {
type: actions.REQUEST_LIST_ADD,
data: { id, error, key },
@ -91,21 +86,21 @@ export function addRequestToRequestList (id, error, key) {
// asset actions
export function addAssetToAssetList (id, error, name, claimId, shortId, claimData, claimViews) {
export function addAssetToAssetList(id, error, name, claimId, shortId, claimData, claimViews) {
return {
type: actions.ASSET_ADD,
data: { id, error, name, claimId, shortId, claimData, claimViews },
};
}
export function updateAssetViewsInList (id, claimId, claimViews) {
export function updateAssetViewsInList(id, claimId, claimViews) {
return {
type: actions.ASSET_VIEWS_UPDATE,
data: { id, claimId, claimViews },
};
}
export function removeAsset (data) {
export function removeAsset(data) {
return {
type: actions.ASSET_REMOVE,
data,
@ -114,7 +109,7 @@ export function removeAsset (data) {
// channel actions
export function addNewChannelToChannelList (id, name, shortId, longId, claimsData) {
export function addNewChannelToChannelList(id, name, shortId, longId, claimsData) {
return {
type: actions.CHANNEL_ADD,
data: {
@ -127,39 +122,47 @@ export function addNewChannelToChannelList (id, name, shortId, longId, claimsDat
};
}
export function onUpdateChannelClaims (channelKey, name, longId, page) {
export function onUpdateChannelClaims(channelKey, name, longId, page) {
return {
type: actions.CHANNEL_CLAIMS_UPDATE_ASYNC,
data: {channelKey, name, longId, page},
data: { channelKey, name, longId, page },
};
}
export function updateChannelClaims (channelListId, claimsData) {
export function updateChannelClaims(channelListId, claimsData) {
return {
type: actions.CHANNEL_CLAIMS_UPDATE_SUCCEEDED,
data: {channelListId, claimsData},
data: { channelListId, claimsData },
};
}
// display a file
export function fileRequested (name, claimId) {
export function fileRequested(name, claimId) {
return {
type: actions.FILE_REQUESTED,
data: { name, claimId },
};
}
export function updateFileAvailability (status) {
export function updateFileAvailability(status) {
return {
type: actions.FILE_AVAILABILITY_UPDATE,
data: status,
};
}
export function updateDisplayAssetError (error) {
export function updateDisplayAssetError(error) {
return {
type: actions.DISPLAY_ASSET_ERROR,
data: error,
};
}
// viewer settings
export function toggleDetailsExpanded(isExpanded) {
return {
type: actions.TOGGLE_DETAILS_EXPANDED,
data: isExpanded,
};
}

View file

@ -1,6 +1,6 @@
import Request from '../utils/request';
export function getLongClaimId (host, name, modifier) {
export function getLongClaimId(host, name, modifier) {
let body = {};
// create request params
if (modifier) {
@ -13,40 +13,40 @@ export function getLongClaimId (host, name, modifier) {
}
body['claimName'] = name;
const params = {
method : 'POST',
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body : JSON.stringify(body),
body: JSON.stringify(body),
};
// create url
const url = `${host}/api/claim/long-id`;
// return the request promise
return Request(url, params);
};
}
export function getShortId (host, name, claimId) {
export function getShortId(host, name, claimId) {
const url = `${host}/api/claim/short-id/${claimId}/${name}`;
return Request(url);
};
}
export function getClaimData (host, name, claimId) {
export function getClaimData(host, name, claimId) {
const url = `${host}/api/claim/data/${name}/${claimId}`;
return Request(url);
};
}
export function checkClaimAvailability (claim) {
export function checkClaimAvailability(claim) {
const url = `/api/claim/availability/${claim}`;
return Request(url);
}
export function getClaimViews (claimId) {
export function getClaimViews(claimId) {
const url = `/api/claim/views/${claimId}`;
return Request(url);
}
export function doAbandonClaim (claimId) {
export function doAbandonClaim(outpoint) {
const params = {
method : 'POST',
body : JSON.stringify({claimId}),
method: 'POST',
body: JSON.stringify({ outpoint }),
headers: new Headers({
'Content-Type': 'application/json',
}),

View file

@ -16,7 +16,7 @@ import EditPage from '@pages/EditPage';
const App = () => {
return (
<Switch>
<Route exact path='/' component={HomePage} />
<Route exact path='/' component={AboutPage} />
<Route exact path='/about' component={AboutPage} />
<Route exact path='/tos' component={TosPage} />
<Route exact path='/faq' component={FaqPage} />

View file

@ -22,14 +22,29 @@ export const makePublishRequestChannel = (fd, isUpdate) => {
xhr.upload.addEventListener('load', onLoad);
// set state change handler
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
const response = JSON.parse(xhr.response);
if ((xhr.status === 200) && response.success) {
emitter({success: response});
emitter(END);
} else {
emitter({error: new Error(response.message)});
emitter(END);
if (xhr.readyState === XMLHttpRequest.DONE) {
switch (xhr.status) {
case 413:
emitter({error: new Error("Unfortunately it appears this web server " +
"has been misconfigured, please inform the service administrators " +
"that they must set their nginx/apache request size maximums higher " +
"than their file size limits.")});
emitter(END);
break;
case 200:
var response = JSON.parse(xhr.response);
if (response.success) {
emitter({success: response});
emitter(END);
} else {
emitter({error: new Error(response.message)});
emitter(END);
}
break;
default:
emitter({error: new Error("Received an unexpected response from " +
"server: " + xhr.status)});
emitter(END);
}
}
};

View file

@ -1,32 +1,13 @@
import React from 'react';
import Row from '@components/Row';
import {Link} from 'react-router-dom';
const AboutSpeechDetails = () => {
return (
<div>
<Row>
<p className={'text--large'}>
<Link className={'link--primary'} to='/tos'>Terms of Service</Link>
<br />
<Link className={'link--primary'} to='/faq'>Frequently Asked Questions</Link>
</p>
</Row>
<Row>
<p className={'text--large'}>
Spee.ch is a media-hosting site that reads from and publishes content to the <a className='link--primary' href='https://lbry.io'>LBRY</a> blockchain.
</p>
<p className={'text--large'}>
Spee.ch is a hosting service, but with the added benefit that it stores your content on a decentralized network of computers -- the <a className='link--primary' href='https://lbry.io/get'>LBRY</a> network. This means that your images are stored in multiple locations without a single point of failure.
</p>
</Row>
<Row>
<h3>Contribute</h3>
<p className={'text--large'}>
If you have an idea for your own spee.ch-like site on top of LBRY, fork our <a className='link--primary' href='https://github.com/lbryio/spee.ch'>github repo</a> and go to town!
</p>
<p className={'text--large'}>
If you want to improve spee.ch, join our <a className='link--primary' href='https://chat.lbry.io'>discord channel</a> or solve one of our <a className='link--primary' href='https://github.com/lbryio/spee.ch/issues'>github issues</a>.
Spee.ch's journey may be on hold, but LBRY is still on mission. We'd like to thank all of our testers and early adopters for helping us explore this use case.
We're really excited about <a className='link--primary' href='https://lbry.tv' target='_blank'>lbry.tv</a> and can't wait to see you over there for a fully featured experience.
</p>
</Row>
</div>

View file

@ -5,14 +5,13 @@ const AboutSpeechOverview = () => {
return (
<div>
<Row>
<p className={'text--extra-large'}>Spee.ch is an open-source project. Please contribute to the existing site, or fork it and make your own.</p>
<p className={'text--extra-large'}>Lbry is no longer supporting Spee.ch. However, we're excited to show you <a className='link--primary' href='https://lbry.tv' target='_blank'>lbry.tv</a>!</p>
</Row>
<Row>
<div className={'text--large'}>
<a className='link--primary' target='_blank' href='https://twitter.com/spee_ch'>TWITTER</a><br/>
<a className='link--primary' target='_blank' href='https://github.com/lbryio/spee.ch'>GITHUB</a><br/>
<a className='link--primary' target='_blank' href='https://twitter.com/lbry'>TWITTER</a><br/>
<a className='link--primary' target='_blank' href='https://github.com/lbryio/'>GITHUB</a><br/>
<a className='link--primary' target='_blank' href='https://discord.gg/YjYbwhS'>DISCORD CHANNEL</a><br/>
<a className='link--primary' target='_blank' href='https://github.com/lbryio/spee.ch/blob/master/README.md'>DOCUMENTATION</a><br/>
</div>
</Row>
</div>

View file

@ -3,9 +3,13 @@ import Row from '@components/Row';
const AssetInfoFooter = ({ assetUrl, name }) => {
return (
<div className='asset-footer'>
<div className="asset-footer">
<p>
Hosted via the <a className={'link--primary'} href={'https://lbry.io/get'} target={'_blank'}>LBRY</a> blockchain
Hosted via the{' '}
<a className={'link--primary'} href={'https://lbry.com/get'} target={'_blank'}>
LBRY
</a>{' '}
blockchain
</p>
</div>
);

View file

@ -2,11 +2,24 @@ import React from 'react';
import { Link } from 'react-router-dom';
import createCanonicalLink from '@globalutils/createCanonicalLink';
import * as Icon from 'react-feather';
import Img from 'react-image';
const AssetPreview = ({ defaultThumbnail, claimData }) => {
const {name, fileExt, contentType, thumbnail, title, blocked} = claimData;
const {name, fileExt, contentType, thumbnail, title, blocked, transactionTime = 0} = claimData;
const showUrl = createCanonicalLink({asset: {...claimData}});
const embedUrl = `${showUrl}.${fileExt}`;
const ago = Date.now() / 1000 - transactionTime;
const dayInSeconds = 60 * 60 * 24;
const monthInSeconds = dayInSeconds * 30;
let when;
if (ago < dayInSeconds || transactionTime < 1) {
when = 'Just today';
} else if (ago < monthInSeconds) {
when = `${Math.floor(ago / dayInSeconds)} d ago`;
} else {
when = `${Math.floor(ago / monthInSeconds)} mo ago`;
}
/*
we'll be assigning media icon based on supported type / mime types
*/
@ -37,21 +50,36 @@ const AssetPreview = ({ defaultThumbnail, claimData }) => {
} else {
return (
<Link to={showUrl} className='asset-preview'>
<img
className={'asset-preview__image'}
src={thumb || defaultThumbnail}
alt={name}
/>
<div className='asset-preview__image-box'>
<Img
src={[
thumb,
defaultThumbnail,
'/assets/img/default_thumb.jpg',
]}
alt={name}
className={'asset-preview__image'}
/>
</div>
<div className={'asset-preview__label'}>
<div className={'asset-preview__label-text'}>
<p className='asset-preview__title text--medium'>{title}</p>
<p>{title}</p>
</div>
<div className={'asset-preview__label-info'}>
<div className={'text--medium'}>
{ media === 'image' && <Icon.Image />}
{ media === 'text' && <Icon.FileText />}
{ media === 'video' && contentType === 'video/mp4' && <Icon.Video />}
{ media !== 'image' && media !== 'text' && contentType !== 'video/mp4' && <Icon.File />}
<div className={'asset-preview__label-info '}>
<div className={'asset-preview__label-info-datum'}>
<div className={'svg-icon'}>
{ media === 'image' && <Icon.Image />}
{ media === 'text' && <Icon.FileText />}
{ media === 'video' && contentType === 'video/mp4' && <Icon.Video />}
{ media !== 'image' && media !== 'text' && contentType !== 'video/mp4' && <Icon.File />}
</div>
<div>{fileExt}</div>
</div>
<div className={'asset-preview__label-info-datum'}>
<div>{when}</div>
</div>
</div>
</div>

View file

@ -3,7 +3,7 @@ import React from 'react';
const PublishFinePrint = () => {
return (
<p className={'text--extra-small text--secondary'}>
By clicking 'Publish', you affirm that you have the rights to publish this content to the LBRY network, and that you understand the properties of publishing it to a decentralized, user-controlled network. <a className='link--primary' target='_blank' href='https://lbry.io/learn'>Read more.</a>
By clicking 'Publish', you affirm that you have the rights to publish this content to the LBRY network, and that you understand the properties of publishing it to a decentralized, user-controlled network. <a className='link--primary' target='_blank' href='https://lbry.com/learn'>Read more.</a>
</p>
);
};

View file

@ -1,23 +1,28 @@
import React from 'react';
import RowLabeled from '@components/RowLabeled';
import Label from '@components/Label';
import { LICENSES } from '@clientConstants/publish_license_urls';
const PublishLicenseInput = ({ handleSelect }) => {
const PublishLicenseInput = ({ handleSelect, license }) => {
return (
<RowLabeled
label={
<Label value={'License:'} />
<Label value={'License'} />
}
content={
<select
type='text'
name='license'
id='publish-license'
value={license}
onChange={handleSelect}
>
<option value=''>Unspecified</option>
<option value='Public Domain'>Public Domain</option>
<option value='Creative Commons'>Creative Commons</option>
{
LICENSES.map(function(item, i){
return <option key={item + 'license key'} value={item}>{item}</option>;
})
}
</select>
}
/>

View file

@ -0,0 +1,32 @@
import React from 'react';
import RowLabeled from '@components/RowLabeled';
import Label from '@components/Label';
import { CC_LICENSES } from '@clientConstants/publish_license_urls';
const PublishLicenseUrlInput = ({ handleSelect, licenseUrl }) => {
return (
<RowLabeled
label={
<Label value={'License Url'} />
}
content={
<select
type='text'
name='licenseUrl'
id='publish-license-url'
value={licenseUrl}
onChange={handleSelect}
>
<option value=''>Unspecified</option>
{
CC_LICENSES.map(function(item, i){
return <option key={item.url} value={item.url}>{item.value}</option>
})
}
</select>
}
/>
);
};
export default PublishLicenseUrlInput;

View file

@ -0,0 +1,33 @@
export const CC_LICENSES = [
{
value: 'CC Attr. 4.0 Int',
url: 'https://creativecommons.org/licenses/by/4.0/legalcode',
},
{
value: 'CC Attr-ShareAlike 4.0 Int',
url: 'https://creativecommons.org/licenses/by-sa/4.0/legalcode',
},
{
value: 'CC Attr-NoDerivatives 4.0 Int',
url: 'https://creativecommons.org/licenses/by-nd/4.0/legalcode',
},
{
value: 'CC Attr-NonComm 4.0 Int',
url: 'https://creativecommons.org/licenses/by-nc/4.0/legalcode',
},
{
value: 'CC Attr-NonComm-ShareAlike 4.0 Int',
url: 'https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode',
},
{
value: 'CC Attr-NonComm-NoDerivatives 4.0 Int',
url: 'https://creativecommons.org/licenses/by-nc-nd/4.0/legalcode',
},
];
export const LICENSES = ['Public Domain', 'Other', 'Copyright', 'Creative Commons'];
export const PUBLIC_DOMAIN = 'Public Domain';
export const OTHER = 'other';
export const COPYRIGHT = 'copyright';
export const CREATIVE_COMMONS = 'Creative Commons';

View file

@ -24,3 +24,4 @@ export const CHANNEL_CLAIMS_UPDATE_SUCCEEDED = 'CHANNEL_CLAIMS_UPDATE_SUCCEEDED'
export const FILE_REQUESTED = 'FILE_REQUESTED';
export const FILE_AVAILABILITY_UPDATE = 'FILE_AVAILABILITY_UPDATE';
export const DISPLAY_ASSET_ERROR = 'DISPLAY_ASSET_ERROR';
export const TOGGLE_DETAILS_EXPANDED = 'TOGGLE_DETAILS_EXPANDED';

View file

@ -17,7 +17,7 @@ class BlockedRight extends React.PureComponent {
return (
<div className={'asset-blocked__text'} >
<p>In response to a complaint we received under the US Digital Millennium Copyright Act, we have blocked access to this content from our applications.</p>
<p><a href={'https://lbry.io/faq/dmca'} >Click here</a> for more information.</p>
<p><a href={'https://lbry.com/faq/dmca'} >Click here</a> for more information.</p>
</div>
);
}

View file

@ -4,6 +4,8 @@ import ProgressBar from '@components/ProgressBar';
import { LOCAL_CHECK, UNAVAILABLE, ERROR, AVAILABLE } from '../../constants/asset_display_states';
import createCanonicalLink from '@globalutils/createCanonicalLink';
import FileViewer from '@components/FileViewer';
import isBot from 'isbot';
import Img from 'react-image';
class AvailableContent extends React.Component {
render () {
@ -15,20 +17,23 @@ class AvailableContent extends React.Component {
case 'image/gif':
case 'image/svg+xml':
return (
<img
className='asset-image'
src={sourceUrl}
<Img
src={[
sourceUrl,
'/assets/img/default_thumb.jpg',
]}
alt={name}
className={'asset-image'}
/>
);
case 'video/mp4':
return (
<video
className='asset-video'
controls poster={thumbnail}
controls poster={!!thumbnail && thumbnail || '/assets/img/default_thumb.jpg'}
>
<source
src={sourceUrl}
src={!!sourceUrl && sourceUrl}
/>
<p>Your browser does not support the <code>video</code> element.</p>
</video>
@ -36,14 +41,25 @@ class AvailableContent extends React.Component {
case 'text/markdown':
return (
<div className={'asset-document'}><FileViewer sourceUrl={sourceUrl}/></div>
(isBot(window.navigator.userAgent))
? (
<img
className='asset-image'
src={'/assets/img/default_thumb.jpg'}
alt={'markdown available on page load'}
/>
)
: <div className={'asset-document'}><FileViewer sourceUrl={!!sourceUrl && sourceUrl}/></div>
);
default:
return (
<img
className='asset-image'
src={thumbnail}
<Img
src={[
thumbnail,
'/assets/img/default_thumb.jpg',
]}
alt={name}
className={'asset-image'}
/>
);
}
@ -76,7 +92,7 @@ class AssetDisplay extends React.Component {
<div>
<p>Sit tight, we're searching the LBRY blockchain for your asset!</p>
<ProgressBar size={12} />
<p>Curious what magic is happening here? <a className='link--primary' target='blank' href='https://lbry.io/faq/what-is-lbry'>Learn more.</a></p>
<p>Curious what magic is happening here? <a className='link--primary' target='blank' href='https://lbry.com/faq/what-is-lbry'>Learn more.</a></p>
</div>
}
{(status === ERROR) && (
@ -87,7 +103,7 @@ class AssetDisplay extends React.Component {
) : (
<div>
<Row>
<p>Unfortunately, we couldn't download your asset from LBRY. You can help us out by sharing the following error message in the <a className='link--primary' href='https://chat.lbry.io' target='_blank'>LBRY discord</a>.</p>
<p>Unfortunately, we couldn't download your asset from LBRY. You can help us out by sharing the following error message in the <a className='link--primary' href='https://chat.lbry.com' target='_blank'>LBRY discord</a>.</p>
</Row>
<Row>
<p id='error-message'><i>{error}</i></p>

View file

@ -7,7 +7,7 @@ import AssetShareButtons from '@components/AssetShareButtons';
import ClickToCopy from '@components/ClickToCopy';
import siteConfig from '@config/siteConfig.json';
import createCanonicalLink from '@globalutils/createCanonicalLink';
import AssetInfoFooter from '../../components/AssetInfoFooter/index';
import AssetInfoFooter from '@components/AssetInfoFooter/index';
import { createPermanentURI } from '@clientutils/createPermanentURI';
import ReactMarkdown from 'react-markdown';
@ -18,7 +18,20 @@ class AssetInfo extends React.Component {
render () {
const { editable, asset } = this.props;
const { claimViews, claimData } = asset;
const { channelName, claimId, channelShortId, description, name, fileExt, contentType, host, certificateId } = claimData;
const {
channelName,
claimId,
channelShortId,
description,
name,
fileExt,
contentType,
host,
certificateId,
license,
licenseUrl,
transactionTime
} = claimData;
const canonicalUrl = createCanonicalLink({ asset: { ...claimData, shortId: asset.shortId }});
const assetCanonicalUrl = `${host}${canonicalUrl}`;
@ -55,7 +68,7 @@ class AssetInfo extends React.Component {
{editable && (
<RowLabeled
label={<Label value={'Edit'} />}
content={<Link to={`/edit${canonicalUrl}`}>{name}</Link>}
content={<Link className='link--primary' to={`/edit${canonicalUrl}`}>{name}</Link>}
/>
)}
{channelName && (
@ -71,19 +84,35 @@ class AssetInfo extends React.Component {
}
/>
)}
{claimViews ? (
<RowLabeled
label={
<Label value={'Views'} />
}
content={
<span className='text'>
{claimViews}
</span>
}
/>
) : null}
<SpaceBetween>
{claimViews ? (
<RowLabeled
label={
<Label value={'Views'} />
}
content={
<span className='text'>
{claimViews}
</span>
}
/>
) : null}
{license && (
<RowLabeled
label={
<Label value={'License'} />
}
content={
<div className='text'>
{licenseUrl ? (
<a className={'link--primary'} href={licenseUrl} target={'_blank'}>{license}</a>
) : (
<span>{license}</span> )}
</div>
}
/>
)}
</SpaceBetween>
<RowLabeled
label={
<Label value={'Share'} />
@ -151,13 +180,13 @@ class AssetInfo extends React.Component {
<a
className={'link--primary'}
href={`${assetCanonicalUrl}.${fileExt}`}
download={name}
download={`${name}.${fileExt}`}
>
Download
</a>
<a
className={'link--primary'}
href={`https://open.lbry.io/${createPermanentURI(asset)}`}
href={`https://open.lbry.com/${createPermanentURI(asset)}`}
download={name}
>
LBRY URL
@ -165,7 +194,7 @@ class AssetInfo extends React.Component {
<a
className={'link--primary'}
target='_blank'
href='https://lbry.io/dmca'
href={`https://lbry.com/dmca/${claimId}`}
>
Report
</a>

View file

@ -33,14 +33,14 @@ class NavigationLinks extends React.Component {
const { channelName, showPublish, closedRegistration } = this.props;
return (
<div className='navigation-links'>
{showPublish && <NavLink
className='nav-bar-link link--nav'
activeClassName='link--nav-active'
to='/'
exact
>
Publish
</NavLink>}
{/*{showPublish && <NavLink*/}
{/* className='nav-bar-link link--nav'*/}
{/* activeClassName='link--nav-active'*/}
{/* to='/'*/}
{/* exact*/}
{/*>*/}
{/* Publish*/}
{/*</NavLink>}*/}
<NavLink
className='nav-bar-link link--nav'
activeClassName='link--nav-active'
@ -48,24 +48,24 @@ class NavigationLinks extends React.Component {
>
About
</NavLink>
{ channelName ? (
<NavBarChannelOptionsDropdown
channelName={this.props.channelName}
handleSelection={this.handleSelection}
defaultSelection={this.props.channelName}
VIEW={VIEW}
LOGOUT={LOGOUT}
/>
) : !closedRegistration && (
<NavLink
id='nav-bar-login-link'
className='nav-bar-link link--nav'
activeClassName='link--nav-active'
to='/login'
>
Channel
</NavLink>
)}
{/*{ channelName ? (*/}
{/* <NavBarChannelOptionsDropdown*/}
{/* channelName={this.props.channelName}*/}
{/* handleSelection={this.handleSelection}*/}
{/* defaultSelection={this.props.channelName}*/}
{/* VIEW={VIEW}*/}
{/* LOGOUT={LOGOUT}*/}
{/* />*/}
{/*) : !closedRegistration && (*/}
{/* <NavLink*/}
{/* id='nav-bar-login-link'*/}
{/* className='nav-bar-link link--nav'*/}
{/* activeClassName='link--nav-active'*/}
{/* to='/login'*/}
{/* >*/}
{/* Channel*/}
{/* </NavLink>*/}
{/*)}*/}
</div>
);
}

View file

@ -7,6 +7,9 @@ class PublishDisabledMessage extends React.Component {
<div className={'publish-disabled-message'}>
<div className={'message'}>
<p className={'text--secondary'}>Publishing is currently disabled.</p>
<p className={'text--secondary'}>
Try <a className='link--primary' href='https://lbry.tv' target='_blank'>lbry.tv</a>
</p>
<p className={'text--secondary'}>{message}</p>
</div>
</div>

View file

@ -1,14 +1,15 @@
import {connect} from 'react-redux';
import {updateMetadata, toggleMetadataInputs} from '../../actions/publish';
import { connect } from 'react-redux';
import { updateMetadata, toggleMetadataInputs } from '../../actions/publish';
import View from './view';
const mapStateToProps = ({ publish }) => {
return {
showMetadataInputs: publish.showMetadataInputs,
description : publish.metadata.description,
license : publish.metadata.license,
nsfw : publish.metadata.nsfw,
isUpdate : publish.isUpdate,
description: publish.metadata.description,
license: publish.metadata.license,
licenseUrl: publish.metadata.licenseUrl,
nsfw: publish.metadata.nsfw,
isUpdate: publish.isUpdate,
};
};
@ -17,10 +18,13 @@ const mapDispatchToProps = dispatch => {
onMetadataChange: (name, value) => {
dispatch(updateMetadata(name, value));
},
onToggleMetadataInputs: (value) => {
onToggleMetadataInputs: value => {
dispatch(toggleMetadataInputs(value));
},
};
};
export default connect(mapStateToProps, mapDispatchToProps)(View);
export default connect(
mapStateToProps,
mapDispatchToProps
)(View);

View file

@ -1,9 +1,9 @@
import React from 'react';
import PublishDescriptionInput from '@components/PublishDescriptionInput';
import PublishLicenseInput from '@components/PublishLicenseInput';
import PublishLicenseUrlInput from '@components/PublishLicenseUrlInput';
import PublishNsfwInput from '@components/PublishNsfwInput';
import ButtonSecondary from '@components/ButtonSecondary';
import Row from '@components/Row';
class PublishMetadataInputs extends React.Component {
constructor (props) {
@ -25,22 +25,32 @@ class PublishMetadataInputs extends React.Component {
const name = event.target.name;
const selectedOption = event.target.selectedOptions[0].value;
this.props.onMetadataChange(name, selectedOption);
if (name === 'license' && selectedOption !== 'Creative Commons'){
this.props.onMetadataChange('licenseUrl', '');
}
}
render () {
const { showMetadataInputs, description, isUpdate, nsfw } = this.props;
const { showMetadataInputs, description, isUpdate, nsfw, license, licenseUrl } = this.props;
return (
<div>
{(showMetadataInputs || isUpdate) && (
{(showMetadataInputs || isUpdate) && (
<React.Fragment>
<PublishDescriptionInput
description={this.props.description}
description={description}
handleInput={this.handleInput}
/>
<PublishLicenseInput
handleSelect={this.handleSelect}
license={license}
/>
{ (this.props.license === 'Creative Commons') && (
<PublishLicenseUrlInput
handleSelect={this.handleSelect}
licenseUrl={licenseUrl}
/>
)}
<PublishNsfwInput
nsfw={this.props.nsfw}
nsfw={nsfw}
handleInput={this.handleInput}
/>
</React.Fragment>

View file

@ -38,7 +38,7 @@ class PublishStatus extends React.Component {
<ProgressBar size={12} />
</Row>
<Row>
<p>Curious what magic is happening here? <a className='link--primary' target='blank' href='https://lbry.io/faq/what-is-lbry'>Learn more.</a></p>
<p>Curious what magic is happening here? <a className='link--primary' target='blank' href='https://lbry.com/faq/what-is-lbry'>Learn more.</a></p>
</Row>
</div>
}
@ -61,7 +61,7 @@ class PublishStatus extends React.Component {
<p className={'text--strong'}>{message}</p>
</Row>
<Row>
<p>For help, post the above error text in the #speech channel on the <a className='link--primary' href='https://chat.lbry.io' target='_blank'>lbry discord</a></p>
<p>For help, post the above error text in the #speech channel on the <a className='link--primary' href='https://chat.lbry.com' target='_blank'>lbry discord</a></p>
</Row>
<Row>
<ButtonSecondary

View file

@ -1,5 +1,5 @@
import {connect} from 'react-redux';
import {updateMetadata} from '../../actions/publish';
import { connect } from 'react-redux';
import { updateMetadata } from '../../actions/publish';
import View from './view';
const mapStateToProps = ({ publish }) => {
@ -16,4 +16,7 @@ const mapDispatchToProps = dispatch => {
};
};
export default connect(mapStateToProps, mapDispatchToProps)(View);
export default connect(
mapStateToProps,
mapDispatchToProps
)(View);

View file

@ -3,7 +3,7 @@ import ErrorPage from '@pages/ErrorPage';
import ShowAssetLite from '@pages/ShowAssetLite';
import ShowAssetDetails from '@pages/ShowAssetDetails';
import ShowChannel from '@pages/ShowChannel';
import { withRouter } from 'react-router-dom';
import { withRouter, Redirect } from 'react-router-dom';
import {
CHANNEL,
@ -15,15 +15,24 @@ import {
class ContentPageWrapper extends React.Component {
componentDidMount () {
const { onHandleShowPageUri, match, homeChannel } = this.props;
onHandleShowPageUri(homeChannel ? { claim: homeChannel } : match.params);
//onHandleShowPageUri(homeChannel ? { claim: homeChannel } : match.params);
}
componentWillReceiveProps (nextProps) {
if (nextProps.match.params !== this.props.match.params) {
this.props.onHandleShowPageUri(nextProps.match.params);
//this.props.onHandleShowPageUri(nextProps.match.params);
}
}
render () {
const { error, requestType } = this.props;
const { error, requestType, match } = this.props;
const { params } = match;
const { claim, identifier } = params;
if (identifier && claim) {
return <Redirect to={`https://lbry.tv/${identifier}/${claim}`} />;
} else if (identifier) {
// return <Redirect to={`https://lbry.tv/${identifier}/`} />
} else {
return <Redirect to={`https://lbry.tv/${claim}`} />;
}
if (error) {
return (
<ErrorPage error={error} />
@ -31,13 +40,13 @@ class ContentPageWrapper extends React.Component {
}
switch (requestType) {
case CHANNEL:
return <ShowChannel />;
// return <ShowChannel />;
case ASSET_LITE:
return <ShowAssetLite />;
// return <ShowAssetLite />;
case ASSET_DETAILS:
return <ShowAssetDetails />;
// return <ShowAssetDetails />;
case SPECIAL_ASSET:
return <ShowChannel />;
// return <ShowChannel />;
default:
return <p>loading...</p>;
}

View file

@ -9,7 +9,7 @@ class EditPage extends React.Component {
onHandleShowPageUri(match.params);
setUpdateTrue();
if (asset) {
['title', 'description', 'license', 'nsfw'].forEach(meta => updateMetadata(meta, asset.claimData[meta]));
['title', 'description', 'license', 'licenseUrl', 'nsfw'].forEach(meta => updateMetadata(meta, asset.claimData[meta]));
}
setHasChanged(false);
}

View file

@ -15,7 +15,7 @@ class FaqPage extends React.Component {
</Row>
<Row>
<h3>What is spee.ch?</h3>
<p>Spee.ch is a media-hosting site that reads from and publishes content to the <a href='http://lbry.io/'>LBRY blockchain</a>.</p>
<p>Spee.ch is a media-hosting site that reads from and publishes content to the <a href='http://lbry.com/'>LBRY blockchain</a>.</p>
</Row>
<Row>
<h3>OK But Why Should I Care?</h3>
@ -29,7 +29,7 @@ class FaqPage extends React.Component {
<p>Its easy. Drag the image or video file of your choice into the center of the spee.ch homepage.</p>
<p>Spee.ch is currently best suited for web optimized MP4 video and standard image filetypes (JPEG, PNG, GIF).</p>
<p>If you want to refer to a piece of content repeatedly, or to build a collection of related content, you could create a channel. Channels work both for private collections and for public repositories. Theres more info about how to do this <a href='https://spee.ch/login'>on the channel page</a>.</p>
<p>Published files will be wiewable and embeddable with any web browser and accesible in the LBRY app. You can also use spee.ch to view free and non-NSFW content published on LBRY network from LBRY app. You just need to replace "lbry://" with "http://spee.ch/" in the URL.</p>
<p>Published files will be viewable and embeddable with any web browser and accesible in the LBRY app. You can also use spee.ch to view free and non-NSFW content published on LBRY network from LBRY app. You just need to replace "lbry://" with "http://spee.ch/" in the URL.</p>
</Row>
<Row>
<h3>How Long Does Content Stay on Spee.ch?</h3>
@ -38,7 +38,7 @@ class FaqPage extends React.Component {
<Row>
<h3>Contribute</h3>
<p>If you have an idea for your own spee.ch-like site on top of LBRY, fork our <a href='https://github.com/lbryio/spee.ch'>github repo</a> and go to town!</p>
<p>If you want to improve spee.ch, join <a href='https://chat.lbry.io/'>our discord channel</a> or solve one of our <a href='https://github.com/lbryio/spee.ch/issues'>github issues</a>.</p>
<p>If you want to improve spee.ch, join <a href='https://chat.lbry.com/'>our discord channel</a> or solve one of our <a href='https://github.com/lbryio/spee.ch/issues'>github issues</a>.</p>
</Row>
</Row>
</PageLayout>

View file

@ -7,7 +7,7 @@ const MultisiteContent = () => {
<p className='text--pull-quote'>Introducing Spee.ch Multisite</p>
<p>Hi there! My name is <a href={'https://github.com/billbitt'} target={'_blank'}>Bill</a>, and Id like to speak with you about Spee.ch. No, not speech, <i><a href={'https://spee.ch'} target={'_blank'}>Spee.ch.</a></i> You know what, just read on...</p>
<h2>A Little Background</h2>
<p>Wow, time flies! A little over a year ago Spee.ch was nothing more than a glimmer in the eye of LBRY CEO Jeremy Kaufman. At that time, the <a href={'https://lbry.io/faq/what-is-lbry'} target={'_blank'}>LBRY protocol</a> was still so early in its development, that there were no web-based applications for interacting with the LBRY blockchain. But then, something beautiful happened. On March 29th, 2017, Jeremy sat down with Jack, and together they <a href={'https://www.youtube.com/watch?v=C9LCapt_OYw'} target={'_blank'}>live coded a single-page PHP site</a> that could publish images to the LBRY network. And just like that, Spee.ch was born!</p>
<p>Wow, time flies! A little over a year ago Spee.ch was nothing more than a glimmer in the eye of LBRY CEO Jeremy Kaufman. At that time, the <a href={'https://lbry.com/faq/what-is-lbry'} target={'_blank'}>LBRY protocol</a> was still so early in its development, that there were no web-based applications for interacting with the LBRY blockchain. But then, something beautiful happened. On March 29th, 2017, Jeremy sat down with Jack, and together they <a href={'https://www.youtube.com/watch?v=C9LCapt_OYw'} target={'_blank'}>live coded a single-page PHP site</a> that could publish images to the LBRY network. And just like that, Spee.ch was born!</p>
<p>Being that LBRY is an open source project, Jeremy ended the session by inviting community members who were interested in the project to take the reigns and see where Spee.ch could go. I was one of the devs that did just that, and it wasnt long before I was on a weekly call dedicated to this project with contributors from around the world.</p>
<p>At this point in time, the vision for Spee.ch was pretty simple: create a web-based hosting service that used the LBRY network as a database for free image and video sharing. In other words, an imgur on the blockchain.</p>
<h2>Growth</h2>
@ -41,7 +41,7 @@ const MultisiteContent = () => {
<li>Where: Google Hangouts</li>
<li>Link: <a href={'https://meet.google.com/aex-ghqg-kcs'} target={'_blank'}>meet.google.com/aex-ghqg-kcs</a></li>
<li>System Requirements: If you have a server, please make sure you have MySql, Node and NPM installed. If you need help installing the above, or if you need a server to run your own instance on, please join the Hangout 30 minutes ahead of time and we will help get you set up =]</li>
<li>Questions: hello@lbry.io</li>
<li>Questions: hello@lbry.com</li>
</ul>
</div>
);

View file

@ -1,11 +1,25 @@
import { connect } from 'react-redux';
import { selectAsset } from '../../selectors/show';
import { selectAsset, selectDetailsExpanded } from '../../selectors/show';
import { toggleDetailsExpanded } from '../../actions/show';
import View from './view';
const mapStateToProps = ({ show }) => {
const mapStateToProps = state => {
return {
asset: selectAsset(show),
asset: selectAsset(state.show),
detailsExpanded: selectDetailsExpanded(state),
};
};
export default connect(mapStateToProps, null)(View);
const mapDispatchToProps = dispatch => {
return {
onToggleDetailsExpanded: value => {
dispatch(toggleDetailsExpanded(value));
},
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(View);

View file

@ -11,24 +11,15 @@ class ShowAssetDetails extends React.Component {
constructor (props) {
super(props);
this.collapse = this.collapse.bind(this);
// this.storageKey = 'vert-split-state-' + this.props.name;
// const closed = window && window.localStorage
// ? !!window.localStorage.getItem(this.storageKey) : false;
const closed = true;
this.state = { closed: closed };
this.toggleExpandDetails = this.toggleExpandDetails.bind(this);
}
collapse () {
this.setState({ closed: !this.state.closed });
// if (window && window.localStorage) {
// window.localStorage.setItem(this.storageKey, !this.state.closed);
// }
// document.querySelectorAll(`[data-name='${this.props.name}']`).forEach(el => el.classList.toggle('closed'));
toggleExpandDetails () {
this.props.onToggleDetailsExpanded(!this.props.detailsExpanded);
}
render () {
const { asset } = this.props;
const { asset, detailsExpanded } = this.props;
if (asset) {
const { claimData: { name, blocked } } = asset;
if (!blocked) {
@ -37,16 +28,16 @@ class ShowAssetDetails extends React.Component {
pageTitle={`${name} - details`}
asset={asset}
>
<div className="asset-main">
<div className='asset-main'>
<AssetTitle />
<AssetDisplay />
<div>
<button className='collapse-button' onClick={this.collapse}>
{this.state.closed ? <Icon.PlusCircle className='plus-icon' /> : <Icon.MinusCircle />}
<button className='collapse-button' onClick={this.toggleExpandDetails}>
{detailsExpanded ? <Icon.MinusCircle /> : <Icon.PlusCircle className='plus-icon' /> }
</button>
</div>
</div>
{!this.state.closed && <AssetInfo />}
{detailsExpanded && <AssetInfo />}
</PageLayout>
);

View file

@ -9,7 +9,7 @@ const AssetLiteFooter = ({ name, claimId }) => {
return (
<SpaceAround>
<p className={'text--extra-small'}>
<Link className='link--primary' to={`/${claimId}/${name}`}> hosted on spee.ch</Link> via the <a className='link--primary' href={'https://lbry.io/get'} target={'_blank'}>LBRY</a> blockchain
<Link className='link--primary' to={`/${claimId}/${name}`}> hosted on spee.ch</Link> via the <a className='link--primary' href={'https://lbry.com/get'} target={'_blank'}>LBRY</a> blockchain
</p>
</SpaceAround>
);

View file

@ -13,15 +13,15 @@ class TosPage extends React.Component {
<Row>
<h1>Terms of Service</h1>
<p>Last updated: September 25, 2018</p>
<p>Please read these Terms of Service ("Terms", "Terms of Service") carefully before using the <a className='link--primary' href='https://spee.ch'>https://spee.ch</a> website (the "Service") operated by <a className='link--primary' href='https://lbry.io'>LBRY INC</a> ("us", "we", or "our").</p>
<p>Please read these Terms of Service ("Terms", "Terms of Service") carefully before using the <a className='link--primary' href='https://spee.ch'>https://spee.ch</a> website (the "Service") operated by <a className='link--primary' href='https://lbry.com'>LBRY INC</a> ("us", "we", or "our").</p>
<p>Your access to and use of the Service is conditioned upon your acceptance of and compliance with these Terms. These Terms apply to all visitors, users and others who wish to access or use the Service.</p>
<p>By accessing or using the Service you agree to be bound by these Terms. If you disagree with any part of the terms then you do not have permission to access the Service.</p>
</Row>
<Row>
<h3>Links To Other Web Sites</h3>
<p>Our Service may contain links to third party web sites or services that are not owned or controlled by <a className='link--primary' href='https://lbry.io'>LBRY INC</a></p>
<p><a className='link--primary' href='https://lbry.io'>LBRY INC</a> has no control over, and assumes no responsibility for the content, privacy policies, or practices of any third party web sites or services. We do not warrant the offerings of any of these entities/individuals or their websites.</p>
<p>You acknowledge and agree that <a className='link--primary' href='https://lbry.io'>LBRY INC</a> shall not be responsible or liable, directly or indirectly, for any damage or loss caused or alleged to be caused by or in connection with use of or reliance on any such content, goods or services available on or through any such third party web sites or services.</p>
<p>Our Service may contain links to third party web sites or services that are not owned or controlled by <a className='link--primary' href='https://lbry.com'>LBRY INC</a></p>
<p><a className='link--primary' href='https://lbry.com'>LBRY INC</a> has no control over, and assumes no responsibility for the content, privacy policies, or practices of any third party web sites or services. We do not warrant the offerings of any of these entities/individuals or their websites.</p>
<p>You acknowledge and agree that <a className='link--primary' href='https://lbry.com'>LBRY INC</a> shall not be responsible or liable, directly or indirectly, for any damage or loss caused or alleged to be caused by or in connection with use of or reliance on any such content, goods or services available on or through any such third party web sites or services.</p>
<p>We strongly advise you to read the terms and conditions and privacy policies of any third party web sites or services that you visit.</p>
</Row>
<Row>
@ -35,12 +35,12 @@ class TosPage extends React.Component {
</Row>
<Row>
<h3>Limitation Of Liability</h3>
<p>In no event shall <a className='link--primary' href='https://lbry.io'>LBRY INC</a>, nor its directors, employees, partners, agents, suppliers, or affiliates, be liable for any indirect, incidental, special, consequential or punitive damages, including without limitation, loss of profits, data, use, goodwill, or other intangible losses, resulting from (i) your access to or use of or inability to access or use the Service; (ii) any conduct or content of any third party on the Service; (iii) any content obtained from the Service; and (iv) unauthorized access, use or alteration of your transmissions or content, whether based on warranty, contract, tort (including negligence) or any other legal theory, whether or not we have been informed of the possibility of such damage, and even if a remedy set forth herein is found to have failed of its essential purpose.</p>
<p>In no event shall <a className='link--primary' href='https://lbry.com'>LBRY INC</a>, nor its directors, employees, partners, agents, suppliers, or affiliates, be liable for any indirect, incidental, special, consequential or punitive damages, including without limitation, loss of profits, data, use, goodwill, or other intangible losses, resulting from (i) your access to or use of or inability to access or use the Service; (ii) any conduct or content of any third party on the Service; (iii) any content obtained from the Service; and (iv) unauthorized access, use or alteration of your transmissions or content, whether based on warranty, contract, tort (including negligence) or any other legal theory, whether or not we have been informed of the possibility of such damage, and even if a remedy set forth herein is found to have failed of its essential purpose.</p>
</Row>
<Row>
<h3>Disclaimer</h3>
<p>Your use of the Service is at your sole risk. The Service is provided on an "AS IS" and "AS AVAILABLE" basis. The Service is provided without warranties of any kind, whether express or implied, including, but not limited to, implied warranties of merchantability, fitness for a particular purpose, non-infringement or course of performance.</p>
<p><a className='link--primary' href='https://lbry.io'>LBRY INC</a> its subsidiaries, affiliates, and its licensors do not warrant that a) the Service will function uninterrupted, secure or available at any particular time or location; b) any errors or defects will be corrected; c) the Service is free of viruses or other harmful components; or d) the results of using the Service will meet your requirements.</p>
<p><a className='link--primary' href='https://lbry.com'>LBRY INC</a> its subsidiaries, affiliates, and its licensors do not warrant that a) the Service will function uninterrupted, secure or available at any particular time or location; b) any errors or defects will be corrected; c) the Service is free of viruses or other harmful components; or d) the results of using the Service will meet your requirements.</p>
</Row>
<Row>
<h3>Exclusions</h3>
@ -58,7 +58,7 @@ class TosPage extends React.Component {
</Row>
<Row>
<h3>Contact Us</h3>
<p>If you have any questions about these Terms, please <a className='link--primary' href='mailto:hello@lbry.io'>contact us</a>.</p>
<p>If you have any questions about these Terms, please <a className='link--primary' href='mailto:hello@lbry.com'>contact us</a>.</p>
</Row>
</Row>
</PageLayout>

View file

@ -19,40 +19,42 @@ if (siteConfig) {
// create initial state
const initialState = {
disabled : disabledConfig,
disabledMessage : disabledMessageConfig,
publishInChannel : false,
selectedChannel : LOGIN,
disabled: disabledConfig,
disabledMessage: disabledMessageConfig,
publishInChannel: false,
selectedChannel: LOGIN,
showMetadataInputs: false,
status : {
status : null,
status: {
status: null,
message: null,
},
error: {
file : null,
url : null,
file: null,
url: null,
channel: null,
},
file : null,
claim : '',
file: null,
claim: '',
metadata: {
title : '',
title: '',
description: '',
license : '',
nsfw : false,
license: '',
licenseUrl: '',
tags: [],
},
isUpdate : false,
isUpdate: false,
hasChanged: false,
thumbnail : null,
thumbnail: null,
thumbnailChannel,
thumbnailChannelId,
};
export default function (state = initialState, action) {
export default function(state = initialState, action) {
switch (action.type) {
case actions.FILE_SELECTED:
return Object.assign({}, state.isUpdate ? state : initialState, { // note: clears to initial state
file : action.data,
return Object.assign({}, state.isUpdate ? state : initialState, {
// note: clears to initial state
file: action.data,
hasChanged: true,
});
case actions.FILE_CLEAR:
@ -66,13 +68,13 @@ export default function (state = initialState, action) {
});
case actions.CLAIM_UPDATE:
return Object.assign({}, state, {
claim : action.data,
claim: action.data,
hasChanged: true,
});
case actions.SET_PUBLISH_IN_CHANNEL:
return Object.assign({}, state, {
publishInChannel: action.channel,
hasChanged : true,
hasChanged: true,
});
case actions.PUBLISH_STATUS_UPDATE:
return Object.assign({}, state, {
@ -96,7 +98,7 @@ export default function (state = initialState, action) {
case actions.THUMBNAIL_NEW:
return {
...state,
thumbnail : action.data,
thumbnail: action.data,
hasChanged: true,
};
case actions.SET_UPDATE_TRUE:
@ -112,4 +114,4 @@ export default function (state = initialState, action) {
default:
return state;
}
};
}

View file

@ -4,19 +4,20 @@ import { LOCAL_CHECK, ERROR } from '../constants/asset_display_states';
const initialState = {
request: {
error: null,
type : null,
id : null,
type: null,
id: null,
},
requestList : {},
channelList : {},
assetList : {},
requestList: {},
channelList: {},
assetList: {},
displayAsset: {
error : null,
error: null,
status: LOCAL_CHECK,
},
detailsExpanded: true,
};
export default function (state = initialState, action) {
export default function(state = initialState, action) {
switch (action.type) {
// handle request
case actions.REQUEST_ERROR:
@ -29,7 +30,7 @@ export default function (state = initialState, action) {
return Object.assign({}, state, {
request: Object.assign({}, state.request, {
type: action.data.requestType,
id : action.data.requestId,
id: action.data.requestId,
}),
});
// store requests
@ -38,7 +39,7 @@ export default function (state = initialState, action) {
requestList: Object.assign({}, state.requestList, {
[action.data.id]: {
error: action.data.error,
key : action.data.key,
key: action.data.key,
},
}),
});
@ -47,11 +48,11 @@ export default function (state = initialState, action) {
return Object.assign({}, state, {
assetList: Object.assign({}, state.assetList, {
[action.data.id]: {
error : action.data.error,
name : action.data.name,
claimId : action.data.claimId,
shortId : action.data.shortId,
claimData : action.data.claimData,
error: action.data.error,
name: action.data.name,
claimId: action.data.claimId,
shortId: action.data.shortId,
claimData: action.data.claimData,
claimViews: action.data.claimViews,
},
}),
@ -76,7 +77,7 @@ export default function (state = initialState, action) {
return {
...state,
assetList : newAssetList,
assetList: newAssetList,
channelList: {
...state.channelList,
[channelId]: {
@ -107,9 +108,9 @@ export default function (state = initialState, action) {
return Object.assign({}, state, {
channelList: Object.assign({}, state.channelList, {
[action.data.id]: {
name : action.data.name,
longId : action.data.longId,
shortId : action.data.shortId,
name: action.data.name,
longId: action.data.longId,
shortId: action.data.shortId,
claimsData: action.data.claimsData,
},
}),
@ -117,9 +118,13 @@ export default function (state = initialState, action) {
case actions.CHANNEL_CLAIMS_UPDATE_SUCCEEDED:
return Object.assign({}, state, {
channelList: Object.assign({}, state.channelList, {
[action.data.channelListId]: Object.assign({}, state.channelList[action.data.channelListId], {
claimsData: action.data.claimsData,
}),
[action.data.channelListId]: Object.assign(
{},
state.channelList[action.data.channelListId],
{
claimsData: action.data.claimsData,
}
),
}),
});
// display an asset
@ -132,10 +137,15 @@ export default function (state = initialState, action) {
case actions.DISPLAY_ASSET_ERROR:
return Object.assign({}, state, {
displayAsset: Object.assign({}, state.displayAsset, {
error : action.data,
error: action.data,
status: ERROR,
}),
});
case actions.TOGGLE_DETAILS_EXPANDED:
return {
...state,
detailsExpanded: action.data,
};
default:
return state;
}

View file

@ -5,17 +5,19 @@ import { updatePublishStatus, clearFile } from '../actions/publish';
import { removeAsset } from '../actions/show';
import { doAbandonClaim } from '../api/assetApi';
function * abandonClaim (action) {
function* abandonClaim(action) {
const { claimData, history } = action.data;
const { claimId } = claimData;
const { outpoint } = claimData;
const confirm = window.confirm('Are you sure you want to abandon this claim? This action cannot be undone.');
const confirm = window.confirm(
'Are you sure you want to abandon this claim? This action cannot be undone.'
);
if (!confirm) return;
yield put(updatePublishStatus(publishStates.ABANDONING, 'Your claim is being abandoned...'));
try {
yield call(doAbandonClaim, claimId);
yield call(doAbandonClaim, outpoint);
} catch (error) {
return console.log('abandon error:', error.message);
}
@ -25,6 +27,6 @@ function * abandonClaim (action) {
return history.push('/');
}
export function * watchAbandonClaim () {
export function* watchAbandonClaim() {
yield takeLatest(actions.ABANDON_CLAIM, abandonClaim);
};
}

View file

@ -9,11 +9,22 @@ import { selectShowState, selectAsset } from '../selectors/show';
import { validateChannelSelection, validateNoPublishErrors } from '../utils/validate';
import { createPublishMetadata, createPublishFormData, createThumbnailUrl } from '../utils/publish';
import { makePublishRequestChannel } from '../channels/publish';
function * publishFile (action) {
// yep
function* publishFile(action) {
const { history } = action.data;
const publishState = yield select(selectPublishState);
const { publishInChannel, selectedChannel, file, claim, metadata, thumbnailChannel, thumbnailChannelId, thumbnail, isUpdate, error: publishToolErrors } = publishState;
const {
publishInChannel,
selectedChannel,
file,
claim,
metadata,
thumbnailChannel,
thumbnailChannelId,
thumbnail,
isUpdate,
error: publishToolErrors,
} = publishState;
const { loggedInChannel } = yield select(selectChannelState);
const { host } = yield select(selectSiteState);
@ -39,7 +50,7 @@ function * publishFile (action) {
// create metadata
publishMetadata = createPublishMetadata(
isUpdate ? asset.name : claim,
isUpdate ? {type: asset.claimData.contentType} : file,
isUpdate ? { type: asset.claimData.contentType } : file,
metadata,
publishInChannel,
selectedChannel
@ -49,7 +60,12 @@ function * publishFile (action) {
}
if (thumbnail) {
// add thumbnail to publish metadata
publishMetadata['thumbnail'] = createThumbnailUrl(thumbnailChannel, thumbnailChannelId, claim, host);
publishMetadata['thumbnail'] = createThumbnailUrl(
thumbnailChannel,
thumbnailChannelId,
claim,
host
);
}
// create form data for main publish
publishFormData = createPublishFormData(file, thumbnail, publishMetadata);
@ -57,7 +73,7 @@ function * publishFile (action) {
publishChannel = yield call(makePublishRequestChannel, publishFormData, isUpdate);
while (true) {
const {loadStart, progress, load, success, error: publishError} = yield take(publishChannel);
const { loadStart, progress, load, success, error: publishError } = yield take(publishChannel);
if (publishError) {
return yield put(updatePublishStatus(publishStates.FAILED, publishError.message));
}
@ -67,7 +83,7 @@ function * publishFile (action) {
yield put({
type: 'ASSET_UPDATE_CLAIMDATA',
data: {
id : `a#${success.data.name}#${success.data.claimId}`,
id: `a#${success.data.name}#${success.data.claimId}`,
claimData: success.data.claimData,
},
});
@ -91,6 +107,6 @@ function * publishFile (action) {
}
}
export function * watchPublishStart () {
export function* watchPublishStart() {
yield takeLatest(actions.PUBLISH_START, publishFile);
};
}

View file

@ -4,12 +4,16 @@ export const selectAsset = show => {
const request = show.requestList[requestId] || null;
const assetList = show.assetList;
if (request && assetList) {
const assetKey = request.key; // note: just store this in the request
const assetKey = request.key; // note: just store this in the request
asset = assetList[assetKey] || null;
}
return asset;
};
export const selectShowState = (state) => {
export const selectShowState = state => {
return state.show;
};
export const selectDetailsExpanded = state => {
return state.show.detailsExpanded;
};

View file

@ -1,9 +1,17 @@
export const createPublishMetadata = (claim, { type }, { title, description, license, nsfw }, publishInChannel, selectedChannel) => {
export const createPublishMetadata = (
claim,
{ type },
{ title, description, license, licenseUrl, nsfw },
publishInChannel,
selectedChannel
) => {
// this metadata stuff needs to be removed...
let metadata = {
name: claim,
title,
description,
license,
licenseUrl,
nsfw,
type,
};

View file

@ -1,6 +1,6 @@
# Configure your own spee.ch
_note: this guide assumes you have done the [quickstart](https://github.com/lbryio/spee.ch/blob/readme-update/README.md) or [fullstart](https://github.com/lbryio/spee.ch/blob/readme-update/fullstart.md) guide and have a working spee.ch server_
_note: this guide assumes you have done the [quickstart](https://github.com/lbryio/spee.ch/blob/master/README.md) or [fullstart](https://github.com/lbryio/spee.ch/blob/master/fullstart.md) guide and have a working spee.ch server_
## Custom Components
The components used by spee.ch are taken from the `client/` folder, but you can override those components by defining your own in the `site/custom/` folder.

View file

@ -26,7 +26,6 @@ PUBLISHING:
"primaryClaimAddress": null, - generally supplied by your lbrynet sdk
"uploadDirectory": "/home/lbry/Uploads", - lbrynet sdk will know your uploads are here
"lbrynetHome": "/home/lbry",
"thumbnailChannel": null, - when publishing non-image content, thumbnails will go here.
"thumbnailChannelId": null,
"additionalClaimAddresses": [],
@ -39,12 +38,24 @@ PUBLISHING:
"publishingChannelWhitelist": [],
"channelClaimBidAmount": "0.1", - When creating a channel, how much you deposit to control the name
"fileClaimBidAmount": "0.01", - When publishing content, how much you deposit to control the name
"maxSizeImage": 10000000, - You may not want people uploading 50GB files. 1000000 = 1MB
"maxSizeGif": 50000000,
"maxSizeVideo": 50000000
"fileSizeLimits": {
"image": 50000000,
"video": 50000000,
"audio": 50000000,
"text": 10000000,
"model": 50000000,
"application": 500000000,
"customByContentType": {
"application/octet-stream": 50000000
}
}
SERVING:
"dynamicFileSizing": {
"enabled": false, - if you choose to allow your instance to serve transform images
"maxDimension": 2000 - the maximum size you allow transform to scale
},
"markdownSettings": {
"skipHtmlMain": true, - false: render html, in a somewhat unpredictable way~
"escapeHtmlMain": true, - true: rather than render html, escape it and print it visibly
@ -80,9 +91,6 @@ SERVING:
"html", - potentially DANGEROUS, intended for `serveOnlyApproved = true` environments, includes iframes, divs.
"parsedHtml"
],
"disallowedTypesMain": [], - not implemented
"disallowedTypesDescriptions": ["image", "html"], - not implemented
"disallowedTypesExample": ["image", "html"] - not implemented
},
"customFileExtensions": { - suggest a file extension for experimental content types you may be publishing
"application/example-type": "example"

View file

@ -0,0 +1,15 @@
[Unit]
Description="LBRYnet daemon"
After=network.target
[Service]
Environment="HOME=/home/lbry"
ExecStart=/opt/lbry/lbrynet start
User=lbry
Group=lbry
Restart=on-failure
KillMode=process
[Install]
WantedBy=multi-user.target

View file

@ -0,0 +1,16 @@
[Unit]
Description="LBRYnet daemon"
After=network.target
# Change environment to /home/{{USERNAME}}
[Service]
Environment="HOME=/home/{{USERNAME}}"
ExecStart=/opt/lbry/lbrynet start
User={{USERNAME}}
Group={{USERNAME}}
Restart=on-failure
KillMode=process
[Install]
WantedBy=multi-user.target

View file

@ -24,6 +24,8 @@
* Lbrynet DAEMON started on ports 3333 and 4444
* Spee.ch started on port 3000
_note: throughout this guide you'll be replacing `{{xyz}}` with `yourvalue` omitting the brackets_
# 1. Setup OS and install dependencies
## OS
@ -36,26 +38,27 @@ As root# _create user and add to sudo group_
su - username
```
As username: *paste public key in authorized\_keys*
```
`cd`
`mkdir .ssh`
`nano ~/.ssh/authorized_keys`
```
### Prep
Log in as username@domainname or username@ip_address
ssh to username@domainname or username@ip_address
`sudo apt-get update -y`
`ulimit -n 8192`
`wget -qO- https://deb.nodesource.com/setup_8.x | sudo -E bash -`
```
sudo apt-get update -y
ulimit -n 8192
wget -qO- https://deb.nodesource.com/setup_8.x | sudo -E bash -
```
## Git, Curl, Tmux, Unzip, ffmpeg, Node
## Git, Curl, Unzip, ffmpeg, imagemagick, Node
`sudo apt-get install git curl tmux unzip ffmpeg nodejs -y`
`sudo apt-get install git curl unzip ffmpeg nodejs imagemagick -y`
## Clone speech either from your own fork, or from the lbryio/spee.ch repo.
@ -67,7 +70,7 @@ Log in as username@domainname or username@ip_address
`git clone https://github.com/{{youraccount}}/spee.ch.git`
`git clone git@github.com:{{youraccount}}/spee.ch`
`git clone git@github.com:{{youraccount}}/spee.ch`
* For Publishers and Content creators - stable release
@ -78,24 +81,29 @@ Log in as username@domainname or username@ip_address
`chmod 750 -R ~/spee.ch/docs/setup`
# 2 Secure the UFW firewall
## UFW
`sudo ~/spee.ch/docs/setup/scripts/firewall.sh`
_if your distro isn't vanilla ubuntu 16 or 18, you may have to install it_
# 3 Install Caddy to handle https and reverse proxy
## Get Caddy
`curl https://getcaddy.com | sudo bash -s personal`
## Set up Caddy reverse proxy and ssl
`sudo mkdir -p /opt/caddy/logs/`
_Make Caddy's folders, copy the template, edit the Caddyfile, copy the caddyfile to its folder._
`sudo mkdir -p /opt/caddy/store/`
`cp ~/spee.ch/docs/setup/conf/caddy/Caddyfile.template ~/spee.ch/docs/setup/conf/caddy/Caddyfile`
`nano ~/spee.ch/docs/setup/conf/caddy/Caddyfile`
```
sudo mkdir -p /opt/caddy/logs/
sudo mkdir -p /opt/caddy/store/
cp ~/spee.ch/docs/setup/conf/caddy/Caddyfile.template ~/spee.ch/docs/setup/conf/caddy/Caddyfile
nano ~/spee.ch/docs/setup/conf/caddy/Caddyfile
```
( Change {{EXAMPLE.COM}} to YOURDOMAIN.COM )
@ -103,19 +111,15 @@ Log in as username@domainname or username@ip_address
## Set up Caddy to run as systemd service
`sudo cp ~/spee.ch/docs/setup/conf/caddy/caddy.service /etc/systemd/system/caddy.service`
`sudo chmod 644 /etc/systemd/system/caddy.service`
`sudo chown -R www-data:www-data /opt/caddy/`
`sudo setcap 'cap_net_bind_service=+ep' /usr/local/bin/caddy`
`sudo systemctl daemon-reload`
`sudo systemctl start caddy`
`sudo systemctl status caddy`
```
sudo cp ~/spee.ch/docs/setup/conf/caddy/caddy.service /etc/systemd/system/caddy.service
sudo chmod 644 /etc/systemd/system/caddy.service
sudo chown -R www-data:www-data /opt/caddy/
sudo setcap 'cap_net_bind_service=+ep' /usr/local/bin/caddy
sudo systemctl daemon-reload
sudo systemctl start caddy
sudo systemctl status caddy
```
`q` exits
@ -151,7 +155,7 @@ Log in as username@domainname or username@ip_address
mysql>
`ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'your_mysql_password';`
`ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'yourpassword123';`
mysql>
@ -163,57 +167,110 @@ Log in as username@domainname or username@ip_address
`mysql -u root -p` and then entering your_mysql_password should give you the mysql> shell
# 5 Get Lbrynet Daemon
# 5 Get Lbrynet SDK Daemon
## Start tmux
## Get the SDK
tmux allows you to run multiple things in different sessions. Useful for manually starting daemons and watching its console logs.
`tmux`
* `Ctrl+b`, then `d` detaches leaving session running.
* `tmux`, reenters tmux, then
* `Ctrl+b`, `(` goes back to through sessions
## Get the daemon
`wget -O ~/latest_daemon.zip https://lbry.io/get/lbrynet.linux.zip`
`unzip -o -u ~/latest_daemon.zip`
## Start the daemon
`./lbrynet start`
## Detatch tmux session
`Control + b`, then `d`
* `tmux` if you want to get back into tmux
* `Control+b`, then `)` while in tmux session to cycle back to your lbrynet session to see output
We'll be putting it in /opt/lbry.
`tmux`
`sudo mkdir /opt/lbry`
`sudo wget -O /opt/lbry/latest_daemon.zip https://lbry.io/get/lbrynet.linux.zip`
`sudo unzip -o -u /opt/lbry/latest_daemon.zip -d /opt/lbry`
_note: `Control+b`, then `)` while in tmux session to cycle back to your lbrynet session to see output_
## Set up lbrynet to run as systemd service
We'll soon update the setup scripts. Meanwhile, here's an example lbrynet.service file. Again, fully replace {{USERNAME}}
```
[Unit]
Description="LBRYnet daemon"
After=network.target
# Replace {{USERNAME}} with your username, e.g. `bob` or `speechuser`
[Service]
Environment="HOME=/home/{{USERNAME}}"
ExecStart=/opt/lbry/lbrynet start
User={{USERNAME}}
Group={{USERNAME}}
Restart=on-failure
KillMode=process
[Install]
WantedBy=multi-user.target
```
`sudo nano /etc/systemd/system/lbrynet.service`
Then paste the above into the file and edit replacing {{USERNAME}} with yours.
Finally do the following.
```
sudo chmod 644 /etc/systemd/system/lbrynet.service
sudo systemctl daemon-reload
sudo systemctl start lbrynet
sudo systemctl status lbrynet
```
You'll find your lbrynet logs in ~/.local/share/lbry/lbrynet/lbrynet.log
Let's make our lives easier and link /opt/lbry/lbrynet in /usr/local/bin
`sudo ln -s /opt/lbry/lbrynet /usr/local/bin/lbrynet`
Now we can `lbrynet` without `/opt/lbry`. Let's make sure we're back in our home directory. `cd`
## Customize SDK settings
These settings will prevent you and your users from spending your server's LBC on paid content. Full documentation is [here](https://lbry.tech/resources/daemon-settings).
~$
`cd ~/.local/share/lbry/lbrynet`
`nano daemon_settings.yml`
copy and paste in the following code (Ctrl+Shift V)
_upnp is unnecessary for a vps but may be useful behind a properly configured NAT_
```
run_reflector_server: false
max_key_fee: {amount: 0, currency: LBC}
use_upnp: false
auto_re_reflect_interval: 0
```
`CONTROL+O` then `CONTROL+X` to save and exit
Restart lbrynet sdk:
`sudo systemctl restart lbrynet`
`sudo systemctl status lbrynet`
## Display wallet address to which to send 5+ LBC.
_note: These commands work when `./lbrynet start` is already running in another tmux session_
_note: These commands work when `lbrynet` is already running_
`./lbrynet commands` to check out the current commands
`lbrynet commands` to check out the current commands
`./lbrynet address_list` to get your wallet address
`lbrynet address list` to get your wallet address
`Ctrl + Shift + C` after highlighting an address to copy.
Use a LBRY app or daemon to send LBC to the address. Sending LBC may take a few seconds or longer.
`./lbrynet account_balance` to check your balance after you've sent LBC.
## Optional/Production: Set up lbrynet to run as a systemd service
`//coming soon`
`lbrynet account balance` to check your balance after you've sent LBC.
# 6 Set up spee.ch
## Build it
`cd spee.ch`
~/spee.ch:
@ -234,7 +291,8 @@ tmux allows you to run multiple things in different sessions. Useful for manuall
* Port: 3000
* Site Title: Your Site Name
* Enter your site's domain name: https://example.com or http://localhost:3000
* Enter a directory where uploads should be stored: (/home/lbry/Uploads)
* Enter a directory where uploads should be stored: (/home/{{username}}/Uploads) *
_* if you're not sure, `pwd`_
`npm run build` (or `npm run dev` to build for developing)
@ -247,15 +305,34 @@ tmux allows you to run multiple things in different sessions. Useful for manuall
# 7 Production
## pm2 to keep your speech app running
```
npm install -g pm2
```
If your server is running in the terminal from the last section, `Control+C` it.
`sudo npm install -g pm2` _There are tutorials online for avoiding sudo for npm i -g_
`cd spee.ch`
`pm2 start npm --name speech -- run start`
While pm2 installed this way will restart the server, it will not rebuild it on changes. You'll do that manually as discussed before.
### 7 Maintenance Procedures
#### Change daemon
* backup wallet (private keys!) to a safe place
* wget daemon from https://github.com/lbryio/lbry/releases
* wget -O ~/your_name_daemon.zip https://your_copied_file_path.zip
* rm ./lbrynet
* unzip -o -u ~/your_name_daemon.zip
#### Update sdk daemon
* Backup wallet (private keys!) to a safe place. It should be in ~/.local/share/lbry/lbryum/wallets.
* `lbrynet stop`
* Following the instructions in 5: Get the SDK will rename the old daemon and give you the new one.
* `lbrynet start`
* `lbrynet version`
* `lbrynet account balance`
#### Update speech
* Read the release notes to see if there are any breaking changes, address them
* `pm2 stop speech`
* `git pull origin release` (assuming you cloned release)
* `npm i` if necessary
* `npm run build`
* `pm2 start speech`
* Have an exotic energy drink

View file

@ -1,74 +1,82 @@
# Create Your Own Spee.ch!
## 1. Prerequisites
### You will need the following tools installed
* Node (v8 LTS).
* Make sure you install from the **Node** website [link](https://nodejs.org/en/download/).
* npm (should come installed with Node).
* Git
* Curl
* Tmux
* Unzip
- Node (v8 LTS).
- Make sure you install from the **Node** website [link](https://nodejs.org/en/download/).
- npm (should come installed with Node).
- Git
- Curl
- Tmux
- Unzip
### Make sure **npm** is up-to-date.
```
$ npm update
```
### Setup a Webserver to serve **Spee.ch** from Port **3000**.
* If you are using a server provided by **lbry**, we will have **caddy** installed already.
* If you are using your own server, make sure to have a web server installed and set up to serve from port **3000**.
* Nginx instructions (recommended).
* Insert directions for certbot before installing.
* Install [Nginx](http://nginx.org/en/docs/install.html).
* Create a config file called `spee.ch` in */etc/nginx/sites-available*
* see example: [config file](https://github.com/lbryio/spee.ch/blob/master/nginx_example_config).
* Rename all mentions of *sub.domain.com* with your subdomain name.
* Run this command to link the sites-available.
- If you are using a server provided by **lbry**, we will have **caddy** installed already.
- If you are using your own server, make sure to have a web server installed and set up to serve from port **3000**.
- Nginx instructions (recommended).
- Insert directions for certbot before installing.
- Install [Nginx](http://nginx.org/en/docs/install.html).
- Create a config file called `spee.ch` in _/etc/nginx/sites-available_
- see example: [config file](https://github.com/lbryio/spee.ch/blob/master/nginx_example_config).
- Rename all mentions of _sub.domain.com_ with your subdomain name.
- Run this command to link the sites-available.
`$ ln -s /etc/nginx/sites-available/speech /etc/nginx/sites-enabled/speech`
* Restart Nginx.
- Restart Nginx.
`$ sudo service nginx restart`
* Try visiting your website.
* If Nginx is working, you should get a **502** error because there is nothing running on **3000** yet.
* If you get the default Nginx greeting, you have not properly configured it to serve from port **3000**.
* You can find logs in */var/log/nginx/* too.
* Caddy tutorial: [https://caddyserver.com/tutorial](https://caddyserver.com/tutorial)
- Try visiting your website.
- If Nginx is working, you should get a **502** error because there is nothing running on **3000** yet.
- If you get the default Nginx greeting, you have not properly configured it to serve from port **3000**.
- You can find logs in _/var/log/nginx/_ too.
- Caddy tutorial: [https://caddyserver.com/tutorial](https://caddyserver.com/tutorial)
### MySql
* Install MySql
* [Instructions](https://dev.mysql.com/doc/mysql-installation-excerpt/5.7/en)
* Create user **root**.
* Note: We are going to access **mysql** as **root** for this setup, but you may want to create a separate user in the future.
* Keep your password somewhere handy!
* Create a database called **lbry** and make sure you can use it.
- Install MySql
- [Instructions](https://dev.mysql.com/doc/mysql-installation-excerpt/5.7/en)
- Create user **root**. \* Note: We are going to access **mysql** as **root** for this setup, but you may want to create a separate user in the future.
- Keep your password somewhere handy!
- Create a database called **lbry** and make sure you can use it.
`CREATE DATABASE lbry;`
`CREATE DATABASE lbry;`
`$ USE lbry;`
`$ USE lbry;`
`$ exit; (or press ctl + d)`
`$ exit; (or press ctl + d)`
* Try logging into mysql.
- Try logging into mysql.
`$ mysql -u username -p`
`$ mysql -u username -p`
* If you are using a **LBRY** server, your **password** is the one provided for **ssh**.
* Note: If it fails, try using `sudo`.
- If you are using a **LBRY** server, your **password** is the one provided for **ssh**.
- Note: If it fails, try using `sudo`.
##2. Install & Run the LBRY Daemon
##2. Install & Run the LBRY Daemon
### Install **lbrynet**
_note: if you have a server from LBRY, lbrynet is already installed, you can skip to 2.4._
```
$ wget --quiet -O ~/latest_daemon.zip https://lbry.io/get/lbrynet.linux.zip
$ wget --quiet -O ~/latest_daemon.zip https://lbry.com/get/lbrynet.linux.zip
$ unzip -o -u "~/latest_daemon.zip"
```
### Start lbrynet
```
$ tmux
$ ./lbrynet-daemon
@ -100,7 +108,7 @@ You should have **LBC**!
directions: [here](https://www.ffmpeg.org/download.html)
## 3. Set up Spee.ch
## 3. Set up Spee.ch
### Clone the spee.ch repo
@ -127,6 +135,7 @@ $ npm run configure
```
Check your site configs
```
$ cd /site/config/
$ nano siteConfig.json
@ -135,6 +144,7 @@ $ nano siteConfig.json
### Build & run
Run the below command to transpile, build, and start your server.
```
$ npm run start
```
@ -145,17 +155,18 @@ Spee.ch should now be running !
Visit your site in the browser. Try publishing an image!
## 4. Bonus:
### Install PM2 and run your server with PM2
Install PM2
```
$ sudo npm i -g pm2
```
From inside your projects folder, start your server with PM2.
```
$ pm2 start server.js
```

371
package-lock.json generated
View file

@ -1522,20 +1522,11 @@
"integrity": "sha1-6GuBnGAs+IIa1jdBNpjx3sAhhHo=",
"dev": true
},
"align-text": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz",
"integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=",
"requires": {
"kind-of": "^3.0.2",
"longest": "^1.0.1",
"repeat-string": "^1.5.2"
}
},
"amdefine": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz",
"integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU="
"integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=",
"dev": true
},
"ansi-align": {
"version": "2.0.0",
@ -1660,6 +1651,16 @@
"es-abstract": "^1.7.0"
}
},
"array-parallel": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/array-parallel/-/array-parallel-0.1.3.tgz",
"integrity": "sha1-j3hTCJJu1apHjEfmTRszS2wMlH0="
},
"array-series": {
"version": "0.1.5",
"resolved": "https://registry.npmjs.org/array-series/-/array-series-0.1.5.tgz",
"integrity": "sha1-3103v8XC7wdV4qpPkv6ufUtaly8="
},
"array-union": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz",
@ -1728,7 +1729,7 @@
},
"util": {
"version": "0.10.3",
"resolved": "http://registry.npmjs.org/util/-/util-0.10.3.tgz",
"resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz",
"integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=",
"dev": true,
"requires": {
@ -1754,11 +1755,6 @@
"integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=",
"dev": true
},
"async": {
"version": "1.5.2",
"resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz",
"integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo="
},
"async-each": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz",
@ -1807,12 +1803,19 @@
"integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ=="
},
"axios": {
"version": "0.18.0",
"resolved": "http://registry.npmjs.org/axios/-/axios-0.18.0.tgz",
"integrity": "sha1-MtU+SFHv3AoRmTts0AB4nXDAUQI=",
"version": "0.18.1",
"resolved": "http://registry.npmjs.org/axios/-/axios-0.18.1.tgz",
"integrity": "sha512-0BfJq4NSfQXd+SkFdrvFbG7addhYSBA2mQwISr46pD6E5iqkWg02RAs8vyTT/j0RTnoYmeXauBuSv1qKwR179g==",
"requires": {
"follow-redirects": "^1.3.0",
"is-buffer": "^1.1.5"
"follow-redirects": "1.5.10",
"is-buffer": "^2.0.2"
},
"dependencies": {
"is-buffer": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.3.tgz",
"integrity": "sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw=="
}
}
},
"babel-eslint": {
@ -2613,6 +2616,7 @@
"resolved": "http://registry.npmjs.org/bl/-/bl-1.2.2.tgz",
"integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==",
"dev": true,
"optional": true,
"requires": {
"readable-stream": "^2.3.5",
"safe-buffer": "^5.1.1"
@ -3005,12 +3009,6 @@
"integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=",
"dev": true
},
"camelcase": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz",
"integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=",
"optional": true
},
"camelcase-keys": {
"version": "2.1.0",
"resolved": "http://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz",
@ -3078,16 +3076,6 @@
"resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
"integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw="
},
"center-align": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz",
"integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=",
"optional": true,
"requires": {
"align-text": "^0.1.3",
"lazy-cache": "^1.0.3"
}
},
"chai": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz",
@ -3331,25 +3319,6 @@
"resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz",
"integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk="
},
"cliui": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz",
"integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=",
"optional": true,
"requires": {
"center-align": "^0.1.1",
"right-align": "^0.1.1",
"wordwrap": "0.0.2"
},
"dependencies": {
"wordwrap": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz",
"integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=",
"optional": true
}
}
},
"clone-deep": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-2.0.2.tgz",
@ -3642,6 +3611,15 @@
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
},
"cors": {
"version": "2.8.5",
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
"integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
"requires": {
"object-assign": "^4",
"vary": "^1"
}
},
"cosmiconfig": {
"version": "5.0.7",
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.0.7.tgz",
@ -3977,7 +3955,7 @@
"dependencies": {
"pify": {
"version": "2.3.0",
"resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
"dev": true,
"optional": true
@ -3989,6 +3967,7 @@
"resolved": "https://registry.npmjs.org/decompress-tar/-/decompress-tar-4.1.1.tgz",
"integrity": "sha512-JdJMaCrGpB5fESVyxwpCx4Jdj2AagLmv3y58Qy4GE6HMVjWz1FeVQk1Ct4Kye7PftcdOo/7U7UKzYBJgqnGeUQ==",
"dev": true,
"optional": true,
"requires": {
"file-type": "^5.2.0",
"is-stream": "^1.1.0",
@ -4023,6 +4002,7 @@
"resolved": "https://registry.npmjs.org/decompress-targz/-/decompress-targz-4.1.1.tgz",
"integrity": "sha512-4z81Znfr6chWnRDNfFNqLwPvm4db3WuZkqV+UgXQzSngG3CEKdBkw5jrv3axjjL96glyiiKjsxJG3X6WBZwX3w==",
"dev": true,
"optional": true,
"requires": {
"decompress-tar": "^4.1.1",
"file-type": "^5.2.0",
@ -4044,14 +4024,14 @@
"dependencies": {
"file-type": {
"version": "3.9.0",
"resolved": "http://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz",
"resolved": "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz",
"integrity": "sha1-JXoHg4TR24CHvESdEH1SpSZyuek=",
"dev": true,
"optional": true
},
"get-stream": {
"version": "2.3.1",
"resolved": "http://registry.npmjs.org/get-stream/-/get-stream-2.3.1.tgz",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-2.3.1.tgz",
"integrity": "sha1-Xzj5PzRgCWZu4BUKBUFn+Rvdld4=",
"dev": true,
"optional": true,
@ -4062,7 +4042,7 @@
},
"pify": {
"version": "2.3.0",
"resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
"dev": true,
"optional": true
@ -5178,7 +5158,7 @@
},
"external-editor": {
"version": "2.2.0",
"resolved": "http://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz",
"resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz",
"integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==",
"requires": {
"chardet": "^0.4.0",
@ -5413,7 +5393,8 @@
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/file-type/-/file-type-5.2.0.tgz",
"integrity": "sha1-LdvqfHP/42No365J3DOMBYwritY=",
"dev": true
"dev": true,
"optional": true
},
"fill-range": {
"version": "4.0.0",
@ -5530,11 +5511,11 @@
}
},
"follow-redirects": {
"version": "1.5.7",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.7.tgz",
"integrity": "sha512-NONJVIFiX7Z8k2WxfqBjtwqMifx7X42ORLFrOZ2LTKGj71G3C0kfdyTqGqr8fx5zSX6Foo/D95dgGWbPUiwnew==",
"version": "1.5.10",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz",
"integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==",
"requires": {
"debug": "^3.1.0"
"debug": "=3.1.0"
}
},
"for-in": {
@ -5611,7 +5592,8 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
"dev": true
"dev": true,
"optional": true
},
"fs-extra": {
"version": "5.0.0",
@ -5644,8 +5626,7 @@
"fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
"dev": true
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
},
"fsevents": {
"version": "1.2.4",
@ -5667,7 +5648,8 @@
"ansi-regex": {
"version": "2.1.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"aproba": {
"version": "1.2.0",
@ -5688,12 +5670,14 @@
"balanced-match": {
"version": "1.0.0",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@ -5708,17 +5692,20 @@
"code-point-at": {
"version": "1.1.0",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"concat-map": {
"version": "0.0.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"console-control-strings": {
"version": "1.1.0",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"core-util-is": {
"version": "1.0.2",
@ -5835,7 +5822,8 @@
"inherits": {
"version": "2.0.3",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"ini": {
"version": "1.3.5",
@ -5847,6 +5835,7 @@
"version": "1.0.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@ -5861,6 +5850,7 @@
"version": "3.0.4",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@ -5868,12 +5858,14 @@
"minimist": {
"version": "0.0.8",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"minipass": {
"version": "2.2.4",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"safe-buffer": "^5.1.1",
"yallist": "^3.0.0"
@ -5892,6 +5884,7 @@
"version": "0.5.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"minimist": "0.0.8"
}
@ -5972,7 +5965,8 @@
"number-is-nan": {
"version": "1.0.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"object-assign": {
"version": "4.1.1",
@ -5984,6 +5978,7 @@
"version": "1.4.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"wrappy": "1"
}
@ -6069,7 +6064,8 @@
"safe-buffer": {
"version": "5.1.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"safer-buffer": {
"version": "2.1.2",
@ -6105,6 +6101,7 @@
"version": "1.0.2",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@ -6124,6 +6121,7 @@
"version": "3.0.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@ -6167,20 +6165,21 @@
"wrappy": {
"version": "1.0.2",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"yallist": {
"version": "3.0.2",
"bundled": true,
"dev": true
"dev": true,
"optional": true
}
}
},
"fstream": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz",
"integrity": "sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=",
"dev": true,
"version": "1.0.12",
"resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz",
"integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==",
"requires": {
"graceful-fs": "^4.1.2",
"inherits": "~2.0.0",
@ -6254,7 +6253,7 @@
},
"strip-ansi": {
"version": "3.0.1",
"resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
"dev": true,
"requires": {
@ -6441,7 +6440,7 @@
},
"pify": {
"version": "2.3.0",
"resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
"dev": true
}
@ -6474,6 +6473,28 @@
}
}
},
"gm": {
"version": "1.23.1",
"resolved": "https://registry.npmjs.org/gm/-/gm-1.23.1.tgz",
"integrity": "sha1-Lt7rlYCE0PjqeYjl2ZWxx9/BR3c=",
"requires": {
"array-parallel": "~0.1.3",
"array-series": "~0.1.5",
"cross-spawn": "^4.0.0",
"debug": "^3.1.0"
},
"dependencies": {
"cross-spawn": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-4.0.2.tgz",
"integrity": "sha1-e5JHYhwjrf3ThWAEqCPL45dCTUE=",
"requires": {
"lru-cache": "^4.0.1",
"which": "^1.2.9"
}
}
}
},
"got": {
"version": "6.7.1",
"resolved": "http://registry.npmjs.org/got/-/got-6.7.1.tgz",
@ -6512,14 +6533,26 @@
"dev": true
},
"handlebars": {
"version": "4.0.11",
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.0.11.tgz",
"integrity": "sha1-Ywo13+ApS8KB7a5v/F0yn8eYLcw=",
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.1.2.tgz",
"integrity": "sha512-nvfrjqvt9xQ8Z/w0ijewdD/vvWDTOweBUm96NTr66Wfvo1mJenBLwcYmPs3TIBP5ruzYGD7Hx/DaM9RmhroGPw==",
"requires": {
"async": "^1.4.0",
"neo-async": "^2.6.0",
"optimist": "^0.6.1",
"source-map": "^0.4.4",
"uglify-js": "^2.6"
"source-map": "^0.6.1",
"uglify-js": "^3.1.4"
},
"dependencies": {
"neo-async": {
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz",
"integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw=="
},
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
}
}
},
"har-schema": {
@ -6789,7 +6822,7 @@
},
"http-errors": {
"version": "1.6.3",
"resolved": "http://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz",
"integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=",
"requires": {
"depd": "~1.1.2",
@ -7594,6 +7627,11 @@
"buffer-alloc": "^1.2.0"
}
},
"isbot": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/isbot/-/isbot-2.2.1.tgz",
"integrity": "sha512-z0idtpC0uKKKTBhd1g73GREBWhCQdnJq8U5o+8XhgPvuPiRb/vkpNreLvtoneaZX9FNxDFOU0ohEj9hTWm/tPw=="
},
"isemail": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/isemail/-/isemail-3.2.0.tgz",
@ -7685,9 +7723,9 @@
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
},
"js-yaml": {
"version": "3.12.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.0.tgz",
"integrity": "sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A==",
"version": "3.13.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz",
"integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==",
"requires": {
"argparse": "^1.0.7",
"esprima": "^4.0.0"
@ -7775,6 +7813,7 @@
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
"integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
"dev": true,
"requires": {
"is-buffer": "^1.1.5"
}
@ -7788,12 +7827,6 @@
"package-json": "^4.0.0"
}
},
"lazy-cache": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz",
"integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=",
"optional": true
},
"lcid": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz",
@ -8091,9 +8124,9 @@
}
},
"lodash": {
"version": "4.17.11",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
"integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg=="
"version": "4.17.13",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.13.tgz",
"integrity": "sha512-vm3/XWXfWtRua0FkUyEHBZy8kCPjErNBT9fJx8Zvs+U6zjqPbTUOpkaoum3O5uiA8sm+yNMHXfYkTUHFoMxFNA=="
},
"lodash.assign": {
"version": "4.2.0",
@ -8125,9 +8158,9 @@
"dev": true
},
"lodash.mergewith": {
"version": "4.6.1",
"resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.1.tgz",
"integrity": "sha512-eWw5r+PYICtEBgrBE5hhlT6aAa75f411bgDz/ZL2KZqYV03USvucsxcHUIlGTDTECs1eunpI7HOV7U+WLDvNdQ==",
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz",
"integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==",
"dev": true
},
"lodash.tail": {
@ -8173,11 +8206,6 @@
"resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz",
"integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA=="
},
"longest": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz",
"integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc="
},
"loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
@ -8375,7 +8403,7 @@
},
"load-json-file": {
"version": "1.1.0",
"resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
"resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
"integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=",
"dev": true,
"requires": {
@ -8414,7 +8442,7 @@
},
"pify": {
"version": "2.3.0",
"resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
"dev": true
},
@ -8586,9 +8614,9 @@
}
},
"mixin-deep": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz",
"integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==",
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz",
"integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==",
"dev": true,
"requires": {
"for-in": "^1.0.2",
@ -8975,7 +9003,7 @@
},
"semver": {
"version": "5.3.0",
"resolved": "http://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
"integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=",
"dev": true
}
@ -9068,7 +9096,7 @@
},
"chalk": {
"version": "1.1.3",
"resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
"dev": true,
"requires": {
@ -9105,7 +9133,7 @@
},
"strip-ansi": {
"version": "3.0.1",
"resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
"dev": true,
"requires": {
@ -9336,7 +9364,7 @@
},
"ajv": {
"version": "5.5.2",
"resolved": false,
"resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz",
"integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=",
"requires": {
"co": "^4.6.0",
@ -9649,7 +9677,7 @@
},
"co": {
"version": "4.6.0",
"resolved": false,
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
"integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ="
},
"code-point-at": {
@ -9986,7 +10014,7 @@
},
"fast-deep-equal": {
"version": "1.1.0",
"resolved": false,
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz",
"integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ="
},
"fast-json-stable-stringify": {
@ -10086,17 +10114,6 @@
"resolved": false,
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
},
"fstream": {
"version": "1.0.11",
"resolved": false,
"integrity": "sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=",
"requires": {
"graceful-fs": "^4.1.2",
"inherits": "~2.0.0",
"mkdirp": ">=0.5 0",
"rimraf": "2"
}
},
"gauge": {
"version": "2.7.4",
"resolved": false,
@ -10498,7 +10515,7 @@
},
"json-schema-traverse": {
"version": "0.3.1",
"resolved": false,
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz",
"integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A="
},
"json-stringify-safe": {
@ -13299,6 +13316,15 @@
}
}
},
"react-image": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/react-image/-/react-image-2.0.0.tgz",
"integrity": "sha512-yWf+UAtkavJFSG1Qa4p111KncN7/a8dAOUUi5On3jjwZU1tzMXFpBnOFp04vYQr8fJmS/7ePp1OsK440WZ4fLA==",
"requires": {
"@babel/runtime": "^7.0.0",
"prop-types": "15.6.2"
}
},
"react-input-autosize": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/react-input-autosize/-/react-input-autosize-2.2.1.tgz",
@ -13838,20 +13864,10 @@
}
}
},
"right-align": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz",
"integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=",
"optional": true,
"requires": {
"align-text": "^0.1.1"
}
},
"rimraf": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz",
"integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==",
"dev": true,
"requires": {
"glob": "^7.0.5"
},
@ -13860,7 +13876,6 @@
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz",
"integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==",
"dev": true,
"requires": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
@ -14018,7 +14033,7 @@
},
"load-json-file": {
"version": "1.1.0",
"resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
"resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
"integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=",
"dev": true,
"requires": {
@ -14031,7 +14046,7 @@
},
"os-locale": {
"version": "1.4.0",
"resolved": "http://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz",
"resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz",
"integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=",
"dev": true,
"requires": {
@ -14060,7 +14075,7 @@
},
"pify": {
"version": "2.3.0",
"resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
"dev": true
},
@ -14098,7 +14113,7 @@
},
"strip-ansi": {
"version": "3.0.1",
"resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
"dev": true,
"requires": {
@ -14234,7 +14249,7 @@
"dependencies": {
"commander": {
"version": "2.8.1",
"resolved": "http://registry.npmjs.org/commander/-/commander-2.8.1.tgz",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.8.1.tgz",
"integrity": "sha1-Br42f+v9oMMwqh4qBy09yXYkJdQ=",
"dev": true,
"optional": true,
@ -14714,6 +14729,7 @@
"version": "0.4.4",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz",
"integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=",
"dev": true,
"requires": {
"amdefine": ">=0.0.4"
}
@ -15153,6 +15169,7 @@
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz",
"integrity": "sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==",
"dev": true,
"optional": true,
"requires": {
"bl": "^1.0.0",
"buffer-alloc": "^1.2.0",
@ -15415,7 +15432,8 @@
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz",
"integrity": "sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==",
"dev": true
"dev": true,
"optional": true
},
"to-fast-properties": {
"version": "2.0.0",
@ -15619,30 +15637,23 @@
"dev": true
},
"uglify-js": {
"version": "2.8.29",
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz",
"integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=",
"version": "3.4.9",
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.9.tgz",
"integrity": "sha512-8CJsbKOtEbnJsTyv6LE6m6ZKniqMiFWmm9sRbopbkGs3gMPPfd3Fh8iIA4Ykv5MgaTbqHr4BaoGLJLZNhsrW1Q==",
"optional": true,
"requires": {
"source-map": "~0.5.1",
"uglify-to-browserify": "~1.0.0",
"yargs": "~3.10.0"
"commander": "~2.17.1",
"source-map": "~0.6.1"
},
"dependencies": {
"source-map": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
"integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"optional": true
}
}
},
"uglify-to-browserify": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz",
"integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=",
"optional": true
},
"uid-safe": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz",
@ -16597,7 +16608,7 @@
},
"strip-ansi": {
"version": "3.0.1",
"resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
"dev": true,
"requires": {
@ -16684,12 +16695,6 @@
"string-width": "^2.1.1"
}
},
"window-size": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz",
"integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=",
"optional": true
},
"winston": {
"version": "2.4.4",
"resolved": "https://registry.npmjs.org/winston/-/winston-2.4.4.tgz",
@ -16705,7 +16710,7 @@
"dependencies": {
"async": {
"version": "1.0.0",
"resolved": "http://registry.npmjs.org/async/-/async-1.0.0.tgz",
"resolved": "https://registry.npmjs.org/async/-/async-1.0.0.tgz",
"integrity": "sha1-+PwEyjoTeErenhZBr5hXjPvWR6k="
}
}
@ -16849,18 +16854,6 @@
"resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
"integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI="
},
"yargs": {
"version": "3.10.0",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz",
"integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=",
"optional": true,
"requires": {
"camelcase": "^1.0.2",
"cliui": "^2.1.0",
"decamelize": "^1.0.0",
"window-size": "0.1.0"
}
},
"yargs-parser": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-7.0.0.tgz",

View file

@ -36,21 +36,24 @@
"@fortawesome/fontawesome-svg-core": "^1.2.8",
"@fortawesome/free-solid-svg-icons": "^5.5.0",
"@fortawesome/react-fontawesome": "^0.1.3",
"axios": "^0.18.0",
"axios": "^0.18.1",
"bcrypt": "^3.0.3",
"body-parser": "^1.18.3",
"connect-multiparty": "^2.2.0",
"cookie-session": "^2.0.0-beta.3",
"cors": "^2.8.5",
"express": "^4.16.4",
"express-handlebars": "^3.0.0",
"express-http-context": "^1.2.0",
"generate-password": "^1.4.1",
"get-video-dimensions": "^1.0.0",
"gm": "^1.23.1",
"helmet": "^3.15.0",
"image-size": "^0.6.3",
"inquirer": "^5.2.0",
"ip": "^1.1.5",
"lodash": "^4.17.11",
"isbot": "^2.2.1",
"lodash": "^4.17.13",
"make-dir": "^1.3.0",
"mime-types": "^2.1.21",
"module-alias": "^2.1.0",
@ -65,6 +68,7 @@
"react-feather": "^1.1.4",
"react-ga": "^2.5.3",
"react-helmet": "^5.2.0",
"react-image": "^2.0.0",
"react-markdown": "^4.0.6",
"react-redux": "^5.1.1",
"react-router-dom": "^4.3.1",

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

View file

@ -6,7 +6,7 @@ const {
const getterMethods = {
generated_extension() {
logger.info('trying to generate extension', this.content_type);
logger.debug('trying to generate extension', this.content_type);
if (customFileExtensions.hasOwnProperty(this.content_type)) {
return customFileExtensions[this.content_type];
} else {
@ -136,6 +136,14 @@ export default (sequelize, { BOOLEAN, DATE, DECIMAL, ENUM, INTEGER, STRING, TEXT
type: STRING,
set() {},
},
license: {
type: STRING,
set() {},
},
license_url: {
type: STRING,
set() {},
},
},
{
freezeTableName: true,

View file

@ -82,6 +82,7 @@ export default (db, table, sequelize) => ({
};
const selectWhere = {
...whereClause,
claim_type: 1,
publisher_id: channelClaimId,
};
return await table
@ -103,6 +104,7 @@ export default (db, table, sequelize) => ({
.findAll({
where: {
name: claimName,
claim_type: 1,
publisher_id: channelClaimId,
bid_state: { [sequelize.Op.or]: ['Controlling', 'Active', 'Accepted'] },
},
@ -201,6 +203,10 @@ export default (db, table, sequelize) => ({
}
return claimArray[0];
})
.catch(error => {
logger.verbose(`resolveClaim failed: ${error}`)
reject(error);
});
},
@ -210,6 +216,7 @@ export default (db, table, sequelize) => ({
.findAll({
where: {
name: claimName,
claim_type: 1,
publisher_id: channelId,
},
})

View file

@ -1,8 +1,10 @@
const chainquery = require('chainquery').default;
const logger = require('winston');
const getClaimData = require('server/utils/getClaimData');
const { returnPaginatedChannelClaims } = require('./channelPagination.js');
const getChannelClaims = async (channelName, channelLongId, page) => {
logger.debug(`getChannelClaims: ${channelName}, ${channelLongId}, ${page}`);
let channelShortId = await chainquery.claim.queries.getShortClaimIdFromLongClaimId(
channelLongId,
channelName

View file

@ -9,28 +9,28 @@ const authenticateUser = require('../publish/authentication.js');
*/
const claimAbandon = async (req, res) => {
const {claimId} = req.body;
const {user} = req;
const { outpoint } = req.body;
const { user } = req;
try {
const [channel, claim] = await Promise.all([
authenticateUser(user.channelName, null, null, user),
db.Claim.findOne({where: {claimId}}),
db.Claim.findOne({ where: { outpoint } }),
]);
if (!claim) throw new Error('That channel does not exist');
if (!channel.channelName) throw new Error('You don\'t own this channel');
if (!channel.channelName) throw new Error("You don't own this channel");
await abandonClaim({claimId});
const file = await db.File.findOne({where: {claimId}});
await abandonClaim({ outpoint });
const file = await db.File.findOne({ where: { outpoint } });
await Promise.all([
deleteFile(file.filePath),
db.File.destroy({where: {claimId}}),
db.Claim.destroy({where: {claimId}}),
db.File.destroy({ where: { outpoint } }),
db.Claim.destroy({ where: { outpoint } }),
]);
logger.debug(`Claim abandoned: ${claimId}`);
logger.debug(`Claim abandoned: ${outpoint}`);
res.status(200).json({
success: true,
message: `Claim with id ${claimId} abandonded`,
message: `Claim with outpoint ${outpoint} abandonded`,
});
} catch (error) {
logger.error('abandon claim error:', error);

View file

@ -1,11 +1,12 @@
const { getClaim } = require('../../../../lbrynet');
const { createFileRecordDataAfterGet } = require('../../../../models/utils/createFileRecordData.js');
const { getClaim, resolveUri } = require('server/lbrynet');
const { createFileRecordDataAfterGet } = require('server/models/utils/createFileRecordData.js');
const { handleErrorResponse } = require('../../../utils/errorHandlers.js');
const getClaimData = require('server/utils/getClaimData');
const chainquery = require('chainquery').default;
const db = require('../../../../models');
const waitOn = require('wait-on');
const db = require('server/models');
const logger = require('winston');
const awaitFileSize = require('server/utils/awaitFileSize');
const isBot = require('isbot');
/*
@ -13,14 +14,14 @@ const logger = require('winston');
*/
const claimGet = async ({ ip, originalUrl, params }, res) => {
const claimGet = async ({ ip, originalUrl, params, headers }, res) => {
const name = params.name;
const claimId = params.claimId;
try {
let claimInfo = await chainquery.claim.queries.resolveClaim(name, claimId).catch(() => {});
if (claimInfo) {
logger.info('claim/get: claim resolved in chainquery');
logger.debug(`claim/get: claim resolved in chainquery`);
}
if (!claimInfo) {
claimInfo = await db.Claim.resolveClaim(name, claimId);
@ -28,6 +29,16 @@ const claimGet = async ({ ip, originalUrl, params }, res) => {
if (!claimInfo) {
throw new Error('claim/get: resolveClaim: No matching uri found in Claim table');
}
if (headers && headers['user-agent'] && isBot(headers['user-agent'])) {
logger.info(`Bot GetClaim: claimId: ${claimId}`);
res.status(200).json({
success: true,
message: 'bot',
completed: false,
});
return true;
}
logger.info(`GetClaim: ${claimId} UA: ${headers['user-agent']}`);
let lbrynetResult = await getClaim(`${name}#${claimId}`);
if (!lbrynetResult) {
throw new Error(`claim/get: getClaim Unable to Get ${name}#${claimId}`);
@ -36,11 +47,11 @@ const claimGet = async ({ ip, originalUrl, params }, res) => {
if (!claimData) {
throw new Error('claim/get: getClaimData failed to get file blobs');
}
await waitOn({
resources: [ lbrynetResult.download_path ],
timeout : 10000, // 10 seconds
window : 500,
});
const fileReady = await awaitFileSize(lbrynetResult.outpoint, 10000000, 250, 10000);
if (fileReady !== 'ready') {
throw new Error('claim/get: failed to get file after 10 seconds');
}
const fileData = await createFileRecordDataAfterGet(claimData, lbrynetResult);
if (!fileData) {
throw new Error('claim/get: createFileRecordDataAfterGet failed to create file in time');

View file

@ -36,7 +36,7 @@ const claimLongId = ({ ip, originalUrl, body, params }, res) => {
return db.Blocked.isNotBlocked(outpoint);
})
.then(() => {
res.status(200).json({success: true, data: claimId});
res.status(200).json({ success: true, data: claimId });
})
.catch(error => {
if (error === NO_CLAIM) {
@ -54,7 +54,8 @@ const claimLongId = ({ ip, originalUrl, body, params }, res) => {
if (error === BLOCKED_CLAIM) {
return res.status(410).json({
success: false,
message: 'In response to a complaint we received under the US Digital Millennium Copyright Act, we have blocked access to this content from our applications. For more details, see https://lbry.io/faq/dmca',
message:
'In response to a complaint we received under the US Digital Millennium Copyright Act, we have blocked access to this content from our applications. For more details, see https://lbry.com/faq/dmca',
});
}
handleErrorResponse(originalUrl, ip, error, res);

View file

@ -1,6 +1,17 @@
const logger = require('winston');
const { details, publishing } = require('@config/siteConfig');
const createPublishParams = (filePath, name, title, description, license, nsfw, thumbnail, channelName, channelClaimId) => {
const createPublishParams = (
filePath,
name,
title,
description,
license,
licenseUrl,
nsfw,
thumbnail,
channelName,
channelClaimId
) => {
// provide defaults for title
if (title === null || title.trim() === '') {
title = name;
@ -11,31 +22,37 @@ const createPublishParams = (filePath, name, title, description, license, nsfw,
}
// provide default for license
if (license === null || license.trim() === '') {
license = ''; // default to empty string
license = ''; // default to empty string
}
// provide default for licenseUrl
if (licenseUrl === null || licenseUrl.trim() === '') {
licenseUrl = ''; // default to empty string
}
// create the basic publish params
const publishParams = {
name,
file_path: filePath,
bid : publishing.fileClaimBidAmount,
metadata : {
description,
title,
author : details.title,
language: 'en',
license,
nsfw,
},
bid: publishing.fileClaimBidAmount,
description,
title,
author: details.title,
languages: ['en'],
license,
license_url: licenseUrl,
tags: [],
claim_address: publishing.primaryClaimAddress,
};
// add thumbnail to channel if video
if (thumbnail) {
publishParams['metadata']['thumbnail'] = thumbnail;
publishParams['thumbnail_url'] = thumbnail;
}
if (nsfw) {
publishParams.tags = ['mature'];
}
// add channel details if publishing to a channel
if (channelName && channelClaimId) {
publishParams['channel_name'] = channelName;
publishParams['channel_id'] = channelClaimId;
publishParams['channel_name'] = channelName;
}
// log params
logger.debug('publish params:', publishParams);

View file

@ -1,27 +1,34 @@
const logger = require('winston');
const { details, publishing } = require('@config/siteConfig');
const createThumbnailPublishParams = (thumbnailFilePath, claimName, license, nsfw) => {
const createThumbnailPublishParams = (thumbnailFilePath, claimName, license, licenseUrl, nsfw) => {
if (!thumbnailFilePath) {
return;
}
logger.debug(`Creating Thumbnail Publish Parameters`);
// create the publish params
if (license === null || license.trim() === '') {
license = ''; // default to empty string
}
// provide default for licenseUrl
if (licenseUrl === null || licenseUrl.trim() === '') {
licenseUrl = ''; // default to empty string
}
return {
name : `${claimName}-thumb`,
name: `${claimName}-thumb`,
file_path: thumbnailFilePath,
bid : publishing.fileClaimBidAmount,
metadata : {
title : `${claimName} thumbnail`,
description: `a thumbnail for ${claimName}`,
author : details.title,
language : 'en',
license,
nsfw,
},
bid: publishing.fileClaimBidAmount,
title: `${claimName} thumbnail`,
description: `a thumbnail for ${claimName}`,
author: details.title,
languages: ['en'],
license,
license_url: licenseUrl,
claim_address: publishing.primaryClaimAddress,
channel_name : publishing.thumbnailChannel,
channel_id : publishing.thumbnailChannelId,
channel_name: publishing.thumbnailChannel,
channel_id: publishing.thumbnailChannelId,
};
};

View file

@ -1,10 +1,15 @@
const logger = require('winston');
const { details: { host }, publishing: { disabled, disabledMessage } } = require('@config/siteConfig');
const {
details: { host },
publishing: { disabled, disabledMessage },
} = require('@config/siteConfig');
const { sendGATimingEvent } = require('../../../../utils/googleAnalytics.js');
const { sendGATimingEvent } = require('server/utils/googleAnalytics.js');
const isApprovedChannel = require('@globalutils/isApprovedChannel');
const { publishing: { publishOnlyApproved, approvedChannels } } = require('@config/siteConfig');
const {
publishing: { publishOnlyApproved, approvedChannels },
} = require('@config/siteConfig');
const { handleErrorResponse } = require('../../../utils/errorHandlers.js');
@ -55,6 +60,7 @@ const claimPublish = ({ body, files, headers, ip, originalUrl, user, tor }, res)
fileType,
gaStartTime,
license,
licenseUrl,
name,
nsfw,
thumbnail,
@ -69,18 +75,34 @@ const claimPublish = ({ body, files, headers, ip, originalUrl, user, tor }, res)
// validate the body and files of the request
try {
// validateApiPublishRequest(body, files);
({name, nsfw, license, title, description, thumbnail} = parsePublishApiRequestBody(body));
({fileName, filePath, fileExtension, fileType, thumbnailFileName, thumbnailFilePath, thumbnailFileType} = parsePublishApiRequestFiles(files));
({channelName, channelId, channelPassword} = body);
({
name,
nsfw,
license,
licenseUrl,
title,
description,
thumbnail,
} = parsePublishApiRequestBody(body));
({
fileName,
filePath,
fileExtension,
fileType,
thumbnailFileName,
thumbnailFilePath,
thumbnailFileType,
} = parsePublishApiRequestFiles(files));
({ channelName, channelId, channelPassword } = body);
} catch (error) {
return res.status(400).json({success: false, message: error.message});
return res.status(400).json({ success: false, message: error.message });
}
// check channel authorization
authenticateUser(channelName, channelId, channelPassword, user)
.then(({ channelName, channelClaimId }) => {
if (publishOnlyApproved && !isApprovedChannel({ longId: channelClaimId }, approvedChannels)) {
const error = {
name : UNAPPROVED_CHANNEL,
name: UNAPPROVED_CHANNEL,
message: 'This spee.ch instance only allows publishing to approved channels',
};
throw error;
@ -88,14 +110,25 @@ const claimPublish = ({ body, files, headers, ip, originalUrl, user, tor }, res)
return Promise.all([
checkClaimAvailability(name),
createPublishParams(filePath, name, title, description, license, nsfw, thumbnail, channelName, channelClaimId),
createThumbnailPublishParams(thumbnailFilePath, name, license, nsfw),
createPublishParams(
filePath,
name,
title,
description,
license,
licenseUrl,
nsfw,
thumbnail,
channelName,
channelClaimId
),
createThumbnailPublishParams(thumbnailFilePath, name, license, licenseUrl, nsfw),
]);
})
.then(([ claimAvailable, publishParams, thumbnailPublishParams ]) => {
.then(([claimAvailable, publishParams, thumbnailPublishParams]) => {
if (!claimAvailable) {
const error = {
name : CLAIM_TAKEN,
name: CLAIM_TAKEN,
message: 'That claim name is already taken',
};
throw error;
@ -108,16 +141,22 @@ const claimPublish = ({ body, files, headers, ip, originalUrl, user, tor }, res)
return publish(publishParams, fileName, fileType, filePath);
})
.then(publishResults => {
logger.info('Publish success >', publishResults);
logger.debug('Publish success >', publishResults);
claimData = publishResults;
({claimId} = claimData);
({ claimId } = claimData);
if (channelName) {
return chainquery.claim.queries.getShortClaimIdFromLongClaimId(claimData.certificateId, channelName);
logger.debug(`api/claim/publish: claimData.certificateId ${claimData.certificateId}`);
return chainquery.claim.queries.getShortClaimIdFromLongClaimId(
claimData.certificateId,
channelName
);
} else {
return chainquery.claim.queries.getShortClaimIdFromLongClaimId(claimId, name, claimData).catch(() => {
return claimId.slice(0, 1);
});
return chainquery.claim.queries
.getShortClaimIdFromLongClaimId(claimId, name, claimData)
.catch(() => {
return claimId.slice(0, 1);
});
}
})
.then(shortId => {
@ -131,13 +170,13 @@ const claimPublish = ({ body, files, headers, ip, originalUrl, user, tor }, res)
res.status(200).json({
success: true,
message: 'publish completed successfully',
data : {
data: {
name,
claimId,
url : `${host}${canonicalUrl}`, // for backwards compatability with app
showUrl : `${host}${canonicalUrl}`,
url: `${host}${canonicalUrl}`, // for backwards compatability with app
showUrl: `${host}${canonicalUrl}`,
serveUrl: `${host}${canonicalUrl}${fileExtension}`,
pushTo : canonicalUrl,
pushTo: canonicalUrl,
claimData,
},
});

View file

@ -1,15 +1,26 @@
const parsePublishApiRequestBody = ({name, nsfw, license, title, description, thumbnail}) => {
const parsePublishApiRequestBody = ({
name,
nsfw,
license,
licenseUrl,
title,
description,
thumbnail,
}) => {
// validate name
if (!name) {
throw new Error('no name field found in request');
}
const invalidNameCharacters = /[^A-Za-z0-9,-]/.exec(name);
if (invalidNameCharacters) {
throw new Error('The claim name you provided is not allowed. Only the following characters are allowed: A-Z, a-z, 0-9, and "-"');
throw new Error(
'The claim name you provided is not allowed. Only the following characters are allowed: A-Z, a-z, 0-9, and "-"'
);
}
// optional parameters
nsfw = (nsfw === 'true');
nsfw = nsfw === 'true';
license = license || null;
licenseUrl = licenseUrl || null;
title = title || null;
description = description || null;
thumbnail = thumbnail || null;
@ -18,6 +29,7 @@ const parsePublishApiRequestBody = ({name, nsfw, license, title, description, th
name,
nsfw,
license,
licenseUrl,
title,
description,
thumbnail,

View file

@ -1,8 +1,10 @@
const logger = require('winston');
const db = require('../../../../models');
const { publishClaim } = require('../../../../lbrynet');
const { createFileRecordDataAfterPublish } = require('../../../../models/utils/createFileRecordData.js');
const { createClaimRecordDataAfterPublish } = require('../../../../models/utils/createClaimRecordData.js');
const db = require('server/models');
const { publishClaim } = require('server/lbrynet');
const { createFileRecordDataAfterPublish } = require('server/models/utils/createFileRecordData.js');
const {
createClaimRecordDataAfterPublish,
} = require('server/models/utils/createClaimRecordData.js');
const deleteFile = require('./deleteFile.js');
const publish = async (publishParams, fileName, fileType) => {
@ -10,58 +12,76 @@ const publish = async (publishParams, fileName, fileType) => {
let channel;
let fileRecord;
let newFile = Boolean(publishParams.file_path);
let publishResultsOutput;
try {
publishResults = await publishClaim(publishParams);
logger.info(`Successfully published ${publishParams.name} ${fileName}`, publishResults);
const outpoint = `${publishResults.output.txid}:${publishResults.output.nout}`;
// get the channel information
if (publishParams.channel_name) {
logger.debug(`this claim was published in channel: ${publishParams.channel_name}`);
channel = await db.Channel.findOne({
where: {
channelName: publishParams.channel_name,
},
});
} else {
channel = null;
}
const certificateId = channel ? channel.channelClaimId : null;
const channelName = channel ? channel.channelName : null;
publishResultsOutput = publishResults && publishResults.outputs && publishResults.outputs[0];
const claimRecord = await createClaimRecordDataAfterPublish(certificateId, channelName, fileName, fileType, publishParams, publishResults);
const {claimId} = claimRecord;
const upsertCriteria = {name: publishParams.name, claimId};
logger.verbose(`Successfully published ${publishParams.name} ${fileName}`, publishResults);
const outpoint = `${publishResultsOutput.txid}:${publishResultsOutput.nout}`;
// get the channel information
// if (publishParams.channel_name) {
// logger.debug(`this claim was published in channel: ${publishParams.channel_name}`);
// channel = await db.Channel.findOne({
// where: {
// channelName: publishParams.channel_name,
// },
// });
// } else {
// channel = null;
// }
const certificateId = publishResultsOutput.signing_channel
? publishResultsOutput.signing_channel.claim_id
: null;
const channelName = publishResultsOutput.signing_channel
? publishResultsOutput.signing_channel.name
: null;
const claimRecord = await createClaimRecordDataAfterPublish(
certificateId,
channelName,
fileName,
fileType,
publishParams,
publishResultsOutput
);
const { claimId } = claimRecord;
const upsertCriteria = { name: publishParams.name, claimId };
if (newFile) {
// this is the problem
//
fileRecord = await createFileRecordDataAfterPublish(fileName, fileType, publishParams, publishResults);
fileRecord = await createFileRecordDataAfterPublish(
fileName,
fileType,
publishParams,
publishResultsOutput
);
} else {
fileRecord = await db.File.findOne({where: {claimId}}).then(result => result.dataValues);
fileRecord = await db.File.findOne({ where: { claimId } }).then(result => result.dataValues);
}
const [file, claim] = await Promise.all([
db.upsert(db.File, fileRecord, upsertCriteria, 'File'),
db.upsert(db.Claim, claimRecord, upsertCriteria, 'Claim'),
]);
logger.info(`File and Claim records successfully created (${publishParams.name})`);
logger.debug(`File and Claim records successfully created (${publishParams.name})`);
await Promise.all([
file.setClaim(claim),
claim.setFile(file),
]);
logger.info(`File and Claim records successfully associated (${publishParams.name})`);
await Promise.all([file.setClaim(claim), claim.setFile(file)]);
logger.debug(`File and Claim records successfully associated (${publishParams.name})`);
return Object.assign({}, claimRecord, {outpoint});
return Object.assign({}, claimRecord, { outpoint });
} catch (err) {
// parse daemon response when err is a string
// this needs work
logger.info('publish/publish err:', err);
logger.error('publish/publish err:', err);
const error = typeof err === 'string' ? JSON.parse(err) : err;
if (publishParams.file_path) {
await deleteFile(publishParams.file_path);
}
const message = error.error && error.error.message ? error.error.message : 'Unknown publish error';
const message =
error.error && error.error.message ? error.error.message : 'Unknown publish error';
return {
error: true,
message,

View file

@ -1,6 +1,9 @@
const logger = require('winston');
const db = require('server/models');
const { details, publishing: { disabled, disabledMessage, primaryClaimAddress } } = require('@config/siteConfig');
const {
details,
publishing: { disabled, disabledMessage, primaryClaimAddress },
} = require('@config/siteConfig');
const { resolveUri } = require('server/lbrynet');
const { sendGATimingEvent } = require('../../../../utils/googleAnalytics.js');
const { handleErrorResponse } = require('../../../utils/errorHandlers.js');
@ -16,10 +19,11 @@ const createCanonicalLink = require('@globalutils/createCanonicalLink');
route to update a claim through the daemon
*/
const updateMetadata = ({nsfw, license, title, description}) => {
const updateMetadata = ({ nsfw, license, licenseUrl, title, description }) => {
const update = {};
if (nsfw) update['nsfw'] = nsfw;
if (license) update['license'] = license;
if (licenseUrl) update['licenseUrl'] = licenseUrl;
if (title) update['title'] = title;
if (description) update['description'] = description;
return update;
@ -34,7 +38,7 @@ const rando = () => {
const claimUpdate = ({ body, files, headers, ip, originalUrl, user, tor }, res) => {
// logging
logger.info('Claim update request:', {
logger.debug('Claim update request:', {
ip,
headers,
body,
@ -62,6 +66,7 @@ const claimUpdate = ({ body, files, headers, ip, originalUrl, user, tor }, res)
thumbnail,
fileExtension,
license,
licenseUrl,
name,
nsfw,
thumbnailFileName,
@ -76,11 +81,27 @@ const claimUpdate = ({ body, files, headers, ip, originalUrl, user, tor }, res)
gaStartTime = Date.now();
try {
({name, nsfw, license, title, description, thumbnail} = parsePublishApiRequestBody(body));
({fileName, filePath, fileExtension, fileType, thumbnailFileName, thumbnailFilePath, thumbnailFileType} = parsePublishApiRequestFiles(files, true));
({channelName, channelId, channelPassword} = body);
({
name,
nsfw,
license,
licenseUrl,
title,
description,
thumbnail,
} = parsePublishApiRequestBody(body));
({
fileName,
filePath,
fileExtension,
fileType,
thumbnailFileName,
thumbnailFilePath,
thumbnailFileType,
} = parsePublishApiRequestFiles(files, true));
({ channelName, channelId, channelPassword } = body);
} catch (error) {
return res.status(400).json({success: false, message: error.message});
return res.status(400).json({ success: false, message: error.message });
}
// check channel authorization
@ -89,7 +110,9 @@ const claimUpdate = ({ body, files, headers, ip, originalUrl, user, tor }, res)
if (!channelId) {
channelId = channelClaimId;
}
return chainquery.claim.queries.resolveClaimInChannel(name, channelClaimId).then(claim => claim.dataValues);
return chainquery.claim.queries
.resolveClaimInChannel(name, channelClaimId)
.then(claim => claim.dataValues);
})
.then(claim => {
claimRecord = claim;
@ -107,40 +130,60 @@ const claimUpdate = ({ body, files, headers, ip, originalUrl, user, tor }, res)
return [null, null];
})
.then(([fileResult, resolution]) => {
metadata = Object.assign({}, {
title : claimRecord.title,
description: claimRecord.description,
nsfw : claimRecord.nsfw,
license : claimRecord.license,
language : 'en',
author : details.title,
}, updateMetadata({title, description, nsfw, license}));
metadata = Object.assign(
{},
{
title: claimRecord.title,
description: claimRecord.description,
nsfw: claimRecord.nsfw,
license: claimRecord.license,
licenseUrl: claimRecord.license_url,
languages: ['en'],
author: details.title,
},
updateMetadata({ title, description, nsfw, license, licenseUrl })
);
const publishParams = {
name,
bid : '0.01',
bid: '0.01',
claim_address: primaryClaimAddress,
channel_name : channelName,
channel_id : channelId,
metadata,
channel_name: channelName,
channel_id: channelId,
title,
description,
author: details.title,
languages: ['en'],
license: license || '',
license_url: licenseUrl || '',
tags: [],
};
if (nsfw) {
publishParams.tags = ['mature'];
}
if (files.file) {
if (thumbnailUpdate) {
// publish new thumbnail
const newThumbnailName = `${name}-${rando()}`;
const newThumbnailParams = createThumbnailPublishParams(filePath, newThumbnailName, license, nsfw);
const newThumbnailParams = createThumbnailPublishParams(
filePath,
newThumbnailName,
license,
nsfw
);
newThumbnailParams['file_path'] = filePath;
publish(newThumbnailParams, fileName, fileType);
publishParams['sources'] = resolution.claim.value.stream.source;
publishParams['thumbnail'] = `${details.host}/${newThumbnailParams.channel_name}:${newThumbnailParams.channel_id}/${newThumbnailName}-thumb.jpg`;
publishParams['thumbnail'] = `${details.host}/${newThumbnailParams.channel_name}:${
newThumbnailParams.channel_id
}/${newThumbnailName}-thumb.jpg`;
} else {
publishParams['file_path'] = filePath;
}
} else {
fileName = fileResult.fileName;
fileType = fileResult.fileType;
publishParams['sources'] = resolution.claim.value.stream.source;
publishParams['thumbnail'] = claimRecord.thumbnail_url;
}
@ -151,17 +194,24 @@ const claimUpdate = ({ body, files, headers, ip, originalUrl, user, tor }, res)
publishResult = result;
if (channelName) {
return chainquery.claim.queries.getShortClaimIdFromLongClaimId(result.certificateId, channelName);
return chainquery.claim.queries.getShortClaimIdFromLongClaimId(
publishResult.certificateId,
channelName
);
} else {
return chainquery.claim.queries.getShortClaimIdFromLongClaimId(result.claimId, name, result).catch(() => {
return result.claimId.slice(0, 1);
});
return chainquery.claim.queries
.getShortClaimIdFromLongClaimId(publishResult.claimId, name, publishResult)
.catch(() => {
return publishResult.claimId.slice(0, 1);
});
}
})
.then(shortId => {
let canonicalUrl;
if (channelName) {
canonicalUrl = createCanonicalLink({ asset: { ...publishResult, channelShortId: shortId } });
canonicalUrl = createCanonicalLink({
asset: { ...publishResult, channelShortId: shortId },
});
} else {
canonicalUrl = createCanonicalLink({ asset: { ...publishResult, shortId } });
}
@ -173,17 +223,17 @@ const claimUpdate = ({ body, files, headers, ip, originalUrl, user, tor }, res)
});
}
const {claimId} = publishResult;
const { claimId } = publishResult;
res.status(200).json({
success: true,
message: 'update successful',
data : {
data: {
name,
claimId,
url : `${details.host}${canonicalUrl}`, // for backwards compatability with app
showUrl : `${details.host}${canonicalUrl}`,
serveUrl : `${details.host}${canonicalUrl}${fileExtension}`,
pushTo : canonicalUrl,
url: `${details.host}${canonicalUrl}`, // for backwards compatability with app
showUrl: `${details.host}${canonicalUrl}`,
serveUrl: `${details.host}${canonicalUrl}${fileExtension}`,
pushTo: canonicalUrl,
claimData: publishResult,
},
});

View file

@ -1,5 +1,9 @@
const logger = require('winston');
const { handleErrorResponse } = require('../../../utils/errorHandlers.js');
const db = require('../../../../models');
const { getFileListFileByOutpoint } = require('server/lbrynet');
const chainquery = require('chainquery').default;
/*
@ -10,18 +14,24 @@ const db = require('../../../../models');
const fileAvailability = ({ ip, originalUrl, params }, res) => {
const name = params.name;
const claimId = params.claimId;
db.File
.findOne({
where: {
name,
claimId,
},
logger.verbose(`fileAvailability params: name:${name} claimId:${claimId}`);
// TODO: we probably eventually want to check the publishCache for the claimId too,
// and shop the outpoint to file_list.
return chainquery.claim.queries
.resolveClaim(name, claimId)
.then(result => {
return `${result.dataValues.transaction_hash_id}:${result.dataValues.vout}`;
})
.then(outpoint => {
logger.debug(`fileAvailability: outpoint: ${outpoint}`);
return getFileListFileByOutpoint(outpoint);
})
.then(result => {
if (result) {
return res.status(200).json({success: true, data: true});
if (result && result[0]) {
return res.status(200).json({ success: true, data: true });
} else {
res.status(200).json({ success: true, data: false });
}
res.status(200).json({success: true, data: false});
})
.catch(error => {
handleErrorResponse(originalUrl, ip, error, res);

View file

@ -15,9 +15,20 @@ const BLOCKED_CLAIM = 'BLOCKED_CLAIM';
const NO_FILE = 'NO_FILE';
const CONTENT_UNAVAILABLE = 'CONTENT_UNAVAILABLE';
const { publishing: { serveOnlyApproved, approvedChannels } } = require('@config/siteConfig');
const {
publishing: { serveOnlyApproved, approvedChannels },
} = require('@config/siteConfig');
const getClaimIdAndServeAsset = (channelName, channelClaimId, claimName, claimId, originalUrl, ip, res, headers) => {
const getClaimIdAndServeAsset = (
channelName,
channelClaimId,
claimName,
claimId,
originalUrl,
ip,
res,
headers
) => {
getClaimId(channelName, channelClaimId, claimName, claimId)
.then(fullClaimId => {
claimId = fullClaimId;
@ -39,24 +50,33 @@ const getClaimIdAndServeAsset = (channelName, channelClaimId, claimName, claimId
.then(claim => {
let claimDataValues = claim.dataValues;
if (serveOnlyApproved && !isApprovedChannel({ longId: claimDataValues.publisher_id || claimDataValues.certificateId }, approvedChannels)) {
if (
serveOnlyApproved &&
!isApprovedChannel(
{ longId: claimDataValues.publisher_id || claimDataValues.certificateId },
approvedChannels
)
) {
throw new Error(CONTENT_UNAVAILABLE);
}
let outpoint = claimDataValues.outpoint || `${claimDataValues.transaction_hash_id}:${claimDataValues.vout}`;
let outpoint =
claimDataValues.outpoint ||
`${claimDataValues.transaction_hash_id}:${claimDataValues.vout}`;
logger.debug('Outpoint:', outpoint);
return db.Blocked.isNotBlocked(outpoint).then(() => {
return db.Blocked.isNotBlocked(outpoint)
// .then(() => {
// If content was found, is approved, and not blocked - log a view.
if (headers && headers['user-agent'] && /LBRY/.test(headers['user-agent']) === false) {
db.Views.create({
time : Date.now(),
isChannel : false,
claimId : claimDataValues.claim_id || claimDataValues.claimId,
publisherId: claimDataValues.publisher_id || claimDataValues.certificateId,
ip,
});
}
});
// if (headers && headers['user-agent'] && /LBRY/.test(headers['user-agent']) === false) {
// db.Views.create({
// time: Date.now(),
// isChannel: false,
// claimId: claimDataValues.claim_id || claimDataValues.claimId,
// publisherId: claimDataValues.publisher_id || claimDataValues.certificateId,
// ip,
// });
// }
// });
})
.then(() => {
return db.File.findOne({
@ -70,7 +90,7 @@ const getClaimIdAndServeAsset = (channelName, channelClaimId, claimName, claimId
if (!fileRecord) {
throw NO_FILE;
}
serveFile(fileRecord.dataValues, res);
serveFile(fileRecord.dataValues, res, originalUrl);
})
.catch(error => {
if (error === NO_CLAIM) {
@ -98,7 +118,8 @@ const getClaimIdAndServeAsset = (channelName, channelClaimId, claimName, claimId
logger.debug('claim was blocked');
return res.status(451).json({
success: false,
message: 'In response to a complaint we received under the US Digital Millennium Copyright Act, we have blocked access to this content from our applications. For more details, see https://lbry.io/faq/dmca',
message:
'In response to a complaint we received under the US Digital Millennium Copyright Act, we have blocked access to this content from our applications. For more details, see https://lbry.com/faq/dmca',
});
}
if (error === NO_FILE) {

View file

@ -1,19 +1,68 @@
const logger = require('winston');
const transformImage = require('./transformImage');
const isValidQueryObject = require('server/utils/isValidQueryObj');
const {
serving: { dynamicFileSizing },
} = require('@config/siteConfig');
const { enabled: dynamicEnabled } = dynamicFileSizing;
const serveFile = async ({ filePath, fileType }, res, originalUrl) => {
const queryObject = {};
// TODO: replace quick/dirty try catch with better practice
try {
originalUrl
.split('?')[1]
.split('&')
.map(pair => {
if (pair.includes('=')) {
let parr = pair.split('=');
queryObject[parr[0]] = parr[1];
} else queryObject[pair] = true;
});
} catch (e) {}
const serveFile = ({ filePath, fileType }, res) => {
if (!fileType) {
logger.error(`no fileType provided for ${filePath}`);
}
let mediaType = fileType ? fileType.substr(0, fileType.indexOf('/')) : '';
const transform =
mediaType === 'image' &&
queryObject.hasOwnProperty('h') &&
queryObject.hasOwnProperty('w') &&
dynamicEnabled;
const sendFileOptions = {
headers: {
'X-Content-Type-Options' : 'nosniff',
'Content-Type' : fileType,
'Access-Control-Allow-Origin' : '*',
'X-Content-Type-Options': 'nosniff',
'Content-Type': fileType,
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'Origin, X-Requested-With, Content-Type, Accept',
},
};
logger.debug(`fileOptions for ${filePath}:`, sendFileOptions);
res.status(200).sendFile(filePath, sendFileOptions);
try {
if (transform) {
if (!isValidQueryObject(queryObject)) {
logger.debug(`Unacceptable querystring`, { queryObject });
res.status(400).json({
success: false,
message: 'Querystring may not have dimensions greater than 2000',
});
res.end();
}
logger.debug(`transforming and sending file`);
let xformed = await transformImage(filePath, queryObject);
res.status(200).set(sendFileOptions.headers);
res.end(xformed, 'binary');
} else {
res.status(200).sendFile(filePath, sendFileOptions);
}
} catch (e) {
logger.debug(e);
}
};
module.exports = serveFile;

View file

@ -0,0 +1,76 @@
const gm = require('gm');
const logger = require('winston');
const imageMagick = gm.subClass({ imageMagick: true });
const { getImageHeightAndWidth } = require('../../../utils/imageProcessing');
module.exports = function transformImage(path, queryObj) {
return new Promise((resolve, reject) => {
let { h: cHeight = null } = queryObj;
let { w: cWidth = null } = queryObj;
let { t: transform = null } = queryObj;
let { x: xOrigin = null } = queryObj;
let { y: yOrigin = null } = queryObj;
let oHeight,
oWidth = null;
try {
getImageHeightAndWidth(path).then(hwarr => {
oHeight = hwarr[0];
oWidth = hwarr[1];
// conditional logic here
if (transform === 'crop') {
resolve(_cropCenter(path, cWidth, cHeight, oWidth, oHeight));
} else if (transform === 'stretch') {
imageMagick(path)
.resize(cWidth, cHeight, '!')
.toBuffer(null, (err, buf) => {
resolve(buf);
});
} else {
// resize scaled
imageMagick(path)
.resize(cWidth, cHeight)
.toBuffer(null, (err, buf) => {
resolve(buf);
});
}
});
} catch (e) {
logger.error(e);
reject(e);
}
});
};
function _cropCenter(path, cropWidth, cropHeight, originalWidth, originalHeight) {
let oAspect = originalWidth / originalHeight;
let cAspect = cropWidth / cropHeight;
let resizeX,
resizeY,
xpoint,
ypoint = null;
if (oAspect >= cAspect) {
// if crop is narrower aspect than original
resizeY = cropHeight;
xpoint = (oAspect * cropHeight) / 2 - cropWidth / 2;
ypoint = 0;
} else {
// if crop is wider aspect than original
resizeX = cropWidth;
xpoint = 0;
ypoint = cropWidth / oAspect / 2 - cropHeight / 2;
}
return new Promise((resolve, reject) => {
try {
imageMagick(path)
.resize(resizeX, resizeY)
.crop(cropWidth, cropHeight, xpoint, ypoint)
.toBuffer(null, (err, buf) => {
resolve(buf);
});
} catch (e) {
logger.error(e);
reject(e);
}
});
}

View file

@ -20,10 +20,7 @@ const { setupBlockList } = require('./utils/blockList');
const speechPassport = require('./speechPassport');
const processTrending = require('./utils/processTrending');
const {
logMetricsMiddleware,
setRouteDataInContextMiddleware,
} = require('./middleware/logMetricsMiddleware');
const { setRouteDataInContextMiddleware } = require('./middleware/httpContextMiddleware');
const {
details: { port: PORT, blockListEndpoint },
@ -75,7 +72,7 @@ function Server() {
res
.status(403)
.send(
'<h1>Forbidden</h1>If you are seeing this by mistake, please contact us using <a href="https://chat.lbry.io/">https://chat.lbry.io/</a>'
'<h1>Forbidden</h1>If you are seeing this by mistake, please contact us using <a href="https://chat.lbry.com/">https://chat.lbry.com/</a>'
);
res.end();
} else {
@ -145,7 +142,7 @@ function Server() {
app[routeMethod](
routePath,
logMetricsMiddleware,
// logMetricsMiddleware,
setRouteDataInContextMiddleware(routePath, routeData),
...controllers
);
@ -195,7 +192,7 @@ function Server() {
'Continuing with default LBRY blocklist api endpoint. \n ' +
'(Specify /"blockListEndpoint" : ""/ to disable.'
);
finalBlockListEndpoint = 'https://api.lbry.io/file/list_blocked';
finalBlockListEndpoint = 'https://api.lbry.com/file/list_blocked';
}
}
logger.info(`Peforming updates...`);

View file

@ -53,13 +53,34 @@ module.exports = {
});
});
},
async abandonClaim({ claimId }) {
logger.debug(`lbryApi >> Abandon claim "${claimId}"`);
getFileListFileByOutpoint(outpoint) {
logger.debug(`lbryApi >> Getting File_List for "${outpoint}"`);
const gaStartTime = Date.now();
return new Promise((resolve, reject) => {
axios
.post(lbrynetUri, {
method: 'file_list',
params: {
outpoint,
},
})
.then(response => {
sendGATimingEvent('lbrynet', 'getFileList', 'FILE_LIST', gaStartTime, Date.now());
handleLbrynetResponse(response, resolve, reject);
})
.catch(error => {
reject(error);
});
});
},
async abandonClaim({ outpoint }) {
logger.debug(`lbryApi >> Abandon claim "${outpoint}"`);
const gaStartTime = Date.now();
const [txid, nout] = outpoint.split(':');
try {
const abandon = await axios.post(lbrynetUri, {
method: 'claim_abandon',
params: { claim_id: claimId },
method: 'stream_abandon',
params: { txid: txid, nout: Number(nout) },
});
sendGATimingEvent('lbrynet', 'abandonClaim', 'ABANDON_CLAIM', gaStartTime, Date.now());
return abandon.data;
@ -93,7 +114,7 @@ module.exports = {
axios
.post(lbrynetUri, {
method: 'resolve',
params: { uri },
params: { urls: uri },
})
.then(({ data }) => {
sendGATimingEvent('lbrynet', 'resolveUri', 'RESOLVE', gaStartTime, Date.now());
@ -133,7 +154,7 @@ module.exports = {
Date.now()
);
if (data.result) {
resolve(data.result.download_directory);
resolve(data.result.download_dir);
} else {
return new Error(
'Successfully connected to lbry daemon, but unable to retrieve the download directory.'
@ -152,10 +173,10 @@ module.exports = {
return new Promise((resolve, reject) => {
axios
.post(lbrynetUri, {
method: 'channel_new',
method: 'channel_create',
params: {
channel_name: name,
amount: publishing.channelClaimBidAmount,
name: name,
bid: publishing.channelClaimBidAmount,
},
})
.then(response => {

View file

@ -1,28 +1,52 @@
const fs = require('fs');
const logger = require('winston');
const { publishing: { publishingChannelWhitelist } } = require('@config/siteConfig');
const {
publishing: { publishingChannelWhitelist },
} = require('@config/siteConfig');
const ipBanFile = './site/config/ipBan.txt';
const forbiddenMessage = '<h1>Forbidden</h1>If you are seeing this by mistake, please contact us using <a href="https://chat.lbry.io/">https://chat.lbry.io/</a>';
const ipWhitelist = './site/config/ipWhitelist.txt';
const forbiddenMessage =
'<h1>Forbidden</h1>If you are seeing this by mistake, please contact us using <a href="https://chat.lbry.com/">https://chat.lbry.com/</a>';
const maxPublishesInTenMinutes = 20;
let ipCounts = {};
let blockedAddresses = [];
let whitelistedAddresses = [];
if (fs.existsSync(ipBanFile)) {
const lineReader = require('readline').createInterface({
input: require('fs').createReadStream(ipBanFile),
});
lineReader.on('line', (line) => {
lineReader.on('line', line => {
if (line && line !== '') {
blockedAddresses.push(line);
}
});
}
// If a file called ipWhitelist.txt exists
// Please comment above each whitelisted IP why/who/when etc
// # Jim because he's awesome - January 2018
if (fs.existsSync(ipWhitelist)) {
const lineReader = require('readline').createInterface({
input: require('fs').createReadStream(ipWhitelist),
});
lineReader.on('line', line => {
if (line && line !== '' && line[0] !== '#') {
whitelistedAddresses.push(line);
}
});
}
const autoblockPublishMiddleware = (req, res, next) => {
let ip = (req.headers['x-forwarded-for'] || req.connection.remoteAddress).split(/,\s?/)[0];
if (whitelistedAddresses.indexOf(ip) !== -1) {
next();
return;
}
if (blockedAddresses.indexOf(ip) !== -1) {
res.status(403).send(forbiddenMessage);
res.end();
@ -30,7 +54,7 @@ const autoblockPublishMiddleware = (req, res, next) => {
return;
}
let count = ipCounts[ip] = (ipCounts[ip] || 0) + 1;
let count = (ipCounts[ip] = (ipCounts[ip] || 0) + 1);
setTimeout(() => {
if (ipCounts[ip]) {
@ -41,7 +65,7 @@ const autoblockPublishMiddleware = (req, res, next) => {
}
}, 600000 /* 10 minute retainer */);
if (count === 10) {
if (count === maxPublishesInTenMinutes) {
logger.error(`Banning IP: ${ip}`);
blockedAddresses.push(ip);
res.status(403).send(forbiddenMessage);

View file

@ -0,0 +1,13 @@
const httpContext = require('express-http-context');
function setRouteDataInContextMiddleware(routePath, routeData) {
return function(req, res, next) {
httpContext.set('routePath', routePath);
httpContext.set('routeData', routeData);
next();
};
}
module.exports = {
setRouteDataInContextMiddleware,
};

View file

@ -388,7 +388,9 @@ module.exports = (sequelize, { STRING, BOOLEAN, INTEGER, TEXT, DECIMAL }) => {
return new Promise((resolve, reject) => {
this.fetchClaim(name, claimId)
.then(claim => {
logger.info('resolveClaim claims:', claim);
logger.debug(
`resolveClaim: ${name}, ${claimId}, -> certificateId: ${claim && claim.certificateId}`
);
if (
serveOnlyApproved &&
!isApprovedChannel({ longId: claim.certificateId }, approvedChannels)

View file

@ -1,42 +1,42 @@
const db = require('../index.js');
const createClaimRecordDataAfterPublish = (certificateId, channelName, fileName, fileType, publishParams, publishResults) => {
const createClaimRecordDataAfterPublish = (
certificateId,
channelName,
fileName,
fileType,
publishParams,
publishResultsOutput
) => {
const {
name,
metadata: {
title,
description,
thumbnail,
nsfw,
},
title,
description,
thumbnail,
nsfw,
claim_address: address,
bid: amount,
} = publishParams;
const {
claim_id: claimId,
txid,
nout,
} = publishResults;
const { claim_id: claimId, txid, nout } = publishResultsOutput;
return db.Claim.getCurrentHeight()
.then(height => {
return {
name,
claimId,
title,
description,
address,
thumbnail,
outpoint : `${txid}:${nout}`,
height,
contentType: fileType,
nsfw,
amount,
certificateId,
channelName,
};
});
return db.Claim.getCurrentHeight().then(height => {
return {
name,
claimId,
title,
description,
address,
thumbnail,
outpoint: `${txid}:${nout}`,
height,
contentType: fileType,
nsfw,
amount,
certificateId,
channelName,
};
});
};
module.exports = {

View file

@ -1,22 +1,11 @@
const getMediaDimensions = require('../../utils/getMediaDimensions.js');
async function createFileRecordDataAfterGet (resolveResult, getResult) {
const {
name,
claimId,
outpoint,
contentType: fileType,
} = resolveResult;
async function createFileRecordDataAfterGet(resolveResult, getResult) {
const { name, claimId, outpoint, contentType: fileType } = resolveResult;
const {
file_name: fileName,
download_path: filePath,
} = getResult;
const { file_name: fileName, download_path: filePath } = getResult;
const {
height: fileHeight,
width: fileWidth,
} = await getMediaDimensions(fileType, filePath);
const { height: fileHeight, width: fileWidth } = await getMediaDimensions(fileType, filePath);
return {
name,
@ -30,22 +19,17 @@ async function createFileRecordDataAfterGet (resolveResult, getResult) {
};
}
async function createFileRecordDataAfterPublish (fileName, fileType, publishParams, publishResults) {
const {
name,
file_path: filePath,
} = publishParams;
async function createFileRecordDataAfterPublish(
fileName,
fileType,
publishParams,
publishResultsOutput
) {
const { name, file_path: filePath } = publishParams;
const {
claim_id: claimId,
txid,
nout,
} = publishResults;
const { claim_id: claimId, txid, nout } = publishResultsOutput;
const {
height: fileHeight,
width: fileWidth,
} = await getMediaDimensions(fileType, filePath);
const { height: fileHeight, width: fileWidth } = await getMediaDimensions(fileType, filePath);
return {
name,

View file

@ -8,6 +8,7 @@ import createSagaMiddleware from 'redux-saga';
import { call } from 'redux-saga/effects';
import Helmet from 'react-helmet';
import * as httpContext from 'express-http-context';
import logger from 'winston';
import Reducers from '@reducers';
import GAListener from '@components/GAListener';
@ -92,6 +93,7 @@ export default (req, res) => {
const preloadedState = store.getState();
// send the rendered page back to the client
res.send(renderFullPage(helmet, html, preloadedState));
};
@ -121,8 +123,8 @@ export default (req, res) => {
}
if (canonicalUrl && canonicalUrl !== req.originalUrl) {
console.log(`redirecting ${req.originalUrl} to ${canonicalUrl}`);
res.redirect(canonicalUrl);
logger.verbose(`redirecting ${req.originalUrl} to ${canonicalUrl}`);
return res.redirect(canonicalUrl);
}
return renderPage(store);

View file

@ -3,7 +3,7 @@ const path = require('path');
const bundlePath = path.resolve('./public/bundle/bundle.js');
const bundleHash = md5File.sync(bundlePath);
const shortBundleHash = bundleHash.substring(0,4);
const shortBundleHash = bundleHash.substring(0, 4);
module.exports = (helmet, html, preloadedState) => {
// take the html and preloadedState and return the full page
@ -14,6 +14,7 @@ module.exports = (helmet, html, preloadedState) => {
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1, user-scalable=no">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<meta name="google-site-verification" content="U3240KfVplLZSRCcOHxGuDFQO6eVUXKeFsSD2WJvdLo" />
<!--helmet-->
${helmet.title.toString()}
${helmet.meta.toString()}
@ -27,7 +28,10 @@ module.exports = (helmet, html, preloadedState) => {
<body>
<div id="react-app">${html}</div>
<script>
window.__PRELOADED_STATE__ = ${JSON.stringify(preloadedState).replace(/</g, '\\\u003c')}
window.__PRELOADED_STATE__ = ${JSON.stringify(preloadedState).replace(
/</g,
'\\\u003c'
)}
</script>
<script src="/bundle/bundle.js?${shortBundleHash}"></script>
</body>

View file

@ -25,6 +25,7 @@ const publishingConfig = require('../../controllers/api/config/site/publishing')
const getTorList = require('../../controllers/api/tor');
const getBlockedList = require('../../controllers/api/blocked');
const getOEmbedData = require('../../controllers/api/oEmbed');
const cors = require('cors');
export default {
// homepage routes
@ -43,10 +44,10 @@ export default {
'/api/claim/data/:claimName/:claimId' : { controller: [ torCheckMiddleware, claimData ] },
'/api/claim/get/:name/:claimId' : { controller: [ torCheckMiddleware, claimGet ] },
'/api/claim/list/:name' : { controller: [ torCheckMiddleware, claimList ] },
'/api/claim/long-id' : { method: 'post', controller: [ torCheckMiddleware, claimLongId ] }, // note: should be a 'get'
'/api/claim/publish' : { method: 'post', controller: [ torCheckMiddleware, autoblockPublishMiddleware, multipartMiddleware, autoblockPublishBodyMiddleware, claimPublish ] },
'/api/claim/update' : { method: 'post', controller: [ torCheckMiddleware, multipartMiddleware, claimUpdate ] },
'/api/claim/abandon' : { method: 'post', controller: [ torCheckMiddleware, multipartMiddleware, claimAbandon ] },
'/api/claim/long-id' : { method: 'post', controller: [ cors(), torCheckMiddleware, claimLongId ] }, // note: should be a 'get'
'/api/claim/publish' : { method: 'post', controller: [ cors(), torCheckMiddleware, autoblockPublishMiddleware, multipartMiddleware, autoblockPublishBodyMiddleware, claimPublish ] },
'/api/claim/update' : { method: 'post', controller: [ cors(), torCheckMiddleware, multipartMiddleware, claimUpdate ] },
'/api/claim/abandon' : { method: 'post', controller: [ cors(), torCheckMiddleware, multipartMiddleware, claimAbandon ] },
'/api/claim/resolve/:name/:claimId' : { controller: [ torCheckMiddleware, claimResolve ] },
'/api/claim/short-id/:longId/:name' : { controller: [ torCheckMiddleware, claimShortId ] },
'/api/claim/views/:claimId' : { controller: [ torCheckMiddleware, claimViews ] },
@ -55,7 +56,7 @@ export default {
// user routes
'/api/user/password/' : { method: 'put', controller: [ torCheckMiddleware, userPassword ] },
// configs
'/api/config/site/publishing' : { controller: [ torCheckMiddleware, publishingConfig ] },
'/api/config/site/publishing' : { controller: [ cors(), torCheckMiddleware, publishingConfig ] },
// tor
'/api/tor' : { controller: [ torCheckMiddleware, getTorList ] },
// blocked

View file

@ -2,6 +2,6 @@ module.exports = {
...require('./pages').default,
...require('./api').default,
...require('./auth').default,
...require('./assets').default,
// ...require('./assets').default,
...require('./fallback').default,
};

View file

@ -2,7 +2,9 @@ const PassportLocalStrategy = require('passport-local').Strategy;
const { createChannel } = require('../../lbrynet');
const logger = require('winston');
const db = require('../../models');
const { publishing: { closedRegistration } } = require('@config/siteConfig');
const {
publishing: { closedRegistration },
} = require('@config/siteConfig');
module.exports = new PassportLocalStrategy(
{
@ -28,19 +30,23 @@ module.exports = new PassportLocalStrategy(
logger.verbose('userData >', userData);
// create user record
const channelData = {
channelName : `@${username}`,
channelClaimId: tx.claim_id,
channelName: `@${username}`,
channelClaimId: tx.outputs[0].claim_id,
};
logger.verbose('channelData >', channelData);
// create certificate record
const certificateData = {
claimId: tx.claim_id,
name : `@${username}`,
claimId: tx.outputs[0].claim_id,
name: `@${username}`,
// address,
};
logger.verbose('certificateData >', certificateData);
// save user and certificate to db
return Promise.all([db.User.create(userData), db.Channel.create(channelData), db.Certificate.create(certificateData)]);
return Promise.all([
db.User.create(userData),
db.Channel.create(channelData),
db.Certificate.create(certificateData),
]);
})
.then(([newUser, newChannel, newCertificate]) => {
logger.verbose('user and certificate successfully created');
@ -54,7 +60,10 @@ module.exports = new PassportLocalStrategy(
})
.then(() => {
logger.verbose('user and certificate successfully associated');
return db.Certificate.getShortChannelIdFromLongChannelId(userInfo.channelClaimId, userInfo.channelName);
return db.Certificate.getShortChannelIdFromLongChannelId(
userInfo.channelClaimId,
userInfo.channelName
);
})
.then(shortChannelId => {
userInfo['shortChannelId'] = shortChannelId;

View file

@ -0,0 +1,32 @@
const { getFileListFileByOutpoint } = require('server/lbrynet');
const logger = require('winston');
function delay(t) {
return new Promise(function(resolve) {
setTimeout(resolve, t);
});
}
const awaitFileSize = (outpoint, size, interval, timeout) => {
logger.debug('awaitFileSize');
let start = Date.now();
function checkFileList() {
logger.debug('checkFileList');
return getFileListFileByOutpoint(outpoint).then(result => {
const { items: fileInfos } = result;
const fileInfo = fileInfos[0];
logger.debug('File List Result', fileInfo);
if (fileInfo.completed === true || fileInfo.written_bytes > size) {
logger.debug('FILE READY');
return 'ready';
} else if (timeout !== 0 && Date.now() - start > timeout) {
throw new Error('Timeout on awaitFileSize');
} else {
return delay(interval).then(checkFileList);
}
});
}
return checkFileList();
};
module.exports = awaitFileSize;

View file

@ -3,7 +3,7 @@ const logger = require('winston');
const config = require('@config/loggerConfig');
const { logLevel } = config;
function configureLogging () {
function configureLogging() {
logger.info('configuring winston logger...');
if (!config) {
return logger.warn('No logger config found');
@ -14,12 +14,12 @@ function configureLogging () {
// configure the winston logger
logger.configure({
transports: [
new (logger.transports.Console)({
level : logLevel || 'debug',
timestamp : false,
colorize : true,
prettyPrint : true,
handleExceptions : true,
new logger.transports.Console({
level: logLevel || 'debug',
timestamp: true,
colorize: true,
prettyPrint: true,
handleExceptions: true,
humanReadableUnhandledException: true,
}),
],

View file

@ -59,5 +59,8 @@ module.exports = async (data, chName = null, chShortId = null) => {
host,
pending: Boolean(dataVals.height === 0),
blocked: blocked,
license: dataVals.license,
licenseUrl: dataVals.license_url,
transactionTime: dataVals.transaction_time,
};
};

View file

@ -0,0 +1,24 @@
const {
serving: { dynamicFileSizing },
} = require('@config/siteConfig');
const { maxDimension } = dynamicFileSizing;
const isValidQueryObj = queryObj => {
let {
h: cHeight = null,
w: cWidth = null,
t: transform = null,
x: xOrigin = null,
y: yOrigin = null,
} = queryObj;
return (
((cHeight <= maxDimension && cHeight > 0) || cHeight === null) &&
((cWidth <= maxDimension && cWidth > 0) || cWidth === null) &&
(transform === null || transform === 'crop' || transform === 'stretch') &&
((xOrigin <= maxDimension && xOrigin >= 0) || xOrigin === null) &&
((yOrigin <= maxDimension && yOrigin >= 0) || yOrigin === null)
);
};
module.exports = isValidQueryObj;

View file

@ -39,6 +39,9 @@ module.exports = () => {
moduleAliases['@clientutils'] = resolve(`${DEFAULT_ROOT}/utils`);
// moduleAliases['@serverutils'] = resolve('server/utils');
// aliases for constants
moduleAliases['@clientConstants'] = resolve(`${DEFAULT_ROOT}/constants`);
// create specific aliases for locally defined components in the following folders
moduleAliases = addAliasesForCustomComponentFolder('containers', moduleAliases);
moduleAliases = addAliasesForCustomComponentFolder('components', moduleAliases);
@ -53,7 +56,6 @@ module.exports = () => {
moduleAliases['@sagas'] = resolve(`${DEFAULT_ROOT}/sagas`);
moduleAliases['@app'] = resolve(`${DEFAULT_ROOT}/app.js`);
// return finished aliases
return moduleAliases;
};