cuts release from staging #905

Merged
jessopb merged 31 commits from staging into release 2019-02-11 02:48:48 +01:00
54 changed files with 2725 additions and 1475 deletions

5
.prettierrc.json Normal file
View file

@ -0,0 +1,5 @@
{
"trailingComma": "es5",
"printWidth": 100,
"singleQuote": true
}

View file

@ -85,6 +85,8 @@ $ npm run configure
#### Build & start the app #### Build & start the app
``` ```
$ npm run build
$ npm run start $ npm run start
``` ```
@ -114,7 +116,7 @@ curl -F 'name=MyPictureName' -F 'file=@/path/to/myPicture.jpeg' https://spee.ch/
``` ```
Parameters: Parameters:
* `name` (required) * `name` (required, must be unique across the instance)
* `file` (required) (must be type .mp4, .jpeg, .jpg, .gif, or .png) * `file` (required) (must be type .mp4, .jpeg, .jpg, .gif, or .png)
* `nsfw` (optional) * `nsfw` (optional)
* `license` (optional) * `license` (optional)
@ -262,4 +264,4 @@ We take security seriously. Please contact security@lbry.io regarding any securi
## Contact ## Contact
The primary contact for this project is [@skhameneh](mailto:shawn@lbry.io). The primary contact for this project is [@jessopb](mailto:jessop@lbry.io).

View file

@ -23,6 +23,7 @@
"publishing": { "publishing": {
"primaryClaimAddress": null, "primaryClaimAddress": null,
"uploadDirectory": "/home/lbry/Uploads", "uploadDirectory": "/home/lbry/Uploads",
"lbrynetHome": "/home/lbry",
"thumbnailChannel": null, "thumbnailChannel": null,
"thumbnailChannelId": null, "thumbnailChannelId": null,
"additionalClaimAddresses": [], "additionalClaimAddresses": [],
@ -39,6 +40,18 @@
"maxSizeGif": 50000000, "maxSizeGif": 50000000,
"maxSizeVideo": 50000000 "maxSizeVideo": 50000000
}, },
"serving": {
"markdownSettings": {
"skipHtml": true,
"privilegedDisallowedTypesDescriptions": ["Image"],
"privilegedDisallowedTypesMain": [],
"publicDisallowedTypesDescriptions": ["Image"],
"publicDisallowedTypesMain": []
},
"customFileExtensions": {
"application/example-type": "example"
}
},
"startup": { "startup": {
"performChecks": true, "performChecks": true,
"performUpdates": true "performUpdates": true

View file

@ -0,0 +1,7 @@
.asset-blocked__image {
width: 100%;
}
.asset-blocked__text {
width: 100%;
}

View file

@ -1,29 +1,28 @@
.asset-main { .asset-main {
height: 75vh;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
} }
.asset-document {
width: 100%;
padding: $thin-padding;
height: fit-content;
box-sizing: border-box;
}
.asset-display { .asset-display {
display: flex; height: fit-content;
min-height: 50vh width: fit-content;
} }
.asset-title { .asset-title {
padding-bottom: $thin-padding; padding-bottom: $thin-padding;
text-align: center; text-align: center;
@media (min-width: $break-point-mobile) {
padding-top: $secondary-padding;
}
} }
.asset-image, .asset-video { .asset-image, .asset-video {
max-height: 100%; max-height: 95vh;
max-width: 100%; max-width: 95vw;
margin-left: auto;
margin-right: auto;
object-fit: contain; object-fit: contain;
object-position: center; object-position: center;
} }
@ -110,7 +109,7 @@
$asset-info-width: 1000px; $asset-info-width: 1000px;
max-width: $asset-info-width; max-width: $asset-info-width;
margin: $primary-padding; margin: $primary-padding;
max-width: 100%; width: 100%;
@media (max-width: $break-point-tablet) { @media (max-width: $break-point-tablet) {
margin: $primary-padding $secondary-padding; margin: $primary-padding $secondary-padding;
@ -122,7 +121,7 @@
} }
.asset-footer { .asset-footer {
border-top: 1px solid $grey-border; border-top: $subtle-border;
padding-top: $primary-padding; padding-top: $primary-padding;
margin-top: $primary-padding; margin-top: $primary-padding;
color: $grey; color: $grey;

View file

@ -1,28 +1,48 @@
.asset-preview { .asset-preview {
position: relative; position: relative;
background: $card-color;
padding: $thin-padding;
color: $text-color;
width: 240px;
border: $subtle-border;
height: 280px;
&:hover {
border: 1px solid $highlight-border-color;
color: #000000;
}
}
.asset-preview__label {
display: flex;
flex-direction: column;
justify-content: space-between;
height: 7.3em;
}
.asset-preview__label-text {
height: 4.5em;
overflow: hidden;
text-overflow: ellipsis;
box-sizing: border-box;
} }
.asset-preview__blocked { .asset-preview__blocked {
box-sizing: border-box; box-sizing: border-box;
background: black; background: black;
color: white; color: white;
height: 80%; height: 64%;
padding: 5px; padding: $thin-padding;
//remove margin-bottom after mystery 5px on wrapper is gone. margin-bottom: $thin-padding;
margin-bottom: 5px;
} }
.asset-preview__image { .asset-preview__image {
width : 100%; width : 240px;
height : 180px;
overflow: hidden;
object-fit: cover;
padding: 0; padding: 0;
margin : 0; margin : 0;
} box-sizing: border-box;
.asset-preview__video {
cursor: pointer;
background-color: #ffffff;
width: 100%;
position: relative;
} }
h3.asset-preview__title { h3.asset-preview__title {
@ -30,34 +50,6 @@ h3.asset-preview__title {
text-overflow: ellipsis; text-overflow: ellipsis;
word-wrap: break-word; word-wrap: break-word;
overflow: hidden; overflow: hidden;
line-height: 1em; max-height: 4em;
max-height: 2em; font-size: $text-large;
}
.asset-preview__play-wrapper {
border: 0px;
padding: 0px;
margin: auto;
position: relative;
}
.asset-preview__play-overlay {
padding: 0;
border: 0;
position: absolute;
opacity: 0.80;
height: 25%;
top: 37.5%;
left: 0;
right: 0;
bottom: 0;
z-index: 1000;
margin: 0 auto;
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'%3E %3Cg stroke='black' stroke-width='2' fill='black' fill-rule='evenodd' stroke-linejoin='round'%3E %3Ccircle cx='30' cy='30' r='28'/%3E%3C/g%3E %3Cg stroke='white' stroke-width='1' fill='white' fill-rule='evenodd' stroke-linejoin='round'%3E %3Cpolygon points='25 19 42 30 25 41'/%3E %3C/g%3E %3C/svg%3E");
background-repeat: no-repeat;
background-position: center;
}
.asset-preview__play-wrapper:hover .asset-preview__play-overlay {
opacity: 0.2;
} }

View file

@ -1,23 +1,24 @@
.channel-claims-display { .channel-claims-display {
width: 100%; width: 100%;
display: grid; display: grid;
grid-gap: 16px; grid-gap: $thin-padding;
} align-content: space-around;
@media (min-width: $break-point-x-large) {
@media (min-width: 1040px) { grid-template-columns: 1fr 1fr 1fr 1fr 1fr;
.channel-claims-display { }
@media (min-width: $break-point-large) and (max-width: $break-point-x-large){
grid-template-columns: 1fr 1fr 1fr 1fr; grid-template-columns: 1fr 1fr 1fr 1fr;
} }
}
@media (min-width: 768px) and (max-width: 1039px) { @media (min-width: $break-point-tablet) and (max-width: $break-point-large) {
.channel-claims-display { grid-template-columns: 1fr 1fr 1fr;
}
@media (min-width: $break-point-mobile) and (max-width: $break-point-tablet) {
grid-template-columns: 1fr 1fr; grid-template-columns: 1fr 1fr;
} }
}
@media (max-width: 767px) { @media (max-width: $break-point-mobile) {
.channel-claims-display {
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }
} }

View file

@ -4,7 +4,7 @@
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
cursor: pointer; cursor: pointer;
border: 1px solid $grey-border; border: 1px solid $subtle-border-color;
border-radius: 6px; border-radius: 6px;
.click-to-copy { .click-to-copy {
border: none; border: none;
@ -17,7 +17,7 @@
line-height: 20px; line-height: 20px;
letter-spacing: 0; letter-spacing: 0;
font-family: monospace; font-family: monospace;
border-right: 1px solid $grey-border; border-right: 1px solid $subtle-border-color;
} }
.icon-wrap { .icon-wrap {
width: 30px; width: 30px;

View file

@ -5,15 +5,18 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
position: relative; position: relative;
width: 100%;
box-sizing: border-box;
padding: 0px;
} }
.dropzone { .dropzone {
border: 2px dashed #9b9b9b; border: 2px dashed $drop-zone-border-color;
// fill the parent flex container // fill the parent flex container
flex: 1 0 auto; flex: 1 0 auto;
// be a flex container for children // be a flex container for children
display: flex; display: flex;
padding: 1em; padding: $thin-padding;
-webkit-flex-direction: column; -webkit-flex-direction: column;
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
@ -22,7 +25,7 @@
} }
.dropzone:hover, .dropzone--active { .dropzone:hover, .dropzone--active {
border: 2px dashed #4156C5; border: 2px dashed $drop-zone-border-hover;
cursor: pointer; cursor: pointer;
} }

View file

@ -1,55 +1,55 @@
.horizontal-split { .horizontal-split {
max-width: $width-content-constrained; max-width: $width-content-constrained;
width: 100%; width: 100%;
margin-left: auto;
margin-right: auto;
display : flex; display : flex;
flex-direction : row; flex-direction : row;
justify-content: space-between; justify-content: center;
box-sizing: border-box;
&.horizontal-split--mobile-collapse { &.horizontal-split--mobile-collapse {
@media (max-width: $break-point-mobile) { @media (max-width: $break-point-tablet) {
flex-direction: column; flex-direction: column;
.horizontal-split__column { .horizontal-split__column {
width: 100%;
} }
.horizontal-split__column--left {
padding-top: $thin-padding;
}
.horizontal-split__column--right { .horizontal-split__column--right {
padding-left: 0; padding-top: $thin-padding;
padding-top: $secondary-padding;
} }
} }
} }
}; };
.horizontal-split__column { .horizontal-split__column {
width: 50%; display : flex;
flex: 1 0 auto; flex: 1 1 auto;
box-sizing: border-box; box-sizing: border-box;
width: 100%;
} }
.horizontal-split__column--left { .horizontal-split__column--left {
padding-right: $primary-padding; padding: $tertiary-padding;
@media (max-width: $break-point-mobile) { @media (max-width: $break-point-tablet) {
padding-right: $thin-padding; padding-left: 0px;
padding-right: 0px;
} }
} }
.horizontal-split__column--right { .horizontal-split__column--right {
padding-left: $primary-padding;
@media (max-width: $break-point-mobile) { padding: $tertiary-padding;
padding-left: $thin-padding;
@media (max-width: $break-point-tablet) {
padding-left: 0px;
padding-right: 0px;
} }
} }
@media (max-width: $break-point-tablet) { @media (max-width: $break-point-tablet) {
.horizontal-split__column { .horizontal-split__column {
display : flex;
flex-direction : column;
justify-content: space-between; justify-content: space-between;
}; };

View file

@ -2,13 +2,17 @@
padding-top: $thin-padding; padding-top: $thin-padding;
padding-bottom: $thin-padding; padding-bottom: $thin-padding;
display: inline-block; display: inline-block;
font-size: 12px; font-weight: bold;
font-size: $text-medium;
width: 100%;
box-sizing: border-box;
} }
.label-radio { .label-radio {
padding-left: $thin-padding; padding-left: $thin-padding;
padding-right: $thin-padding; padding-right: $thin-padding;
cursor: pointer; cursor: pointer;
font-weight: bold;
} }
@media (max-width: $break-point-tablet ) { @media (max-width: $break-point-tablet ) {

141
client/scss/_markdown.scss Normal file
View file

@ -0,0 +1,141 @@
.markdown-preview {
// Headers
h1,
h2,
h3,
h4,
h5,
h6 {
font-size: inherit;
font-weight: 600;
margin-bottom: $tertiary-padding;
padding-top: $tertiary-padding;
}
// Paragraphs
p {
font-size: 1.15rem;
margin-bottom: $tertiary-padding;
white-space: pre-line;
svg {
width: 1rem;
height: 1rem;
margin-left: 0.2rem;
position: relative;
top: 1px;
}
}
blockquote {
border-radius: 8px;
background: $blockquote-background;
padding: $tertiary-padding;
min-width: 60%;
}
// Strikethrough text
del {
}
// Tables
table {
width: 100%;
margin-bottom: 1.2rem;
background-color: $base-color;
border-spacing: 0;
border: .5px solid $chrome-color;
tr {
td,
th,
td:first-of-type,
th:first-of-type,
td:last-of-type,
th:last-of-type {
padding: $thin-padding $tertiary-padding;
text-overflow: ellipsis;
}
td:last-of-type {
text-align: right;
}
th {
background: $chrome-color;
}
}
tr:nth-child(even){
background: $chrome-color;
}
}
// Image
img {
margin-bottom: $tertiary-padding;
margin-top: $tertiary-padding;
padding: $secondary-padding;
object-fit: scale-down;
max-width: 100%;
border: $subtle-border;
box-sizing: border-box;
}
// Horizontal Rule
hr {
width: 100%;
height: 1px;
background-color: $base-color;
margin-bottom: 2rem;
position: relative;
top: 1rem;
html[data-theme='dark'] & {
background-color: rgba($base-color, 0.2);
}
}
// Code
pre {
white-space: normal;
}
code {
margin-bottom: $tertiary-padding;
padding: $tertiary-padding;
background-color: $subtle-border-color;
color: $text-color;
display: block;
font-family: Consolas, 'Lucida Console', 'Source Sans', monospace;
}
a {
color: $primary-color;
display: inline-block;
}
// Lists
ul,
ol {
margin-bottom: $thin-padding;
> li {
list-style-position: outside;
}
}
ul {
list-style: initial;
}
li {
margin-left: $primary-padding;
p {
display: inline-block;
}
}
}

View file

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

View file

@ -2,26 +2,24 @@
flex: 1 0 auto; flex: 1 0 auto;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center;
max-width: 100%;
.content { .content {
flex: 1 0 auto; flex: 1 0 auto;
display: flex; display: flex;
-webkit-flex-direction: column; -webkit-flex-direction: column;
flex-direction: column; flex-direction: column;
margin: $secondary-padding; width: 100%;
} align-items: center;
} box-sizing: border-box;
background: $base-color;
@media (max-width: $break-point-tablet) {
.page-layout .content { margin: $tertiary-padding; } @media (min-width: $break-point-tablet) {
} padding: $primary-padding;
}
@media (max-width: $break-point-mobile) {
max-width: calc(100% - 30px); @media (max-width: $break-point-tablet) {
} padding: $tertiary-padding;
}
//below should take some styles from _text.scss and probably elsewhere and become "markdown" or "rich" styles
.page-layout {
p {
margin-bottom: $tertiary-padding;
} }
} }

View file

@ -4,21 +4,21 @@
.row-labeled { .row-labeled {
display: flex; display: flex;
flex-direction: row; flex-direction: column;
flex-wrap: nowrap; flex-wrap: nowrap;
justify-content: flex-start; justify-content: flex-start;
padding-bottom: $tertiary-padding; padding-bottom: $tertiary-padding;
} }
.row-labeled-label { .row-labeled-label {
width: 30%; width: 100%;
display: flex; display: flex;
align-items: center; align-items: center;
flex: 1; flex: 1;
} }
.row-labeled-content { .row-labeled-content {
align-self: center; align-self: center;
width: 70%; width: 100%;
} }
@media (max-width: $break-point-tablet ) { @media (max-width: $break-point-tablet ) {

View file

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

View file

@ -7,7 +7,11 @@ h1, h2, h3, h4, p {
body { body {
color: $text-color; color: $text-color;
font-family: 'Circular', serif; font-family: 'Circular', serif;
font-size: 16px; font-size: 14px;
}
body a {
color: $primary-color;
} }
h1 { h1 {

View file

@ -1,40 +1,62 @@
$base-color: white; //backgrounds
$primary-color: #005da0; $base-color: white; //default white
$card-color: white; //default white
$chrome-color: lightgray; //default white (navbar)
$blockquote-background: #EEEEFF;
$background-color: $base-color;
//text colors
$primary-color: #005da0; //link default light blue #005da0
$secondary-color: $primary-color; $secondary-color: $primary-color;
$text-color: #333;
$success-color: green; $success-color: green;
$failure-color: red; $failure-color: red;
$grey: #9095A5; $grey: #9095A5;
$help-color: $grey; $blockquote-text: $text-color;
$grey-border: #DDDFE4;
$shadow-color: rgba(169, 173, 186, 0.2);
//borders and highlights
$grey: #9095A5;
$help-color: $grey;
$subtle-border-color: #DDD;
$highlight-border-color: #333;
$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
$drop-zone-border-color: #9b9b9b; //default #9b9b9b
$drop-zone-border-hover: #4156C5; //default #4156C5
//padding
$primary-padding: 3em; $primary-padding: 3em;
$secondary-padding: 2em; $secondary-padding: 2em;
$tertiary-padding: 1em; $tertiary-padding: 1em;
$thin-padding: 0.3em; $thin-padding: 0.3em;
$full-width-thin-padding: calc(100% - 0.6em); $full-width-thin-padding: calc(100% - 0.6em);
$input-padding: 0.3em;
$width-content-constrained: 1000px; $width-content-constrained: 1000px;
$background-color: $base-color;
$text-color: #333;
$button-border-width: 1px; $button-border-width: 1px;
$button-border-strength: solid; $button-border-strength: solid;
$button-full-width: calc(100% - 2px); $button-full-width: calc(100% - 2px);
$input-padding: 0.3em;
$input-full-width: calc(100% - 0.6em); $input-full-width: calc(100% - 0.6em);
//text sizes
$base-font-size: 14px;
$text-xx-large: 2.5em; $text-xx-large: 2.5em;
$text-x-large: 2.0em; $text-x-large: 2.0em;
$text-large: 1.5em; $text-large: 1.5em;
$text-medium: 1.0em; $text-medium: 1.2em;
$text-small: 0.9em; $text-small: 0.9em;
$text-x-small: 0.8em; $text-x-small: 0.8em;
//@media sizes
$break-point-xx-large: 1400px; $break-point-xx-large: 1400px;
$break-point-x-large: 1290px; $break-point-x-large: 1290px;
$break-point-large: 1000px; $break-point-large: 1024px;
$break-point-tablet: 800px; $break-point-tablet: 800px;
$break-point-mobile: 500px; $break-point-mobile: 500px;
$break-point-phone: 300px;
$break-point-phone: 300px;

View file

@ -5,6 +5,7 @@
@import '~scss/_body'; @import '~scss/_body';
@import '~scss/_react-app'; @import '~scss/_react-app';
@import '~scss/_text'; @import '~scss/_text';
@import '~scss/_markdown';
@import '~scss/_link'; @import '~scss/_link';
@import '~scss/_input'; @import '~scss/_input';
@ -15,6 +16,7 @@
@import '~scss/_asset-display'; @import '~scss/_asset-display';
@import '~scss/_asset-preview'; @import '~scss/_asset-preview';
@import '~scss/_asset-blocked';
@import '~scss/_button'; @import '~scss/_button';
@import '~scss/_button-primary'; @import '~scss/_button-primary';
@import '~scss/_button-secondary'; @import '~scss/_button-secondary';

View file

@ -1,12 +1,20 @@
import React from 'react'; import React from 'react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import createCanonicalLink from '../../../../utils/createCanonicalLink'; import createCanonicalLink from '@globalutils/createCanonicalLink';
import * as Icon from 'react-feather';
const AssetPreview = ({ defaultThumbnail, claimData }) => { const AssetPreview = ({ defaultThumbnail, claimData }) => {
const {name, fileExt, contentType, thumbnail, title, blocked} = claimData; const {name, fileExt, contentType, thumbnail, title, blocked} = claimData;
const showUrl = createCanonicalLink({asset: {...claimData}}); const showUrl = createCanonicalLink({asset: {...claimData}});
const embedUrl = `${showUrl}.${fileExt}`; const embedUrl = `${showUrl}.${fileExt}`;
/*
we'll be assigning media icon based on supported type / mime types
*/
const media = contentType.split('/')[0];
/*
make sure thumb has the right url
*/
const thumb = media === 'image' ? embedUrl : thumbnail;
/* /*
This blocked section shouldn't be necessary after pagination is reworked, This blocked section shouldn't be necessary after pagination is reworked,
though it might be useful for channel_mine situations. though it might be useful for channel_mine situations.
@ -16,45 +24,39 @@ const AssetPreview = ({ defaultThumbnail, claimData }) => {
return ( return (
<div className='asset-preview'> <div className='asset-preview'>
<div className='asset-preview__blocked'> <div className='asset-preview__blocked'>
<h3>Error 451</h3> <p>Error 451</p>
<p>This content is blocked for legal reasons.</p> <p>This content is blocked for legal reasons.</p>
</div> </div>
<h3 className='asset-preview__title'>Blocked Content</h3> <div className={'asset-preview__label'}>
<div className={'asset-preview__label-text'}>
<p className='asset-preview__title text--medium'>Blocked Content</p>
</div>
</div>
</div> </div>
); );
} else { } else {
switch (contentType) {
case 'image/jpeg':
case 'image/jpg':
case 'image/png':
case 'image/gif':
return ( return (
<Link to={showUrl} className='asset-preview'> <Link to={showUrl} className='asset-preview'>
<img <img
className={'asset-preview__image'} className={'asset-preview__image'}
src={embedUrl} src={thumb || defaultThumbnail}
alt={name} alt={name}
/> />
<h3 className='asset-preview__title'>{title}</h3> <div className={'asset-preview__label'}>
</Link> <div className={'asset-preview__label-text'}>
); <p className='asset-preview__title text--medium'>{title}</p>
case 'video/mp4': </div>
return ( <div className={'asset-preview__label-info'}>
<Link to={showUrl} className='asset-preview'> <div className={'text--medium'}>
<div className='asset-preview__play-wrapper'> { media === 'image' && <Icon.Image />}
<img { media === 'text' && <Icon.FileText />}
className={'asset-preview__video'} { media === 'video' && contentType === 'video/mp4' && <Icon.Video />}
src={thumbnail || defaultThumbnail} { media !== 'image' && media !== 'text' && contentType !== 'video/mp4' && <Icon.File />}
alt={name} </div>
/> </div>
<div className='asset-preview__play-overlay' />
</div> </div>
<h3 className='asset-preview__title'>{title}</h3>
</Link> </Link>
); );
default:
return null;
}
} }
}; };

View file

@ -1,11 +1,9 @@
import React from 'react'; import React from 'react';
// TODO: factor out longId OR implement tooltip display
const ChannelInfoDisplay = ({name, longId, shortId}) => { const ChannelInfoDisplay = ({name, longId, shortId}) => {
return ( return (
<div> <div>
<h2>channel name: {name}</h2> <h2>{name}:{shortId}</h2>
<p className={'text--secondary'}>full channel id: {longId}</p>
<p className={'text--secondary'}>short channel id: {shortId}</p>
</div> </div>
); );
}; };

View file

@ -0,0 +1,53 @@
import React from 'react';
import ReactMarkdown from 'react-markdown';
// TODO: get markdown settings from siteConfig
class FileViewer extends React.Component {
constructor (props) {
super(props);
/*
Prevent memory leak by closing fetch before unmount
*/
this.abortController = new AbortController();
this.abortSignal = this.abortController.signal;
this.state = {
fileLoaded: false,
fileText : '',
};
}
componentDidMount () {
const {sourceUrl} = this.props;
const signal = this.abortSignal;
fetch(sourceUrl, { signal })
.then(response => response.text())
.then((text) => {
this.setState({fileText: text});
this.setState({fileLoaded: true});
return true;
})
.catch(e => { console.log('fetch aborted on unmount ', e) });
}
componentWillUnmount () {
this.abortController.abort();
}
render () {
return (
<div className={'markdown'}>
{
this.state.fileLoaded &&
<ReactMarkdown className={'markdown-preview'} source={this.state.fileText} skipHtml />
}
{
!this.state.fileLoaded &&
<p>Loading your file...</p>
}
</div>
);
}
}
export default FileViewer;

View file

@ -1,16 +1,13 @@
import React from 'react'; import React from 'react';
import createCanonicalLink from '../../../../utils/createCanonicalLink'; import createCanonicalLink from '@globalutils/createCanonicalLink';
import HorizontalSplit from '@components/HorizontalSplit'; import HorizontalSplit from '@components/HorizontalSplit';
/*
This component shouldn't be necessary after pagination is reworked,
though it might be useful for channel_mine situations.
*/
class BlockedLeft extends React.PureComponent { class BlockedLeft extends React.PureComponent {
render () { render () {
return ( return (
<React.Fragment> <div>
<img className='asset-image' src={'https://upload.wikimedia.org/wikipedia/commons/archive/a/af/20120315000030%21OR_451.svg'} alt={'451 image'} /> <img className={'asset-blocked__image'} src={'/assets/img/451sign.svg'} alt={'451 image'} />
</React.Fragment> </div>
); );
} }
} }
@ -18,10 +15,10 @@ class BlockedLeft extends React.PureComponent {
class BlockedRight extends React.PureComponent { class BlockedRight extends React.PureComponent {
render () { render () {
return ( return (
<React.Fragment> <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>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.io/faq/dmca'} >Click here</a> for more information.</p>
</React.Fragment> </div>
); );
} }
} }
@ -53,6 +50,6 @@ class AssetBlocked extends React.Component {
</div> </div>
); );
} }
}; }
export default AssetBlocked; export default AssetBlocked;

View file

@ -2,7 +2,8 @@ import React from 'react';
import Row from '@components/Row'; import Row from '@components/Row';
import ProgressBar from '@components/ProgressBar'; import ProgressBar from '@components/ProgressBar';
import { LOCAL_CHECK, UNAVAILABLE, ERROR, AVAILABLE } from '../../constants/asset_display_states'; import { LOCAL_CHECK, UNAVAILABLE, ERROR, AVAILABLE } from '../../constants/asset_display_states';
import createCanonicalLink from '../../../../utils/createCanonicalLink'; import createCanonicalLink from '@globalutils/createCanonicalLink';
import FileViewer from '@components/FileViewer';
class AvailableContent extends React.Component { class AvailableContent extends React.Component {
render () { render () {
@ -12,6 +13,7 @@ class AvailableContent extends React.Component {
case 'image/jpg': case 'image/jpg':
case 'image/png': case 'image/png':
case 'image/gif': case 'image/gif':
case 'image/svg+xml':
return ( return (
<img <img
className='asset-image' className='asset-image'
@ -31,9 +33,18 @@ class AvailableContent extends React.Component {
<p>Your browser does not support the <code>video</code> element.</p> <p>Your browser does not support the <code>video</code> element.</p>
</video> </video>
); );
case 'text/markdown':
return (
<div className={'asset-document'}><FileViewer sourceUrl={sourceUrl}/></div>
);
default: default:
return ( return (
<p>Unsupported content type</p> <img
className='asset-image'
src={thumbnail}
alt={name}
/>
); );
} }
} }
@ -53,7 +64,7 @@ class AssetDisplay extends React.Component {
if (typeof contentType === 'string') { if (typeof contentType === 'string') {
fileExt = contentType.split('/')[1] || 'jpg'; fileExt = contentType.split('/')[1] || 'jpg';
} }
const sourceUrl = `${createCanonicalLink({ asset: asset.claimData })}.${fileExt}?${outpoint}`; const sourceUrl = `${createCanonicalLink({ asset: asset.claimData })}.${fileExt}?outpoint=${outpoint}`;
return ( return (
<div className={'asset-display'}> <div className={'asset-display'}>
{(status === LOCAL_CHECK) && {(status === LOCAL_CHECK) &&
@ -95,6 +106,6 @@ class AssetDisplay extends React.Component {
</div> </div>
); );
} }
}; }
export default AssetDisplay; export default AssetDisplay;

View file

@ -5,11 +5,11 @@ import RowLabeled from '@components/RowLabeled';
import SpaceBetween from '@components/SpaceBetween'; import SpaceBetween from '@components/SpaceBetween';
import AssetShareButtons from '@components/AssetShareButtons'; import AssetShareButtons from '@components/AssetShareButtons';
import ClickToCopy from '@components/ClickToCopy'; import ClickToCopy from '@components/ClickToCopy';
import HorizontalSplit from '@components/HorizontalSplit';
import siteConfig from '@config/siteConfig.json'; import siteConfig from '@config/siteConfig.json';
import createCanonicalLink from '../../../../utils/createCanonicalLink'; import createCanonicalLink from '@globalutils/createCanonicalLink';
import AssetInfoFooter from '../../components/AssetInfoFooter/index'; import AssetInfoFooter from '../../components/AssetInfoFooter/index';
import { createPermanentURI } from '@clientutils/createPermanentURI'; import { createPermanentURI } from '@clientutils/createPermanentURI';
import ReactMarkdown from 'react-markdown';
const { details: { host } } = siteConfig; const { details: { host } } = siteConfig;
@ -21,6 +21,9 @@ class AssetInfo extends React.Component {
const canonicalUrl = createCanonicalLink({ asset: { ...claimData, shortId: asset.shortId }}); const canonicalUrl = createCanonicalLink({ asset: { ...claimData, shortId: asset.shortId }});
const assetCanonicalUrl = `${host}${canonicalUrl}`; const assetCanonicalUrl = `${host}${canonicalUrl}`;
// Todo Issue #882 centralize all this media type detection
// Todo get markdown settings from siteConfig
const embedable = contentType.split('/')[0] === 'image' || contentType === 'video/mp4';
let channelCanonicalUrl; let channelCanonicalUrl;
if (channelName) { if (channelName) {
@ -32,21 +35,20 @@ class AssetInfo extends React.Component {
} }
return ( return (
<div className='asset-info'> <div className='asset-info'>
<HorizontalSplit { description && (
leftSide={ <RowLabeled
description && ( label={<Label value={'Description'} />}
<p className='asset-info__description'>{description}</p> content={<div className='asset-info__description'><ReactMarkdown className={'markdown-preview'} skipHtml disallowedTypes={['image']} source={description} /></div>}
) />
} )}
rightSide={
<div>
{editable && ( {editable && (
<RowLabeled <RowLabeled
label={<Label value={'Edit:'} />} label={<Label value={'Edit'} />}
content={<Link to={`/edit${canonicalUrl}`}>{name}</Link>} content={<Link to={`/edit${canonicalUrl}`}>{name}</Link>}
/> />
)} )}
{channelName && ( {channelName && (
<RowLabeled <RowLabeled
label={ label={
<Label value={'Channel'} /> <Label value={'Channel'} />
@ -94,7 +96,7 @@ class AssetInfo extends React.Component {
/> />
} }
/> />
{embedable && (
<RowLabeled <RowLabeled
label={ label={
<Label value={'Embed'} /> <Label value={'Embed'} />
@ -109,13 +111,13 @@ class AssetInfo extends React.Component {
) : ( ) : (
<ClickToCopy <ClickToCopy
id={'embed-text-image'} id={'embed-text-image'}
value={`<img src="${assetCanonicalUrl}.${fileExt}"/>`} value={`<img alt="${name}" src="${assetCanonicalUrl}.${fileExt}" />`}
/> />
)} )}
</div> </div>
} }
/> />
)}
<RowLabeled <RowLabeled
label={ label={
<Label value={'LBRY URI'} /> <Label value={'LBRY URI'} />
@ -157,8 +159,6 @@ class AssetInfo extends React.Component {
Report Report
</a> </a>
</SpaceBetween> </SpaceBetween>
</div>
} />
<AssetInfoFooter /> <AssetInfoFooter />
</div> </div>
); );

View file

@ -2,7 +2,7 @@ import React from 'react';
const AssetTitle = ({ title }) => { const AssetTitle = ({ title }) => {
return ( return (
<h1 className='asset-title'>{title}</h1> <h2 className='asset-title'>{title}</h2>
); );
}; };

View file

@ -3,11 +3,14 @@ import { selectFile, updateError, clearFile } from '../../actions/publish';
import { selectAsset } from '../../selectors/show'; import { selectAsset } from '../../selectors/show';
import View from './view'; import View from './view';
import siteConfig from '@config/siteConfig.json'; import siteConfig from '@config/siteConfig.json';
import createCanonicalLink from '../../../../utils/createCanonicalLink'; import createCanonicalLink from '@globalutils/createCanonicalLink';
const { assetDefaults: { thumbnail: defaultThumbnail } } = siteConfig; const {
assetDefaults: { thumbnail: defaultThumbnail },
} = siteConfig;
const mapStateToProps = ({ show, publish: { file, thumbnail, fileError, isUpdate } }) => { const mapStateToProps = ({ show, publish: { file, thumbnail, error, isUpdate } }) => {
const fileError = error.file;
const obj = { file, thumbnail, fileError, isUpdate }; const obj = { file, thumbnail, fileError, isUpdate };
let asset, name, claimId, fileExt, outpoint, sourceUrl; let asset, name, claimId, fileExt, outpoint, sourceUrl;
if (isUpdate) { if (isUpdate) {
@ -18,7 +21,7 @@ const mapStateToProps = ({ show, publish: { file, thumbnail, fileError, isUpdate
if (obj.fileExt === 'mp4') { if (obj.fileExt === 'mp4') {
obj.sourceUrl = claimData.thumbnail ? claimData.thumbnail : defaultThumbnail; obj.sourceUrl = claimData.thumbnail ? claimData.thumbnail : defaultThumbnail;
} else { } else {
({fileExt, outpoint} = claimData); ({ fileExt, outpoint } = claimData);
obj.sourceUrl = `${createCanonicalLink({ asset: claimData })}.${fileExt}?${outpoint}`; obj.sourceUrl = `${createCanonicalLink({ asset: claimData })}.${fileExt}?${outpoint}`;
} }
} }
@ -28,14 +31,17 @@ const mapStateToProps = ({ show, publish: { file, thumbnail, fileError, isUpdate
const mapDispatchToProps = dispatch => { const mapDispatchToProps = dispatch => {
return { return {
selectFile: (file) => { selectFile: file => {
dispatch(selectFile(file)); dispatch(selectFile(file));
}, },
setFileError: (value) => { setFileError: value => {
dispatch(clearFile()); dispatch(clearFile());
dispatch(updateError('file', value)); dispatch(updateError('file', value));
}, },
}; };
}; };
export default connect(mapStateToProps, mapDispatchToProps)(View); export default connect(
mapStateToProps,
mapDispatchToProps
)(View);

View file

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { NavLink, withRouter } from 'react-router-dom'; import { NavLink, withRouter } from 'react-router-dom';
import NavBarChannelOptionsDropdown from '@components/NavBarChannelOptionsDropdown'; import NavBarChannelOptionsDropdown from '@components/NavBarChannelOptionsDropdown';
import createCanonicalLink from '../../../../utils/createCanonicalLink'; import createCanonicalLink from '@globalutils/createCanonicalLink';
const VIEW = 'VIEW'; const VIEW = 'VIEW';
const LOGOUT = 'LOGOUT'; const LOGOUT = 'LOGOUT';

View file

@ -5,7 +5,7 @@ import PropTypes from 'prop-types';
import createPageTitle from '../../utils/createPageTitle'; import createPageTitle from '../../utils/createPageTitle';
import createMetaTags from '../../utils/createMetaTags'; import createMetaTags from '../../utils/createMetaTags';
import oEmbed from '../../utils/oEmbed.js'; import oEmbed from '../../utils/oEmbed.js';
import createCanonicalLink from '../../../../utils/createCanonicalLink'; import createCanonicalLink from '@globalutils/createCanonicalLink';
import siteConfig from '@config/siteConfig.json'; import siteConfig from '@config/siteConfig.json';
const { details: { host } } = siteConfig; const { details: { host } } = siteConfig;

View file

@ -9,6 +9,7 @@ class FaqPage extends React.Component {
pageTitle={'Frequently Asked Questions'} pageTitle={'Frequently Asked Questions'}
pageUri={'tos'} pageUri={'tos'}
> >
<Row>
<Row> <Row>
<h1>Frequently Asked Questions</h1> <h1>Frequently Asked Questions</h1>
</Row> </Row>
@ -39,6 +40,7 @@ class FaqPage extends React.Component {
<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 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.io/'>our discord channel</a> or solve one of our <a href='https://github.com/lbryio/spee.ch/issues'>github issues</a>.</p>
</Row> </Row>
</Row>
</PageLayout> </PageLayout>
); );
} }

View file

@ -38,13 +38,16 @@ class ShowAssetDetails extends React.Component {
asset={asset} asset={asset}
> >
<div className="asset-main"> <div className="asset-main">
<AssetDisplay />
<AssetTitle /> <AssetTitle />
<AssetDisplay />
<div>
<button className='collapse-button' onClick={this.collapse}> <button className='collapse-button' onClick={this.collapse}>
{this.state.closed ? <Icon.PlusCircle className='plus-icon' /> : <Icon.MinusCircle />} {this.state.closed ? <Icon.PlusCircle className='plus-icon' /> : <Icon.MinusCircle />}
</button> </button>
</div> </div>
</div>
{!this.state.closed && <AssetInfo />} {!this.state.closed && <AssetInfo />}
</PageLayout> </PageLayout>
); );
} else { } else {

View file

@ -9,6 +9,7 @@ class TosPage extends React.Component {
pageTitle={'Terms of Service'} pageTitle={'Terms of Service'}
pageUri={'tos'} pageUri={'tos'}
> >
<Row>
<Row> <Row>
<h1>Terms of Service</h1> <h1>Terms of Service</h1>
<p>Last updated: September 25, 2018</p> <p>Last updated: September 25, 2018</p>
@ -59,6 +60,7 @@ class TosPage extends React.Component {
<h3>Contact Us</h3> <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.io'>contact us</a>.</p>
</Row> </Row>
</Row>
</PageLayout> </PageLayout>
); );
} }

View file

@ -1,44 +1,44 @@
import siteConfig from '@config/siteConfig.json'; import siteConfig from '@config/siteConfig.json';
import determineContentTypeFromExtension from './determineContentTypeFromExtension'; import determineContentTypeFromExtension from './determineContentTypeFromExtension';
import createMetaTagsArray from './createMetaTagsArray'; import createMetaTagsArray from './createMetaTagsArray';
import createCanonicalLink from '../../../utils/createCanonicalLink'; import createCanonicalLink from '@globalutils/createCanonicalLink';
const { const {
details: { details: { host, title: siteTitle, twitter },
host, assetDefaults: { description: defaultDescription, thumbnail: defaultThumbnail },
title: siteTitle,
twitter,
},
assetDefaults: {
description: defaultDescription,
thumbnail: defaultThumbnail,
},
} = siteConfig; } = siteConfig;
const VIDEO = 'VIDEO'; const VIDEO = 'VIDEO';
const IMAGE = 'IMAGE'; const IMAGE = 'IMAGE';
const GIF = 'GIF'; const GIF = 'GIF';
const TEXT = 'TEXT';
const determineMediaType = (contentType) => { const determineMediaType = contentType => {
switch (contentType) { switch (contentType) {
case 'image/jpg': case 'image/jpg':
case 'image/jpeg': case 'image/jpeg':
case 'image/png': case 'image/png':
case 'image/svg+xml':
return IMAGE; return IMAGE;
case 'image/gif': case 'image/gif':
return GIF; return GIF;
case 'video/mp4': case 'video/mp4':
case 'video/webm': case 'video/webm':
return VIDEO; return VIDEO;
case 'text/markdown':
case 'text/plain':
return TEXT;
default: default:
return ''; return '';
} }
}; };
const createAssetMetaTags = (asset) => { const createAssetMetaTags = asset => {
const { claimData } = asset; const { claimData } = asset;
const { contentType } = claimData; const { contentType } = claimData;
const canonicalLink = createCanonicalLink({ asset: { ...asset.claimData, shortId: asset.shortId }}); const canonicalLink = createCanonicalLink({
asset: { ...asset.claimData, shortId: asset.shortId },
});
const showUrl = `${host}${canonicalLink}`; const showUrl = `${host}${canonicalLink}`;
const serveUrl = `${showUrl}.${claimData.fileExt}`; const serveUrl = `${showUrl}.${claimData.fileExt}`;
@ -46,16 +46,19 @@ const createAssetMetaTags = (asset) => {
const ogDescription = claimData.description || defaultDescription; const ogDescription = claimData.description || defaultDescription;
const ogThumbnailContentType = determineContentTypeFromExtension(claimData.thumbnail); const ogThumbnailContentType = determineContentTypeFromExtension(claimData.thumbnail);
const ogThumbnail = claimData.thumbnail || defaultThumbnail; const ogThumbnail = claimData.thumbnail || defaultThumbnail;
console.log('asset.claimData', asset.claimData);
// {property: 'og:title'] = ogTitle}, // {property: 'og:title'] = ogTitle},
const metaTags = { const metaTags = {
'og:title' : ogTitle, 'og:title': ogTitle,
'twitter:title' : ogTitle, 'twitter:title': ogTitle,
'og:description' : ogDescription, 'og:description': ogDescription,
'twitter:description': ogDescription, 'twitter:description': ogDescription,
'og:url' : showUrl, 'og:url': showUrl,
'og:site_name' : siteTitle, 'og:site_name': siteTitle,
'twitter:site' : twitter, 'twitter:site': twitter,
'fb:app_id' : '1371961932852223', 'fb:app_id': '1371961932852223',
}; };
if (determineMediaType(contentType) === VIDEO) { if (determineMediaType(contentType) === VIDEO) {
const videoEmbedUrl = `${host}/video-embed${canonicalLink}`; const videoEmbedUrl = `${host}/video-embed${canonicalLink}`;
@ -85,8 +88,6 @@ const createAssetMetaTags = (asset) => {
// image tags // image tags
metaTags['og:image'] = serveUrl; metaTags['og:image'] = serveUrl;
metaTags['og:image'] = serveUrl; metaTags['og:image'] = serveUrl;
metaTags['og:image:width'] = 600;
metaTags['og:image:height'] = 315;
metaTags['og:image:type'] = contentType; metaTags['og:image:type'] = contentType;
metaTags['twitter:image'] = serveUrl; metaTags['twitter:image'] = serveUrl;
} }

View file

@ -1,7 +1,7 @@
import siteConfig from '@config/siteConfig.json'; import siteConfig from '@config/siteConfig.json';
import determineContentTypeFromExtension from './determineContentTypeFromExtension'; import determineContentTypeFromExtension from './determineContentTypeFromExtension';
import createMetaTagsArray from './createMetaTagsArray'; import createMetaTagsArray from './createMetaTagsArray';
import createCanonicalLink from '../../../utils/createCanonicalLink'; import createCanonicalLink from '@globalutils/createCanonicalLink';
const { const {
details: { details: {

View file

@ -1,4 +1,4 @@
const determineContentTypeFromExtension = (thumbnail) => { const determineContentTypeFromExtension = thumbnail => {
if (thumbnail) { if (thumbnail) {
const fileExt = thumbnail.substring(thumbnail.lastIndexOf('.')); const fileExt = thumbnail.substring(thumbnail.lastIndexOf('.'));
switch (fileExt) { switch (fileExt) {
@ -11,6 +11,11 @@ const determineContentTypeFromExtension = (thumbnail) => {
return 'image/gif'; return 'image/gif';
case 'mp4': case 'mp4':
return 'video/mp4'; return 'video/mp4';
case 'svg':
return 'image/svg+xml';
case 'md':
case 'markdown':
return 'text/markdown';
default: default:
return ''; return '';
} }

View file

@ -1,15 +1,12 @@
import siteConfig from '@config/siteConfig.json'; import siteConfig from '@config/siteConfig.json';
const { const {
publishing: { publishing: { maxSizeImage = 10000000, maxSizeGif = 50000000, maxSizeVideo = 50000000 },
maxSizeImage = 10000000,
maxSizeGif = 50000000,
maxSizeVideo = 50000000,
}
} = siteConfig; } = siteConfig;
// TODO: central constants location
const SIZE_MB = 1000000;
export function validateFile(file) {
export function validateFile (file) {
if (!file) { if (!file) {
throw new Error('no file provided'); throw new Error('no file provided');
} }
@ -21,6 +18,7 @@ export function validateFile (file) {
case 'image/jpeg': case 'image/jpeg':
case 'image/jpg': case 'image/jpg':
case 'image/png': case 'image/png':
case 'image/svg+xml':
if (file.size > maxSizeImage) { if (file.size > maxSizeImage) {
throw new Error(`Sorry, images are limited to ${maxSizeImage / SIZE_MB} megabytes.`); throw new Error(`Sorry, images are limited to ${maxSizeImage / SIZE_MB} megabytes.`);
} }
@ -36,6 +34,9 @@ export function validateFile (file) {
} }
break; break;
default: default:
throw new Error(file.type + ' is not a supported file type. Only, .jpeg, .png, .gif, and .mp4 files are currently supported.'); throw new Error(
file.type +
' is not a supported file type. Only, .jpeg, .png, .gif, and .mp4 files are currently supported.'
);
} }
} }

6
lintstagedrc.json Normal file
View file

@ -0,0 +1,6 @@
{
"linters": {
"src/**/*.{js,jsx,scss,json}": ["prettier --write", "git add"],
"src/**/*.{js,jsx}": ["eslint --fix", "flow focus-check --color always", "git add"]
}
}

1983
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -51,6 +51,7 @@
"inquirer": "^5.2.0", "inquirer": "^5.2.0",
"ip": "^1.1.5", "ip": "^1.1.5",
"make-dir": "^1.3.0", "make-dir": "^1.3.0",
"mime-types": "^2.1.21",
"module-alias": "^2.1.0", "module-alias": "^2.1.0",
"mysql2": "^1.6.4", "mysql2": "^1.6.4",
"npm": "^6.3.0", "npm": "^6.3.0",
@ -63,6 +64,7 @@
"react-feather": "^1.1.4", "react-feather": "^1.1.4",
"react-ga": "^2.5.3", "react-ga": "^2.5.3",
"react-helmet": "^5.2.0", "react-helmet": "^5.2.0",
"react-markdown": "^4.0.6",
"react-redux": "^5.1.1", "react-redux": "^5.1.1",
"react-router-dom": "^4.3.1", "react-router-dom": "^4.3.1",
"react-select": "^2.1.1", "react-select": "^2.1.1",
@ -103,13 +105,15 @@
"extract-css-chunks-webpack-plugin": "^3.2.1", "extract-css-chunks-webpack-plugin": "^3.2.1",
"file-loader": "^2.0.0", "file-loader": "^2.0.0",
"har-validator": "^5.1.3", "har-validator": "^5.1.3",
"husky": "^1.1.3", "husky": "^1.3.1",
"lint-staged": "^8.1.0",
"md5-file": "^4.0.0", "md5-file": "^4.0.0",
"mini-css-extract-plugin": "^0.5.0", "mini-css-extract-plugin": "^0.5.0",
"mocha": "^5.2.0", "mocha": "^5.2.0",
"ndb": "^1.0.26", "ndb": "^1.0.42",
"node-sass": "^4.11.0", "node-sass": "^4.11.0",
"nodemon": "^1.18.6", "nodemon": "^1.18.6",
"prettier": "1.15.3",
"react-color": "^2.14.1", "react-color": "^2.14.1",
"react-hot-loader": "^4.6.0", "react-hot-loader": "^4.6.0",
"redux-devtools": "^3.4.1", "redux-devtools": "^3.4.1",
@ -128,7 +132,18 @@
}, },
"husky": { "husky": {
"hooks": { "hooks": {
"pre-commit": "eslint ." "pre-commit": "lint-staged"
} }
},
"lint-staged": {
"*.js": [
"eslint --fix",
"prettier --write",
"git add"
],
"*.{json,css,md}": [
"prettier --write",
"git add"
]
} }
} }

View file

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
version="1.0"
width="750"
height="600"
id="svg2450">
<defs
id="defs2453" />
<path
d="M 49.593496,600 C 12.021823,606.01223 0.374478,584.2484 0,550.4065 L 0,50 C 0.26436,7.306444 22.977953,0.531662 49.593496,0 L 700,0 C 737.99534,1.025643 751.96677,19.66882 750,50 L 750,550.4065 C 750.61508,591.42014 731.34035,604.25656 700,600 L 49.593496,600 z"
id="path2477"
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
d="M 376,580 C 120.0594,566.76664 10.515234,350.74039 13,201 C 25.291003,120.21718 21.978524,25.547435 376,19 C 654.93316,28.316194 722.07201,66.834005 735,201 C 734.63343,336.58827 649.04448,559.11396 376,580 z"
id="path2475"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
d="M 266.04757,331.75287 L 266.04757,401.89337 L 224.48919,401.89337 L 224.48919,331.75287 L 104.10864,331.75287 L 104.10864,302.29861 L 224.48919,111.77756 L 266.04757,111.77756 L 266.04757,292.77124 L 283.37127,292.77124 L 283.37127,331.75287 L 266.04757,331.75287 z M 224.48919,184.09839 L 155.20761,292.77124 L 224.48919,292.77124 L 224.48919,184.09839 z M 489.08856,300.14471 C 489.08838,315.43793 486.77811,329.57922 482.15776,342.56862 C 477.53705,355.55814 471.18548,366.81655 463.10301,376.34389 C 455.02023,385.87128 445.49507,393.37689 434.52749,398.86073 C 423.55966,404.34459 411.57888,407.08651 398.58511,407.08652 C 385.59997,407.08651 373.11705,404.9216 361.13632,400.59178 C 349.15548,396.26196 337.8266,389.91259 327.14962,381.54364 L 354.42355,352.1026 C 361.6384,357.00948 368.27849,360.61693 374.34382,362.92496 C 380.40903,365.23306 387.04911,366.38709 394.26409,366.38705 C 402.05152,366.38709 409.26642,364.72652 415.90881,361.40533 C 422.55099,358.08423 428.25287,353.39323 433.01448,347.33231 C 437.77583,341.27149 441.52864,334.0566 444.2729,325.68759 C 447.01689,317.31874 448.38896,308.08208 448.38909,297.9776 C 448.38896,282.09436 444.7793,269.67971 437.56013,260.73363 C 430.3407,251.78782 420.67018,247.31485 408.54855,247.3147 C 395.8453,247.31485 384.30058,252.07633 373.91436,261.59915 L 334.07382,256.40601 L 341.43408,111.77756 L 477.83013,111.77756 L 477.83013,152.49023 L 379.96643,152.49023 L 376.94039,213.53943 C 384.15522,210.94084 390.00026,209.28027 394.47552,208.55771 C 398.95061,207.83553 403.93232,207.47435 409.42068,207.47415 C 421.2516,207.47435 432.07395,209.71193 441.88775,214.18692 C 451.70129,218.66228 460.07242,225.01386 467.00117,233.24167 C 473.9296,241.46982 479.34298,251.21301 483.24131,262.47128 C 487.1393,273.72983 489.08838,286.28763 489.08856,300.14471 L 489.08856,300.14471 z M 557.52004,401.89337 L 557.52004,163.74866 L 539.33743,163.74866 L 539.33743,132.1405 C 547.70633,131.55935 554.41689,129.5376 559.46913,126.07523 C 564.52127,122.61341 568.63306,117.84752 571.8045,111.77756 L 599.5277,111.77756 L 599.5277,401.89337 L 557.52004,401.89337 z"
id="text3960"
style="font-size:433px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:100%;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Arial;-inkscape-font-specification:Arial" />
</svg>

After

Width:  |  Height:  |  Size: 3.6 KiB

View file

@ -1,166 +1,132 @@
const logger = require('winston'); const logger = require('winston');
const mime = require('mime-types');
const {
assetDefaults: { thumbnail: defaultThumbnail },
details: { host }
} = require('@config/siteConfig');
const getterMethods = { const getterMethods = {
generated_extension() { generated_extension() {
switch (this.content_type) { return mime.extension(this.content_type) ? mime.extension(this.content_type) : 'jpg';
case 'image/jpeg':
case 'image/jpg':
return 'jpg';
case 'image/png':
return 'png';
case 'image/gif':
return 'gif';
case 'video/mp4':
return 'mp4';
default:
logger.debug('setting unknown file type as file extension jpg');
return 'jpg';
}
}, },
};
// TODO: Factor this out. export default (sequelize, { BOOLEAN, DATE, DECIMAL, ENUM, INTEGER, STRING, TEXT }) =>
generated_thumbnail() { sequelize.define(
return this.thumbnail_url || defaultThumbnail;
},
generated_channel() {
console.log(this);
//
}
}
export default (sequelize, {
BOOLEAN,
DATE,
DECIMAL,
ENUM,
INTEGER,
STRING,
TEXT,
}) => sequelize.define(
'claim', 'claim',
{ {
id: { id: {
primaryKey: true, primaryKey: true,
type: INTEGER, type: INTEGER,
set() { }, set() {},
}, },
transaction_hash_id: { transaction_hash_id: {
type: STRING, type: STRING,
set() { }, set() {},
}, },
vout: { vout: {
type: INTEGER, type: INTEGER,
set() { }, set() {},
}, },
name: { name: {
type: STRING, type: STRING,
set() { }, set() {},
}, },
claim_id: { claim_id: {
type: STRING, type: STRING,
set() { }, set() {},
}, },
claim_type: { claim_type: {
type: INTEGER, type: INTEGER,
set() { }, set() {},
}, },
publisher_id: { publisher_id: {
type: STRING, type: STRING,
set() { }, set() {},
}, },
publisher_sig: { publisher_sig: {
type: STRING, type: STRING,
set() { }, set() {},
}, },
certificate: { certificate: {
type: STRING, type: STRING,
set() { }, set() {},
}, },
sd_hash: { sd_hash: {
type: STRING, type: STRING,
set() { }, set() {},
}, },
transaction_time: { transaction_time: {
type: INTEGER, type: INTEGER,
set() { }, set() {},
}, },
version: { version: {
type: STRING, type: STRING,
set() { }, set() {},
}, },
valid_at_height: { valid_at_height: {
type: INTEGER, type: INTEGER,
set() { }, set() {},
}, },
height: { height: {
type: INTEGER, type: INTEGER,
set() { }, set() {},
}, },
effective_amount: { effective_amount: {
type: INTEGER, type: INTEGER,
set() { }, set() {},
}, },
author: { author: {
type: STRING, type: STRING,
set() { }, set() {},
}, },
description: { description: {
type: STRING, type: STRING,
set() { }, set() {},
}, },
content_type: { content_type: {
type: STRING, type: STRING,
set() { }, set() {},
}, },
is_nsfw: { is_nsfw: {
type: BOOLEAN, type: BOOLEAN,
set() { }, set() {},
}, },
language: { language: {
type: STRING, type: STRING,
set() { }, set() {},
}, },
thumbnail_url: { thumbnail_url: {
type: STRING, type: STRING,
set() { }, set() {},
}, },
title: { title: {
type: STRING, type: STRING,
set() { }, set() {},
}, },
fee: { fee: {
type: DECIMAL(58, 8), type: DECIMAL(58, 8),
set() { }, set() {},
}, },
fee_currency: { fee_currency: {
type: STRING, type: STRING,
set() { }, set() {},
}, },
bid_state: { bid_state: {
type: ENUM('Active', 'Expired', 'Controlling', 'Spent', 'Accepted'), type: ENUM('Active', 'Expired', 'Controlling', 'Spent', 'Accepted'),
set() { }, set() {},
}, },
created_at: { created_at: {
type: DATE(6), type: DATE(6),
set() { }, set() {},
}, },
modified_at: { modified_at: {
type: DATE(6), type: DATE(6),
set() { }, set() {},
}, },
fee_address: { fee_address: {
type: STRING, type: STRING,
set() { }, set() {},
}, },
claim_address: { claim_address: {
type: STRING, type: STRING,
set() { }, set() {},
}, },
}, },
{ {
@ -168,4 +134,4 @@ export default (sequelize, {
getterMethods, getterMethods,
timestamps: false, // don't use default timestamps columns timestamps: false, // don't use default timestamps columns
} }
); );

View file

@ -18,30 +18,31 @@ const returnShortId = (claimsArray, longId) => {
shortIdLength += 1; shortIdLength += 1;
shortId = longId.substring(0, shortIdLength); shortId = longId.substring(0, shortIdLength);
possibleMatches = possibleMatches.filter(element => { possibleMatches = possibleMatches.filter(element => {
return (element.claim_id && (element.claim_id.substring(0, shortIdLength) === shortId)); return element.claim_id && element.claim_id.substring(0, shortIdLength) === shortId;
}); });
} }
return shortId; return shortId;
}; };
const isLongClaimId = (claimId) => { const isLongClaimId = claimId => {
return (claimId && (claimId.length === 40)); return claimId && claimId.length === 40;
} };
const isShortClaimId = (claimId) => { const isShortClaimId = claimId => {
return (claimId && (claimId.length < 40)); return claimId && claimId.length < 40;
} };
export default (db, table, sequelize) => ({ export default (db, table, sequelize) => ({
getClaimChannelName: async publisher_id => {
getClaimChannelName: async (publisher_id) => { return await table
return await table.findAll({ .findAll({
where : { claim_id: publisher_id }, where: { claim_id: publisher_id },
attributes: ['name'], attributes: ['name'],
}).then(result => { })
if(result.length === 0) { .then(result => {
if (result.length === 0) {
throw new Error(`no record found for ${claimId}`); throw new Error(`no record found for ${claimId}`);
} else if(result.length !== 1) { } else if (result.length !== 1) {
logger.warn(`more than one record matches ${claimId} in db.Claim`); logger.warn(`more than one record matches ${claimId} in db.Claim`);
} }
@ -51,11 +52,13 @@ export default (db, table, sequelize) => ({
getShortClaimIdFromLongClaimId: async (claimId, claimName, pendingClaim) => { getShortClaimIdFromLongClaimId: async (claimId, claimName, pendingClaim) => {
logger.debug(`claim.getShortClaimIdFromLongClaimId for ${claimName}#${claimId}`); logger.debug(`claim.getShortClaimIdFromLongClaimId for ${claimName}#${claimId}`);
return await table.findAll({ return await table
.findAll({
where: { name: claimName }, where: { name: claimName },
order: [['height', 'ASC']], order: [['height', 'ASC']],
}).then(result => { })
if(result.length === 0) { .then(result => {
if (result.length === 0) {
throw new Error('No claim(s) found with that claim name'); throw new Error('No claim(s) found with that claim name');
} }
@ -68,41 +71,23 @@ export default (db, table, sequelize) => ({
}); });
}, },
getAllChannelClaims: async (channelClaimId, params) => { getAllChannelClaims: async (channelClaimId, bidState) => {
logger.debug(`claim.getAllChannelClaims for ${channelClaimId}`); logger.debug(`claim.getAllChannelClaims for ${channelClaimId}`);
const whereClause = bidState || {
const defaultWhereClauses = { [sequelize.Op.or]: [
bid_state: { [sequelize.Op.or]: ['Controlling', 'Active', 'Accepted'] } { bid_state: 'Controlling' },
{ bid_state: 'Active' },
{ bid_state: 'Accepted' },
],
}; };
const addWhereClauses = (whereClauses, params) => {
/*
input params = { col: ['Val', 'Val']}
output = { col: { Op.or : [ { Op.eq: 'Value'},...]}, col2:...}
*/
const cols = Object.keys(params)
for (let colKey in cols){
let col = Object.keys(params)[colKey]
whereClauses[col] = {}
whereClauses[col][sequelize.Op.or] = []
for (let itemKey in params[col] ){
let itemsArr = params[col]
whereClauses[col][sequelize.Op.or].push({ [sequelize.Op.eq]: itemsArr[itemKey] })
}
}
return whereClauses;
}
const whereClause = addWhereClauses(defaultWhereClauses, params);
const selectWhere = { const selectWhere = {
...whereClause, ...whereClause,
publisher_id: channelClaimId, publisher_id: channelClaimId,
}; };
return await table.findAll({ return await table
.findAll({
where: selectWhere, where: selectWhere,
order: [['height', 'DESC'],['claim_id', 'ASC']], order: [['height', 'DESC'], ['claim_id', 'ASC']],
}) })
.then(channelClaimsArray => { .then(channelClaimsArray => {
if (channelClaimsArray.length === 0) { if (channelClaimsArray.length === 0) {
@ -114,8 +99,13 @@ export default (db, table, sequelize) => ({
getClaimIdByLongChannelId: async (channelClaimId, claimName) => { getClaimIdByLongChannelId: async (channelClaimId, claimName) => {
logger.debug(`finding claim id for claim ${claimName} from channel ${channelClaimId}`); logger.debug(`finding claim id for claim ${claimName} from channel ${channelClaimId}`);
return await table.findAll({ return await table
where: { name: claimName, publisher_id: channelClaimId, bid_state: { [sequelize.Op.or]: ['Controlling', 'Active', 'Accepted'] } }, .findAll({
where: {
name: claimName,
publisher_id: channelClaimId,
bid_state: { [sequelize.Op.or]: ['Controlling', 'Active', 'Accepted'] },
},
order: [['id', 'ASC']], order: [['id', 'ASC']],
}) })
.then(result => { .then(result => {
@ -126,19 +116,23 @@ export default (db, table, sequelize) => ({
return result[0].claim_id; return result[0].claim_id;
default: default:
// Does this actually happen??? (from converted code) // Does this actually happen??? (from converted code)
logger.warn(`${result.length} records found for "${claimName}" in channel "${channelClaimId}"`); logger.warn(
`${result.length} records found for "${claimName}" in channel "${channelClaimId}"`
);
return result[0].claim_id; return result[0].claim_id;
} }
}); });
}, },
validateLongClaimId: async (name, claimId) => { validateLongClaimId: async (name, claimId) => {
return await table.findOne({ return await table
.findOne({
where: { where: {
name, name,
claim_id: claimId, claim_id: claimId,
}, },
}).then(result => { })
.then(result => {
if (!result) { if (!result) {
return false; return false;
} }
@ -147,16 +141,18 @@ export default (db, table, sequelize) => ({
}, },
getLongClaimIdFromShortClaimId: async (name, shortId) => { getLongClaimIdFromShortClaimId: async (name, shortId) => {
return await table.findAll({ return await table
.findAll({
where: { where: {
name, name,
claim_id: { claim_id: {
[sequelize.Op.like]: `${shortId}%`, [sequelize.Op.like]: `${shortId}%`,
}}, },
},
order: [['height', 'ASC']], order: [['height', 'ASC']],
}) })
.then(result => { .then(result => {
if(result.length === 0) { if (result.length === 0) {
return null; return null;
} }
@ -164,17 +160,19 @@ export default (db, table, sequelize) => ({
}); });
}, },
getTopFreeClaimIdByClaimName: async (name) => { getTopFreeClaimIdByClaimName: async name => {
return await table.findAll({ return await table
.findAll({
// TODO: Limit 1 // TODO: Limit 1
where: { name, bid_state: { [sequelize.Op.or]: ['Controlling', 'Active', 'Accepted'] } }, where: { name, bid_state: { [sequelize.Op.or]: ['Controlling', 'Active', 'Accepted'] } },
order: [['effective_amount', 'DESC'], ['height', 'ASC']], order: [['effective_amount', 'DESC'], ['height', 'ASC']],
}).then(result => { })
if(result.length === 0) { .then(result => {
if (result.length === 0) {
return null; return null;
} }
return result[0].claim_id; return result[0].claim_id;
}) });
}, },
getLongClaimId: async (claimName, claimId) => { getLongClaimId: async (claimName, claimId) => {
@ -191,12 +189,14 @@ export default (db, table, sequelize) => ({
resolveClaim: async (name, claimId) => { resolveClaim: async (name, claimId) => {
logger.debug(`Claim.resolveClaim: ${name} ${claimId}`); logger.debug(`Claim.resolveClaim: ${name} ${claimId}`);
return table.findAll({ return table
.findAll({
where: { name, claim_id: claimId }, where: { name, claim_id: claimId },
}).then(claimArray => { })
if(claimArray.length === 0) { .then(claimArray => {
if (claimArray.length === 0) {
return null; return null;
} else if(claimArray.length !== 1) { } else if (claimArray.length !== 1) {
logger.warn(`more than one record matches ${name}#${claimId} in db.Claim`); logger.warn(`more than one record matches ${name}#${claimId} in db.Claim`);
} }
@ -206,12 +206,14 @@ export default (db, table, sequelize) => ({
resolveClaimInChannel: async (claimName, channelId) => { resolveClaimInChannel: async (claimName, channelId) => {
logger.debug(`Claim.resolveClaimByNames: ${claimName} in ${channelId}`); logger.debug(`Claim.resolveClaimByNames: ${claimName} in ${channelId}`);
return table.findAll({ return table
.findAll({
where: { where: {
name: claimName, name: claimName,
publisher_id: channelId, publisher_id: channelId,
}, },
}).then(claimArray => { })
.then(claimArray => {
if (claimArray.length === 0) { if (claimArray.length === 0) {
return null; return null;
} else if (claimArray.length !== 1) { } else if (claimArray.length !== 1) {
@ -225,13 +227,15 @@ export default (db, table, sequelize) => ({
getOutpoint: async (name, claimId) => { getOutpoint: async (name, claimId) => {
logger.debug(`finding outpoint for ${name}#${claimId}`); logger.debug(`finding outpoint for ${name}#${claimId}`);
return await table.findAll({ return await table
where : { name, claim_id: claimId }, .findAll({
where: { name, claim_id: claimId },
attributes: ['transaction_hash_id'], attributes: ['transaction_hash_id'],
}).then(result => { })
if(result.length === 0) { .then(result => {
if (result.length === 0) {
throw new Error(`no record found for ${name}#${claimId}`); throw new Error(`no record found for ${name}#${claimId}`);
} else if(result.length !== 1) { } else if (result.length !== 1) {
logger.warn(`more than one record matches ${name}#${claimId} in db.Claim`); logger.warn(`more than one record matches ${name}#${claimId} in db.Claim`);
} }
@ -240,11 +244,8 @@ export default (db, table, sequelize) => ({
}, },
getCurrentHeight: async () => { getCurrentHeight: async () => {
return await table return await table.max('height').then(result => {
.max('height') return result || 100000;
.then(result => {
return (result || 100000);
}); });
}, },
});
})

View file

@ -1,32 +1,34 @@
const db = require('../../../../models');
const chainquery = require('chainquery').default; const chainquery = require('chainquery').default;
const getClaimData = require('server/utils/getClaimData'); const getClaimData = require('server/utils/getClaimData');
const { returnPaginatedChannelClaims } = require('./channelPagination.js'); const { returnPaginatedChannelClaims } = require('./channelPagination.js');
const getChannelClaims = async (channelName, channelShortId, page) => { const getChannelClaims = async (channelName, channelLongId, page) => {
const channelId = await chainquery.claim.queries.getLongClaimId(channelName, channelShortId); let channelShortId = await chainquery.claim.queries.getShortClaimIdFromLongClaimId(
const params = { content_type: [ channelLongId,
'image/jpeg', channelName
'image/jpg', );
'image/png',
'image/gif',
'video/mp4',
] };
let channelClaims; let channelClaims;
if (channelId) { if (channelLongId) {
channelClaims = await chainquery.claim.queries.getAllChannelClaims(channelId, params); channelClaims = await chainquery.claim.queries.getAllChannelClaims(channelLongId);
} }
/*
Put mempool unconfirmed claims at the beginning
*/
const split = channelClaims.reduce( const split = channelClaims.reduce(
(acc, val) => val.dataValues.height === 0 ? { ...acc, zero: acc.zero.concat(val) } : { ...acc, nonzero: acc.nonzero.concat(val) }, (acc, val) =>
val.dataValues.height === 0
? { ...acc, zero: acc.zero.concat(val) }
: { ...acc, nonzero: acc.nonzero.concat(val) },
{ zero: [], nonzero: [] } { zero: [], nonzero: [] }
); );
channelClaims = split.zero.concat(split.nonzero); channelClaims = split.zero.concat(split.nonzero);
const processingChannelClaims = channelClaims ? channelClaims.map((claim) => getClaimData(claim, channelName, channelShortId)) : []; const processingChannelClaims = channelClaims
? channelClaims.map(claim => getClaimData(claim, channelName, channelShortId))
: [];
const processedChannelClaims = await Promise.all(processingChannelClaims); const processedChannelClaims = await Promise.all(processingChannelClaims);
return returnPaginatedChannelClaims(channelName, channelId, processedChannelClaims, page); return returnPaginatedChannelClaims(channelName, channelShortId, processedChannelClaims, page);
}; };
module.exports = getChannelClaims; module.exports = getChannelClaims;

View file

@ -3,7 +3,7 @@ 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('../../../../utils/googleAnalytics.js');
const isApprovedChannel = require('../../../../../utils/isApprovedChannel'); 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'); const { handleErrorResponse } = require('../../../utils/errorHandlers.js');
@ -18,7 +18,7 @@ const parsePublishApiRequestFiles = require('./parsePublishApiRequestFiles.js');
const authenticateUser = require('./authentication.js'); const authenticateUser = require('./authentication.js');
const chainquery = require('chainquery').default; const chainquery = require('chainquery').default;
const createCanonicalLink = require('../../../../../utils/createCanonicalLink'); const createCanonicalLink = require('@globalutils/createCanonicalLink');
const CLAIM_TAKEN = 'CLAIM_TAKEN'; const CLAIM_TAKEN = 'CLAIM_TAKEN';
const UNAPPROVED_CHANNEL = 'UNAPPROVED_CHANNEL'; const UNAPPROVED_CHANNEL = 'UNAPPROVED_CHANNEL';
@ -115,7 +115,7 @@ const claimPublish = ({ body, files, headers, ip, originalUrl, user, tor }, res)
if (channelName) { if (channelName) {
return chainquery.claim.queries.getShortClaimIdFromLongClaimId(claimData.certificateId, channelName); return chainquery.claim.queries.getShortClaimIdFromLongClaimId(claimData.certificateId, channelName);
} else { } else {
return chainquery.claim.queries.getShortClaimIdFromLongClaimId(claimId, name, claimData).catch(error => { return chainquery.claim.queries.getShortClaimIdFromLongClaimId(claimId, name, claimData).catch(() => {
return claimId.slice(0, 1); return claimId.slice(0, 1);
}); });
} }

View file

@ -1,7 +1,7 @@
const path = require('path'); const path = require('path');
const validateFileTypeAndSize = require('./validateFileTypeAndSize.js'); const validateFileTypeAndSize = require('./validateFileTypeAndSize.js');
const parsePublishApiRequestFiles = ({file, thumbnail}, isUpdate) => { const parsePublishApiRequestFiles = ({ file, thumbnail }, isUpdate) => {
// make sure a file was provided // make sure a file was provided
if (!file) { if (!file) {
if (isUpdate) { if (isUpdate) {
@ -14,39 +14,39 @@ const parsePublishApiRequestFiles = ({file, thumbnail}, isUpdate) => {
} }
return {}; return {};
} }
throw new Error('no file with key of [file] found in request'); throw new Error('No file with key of [file] found in request');
} }
if (!file.path) { if (!file.path) {
throw new Error('no file path found'); throw new Error('No file path found');
} }
if (!file.type) { if (!file.type) {
throw new Error('no file type found'); throw new Error('No file type found');
} }
if (!file.size) { if (!file.size) {
throw new Error('no file size found'); throw new Error('No file size found');
} }
// validate the file name // validate the file name
if (!file.name) { if (!file.name) {
throw new Error('no file name found'); throw new Error('No file name found');
} }
if (file.name.indexOf('.') < 0) { if (file.name.indexOf('.') < 0) {
throw new Error('no file extension found in file name'); throw new Error('No file extension found in file name');
} }
if (file.name.indexOf('.') === 0) { if (file.name.indexOf('.') === 0) {
throw new Error('file name cannot start with "."'); throw new Error('File name cannot start with "."');
} }
if (/'/.test(file.name)) { if (/'/.test(file.name)) {
throw new Error('apostrophes are not allowed in the file name'); throw new Error('Apostrophes are not allowed in the file name');
} }
// validate the file // validate the file
if (file) validateFileTypeAndSize(file); if (file) validateFileTypeAndSize(file);
// return results // return results
const obj = { const obj = {
fileName : file.name, fileName: file.name,
filePath : file.path, filePath: file.path,
fileExtension: path.extname(file.path), fileExtension: path.extname(file.path),
fileType : file.type, fileType: file.type,
}; };
if (thumbnail) { if (thumbnail) {

View file

@ -1,21 +1,18 @@
const logger = require('winston'); const logger = require('winston');
const { const {
publishing: { publishing: { maxSizeImage = 10000000, maxSizeGif = 50000000, maxSizeVideo = 50000000 },
maxSizeImage = 10000000,
maxSizeGif = 50000000,
maxSizeVideo = 50000000,
}
} = require('@config/siteConfig'); } = require('@config/siteConfig');
const SIZE_MB = 1000000; const SIZE_MB = 1000000;
const validateFileTypeAndSize = (file) => { const validateFileTypeAndSize = file => {
// check file type and size // check file type and size
switch (file.type) { switch (file.type) {
case 'image/jpeg': case 'image/jpeg':
case 'image/jpg': case 'image/jpg':
case 'image/png': case 'image/png':
case 'image/svg+xml':
if (file.size > maxSizeImage) { if (file.size > maxSizeImage) {
logger.debug('publish > file validation > .jpeg/.jpg/.png was too big'); logger.debug('publish > file validation > .jpeg/.jpg/.png was too big');
throw new Error(`Sorry, images are limited to ${maxSizeImage / SIZE_MB} megabytes.`); throw new Error(`Sorry, images are limited to ${maxSizeImage / SIZE_MB} megabytes.`);
@ -35,7 +32,11 @@ const validateFileTypeAndSize = (file) => {
break; break;
default: default:
logger.debug('publish > file validation > unrecognized file type'); logger.debug('publish > file validation > unrecognized file type');
throw new Error('The ' + file.type + ' content type is not supported. Only, image/jpg, image/png, image/gif, and video/mp4 content types are currently supported.'); throw new Error(
'The ' +
file.type +
' content type is not supported. Only, image/jpg, image/png, image/gif, and video/mp4 content types are currently supported.'
);
} }
return file; return file;
}; };

View file

@ -10,7 +10,7 @@ const parsePublishApiRequestFiles = require('../publish/parsePublishApiRequestFi
const authenticateUser = require('../publish/authentication.js'); const authenticateUser = require('../publish/authentication.js');
const createThumbnailPublishParams = require('../publish/createThumbnailPublishParams.js'); const createThumbnailPublishParams = require('../publish/createThumbnailPublishParams.js');
const chainquery = require('chainquery').default; const chainquery = require('chainquery').default;
const createCanonicalLink = require('../../../../../utils/createCanonicalLink'); const createCanonicalLink = require('@globalutils/createCanonicalLink');
/* /*
route to update a claim through the daemon route to update a claim through the daemon
@ -153,7 +153,7 @@ const claimUpdate = ({ body, files, headers, ip, originalUrl, user, tor }, res)
if (channelName) { if (channelName) {
return chainquery.claim.queries.getShortClaimIdFromLongClaimId(result.certificateId, channelName); return chainquery.claim.queries.getShortClaimIdFromLongClaimId(result.certificateId, channelName);
} else { } else {
return chainquery.claim.queries.getShortClaimIdFromLongClaimId(result.claimId, name, result).catch(error => { return chainquery.claim.queries.getShortClaimIdFromLongClaimId(result.claimId, name, result).catch(() => {
return result.claimId.slice(0, 1); return result.claimId.slice(0, 1);
}); });
} }

View file

@ -3,19 +3,16 @@ const db = require('../../../models');
const getClaimId = require('../../utils/getClaimId'); const getClaimId = require('../../utils/getClaimId');
const { const {
details: { details: { host, title: siteTitle },
host,
title: siteTitle,
},
} = require('@config/siteConfig'); } = require('@config/siteConfig');
const getOEmbedDataForAsset = (channelName, channelClaimId, claimName, claimId) => { const getOEmbedDataForAsset = (channelName, channelClaimId, claimName, claimId) => {
let fileData, claimData; let fileData, claimData;
let data = { let data = {
version : '1.0', version: '1.0',
provider_name: siteTitle, provider_name: siteTitle,
provider_url : host, provider_url: host,
cache_age : 86400, // one day in seconds cache_age: 86400, // one day in seconds
}; };
return getClaimId(channelName, channelClaimId, claimName, claimId) return getClaimId(channelName, channelClaimId, claimName, claimId)
@ -23,7 +20,7 @@ const getOEmbedDataForAsset = (channelName, channelClaimId, claimName, claimId)
claimId = fullClaimId; claimId = fullClaimId;
return db.Claim.findOne({ return db.Claim.findOne({
where: { where: {
name : claimName, name: claimName,
claimId: fullClaimId, claimId: fullClaimId,
}, },
}); });
@ -43,19 +40,23 @@ const getOEmbedDataForAsset = (channelName, channelClaimId, claimName, claimId)
.then(fileRecord => { .then(fileRecord => {
fileData = fileRecord.dataValues; fileData = fileRecord.dataValues;
logger.debug('file data:', fileData); logger.debug('file data:', fileData);
const serveUrl = `${host}/${fileData.claimId}/${fileData.name}.${fileData.fileType.substring(fileData.fileType.indexOf('/') + 1)}`; const serveUrl = `${host}/${fileData.claimId}/${fileData.name}.${fileData.fileType.substring(
fileData.fileType.indexOf('/') + 1
)}`;
// set the resource type // set the resource type
if (fileData.fileType === 'video/mp4') { if (fileData.fileType === 'video/mp4') {
data['type'] = 'video'; data['type'] = 'video';
data['html'] = `<video width="100%" controls poster="${claimData.thumbnail}" src="${serveUrl}"/></video>`; data['html'] = `<video width="100%" controls poster="${
claimData.thumbnail
}" src="${serveUrl}"/></video>`;
} else { } else {
data['type'] = 'picture'; data['type'] = 'picture';
data['url'] = serveUrl; data['url'] = serveUrl;
} }
// get the data // get the data
data['title'] = claimData.title; data['title'] = claimData.title;
data['width'] = fileData.width || 600; data['width'] = fileData.fileWidth || 600;
data['height'] = fileData.height || 400; data['height'] = fileData.fileHeight || 400;
data['author_name'] = siteTitle; data['author_name'] = siteTitle;
data['author_url'] = host; data['author_url'] = host;
}) })

View file

@ -36,7 +36,7 @@ const {
const { sessionKey } = require('@private/authConfig.json'); const { sessionKey } = require('@private/authConfig.json');
// configure.js doesn't handle new keys in config.json files yet. Make sure it doens't break. // configure.js doesn't handle new keys in config.json files yet. Make sure it doens't break.
let bLE; let finalBlockListEndpoint;
function Server () { function Server () {
this.initialize = () => { this.initialize = () => {
@ -176,30 +176,29 @@ function Server () {
return; return;
} }
if (blockListEndpoint) { if (blockListEndpoint) {
bLE = blockListEndpoint; finalBlockListEndpoint = blockListEndpoint;
} else if (!blockListEndpoint) { } else if (!blockListEndpoint) {
if (typeof (blockListEndpoint) !== 'string') { if (typeof (blockListEndpoint) !== 'string') {
logger.warn('blockListEndpoint is null due to outdated siteConfig file. \n' + logger.warn('blockListEndpoint is null due to outdated siteConfig file. \n' +
'Continuing with default LBRY blocklist api endpoint. \n ' + 'Continuing with default LBRY blocklist api endpoint. \n ' +
'(Specify /"blockListEndpoint" : ""/ to disable.') '(Specify /"blockListEndpoint" : ""/ to disable.');
bLE = 'https://api.lbry.io/file/list_blocked'; finalBlockListEndpoint = 'https://api.lbry.io/file/list_blocked';
} }
} }
logger.info(`Peforming updates...`); logger.info(`Peforming updates...`);
if (!bLE) { if (!finalBlockListEndpoint) {
logger.info('Configured for no Block List') logger.info('Configured for no Block List');
db.Tor.refreshTable().then( (updatedTorList) => { db.Tor.refreshTable().then((updatedTorList) => {
logger.info('Tor list updated, length:', updatedTorList.length); logger.info('Tor list updated, length:', updatedTorList.length);
}); });
} else { } else {
return Promise.all([ return Promise.all([
db.Blocked.refreshTable(bLE), db.Blocked.refreshTable(finalBlockListEndpoint),
db.Tor.refreshTable()]) db.Tor.refreshTable()])
.then(([updatedBlockedList, updatedTorList]) => { .then(([updatedBlockedList, updatedTorList]) => {
logger.info('Blocked list updated, length:', updatedBlockedList.length); logger.info('Blocked list updated, length:', updatedBlockedList.length);
logger.info('Tor list updated, length:', updatedTorList.length); logger.info('Tor list updated, length:', updatedTorList.length);
}) });
} }
}; };
this.start = () => { this.start = () => {

View file

@ -1,12 +1,17 @@
const logger = require('winston'); const logger = require('winston');
const returnShortId = require('./utils/returnShortId.js'); const returnShortId = require('./utils/returnShortId.js');
const isApprovedChannel = require('../../utils/isApprovedChannel'); const isApprovedChannel = require('../../utils/isApprovedChannel');
const { assetDefaults: { thumbnail: defaultThumbnail }, details: { host } } = require('@config/siteConfig'); const {
const { publishing: { serveOnlyApproved, approvedChannels } } = require('@config/siteConfig'); assetDefaults: { thumbnail: defaultThumbnail },
details: { host },
} = require('@config/siteConfig');
const {
publishing: { serveOnlyApproved, approvedChannels },
} = require('@config/siteConfig');
const NO_CLAIM = 'NO_CLAIM'; const NO_CLAIM = 'NO_CLAIM';
function determineFileExtensionFromContentType (contentType) { function determineFileExtensionFromContentType(contentType) {
switch (contentType) { switch (contentType) {
case 'image/jpeg': case 'image/jpeg':
case 'image/jpg': case 'image/jpg':
@ -17,20 +22,22 @@ function determineFileExtensionFromContentType (contentType) {
return 'gif'; return 'gif';
case 'video/mp4': case 'video/mp4':
return 'mp4'; return 'mp4';
case 'image/svg+xml':
return 'svg';
default: default:
logger.debug('setting unknown file type as file extension jpg'); logger.debug('setting unknown file type as file extension jpg');
return 'jpg'; return 'jpg';
} }
} }
function determineThumbnail (storedThumbnail, defaultThumbnail) { function determineThumbnail(storedThumbnail, defaultThumbnail) {
if (storedThumbnail === '') { if (storedThumbnail === '') {
return defaultThumbnail; return defaultThumbnail;
} }
return storedThumbnail; return storedThumbnail;
} }
function prepareClaimData (claim) { function prepareClaimData(claim) {
// logger.debug('preparing claim data based on resolved data:', claim); // logger.debug('preparing claim data based on resolved data:', claim);
claim['thumbnail'] = determineThumbnail(claim.thumbnail, defaultThumbnail); claim['thumbnail'] = determineThumbnail(claim.thumbnail, defaultThumbnail);
claim['fileExt'] = determineFileExtensionFromContentType(claim.contentType); claim['fileExt'] = determineFileExtensionFromContentType(claim.contentType);
@ -38,12 +45,12 @@ function prepareClaimData (claim) {
return claim; return claim;
} }
function isLongClaimId (claimId) { function isLongClaimId(claimId) {
return (claimId && (claimId.length === 40)); return claimId && claimId.length === 40;
} }
function isShortClaimId (claimId) { function isShortClaimId(claimId) {
return (claimId && (claimId.length < 40)); return claimId && claimId.length < 40;
} }
module.exports = (sequelize, { STRING, BOOLEAN, INTEGER, TEXT, DECIMAL }) => { module.exports = (sequelize, { STRING, BOOLEAN, INTEGER, TEXT, DECIMAL }) => {
@ -51,141 +58,141 @@ module.exports = (sequelize, { STRING, BOOLEAN, INTEGER, TEXT, DECIMAL }) => {
'Claim', 'Claim',
{ {
address: { address: {
type : STRING, type: STRING,
default: null, default: null,
}, },
amount: { amount: {
type : DECIMAL(19, 8), type: DECIMAL(19, 8),
default: null, default: null,
}, },
claimId: { claimId: {
type : STRING, type: STRING,
default: null, default: null,
}, },
claimSequence: { claimSequence: {
type : INTEGER, type: INTEGER,
default: null, default: null,
}, },
decodedClaim: { decodedClaim: {
type : BOOLEAN, type: BOOLEAN,
default: null, default: null,
}, },
depth: { depth: {
type : INTEGER, type: INTEGER,
default: null, default: null,
}, },
effectiveAmount: { effectiveAmount: {
type : DECIMAL(19, 8), type: DECIMAL(19, 8),
default: null, default: null,
}, },
hasSignature: { hasSignature: {
type : BOOLEAN, type: BOOLEAN,
default: null, default: null,
}, },
height: { height: {
type : INTEGER, type: INTEGER,
default: null, default: null,
}, },
hex: { hex: {
type : TEXT('long'), type: TEXT('long'),
default: null, default: null,
}, },
name: { name: {
type : STRING, type: STRING,
default: null, default: null,
}, },
nout: { nout: {
type : INTEGER, type: INTEGER,
default: null, default: null,
}, },
txid: { txid: {
type : STRING, type: STRING,
default: null, default: null,
}, },
validAtHeight: { validAtHeight: {
type : INTEGER, type: INTEGER,
default: null, default: null,
}, },
outpoint: { outpoint: {
type : STRING, type: STRING,
default: null, default: null,
}, },
claimType: { claimType: {
type : STRING, type: STRING,
default: null, default: null,
}, },
certificateId: { certificateId: {
type : STRING, type: STRING,
default: null, default: null,
}, },
author: { author: {
type : STRING, type: STRING,
default: null, default: null,
}, },
description: { description: {
type : TEXT('long'), type: TEXT('long'),
default: null, default: null,
}, },
language: { language: {
type : STRING, type: STRING,
default: null, default: null,
}, },
license: { license: {
type : STRING, type: STRING,
default: null, default: null,
}, },
licenseUrl: { licenseUrl: {
type : STRING, type: STRING,
default: null, default: null,
}, },
nsfw: { nsfw: {
type : BOOLEAN, type: BOOLEAN,
default: null, default: null,
}, },
preview: { preview: {
type : STRING, type: STRING,
default: null, default: null,
}, },
thumbnail: { thumbnail: {
type : STRING, type: STRING,
default: null, default: null,
}, },
title: { title: {
type : STRING, type: STRING,
default: null, default: null,
}, },
metadataVersion: { metadataVersion: {
type : STRING, type: STRING,
default: null, default: null,
}, },
contentType: { contentType: {
type : STRING, type: STRING,
default: null, default: null,
}, },
source: { source: {
type : STRING, type: STRING,
default: null, default: null,
}, },
sourceType: { sourceType: {
type : STRING, type: STRING,
default: null, default: null,
}, },
sourceVersion: { sourceVersion: {
type : STRING, type: STRING,
default: null, default: null,
}, },
streamVersion: { streamVersion: {
type : STRING, type: STRING,
default: null, default: null,
}, },
valueVersion: { valueVersion: {
type : STRING, type: STRING,
default: null, default: null,
}, },
channelName: { channelName: {
type : STRING, type: STRING,
allowNull: true, allowNull: true,
default : null, default: null,
}, },
}, },
{ {
@ -201,11 +208,10 @@ module.exports = (sequelize, { STRING, BOOLEAN, INTEGER, TEXT, DECIMAL }) => {
}); });
}; };
Claim.getShortClaimIdFromLongClaimId = function (claimId, claimName) { Claim.getShortClaimIdFromLongClaimId = function(claimId, claimName) {
logger.debug(`Claim.getShortClaimIdFromLongClaimId for ${claimName}#${claimId}`); logger.debug(`Claim.getShortClaimIdFromLongClaimId for ${claimName}#${claimId}`);
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this this.findAll({
.findAll({
where: { name: claimName }, where: { name: claimName },
order: [['height', 'ASC']], order: [['height', 'ASC']],
}) })
@ -223,14 +229,13 @@ module.exports = (sequelize, { STRING, BOOLEAN, INTEGER, TEXT, DECIMAL }) => {
}); });
}; };
Claim.getAllChannelClaims = function (channelClaimId) { Claim.getAllChannelClaims = function(channelClaimId) {
logger.debug(`Claim.getAllChannelClaims for ${channelClaimId}`); logger.debug(`Claim.getAllChannelClaims for ${channelClaimId}`);
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this this.findAll({
.findAll({
where: { certificateId: channelClaimId }, where: { certificateId: channelClaimId },
order: [['height', 'DESC']], order: [['height', 'DESC']],
raw : true, // returns an array of only data, not an array of instances raw: true, // returns an array of only data, not an array of instances
}) })
.then(channelClaimsArray => { .then(channelClaimsArray => {
switch (channelClaimsArray.length) { switch (channelClaimsArray.length) {
@ -251,11 +256,10 @@ module.exports = (sequelize, { STRING, BOOLEAN, INTEGER, TEXT, DECIMAL }) => {
}); });
}; };
Claim.getClaimIdByLongChannelId = function (channelClaimId, claimName) { Claim.getClaimIdByLongChannelId = function(channelClaimId, claimName) {
logger.debug(`finding claim id for claim ${claimName} from channel ${channelClaimId}`); logger.debug(`finding claim id for claim ${claimName} from channel ${channelClaimId}`);
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this this.findAll({
.findAll({
where: { name: claimName, certificateId: channelClaimId }, where: { name: claimName, certificateId: channelClaimId },
order: [['id', 'ASC']], order: [['id', 'ASC']],
}) })
@ -266,7 +270,9 @@ module.exports = (sequelize, { STRING, BOOLEAN, INTEGER, TEXT, DECIMAL }) => {
case 1: case 1:
return resolve(result[0].claimId); return resolve(result[0].claimId);
default: default:
logger.warn(`${result.length} records found for "${claimName}" in channel "${channelClaimId}"`); logger.warn(
`${result.length} records found for "${claimName}" in channel "${channelClaimId}"`
);
return resolve(result[0].claimId); return resolve(result[0].claimId);
} }
}) })
@ -276,7 +282,7 @@ module.exports = (sequelize, { STRING, BOOLEAN, INTEGER, TEXT, DECIMAL }) => {
}); });
}; };
Claim.validateLongClaimId = function (name, claimId) { Claim.validateLongClaimId = function(name, claimId) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.findOne({ this.findOne({
where: { where: {
@ -297,15 +303,15 @@ module.exports = (sequelize, { STRING, BOOLEAN, INTEGER, TEXT, DECIMAL }) => {
}); });
}; };
Claim.getLongClaimIdFromShortClaimId = function (name, shortId) { Claim.getLongClaimIdFromShortClaimId = function(name, shortId) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this this.findAll({
.findAll({
where: { where: {
name, name,
claimId: { claimId: {
[sequelize.Op.like]: `${shortId}%`, [sequelize.Op.like]: `${shortId}%`,
}}, },
},
order: [['height', 'ASC']], order: [['height', 'ASC']],
}) })
.then(result => { .then(result => {
@ -323,10 +329,9 @@ module.exports = (sequelize, { STRING, BOOLEAN, INTEGER, TEXT, DECIMAL }) => {
}); });
}; };
Claim.getTopFreeClaimIdByClaimName = function (name) { Claim.getTopFreeClaimIdByClaimName = function(name) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this this.findAll({
.findAll({
where: { name }, where: { name },
order: [['effectiveAmount', 'DESC'], ['height', 'ASC']], order: [['effectiveAmount', 'DESC'], ['height', 'ASC']],
}) })
@ -345,7 +350,7 @@ module.exports = (sequelize, { STRING, BOOLEAN, INTEGER, TEXT, DECIMAL }) => {
}); });
}; };
Claim.getLongClaimId = function (claimName, claimId) { Claim.getLongClaimId = function(claimName, claimId) {
logger.debug(`getLongClaimId(${claimName}, ${claimId})`); logger.debug(`getLongClaimId(${claimName}, ${claimId})`);
if (isLongClaimId(claimId)) { if (isLongClaimId(claimId)) {
return this.validateLongClaimId(claimName, claimId); return this.validateLongClaimId(claimName, claimId);
@ -356,11 +361,10 @@ module.exports = (sequelize, { STRING, BOOLEAN, INTEGER, TEXT, DECIMAL }) => {
} }
}; };
Claim.fetchClaim = function (name, claimId) { Claim.fetchClaim = function(name, claimId) {
logger.debug(`Claim.resolveClaim: ${name} ${claimId}`); logger.debug(`Claim.resolveClaim: ${name} ${claimId}`);
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this this.findAll({
.findAll({
where: { name, claimId }, where: { name, claimId },
}) })
.then(claimArray => { .then(claimArray => {
@ -380,13 +384,15 @@ module.exports = (sequelize, { STRING, BOOLEAN, INTEGER, TEXT, DECIMAL }) => {
}); });
}; };
Claim.resolveClaim = function (name, claimId) { Claim.resolveClaim = function(name, claimId) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this this.fetchClaim(name, claimId)
.fetchClaim(name, claimId)
.then(claim => { .then(claim => {
logger.info('resolveClaim claims:', claim); logger.info('resolveClaim claims:', claim);
if (serveOnlyApproved && !isApprovedChannel({ longId: claim.certificateId }, approvedChannels)) { if (
serveOnlyApproved &&
!isApprovedChannel({ longId: claim.certificateId }, approvedChannels)
) {
throw new Error('This content is unavailable'); throw new Error('This content is unavailable');
} }
return resolve(claim); return resolve(claim);
@ -397,11 +403,10 @@ module.exports = (sequelize, { STRING, BOOLEAN, INTEGER, TEXT, DECIMAL }) => {
}); });
}; };
Claim.getOutpoint = function (name, claimId) { Claim.getOutpoint = function(name, claimId) {
logger.debug(`finding outpoint for ${name}#${claimId}`); logger.debug(`finding outpoint for ${name}#${claimId}`);
return this return this.findAll({
.findAll({ where: { name, claimId },
where : { name, claimId },
attributes: ['outpoint'], attributes: ['outpoint'],
}) })
.then(result => { .then(result => {
@ -421,10 +426,9 @@ module.exports = (sequelize, { STRING, BOOLEAN, INTEGER, TEXT, DECIMAL }) => {
}); });
}; };
Claim.getCurrentHeight = function () { Claim.getCurrentHeight = function() {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
return this return this.max('height')
.max('height')
.then(result => { .then(result => {
if (result) { if (result) {
return resolve(result); return resolve(result);

View file

@ -12,10 +12,8 @@ import * as httpContext from 'express-http-context';
import Reducers from '@reducers'; import Reducers from '@reducers';
import GAListener from '@components/GAListener'; import GAListener from '@components/GAListener';
import App from '@app'; import App from '@app';
import Sagas from '@sagas';
import Actions from '@actions';
const createCanonicalLink = require('../../utils/createCanonicalLink'); const createCanonicalLink = require('@globalutils/createCanonicalLink');
const getCanonicalUrlFromShow = show => { const getCanonicalUrlFromShow = show => {
const requestId = show.requestList[show.request.id]; const requestId = show.requestList[show.request.id];
@ -56,12 +54,11 @@ export default (req, res) => {
const runSaga = (action !== false && saga !== false); const runSaga = (action !== false && saga !== false);
const renderPage = (store) => { const renderPage = (store) => {
// Workaround, remove when a solution for async httpContext exists // Workaround, remove when a solution for async httpContext exists
const showState = store.getState().show; const showState = store.getState().show;
const assetKeys = Object.keys(showState.assetList); const assetKeys = Object.keys(showState.assetList);
if(assetKeys.length !== 0) { if (assetKeys.length !== 0) {
res.claimId = showState.assetList[assetKeys[0]].claimId; res.claimId = showState.assetList[assetKeys[0]].claimId;
} else { } else {
const channelKeys = Object.keys(showState.channelList); const channelKeys = Object.keys(showState.channelList);
@ -128,7 +125,7 @@ export default (req, res) => {
res.redirect(canonicalUrl); res.redirect(canonicalUrl);
} }
return renderPage(store) return renderPage(store);
}); });
} else { } else {
const store = createStore(Reducers); const store = createStore(Reducers);

View file

@ -1,15 +1,15 @@
const chainquery = require('chainquery').default; const chainquery = require('chainquery').default;
const db = require('server/models'); const db = require('server/models');
const fetchClaimData = async (params) => { const fetchClaimData = async params => {
let { claimId, claimName: name } = params; let { claimId, claimName: name } = params;
if (claimId === 'none') claimId = null; if (claimId === 'none') claimId = null;
const [cq, local] = await Promise.all([ const [cq, local] = await Promise.all([
chainquery.claim.queries.resolveClaim(name, claimId).then(res => res.dataValues).catch(() => {}), chainquery.claim.queries.resolveClaim(name, claimId).catch(() => {}),
db.Claim.resolveClaim(name, claimId).catch(() => {}), db.Claim.resolveClaim(name, claimId).catch(() => {}),
]); ]);
// Todo: don't use localdb to get post publish content
if (!cq && !local) { if (!cq && !local) {
return null; return null;
} }

View file

@ -1,52 +1,62 @@
const { details: { host } } = require('@config/siteConfig'); const {
details: { host },
assetDefaults: { thumbnail },
} = require('@config/siteConfig');
const chainquery = require('chainquery').default; const chainquery = require('chainquery').default;
const { getClaim } = require('server/lbrynet'); // const { getClaim } = require('server/lbrynet');
const { isBlocked } = require('./blockList'); const { isBlocked } = require('./blockList');
module.exports = async (data, chName = null, chShortId = null) => { module.exports = async (data, chName = null, chShortId = null) => {
// TODO: Refactor getching the channel name out; requires invasive changes. // TODO: Refactor getching the channel name out; requires invasive changes.
const certificateId = data.publisher_id || data.certificateId; const dataVals = data.dataValues ? data.dataValues : data;
let lbrynetClaimResult = null; const txid = dataVals.transaction_hash_id || dataVals.txid;
let lbrynetFileExt = null; let nout;
if (typeof dataVals.vout === 'number') {
nout = dataVals.vout;
} else {
nout = dataVals.nout;
}
const outpoint = `${txid}:${nout}`;
const certificateId = dataVals.publisher_id || dataVals.certificateId;
const fileExt = data.generated_extension || dataVals.fileExt;
let channelShortId = chShortId; let channelShortId = chShortId;
let channelName = chName; let channelName = chName;
// TODO: Factor blocked out
let blocked; let blocked;
const outPoint = `${data.transaction_hash_id}:${data.vout}`;
if (isBlocked(outPoint)) { if (isBlocked(outpoint)) {
blocked = true; blocked = true;
} }
if (!chName && certificateId && !channelName) { if (!chName && certificateId && !channelName) {
channelName = await chainquery.claim.queries.getClaimChannelName(certificateId).catch(() => { channelName = await chainquery.claim.queries.getClaimChannelName(certificateId).catch(() => {});
});
} }
if (!chShortId && certificateId && channelName) { if (!chShortId && certificateId && channelName) {
channelShortId = await chainquery.claim.queries.getShortClaimIdFromLongClaimId(certificateId, channelName).catch(() => null); channelShortId = await chainquery.claim.queries
.getShortClaimIdFromLongClaimId(certificateId, channelName)
.catch(() => null);
} }
if (!chName && !chShortId && !data.fileExt) { // Find a solution for the legacy application/octet-stream file extensions
const lbrynetUri = `${data.name}#${data.claim_id}`;
lbrynetClaimResult = await getClaim(lbrynetUri).catch(() => {
return 'invalid URI';
});
lbrynetFileExt = lbrynetClaimResult && lbrynetClaimResult.file_name && lbrynetClaimResult.file_name.includes('.') && lbrynetClaimResult.file_name.split('.').slice(-1).pop();
}
return ({ return {
name : data.name, name: dataVals.name,
title : data.title, title: dataVals.title,
certificateId, certificateId,
channelName, channelName,
channelShortId, channelShortId,
contentType: data.content_type || data.contentType, contentType: dataVals.content_type || data.contentType,
claimId : data.claim_id || data.claimId, claimId: dataVals.claim_id || data.claimId,
fileExt : data.generated_extension || data.fileExt || lbrynetFileExt, fileExt: fileExt,
description: data.description, description: dataVals.description,
thumbnail : data.generated_thumbnail || data.thumbnail_url || data.thumbnail, thumbnail: dataVals.thumbnail_url || data.thumbnail || thumbnail,
outpoint : outPoint || data.outpoint, outpoint,
host, host,
pending : Boolean(data.height === 0), pending: Boolean(dataVals.height === 0),
blocked : blocked, blocked: blocked,
}); };
}; };

View file

@ -18,7 +18,7 @@ async function getMediaDimensions (fileType, filePath) {
[ height, width ] = await getVideoHeightAndWidth(filePath); [ height, width ] = await getVideoHeightAndWidth(filePath);
break; break;
default: default:
logger.error('unable to create File data for unspported file type:', fileType); logger.error('unable to create File dimension data for unspported file type:', fileType);
break; break;
} }
return { return {