mirror of
https://github.com/yzernik/squeaknode.git
synced 2026-08-13 12:33:25 +02:00
Upgrade squeaklib to v013 (#2165)
* Upgrade to squeaklib v0.13 * Allow different squeak types in server reponse * Add core method for create resqueak. * Add make resqueak method for squeak store * Update db migration to include resqueak hash * Add db method to insert and get resqueak * Return resqueak as squeak entry in db query method * Add RPC method for make resqueak * Got make resqueak modal working in frontend * Show different content for resqueak in frontend * Show resqueaed squeak in display * Show resqueak correctly * Got resqueak display working on squeak card component * Fix display for missing resqueaked squeak. * Fix display for missing resqueaked squeak in squeak card. * Got resqueak from squeak card working * Fix prevent default action on resqueak unlock click * Fix prevent default on make reply squeak in squeak card * Got number of replies to resqueaked squeak working * Fix click on squeak card * Fix too many characters warning message * Fix click on squeak card image and name and pubkey * Rename set reply modal state function * Fix toggle modals for resqueaked squeak * Fix form submit action for squeak modals * Include num resqueaks and num resqueaked resqueaks in get squeak entry db query * Show number of resqueaks in frontend * Fix distinct count in query for num replies and num resqueaks * Update resqueaked squeak in slice state * Update frontend build
This commit is contained in:
parent
ef3d49578f
commit
7658a4d95c
35 changed files with 1301 additions and 319 deletions
|
|
@ -88,6 +88,8 @@ import {
|
|||
GetSigningProfilesReply,
|
||||
GetContactProfilesReply,
|
||||
MakeSqueakReply,
|
||||
MakeResqueakRequest,
|
||||
MakeResqueakReply,
|
||||
GetSqueakDisplayReply,
|
||||
GetAncestorSqueakDisplaysReply,
|
||||
GetReplySqueakDisplaysReply,
|
||||
|
|
@ -340,6 +342,20 @@ export const makeSqueak = (profileId, content, replyTo, hasRecipient, recipientP
|
|||
});
|
||||
}
|
||||
|
||||
export const makeResqueak = (profileId, resqueakedHash, replyTo) => {
|
||||
console.log('Calling makeResqueak');
|
||||
const request = new MakeResqueakRequest();
|
||||
request.setProfileId(profileId);
|
||||
request.setResqueakedHash(resqueakedHash);
|
||||
request.setReplyto(replyTo);
|
||||
const deser = MakeResqueakReply.deserializeBinary;
|
||||
return baseRequest({
|
||||
url: '/makeresqueak',
|
||||
req: request,
|
||||
deser: deser,
|
||||
});
|
||||
}
|
||||
|
||||
export const getSigningProfiles = () => {
|
||||
console.log('Calling getSigningProfiles');
|
||||
const request = new GetSigningProfilesRequest();
|
||||
|
|
|
|||
|
|
@ -6,23 +6,25 @@ import moment from 'moment'
|
|||
import { getProfileImageSrcString } from '../../squeakimages/images';
|
||||
import { Link, withRouter } from 'react-router-dom'
|
||||
import { ICON_REPLY, ICON_RETWEET,
|
||||
ICON_HEART, ICON_HEARTFULL, ICON_DELETE, ICON_CLOSE,ICON_IMGUPLOAD, ICON_LOCKFILL} from '../../Icons'
|
||||
import axios from 'axios'
|
||||
import {API_URL} from '../../config'
|
||||
import MakeSqueak from '../../features/squeaks/MakeSqueak'
|
||||
import DeleteSqueak from '../../features/squeaks/DeleteSqueak'
|
||||
import BuySqueak from '../../features/squeaks/BuySqueak'
|
||||
import ContentEditable from 'react-contenteditable'
|
||||
ICON_HEART, ICON_HEARTFULL, ICON_DELETE, ICON_CLOSE,ICON_IMGUPLOAD, ICON_LOCKFILL} from '../../Icons'
|
||||
import axios from 'axios'
|
||||
import {API_URL} from '../../config'
|
||||
import MakeSqueak from '../../features/squeaks/MakeSqueak'
|
||||
import MakeResqueak from '../../features/squeaks/MakeResqueak'
|
||||
import DeleteSqueak from '../../features/squeaks/DeleteSqueak'
|
||||
import BuySqueak from '../../features/squeaks/BuySqueak'
|
||||
import ContentEditable from 'react-contenteditable'
|
||||
|
||||
import {
|
||||
setLikeSqueak,
|
||||
setUnlikeSqueak,
|
||||
fetchSqueakOffers,
|
||||
} from '../../features/squeaks/squeaksSlice'
|
||||
import {
|
||||
setLikeSqueak,
|
||||
setUnlikeSqueak,
|
||||
fetchSqueakOffers,
|
||||
} from '../../features/squeaks/squeaksSlice'
|
||||
|
||||
|
||||
const SqueakCard = React.memo(function SqueakCard(props) {
|
||||
const [replyModalOpen, setModalOpen] = useState(false)
|
||||
const SqueakCard = React.memo(function SqueakCard(props) {
|
||||
const [replyModalOpen, setReplyModalOpen] = useState(false)
|
||||
const [resqueakModalOpen, setResqueakModalOpen] = useState(false)
|
||||
const [deleteModalOpen, setDeleteModalOpen] = useState(false)
|
||||
const [buyModalOpen, setBuyModalOpen] = useState(false)
|
||||
const [parent, setParent] = useState(false)
|
||||
|
|
@ -32,252 +34,308 @@ const SqueakCard = React.memo(function SqueakCard(props) {
|
|||
|
||||
let info
|
||||
const likeSqueak = (e,id) => {
|
||||
if(e){ e.stopPropagation() }
|
||||
console.log('Clicked like with id', id);
|
||||
dispatch(setLikeSqueak(id));
|
||||
if(e){
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
console.log('Clicked like with id', id);
|
||||
dispatch(setLikeSqueak(id));
|
||||
}
|
||||
const unlikeSqueak = (e,id) => {
|
||||
if(e){ e.stopPropagation() }
|
||||
console.log('Clicked unlike with id', id);
|
||||
dispatch(setUnlikeSqueak(id));
|
||||
if(e){
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
console.log('Clicked unlike with id', id);
|
||||
dispatch(setUnlikeSqueak(id));
|
||||
}
|
||||
|
||||
const resqueak = (e,id, resqueakId) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation()
|
||||
if(props.history.location.pathname.slice(1,5) === 'prof'){
|
||||
info = { dest: "profile", id, resqueakId }
|
||||
}else{ info = { id, resqueakId } }
|
||||
alert('Re-Squeak not yet implemented!');
|
||||
e.preventDefault();
|
||||
e.stopPropagation()
|
||||
if(props.history.location.pathname.slice(1,5) === 'prof'){
|
||||
info = { dest: "profile", id, resqueakId }
|
||||
}else{ info = { id, resqueakId } }
|
||||
alert('Re-Squeak not yet implemented!');
|
||||
}
|
||||
|
||||
const toggleReplyModal = (e, type) => {
|
||||
if(e){
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
setStyleBody(!styleBody)
|
||||
if(type === 'parent'){setParent(true)}else{setParent(false)}
|
||||
setTimeout(()=>{ setModalOpen(!replyModalOpen) },20)
|
||||
if(e){
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
setStyleBody(!styleBody)
|
||||
if(type === 'parent'){setParent(true)}else{setParent(false)}
|
||||
setTimeout(()=>{ setReplyModalOpen(!replyModalOpen) },20)
|
||||
}
|
||||
|
||||
const toggleResqueakModal = (e, type) => {
|
||||
if(e){
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
setStyleBody(!styleBody)
|
||||
if(type === 'parent'){setParent(true)}else{setParent(false)}
|
||||
setTimeout(()=>{ setResqueakModalOpen(!resqueakModalOpen) },20)
|
||||
}
|
||||
|
||||
const toggleDeleteModal = (e, type) => {
|
||||
if(e){
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
setStyleBody(!styleBody)
|
||||
if(type === 'parent'){setParent(true)}else{setParent(false)}
|
||||
setTimeout(()=>{ setDeleteModalOpen(!deleteModalOpen) },20)
|
||||
if(e){
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
setStyleBody(!styleBody)
|
||||
if(type === 'parent'){setParent(true)}else{setParent(false)}
|
||||
setTimeout(()=>{ setDeleteModalOpen(!deleteModalOpen) },20)
|
||||
}
|
||||
|
||||
const toggleBuyModal = (e, type) => {
|
||||
if(e){
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
setStyleBody(!styleBody)
|
||||
if(type === 'parent'){setParent(true)}else{setParent(false)}
|
||||
setTimeout(()=>{ setBuyModalOpen(!buyModalOpen) },20)
|
||||
if(e){
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
setStyleBody(!styleBody)
|
||||
if(type === 'parent'){setParent(true)}else{setParent(false)}
|
||||
setTimeout(()=>{ setBuyModalOpen(!buyModalOpen) },20)
|
||||
}
|
||||
|
||||
|
||||
const handleModalClick = (e) => {
|
||||
e.stopPropagation()
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
const isInitialMount = useRef(true);
|
||||
|
||||
useEffect(() => {
|
||||
if (isInitialMount.current){ isInitialMount.current = false }
|
||||
else {
|
||||
document.getElementsByTagName("body")[0].style.cssText = styleBody && "overflow-y: hidden; margin-right: 17px"
|
||||
}
|
||||
}, [styleBody])
|
||||
if (isInitialMount.current){ isInitialMount.current = false }
|
||||
else {
|
||||
document.getElementsByTagName("body")[0].style.cssText = styleBody && "overflow-y: hidden; margin-right: 17px"
|
||||
}
|
||||
}, [styleBody])
|
||||
|
||||
useEffect( () => () => document.getElementsByTagName("body")[0].style.cssText = "", [] )
|
||||
|
||||
useEffect(()=> {
|
||||
if (isInitialMount.current){ isInitialMount.current = false;}
|
||||
else if(document.getElementById("replyBox")) {
|
||||
document.getElementById("replyBox").focus(); }
|
||||
if (isInitialMount.current){ isInitialMount.current = false;}
|
||||
else if(document.getElementById("replyBox")) {
|
||||
document.getElementById("replyBox").focus(); }
|
||||
}, [replyModalOpen])
|
||||
|
||||
const goToUser = (e,username) => {
|
||||
e.stopPropagation()
|
||||
props.history.push(`/app/profile/${username}`)
|
||||
const getRegularSqueakContent = (squeak) => {
|
||||
return squeak.getContentStr() ?
|
||||
<div className="card-content-info">
|
||||
{squeak.getContentStr()}
|
||||
</div> :
|
||||
<div onClick={(e)=> {
|
||||
e.preventDefault();
|
||||
dispatch(fetchSqueakOffers(props.id));
|
||||
toggleBuyModal();
|
||||
}
|
||||
}
|
||||
className="card-content-info card-content-locked-content">
|
||||
<ICON_LOCKFILL styles={{width:'36px', height:"36px", padding: "5px"}} />
|
||||
<div>
|
||||
Locked content
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
const getResqueakContent = (squeak) => {
|
||||
const resqueakedHash = squeak.getResqueakedHash();
|
||||
const resqueakedSqueak = squeak.getResqueakedSqueak();
|
||||
const resqueakedAuthor = resqueakedSqueak && resqueakedSqueak.getAuthor();
|
||||
return <div className="squeak-content">
|
||||
<SqueakCard squeak={resqueakedSqueak} key={resqueakedHash} id={resqueakedHash} user={resqueakedAuthor}
|
||||
replies={[]} hasReply={false} />
|
||||
</div>
|
||||
}
|
||||
|
||||
moment.updateLocale('en', {
|
||||
relativeTime: { future: 'in %s', past: '%s ago', s: 'few seconds ago', ss: '%ss',
|
||||
m: '1m', mm: '%dm', h: '1h', hh: '%dh', d: 'a day', dd: '%dd', M: 'a month',
|
||||
MM: '%dM', y: 'a year', yy: '%dY' }
|
||||
relativeTime: { future: 'in %s', past: '%s ago', s: 'few seconds ago', ss: '%ss',
|
||||
m: '1m', mm: '%dm', h: '1h', hh: '%dh', d: 'a day', dd: '%dd', M: 'a month',
|
||||
MM: '%dM', y: 'a year', yy: '%dY' }
|
||||
});
|
||||
|
||||
const author = props.user;
|
||||
const author = props.user;
|
||||
|
||||
return (
|
||||
return (
|
||||
<div>
|
||||
|
||||
<Link onClick={(e)=>e.stopPropagation()} to={`/app/squeak/${props.id}`} key={props.id} className={props.squeak ? "Squeak-card-wrapper" : "Squeak-card-wrapper missing-squeak"} >
|
||||
<Link onClick={(e)=>{
|
||||
e.stopPropagation()
|
||||
}
|
||||
} to={`/app/squeak/${props.id}`} key={props.id} className={props.squeak ? "Squeak-card-wrapper" : "Squeak-card-wrapper missing-squeak"} >
|
||||
|
||||
{props.squeak ?
|
||||
<>
|
||||
<div className="card-userPic-wrapper">
|
||||
<Link onClick={(e)=>e.stopPropagation()} to={`/app/profile/${props.squeak.getAuthorPubkey()}`}>
|
||||
<img alt="" style={{borderRadius:'50%', minWidth:'49px'}} width="100%" height="49px" src={author ? `${getProfileImageSrcString(author)}` : null}/>
|
||||
</Link>
|
||||
{props.hasReply? <div className="squeak-reply-thread"></div> : null}
|
||||
</div>
|
||||
<div className="card-content-wrapper">
|
||||
<div className="card-content-header">
|
||||
<div className="card-header-detail">
|
||||
<span className="card-header-user">
|
||||
<Link onClick={(e)=>e.stopPropagation()} to={`/app/profile/${props.squeak.getAuthorPubkey()}`}>{author ? author.getProfileName(): 'Unknown Author'}</Link>
|
||||
</span>
|
||||
<span className="card-header-username">
|
||||
<Link onClick={(e)=>e.stopPropagation()} to={`/app/profile/${props.squeak.getAuthorPubkey()}`}>{'@'+ props.squeak.getAuthorPubkey()}</Link>
|
||||
</span>
|
||||
<span className="card-header-dot">·</span>
|
||||
<span className="card-header-date">
|
||||
{moment(props.squeak.getBlockTime() * 1000).fromNow(true)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="card-header-more">
|
||||
{props.squeak ?
|
||||
<>
|
||||
<div className="card-userPic-wrapper">
|
||||
<Link onClick={(e)=>{
|
||||
e.stopPropagation();
|
||||
}} to={`/app/profile/${props.squeak.getAuthorPubkey()}`}>
|
||||
<img alt="" style={{borderRadius:'50%', minWidth:'49px'}} width="100%" height="49px" src={author ? `${getProfileImageSrcString(author)}` : null}/>
|
||||
</Link>
|
||||
{props.hasReply? <div className="squeak-reply-thread"></div> : null}
|
||||
</div>
|
||||
<div className="card-content-wrapper">
|
||||
<div className="card-content-header">
|
||||
<div className="card-header-detail">
|
||||
<span className="card-header-user">
|
||||
<Link onClick={(e)=>{
|
||||
e.stopPropagation();
|
||||
}} to={`/app/profile/${props.squeak.getAuthorPubkey()}`}>{author ? author.getProfileName(): 'Unknown Author'}</Link>
|
||||
</span>
|
||||
<span className="card-header-username">
|
||||
<Link onClick={(e)=>{
|
||||
e.stopPropagation();
|
||||
}} to={`/app/profile/${props.squeak.getAuthorPubkey()}`}>{'@'+ props.squeak.getAuthorPubkey()}</Link>
|
||||
</span>
|
||||
<span className="card-header-dot">·</span>
|
||||
<span className="card-header-date">
|
||||
{moment(props.squeak.getBlockTime() * 1000).fromNow(true)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="card-header-more">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{props.squeak.getContentStr() ?
|
||||
<div className="card-content-info">
|
||||
{props.squeak.getContentStr()}
|
||||
</div> :
|
||||
<div onClick={(e)=> {
|
||||
e.preventDefault();
|
||||
dispatch(fetchSqueakOffers(props.id));
|
||||
toggleBuyModal();
|
||||
}
|
||||
}
|
||||
className="card-content-info card-content-locked-content">
|
||||
<ICON_LOCKFILL styles={{width:'36px', height:"36px", padding: "5px"}} />
|
||||
<div>
|
||||
Locked content
|
||||
</div>
|
||||
</div>
|
||||
{props.squeak.getIsResqueak() ?
|
||||
getResqueakContent(props.squeak) :
|
||||
getRegularSqueakContent(props.squeak)
|
||||
}
|
||||
|
||||
<div className="card-buttons-wrapper">
|
||||
<div onClick={(e)=>toggleReplyModal(e)} className="card-button-wrap reply-wrap">
|
||||
<div className="card-icon reply-icon">
|
||||
<ICON_REPLY styles={{fill:'rgb(101, 119, 134)'}}/>
|
||||
</div>
|
||||
<div className="card-icon-value">
|
||||
{props.squeak.getNumReplies()}
|
||||
</div>
|
||||
<div onClick={(e)=>toggleReplyModal(e)} className="card-button-wrap reply-wrap">
|
||||
<div className="card-icon reply-icon">
|
||||
<ICON_REPLY styles={{fill:'rgb(101, 119, 134)'}}/>
|
||||
</div>
|
||||
<div onClick={(e)=>resqueak(e,props.id)} className="card-button-wrap resqueak-wrap">
|
||||
<div className="card-icon resqueak-icon">
|
||||
<ICON_RETWEET styles={false ? {stroke: 'rgb(23, 191, 99)'} : {fill:'rgb(101, 119, 134)'}}/>
|
||||
</div>
|
||||
<div className="card-icon-value">
|
||||
0
|
||||
</div>
|
||||
<div className="card-icon-value">
|
||||
{props.squeak.getNumReplies()}
|
||||
</div>
|
||||
<div onClick={(e)=> {
|
||||
</div>
|
||||
<div onClick={(e)=>toggleResqueakModal(e)} className="card-button-wrap resqueak-wrap">
|
||||
<div className="card-icon resqueak-icon">
|
||||
<ICON_RETWEET styles={false ? {stroke: 'rgb(23, 191, 99)'} : {fill:'rgb(101, 119, 134)'}}/>
|
||||
</div>
|
||||
<div className="card-icon-value">
|
||||
{props.squeak.getNumResqueaks()}
|
||||
</div>
|
||||
</div>
|
||||
<div onClick={(e)=> {
|
||||
e.preventDefault();
|
||||
props.squeak.getLikedTimeMs() ?
|
||||
unlikeSqueak(e, props.squeak.getSqueakHash()) :
|
||||
likeSqueak(e, props.squeak.getSqueakHash())
|
||||
}} className="card-button-wrap heart-wrap">
|
||||
<div className="card-icon heart-icon">
|
||||
{props.squeak.getLikedTimeMs() ?
|
||||
<ICON_HEARTFULL styles={{fill:'rgb(224, 36, 94)'}}/> :
|
||||
<ICON_HEART styles={{fill:'rgb(101, 119, 134)'}}/>}
|
||||
<div className="card-icon heart-icon">
|
||||
{props.squeak.getLikedTimeMs() ?
|
||||
<ICON_HEARTFULL styles={{fill:'rgb(224, 36, 94)'}}/> :
|
||||
<ICON_HEART styles={{fill:'rgb(101, 119, 134)'}}/>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div onClick={(e)=>toggleDeleteModal(e)} className="card-button-wrap">
|
||||
<div className="card-icon share-icon">
|
||||
<div onClick={(e)=>toggleDeleteModal(e)} className="card-button-wrap">
|
||||
<div className="card-icon share-icon">
|
||||
<ICON_DELETE styles={{fill:'rgb(101, 119, 134)'}} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</> :
|
||||
<>
|
||||
<div className="card-userPic-wrapper">
|
||||
<img alt="" style={{borderRadius:'50%', minWidth:'49px'}} width="100%" height="49px" src={null}/>
|
||||
{props.hasReply? <div className="squeak-reply-thread"></div> : null}
|
||||
</div>
|
||||
<div className="card-content-wrapper">
|
||||
<div className="card-content-info">
|
||||
Missing Squeak
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
|
||||
</Link>
|
||||
|
||||
{/* reply modal */}
|
||||
{props.squeak ?
|
||||
<div onClick={()=>toggleReplyModal()} style={{display: replyModalOpen ? 'block' : 'none'}} className="modal-edit">
|
||||
{replyModalOpen ?
|
||||
<div style={{minHeight: '350px', height: 'initial'}} onClick={(e)=>handleModalClick(e)} className="modal-content">
|
||||
<div className="modal-header">
|
||||
<div className="modal-closeIcon">
|
||||
<div onClick={()=>toggleReplyModal()} className="modal-closeIcon-wrap">
|
||||
<ICON_CLOSE />
|
||||
</> :
|
||||
<>
|
||||
<div className="card-userPic-wrapper">
|
||||
<img alt="" style={{borderRadius:'50%', minWidth:'49px'}} width="100%" height="49px" src={null}/>
|
||||
{props.hasReply? <div className="squeak-reply-thread"></div> : null}
|
||||
</div>
|
||||
<div className="card-content-wrapper">
|
||||
<div className="card-content-info">
|
||||
Missing Squeak
|
||||
</div>
|
||||
</div>
|
||||
<p className="modal-title">Reply</p>
|
||||
</div>
|
||||
<div style={{marginTop:'5px'}} className="modal-body">
|
||||
<MakeSqueak replyToSqueak={props.squeak} submittedCallback={toggleReplyModal} />
|
||||
</div>
|
||||
</div> : null}
|
||||
</div> : null}
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
|
||||
{/* delete modal */}
|
||||
{props.squeak ?
|
||||
<div onClick={()=>toggleDeleteModal()} style={{display: deleteModalOpen ? 'block' : 'none'}} className="modal-edit">
|
||||
{deleteModalOpen ?
|
||||
<div style={{minHeight: '350px', height: 'initial'}} onClick={(e)=>handleModalClick(e)} className="modal-content">
|
||||
<div className="modal-header">
|
||||
<div className="modal-closeIcon">
|
||||
<div onClick={()=>toggleDeleteModal()} className="modal-closeIcon-wrap">
|
||||
<ICON_CLOSE />
|
||||
</Link>
|
||||
|
||||
{/* reply modal */}
|
||||
{props.squeak ?
|
||||
<div onClick={(e)=>toggleReplyModal(e)} style={{display: replyModalOpen ? 'block' : 'none'}} className="modal-edit">
|
||||
{replyModalOpen ?
|
||||
<div style={{minHeight: '350px', height: 'initial'}} onClick={(e)=>handleModalClick(e)} className="modal-content">
|
||||
<div className="modal-header">
|
||||
<div className="modal-closeIcon">
|
||||
<div onClick={(e)=>toggleReplyModal(e)} className="modal-closeIcon-wrap">
|
||||
<ICON_CLOSE />
|
||||
</div>
|
||||
</div>
|
||||
<p className="modal-title">Reply</p>
|
||||
</div>
|
||||
</div>
|
||||
<p className="modal-title">Delete Squeak</p>
|
||||
</div>
|
||||
<div style={{marginTop:'5px'}} className="modal-body">
|
||||
<DeleteSqueak squeakHash={props.id} submittedCallback={toggleDeleteModal} />
|
||||
</div>
|
||||
</div> : null}
|
||||
</div> : null}
|
||||
|
||||
{/* buy modal */}
|
||||
{props.squeak ?
|
||||
<div onClick={()=>toggleBuyModal()} style={{display: buyModalOpen ? 'block' : 'none'}} className="modal-edit">
|
||||
{buyModalOpen ?
|
||||
<div style={{minHeight: '350px', height: 'initial'}} onClick={(e)=>handleModalClick(e)} className="modal-content">
|
||||
<div className="modal-header">
|
||||
<div className="modal-closeIcon">
|
||||
<div onClick={()=>toggleBuyModal()} className="modal-closeIcon-wrap">
|
||||
<ICON_CLOSE />
|
||||
<div style={{marginTop:'5px'}} className="modal-body">
|
||||
<MakeSqueak replyToSqueak={props.squeak} submittedCallback={toggleReplyModal} />
|
||||
</div>
|
||||
</div>
|
||||
<p className="modal-title">Buy Squeak</p>
|
||||
</div>
|
||||
<div style={{marginTop:'5px'}} className="modal-body">
|
||||
<BuySqueak squeak={props.squeak} submittedCallback={toggleBuyModal} />
|
||||
</div>
|
||||
</div> : null}
|
||||
</div> : null}
|
||||
</div> : null}
|
||||
</div> : null}
|
||||
|
||||
{/* resqueak modal */}
|
||||
{props.squeak ?
|
||||
<div onClick={(e)=>toggleResqueakModal(e)} style={{display: resqueakModalOpen ? 'block' : 'none'}} className="modal-edit">
|
||||
{resqueakModalOpen ?
|
||||
<div style={{minHeight: '350px', height: 'initial'}} onClick={(e)=>handleModalClick(e)} className="modal-content">
|
||||
<div className="modal-header">
|
||||
<div className="modal-closeIcon">
|
||||
<div onClick={(e)=>toggleResqueakModal(e)} className="modal-closeIcon-wrap">
|
||||
<ICON_CLOSE />
|
||||
</div>
|
||||
</div>
|
||||
<p className="modal-title">Resqueak</p>
|
||||
</div>
|
||||
<div style={{marginTop:'5px'}} className="modal-body">
|
||||
<MakeResqueak resqueakedSqueak={props.squeak} submittedCallback={toggleResqueakModal} />
|
||||
</div>
|
||||
</div> : null}
|
||||
</div> : null}
|
||||
|
||||
{/* delete modal */}
|
||||
{props.squeak ?
|
||||
<div onClick={(e)=>toggleDeleteModal(e)} style={{display: deleteModalOpen ? 'block' : 'none'}} className="modal-edit">
|
||||
{deleteModalOpen ?
|
||||
<div style={{minHeight: '350px', height: 'initial'}} onClick={(e)=>handleModalClick(e)} className="modal-content">
|
||||
<div className="modal-header">
|
||||
<div className="modal-closeIcon">
|
||||
<div onClick={(e)=>toggleDeleteModal(e)} className="modal-closeIcon-wrap">
|
||||
<ICON_CLOSE />
|
||||
</div>
|
||||
</div>
|
||||
<p className="modal-title">Delete Squeak</p>
|
||||
</div>
|
||||
<div style={{marginTop:'5px'}} className="modal-body">
|
||||
<DeleteSqueak squeakHash={props.id} submittedCallback={toggleDeleteModal} />
|
||||
</div>
|
||||
</div> : null}
|
||||
</div> : null}
|
||||
|
||||
{/* buy modal */}
|
||||
{props.squeak ?
|
||||
<div onClick={(e)=>toggleBuyModal(e)} style={{display: buyModalOpen ? 'block' : 'none'}} className="modal-edit">
|
||||
{buyModalOpen ?
|
||||
<div style={{minHeight: '350px', height: 'initial'}} onClick={(e)=>handleModalClick(e)} className="modal-content">
|
||||
<div className="modal-header">
|
||||
<div className="modal-closeIcon">
|
||||
<div onClick={(e)=>toggleBuyModal(e)} className="modal-closeIcon-wrap">
|
||||
<ICON_CLOSE />
|
||||
</div>
|
||||
</div>
|
||||
<p className="modal-title">Buy Squeak</p>
|
||||
</div>
|
||||
<div style={{marginTop:'5px'}} className="modal-body">
|
||||
<BuySqueak squeak={props.squeak} submittedCallback={toggleBuyModal} />
|
||||
</div>
|
||||
</div> : null}
|
||||
</div> : null}
|
||||
|
||||
|
||||
</div>
|
||||
)
|
||||
});
|
||||
</div>
|
||||
)
|
||||
});
|
||||
|
||||
export default withRouter(SqueakCard)
|
||||
export default withRouter(SqueakCard)
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import { Link } from 'react-router-dom'
|
|||
import { getProfileImageSrcString } from '../../squeakimages/images';
|
||||
import Loader from '../../components/Loader'
|
||||
|
||||
import { Form, Input, Select, Checkbox, Relevant, Debug, TextArea, Option } from 'informed';
|
||||
import { Form, Input, Select, Checkbox, Relevant, Debug, TextArea, Option, useFormApi } from 'informed';
|
||||
|
||||
import { useSelector } from 'react-redux'
|
||||
import { useDispatch } from 'react-redux'
|
||||
|
|
@ -36,6 +36,17 @@ const DeleteSqueak = (props) => {
|
|||
|
||||
const author = props.replyToSqueak && props.replyToSqueak.getAuthor();
|
||||
|
||||
const SubmitButton = () => {
|
||||
const formApi = useFormApi();
|
||||
|
||||
return <button
|
||||
type="submit"
|
||||
className={'squeak-btn-side squeak-btn-active'}
|
||||
onClick={formApi.submitForm}>
|
||||
Delete
|
||||
</button>
|
||||
};
|
||||
|
||||
const DeleteSqueakForm = () => (
|
||||
<Form onSubmit={deleteSqueak} className="Squeak-input-side">
|
||||
<div className="inner-input-links">
|
||||
|
|
@ -44,9 +55,7 @@ const DeleteSqueak = (props) => {
|
|||
<div className="squeak-btn-holder">
|
||||
<div style={{ fontSize: '13px', color: null }}>
|
||||
</div>
|
||||
<button type="submit" className={'squeak-btn-side squeak-btn-active'}>
|
||||
Delete
|
||||
</button>
|
||||
<SubmitButton />
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
|
|
|
|||
165
frontend/src/features/squeaks/MakeResqueak.js
Normal file
165
frontend/src/features/squeaks/MakeResqueak.js
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
import React, { useEffect, useState, useContext, useRef } from 'react'
|
||||
import { withRouter } from 'react-router-dom'
|
||||
import { unwrapResult } from '@reduxjs/toolkit'
|
||||
|
||||
import moment from 'moment'
|
||||
import ContentEditable from 'react-contenteditable'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { getProfileImageSrcString } from '../../squeakimages/images';
|
||||
import Loader from '../../components/Loader'
|
||||
|
||||
import { Form, Input, Select, Checkbox, Relevant, Debug, TextArea, Option, FormStateAccessor, useFormApi } from 'informed';
|
||||
|
||||
import { useSelector } from 'react-redux'
|
||||
import { useDispatch } from 'react-redux'
|
||||
|
||||
|
||||
import {
|
||||
setMakeResqueak,
|
||||
selectMakeResqueakStatus,
|
||||
selectMakeSqueakStatus,
|
||||
} from '../squeaks/squeaksSlice'
|
||||
import {
|
||||
selectSigningProfiles,
|
||||
fetchSigningProfiles,
|
||||
} from '../../features/profiles/profilesSlice'
|
||||
|
||||
const MakeResqueak = (props) => {
|
||||
const signingProfiles = useSelector(selectSigningProfiles);
|
||||
const makeSqueakStatus = useSelector(selectMakeSqueakStatus);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetchSigningProfiles());
|
||||
}, [])
|
||||
|
||||
|
||||
const submitSqueak = ({ values }) => {
|
||||
// TODO: toggle modal off here.
|
||||
console.log(values);
|
||||
console.log(values.signingProfileId);
|
||||
|
||||
if (!values.signingProfileId) {
|
||||
alert('Signing Profile must be selected.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!props.resqueakedSqueak) {
|
||||
alert('Resqueaked squeak cannot be empty.');
|
||||
return;
|
||||
}
|
||||
|
||||
const makeValues = {
|
||||
signingProfile: values.signingProfileId,
|
||||
resqueakedHash: props.resqueakedSqueak.getSqueakHash(),
|
||||
replyTo: props.replyToSqueak ? props.replyToSqueak.getSqueakHash() : null,
|
||||
}
|
||||
console.log('makeResqueak');
|
||||
dispatch(setMakeResqueak(makeValues))
|
||||
.then(unwrapResult)
|
||||
.then((squeakHash) => {
|
||||
props.history.push(`/app/squeak/${squeakHash}`);
|
||||
})
|
||||
.catch((err) => {
|
||||
alert(err.message);
|
||||
});
|
||||
if (props.submittedCallback) {
|
||||
props.submittedCallback();
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Show profile image for selected signing profile.
|
||||
// <div className="Squeak-profile-wrapper">
|
||||
// {signingProfile && <img alt="" style={{ borderRadius: '50%', minWidth: '49px' }} width="100%" height="49px" src={`${getProfileImageSrcString(signingProfile)}`} />}
|
||||
// </div>
|
||||
|
||||
const author = props.resqueakedSqueak && props.resqueakedSqueak.getAuthor();
|
||||
|
||||
const validateContent = value => {
|
||||
if (!value || value.length > 280)
|
||||
return 'Content must be less than 280 characters';
|
||||
};
|
||||
|
||||
const SubmitButton = () => {
|
||||
const formApi = useFormApi();
|
||||
|
||||
return <button
|
||||
type="submit"
|
||||
className={'squeak-btn-side squeak-btn-active'}
|
||||
onClick={formApi.submitForm}>
|
||||
Resqueak
|
||||
</button>
|
||||
};
|
||||
|
||||
const MakeResqueakForm = () => (
|
||||
<Form onSubmit={submitSqueak} className="Squeak-input-side">
|
||||
<Select class="informed-select" name="signingProfileId" initialValue="">
|
||||
<Option value="" disabled>
|
||||
Select Signing Profile...
|
||||
</Option>
|
||||
{signingProfiles.map(p => {
|
||||
return <option value={p.getProfileId()}>{p.getProfileName()}</option>
|
||||
})}
|
||||
</Select>
|
||||
<div className="inner-input-links">
|
||||
<div className="input-links-side">
|
||||
</div>
|
||||
<div className="squeak-btn-holder">
|
||||
<SubmitButton />
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
);
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
{/* Squeak being resqueaked. */}
|
||||
{props.resqueakedSqueak ?
|
||||
<div className="reply-content-wrapper">
|
||||
<div className="card-userPic-wrapper">
|
||||
<Link onClick={(e)=>e.stopPropagation()} to={`/app/profile/${props.resqueakedSqueak.getAuthorPubkey()}`}>
|
||||
<img alt="" style={{borderRadius:'50%', minWidth:'49px'}} width="100%" height="49px" src={author ? `${getProfileImageSrcString(props.resqueakedSqueak.getAuthor())}`: null}/>
|
||||
</Link>
|
||||
</div>
|
||||
<div className="card-content-wrapper">
|
||||
<div className="card-content-header">
|
||||
<div className="card-header-detail">
|
||||
<span className="card-header-user">
|
||||
<Link onClick={(e)=>e.stopPropagation()} to={`/app/profile/${props.resqueakedSqueak.getAuthorPubkey()}`}>{author ? author.getProfileName(): 'Unknown Author'}</Link>
|
||||
</span>
|
||||
<span className="card-header-username">
|
||||
<Link onClick={(e)=>e.stopPropagation()} to={`/app/profile/${props.resqueakedSqueak.getAuthorPubkey()}`}>{'@'+props.resqueakedSqueak.getAuthorPubkey()}</Link>
|
||||
</span>
|
||||
<span className="card-header-dot">·</span>
|
||||
<span className="card-header-date">
|
||||
{moment(props.resqueakedSqueak.getBlockTime() * 1000).fromNow()}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="card-content-info">
|
||||
{props.resqueakedSqueak.getContentStr()}
|
||||
</div>
|
||||
<div className="reply-to-user">
|
||||
<span className="reply-squeak-username">
|
||||
Replying to
|
||||
</span>
|
||||
<span className="main-squeak-user">
|
||||
@{props.resqueakedSqueak.getAuthorPubkey()}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div> : null }
|
||||
|
||||
|
||||
{/* New squeak content input. */}
|
||||
<div className="Squeak-input-wrapper">
|
||||
<MakeResqueakForm />
|
||||
</div>
|
||||
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default withRouter(MakeResqueak)
|
||||
|
|
@ -8,7 +8,7 @@ import { Link } from 'react-router-dom'
|
|||
import { getProfileImageSrcString } from '../../squeakimages/images';
|
||||
import Loader from '../../components/Loader'
|
||||
|
||||
import { Form, Input, Select, Checkbox, Relevant, Debug, TextArea, Option, FormStateAccessor } from 'informed';
|
||||
import { Form, Input, Select, Checkbox, Relevant, Debug, TextArea, Option, FormStateAccessor, useFormApi } from 'informed';
|
||||
|
||||
import { useSelector } from 'react-redux'
|
||||
import { useDispatch } from 'react-redux'
|
||||
|
|
@ -78,9 +78,20 @@ const MakeSqueak = (props) => {
|
|||
const author = props.replyToSqueak && props.replyToSqueak.getAuthor();
|
||||
|
||||
const validateContent = value => {
|
||||
if (!value || value.length > 280)
|
||||
if (value && value.length > 280)
|
||||
return 'Content must be less than 280 characters';
|
||||
};
|
||||
};
|
||||
|
||||
const SubmitButton = () => {
|
||||
const formApi = useFormApi();
|
||||
|
||||
return <button
|
||||
type="submit"
|
||||
className={'squeak-btn-side squeak-btn-active'}
|
||||
onClick={formApi.submitForm}>
|
||||
Squeak
|
||||
</button>
|
||||
};
|
||||
|
||||
const MakeSqueakForm = () => (
|
||||
<Form onSubmit={submitSqueak} className="Squeak-input-side">
|
||||
|
|
@ -104,9 +115,7 @@ const MakeSqueak = (props) => {
|
|||
</div>
|
||||
)}
|
||||
</FormStateAccessor>
|
||||
<button type="submit" className={'squeak-btn-side squeak-btn-active'}>
|
||||
Squeak
|
||||
</button>
|
||||
<SubmitButton />
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import { ICON_ARROWBACK, ICON_HEART, ICON_REPLY, ICON_RETWEET, ICON_HEARTFULL,
|
|||
import SqueakCard from '../../components/SqueakCard'
|
||||
import Loader from '../../components/Loader'
|
||||
import MakeSqueak from '../squeaks/MakeSqueak'
|
||||
import MakeResqueak from '../squeaks/MakeResqueak'
|
||||
import DeleteSqueak from '../../features/squeaks/DeleteSqueak'
|
||||
import BuySqueak from '../squeaks/BuySqueak'
|
||||
|
||||
|
|
@ -66,6 +67,7 @@ import { ICON_ARROWBACK, ICON_HEART, ICON_REPLY, ICON_RETWEET, ICON_HEARTFULL,
|
|||
const dispatch = useDispatch();
|
||||
|
||||
const [replyModalOpen, setModalOpen] = useState(false)
|
||||
const [resqueakModalOpen, setResqueakModalOpen] = useState(false)
|
||||
const [deleteModalOpen, setDeleteModalOpen] = useState(false)
|
||||
const [buyModalOpen, setBuyModalOpen] = useState(false)
|
||||
const [spendingModalOpen, setSpendingModalOpen] = useState(false)
|
||||
|
|
@ -92,6 +94,15 @@ import { ICON_ARROWBACK, ICON_HEART, ICON_REPLY, ICON_RETWEET, ICON_HEARTFULL,
|
|||
setModalOpen(!replyModalOpen)
|
||||
}
|
||||
|
||||
const toggleResqueakModal = (e, type) => {
|
||||
if(e){ e.stopPropagation() }
|
||||
|
||||
console.log('Toggling resqueak modal: ', resqueakModalOpen);
|
||||
// if(param === 'edit'){setSaved(false)}
|
||||
// if(type === 'parent'){setParent(true)}else{setParent(false)}
|
||||
setResqueakModalOpen(!resqueakModalOpen)
|
||||
}
|
||||
|
||||
const toggleDeleteModal = (e, type) => {
|
||||
if(e){ e.stopPropagation() }
|
||||
// if(param === 'edit'){setSaved(false)}
|
||||
|
|
@ -170,6 +181,32 @@ import { ICON_ARROWBACK, ICON_HEART, ICON_REPLY, ICON_RETWEET, ICON_HEARTFULL,
|
|||
// const squeakOffers = [];
|
||||
|
||||
|
||||
const getRegularSqueakContent = (squeak) => {
|
||||
return squeak.getContentStr() ?
|
||||
<div className="squeak-content">
|
||||
{squeak.getContentStr()}
|
||||
</div> :
|
||||
<Link>
|
||||
<div onClick={()=>toggleBuyModal(props.match.params.id)}
|
||||
className="squeak-content locked-content">
|
||||
<ICON_LOCKFILL styles={{width:'48px', height:"48px", padding: "5px"}} />
|
||||
<div>
|
||||
Locked content
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
}
|
||||
|
||||
const getResqueakContent = (squeak) => {
|
||||
const resqueakedHash = squeak.getResqueakedHash();
|
||||
const resqueakedSqueak = squeak.getResqueakedSqueak();
|
||||
const resqueakedAuthor = resqueakedSqueak && resqueakedSqueak.getAuthor();
|
||||
return <div className="squeak-content">
|
||||
<SqueakCard squeak={resqueakedSqueak} key={resqueakedHash} id={resqueakedHash} user={resqueakedAuthor}
|
||||
replies={[]} hasReply={false} />
|
||||
</div>
|
||||
}
|
||||
|
||||
|
||||
const squeak = currentSqueak;
|
||||
const author = currentSqueak && currentSqueak.getAuthor();
|
||||
|
|
@ -199,19 +236,10 @@ import { ICON_ARROWBACK, ICON_HEART, ICON_REPLY, ICON_RETWEET, ICON_HEARTFULL,
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{squeak.getContentStr() ?
|
||||
<div className="squeak-content">
|
||||
{squeak.getContentStr()}
|
||||
</div> :
|
||||
<Link>
|
||||
<div onClick={()=>toggleBuyModal(props.match.params.id)}
|
||||
className="squeak-content locked-content">
|
||||
<ICON_LOCKFILL styles={{width:'48px', height:"48px", padding: "5px"}} />
|
||||
<div>
|
||||
Locked content
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
{squeak.getIsResqueak() ?
|
||||
getResqueakContent(squeak) :
|
||||
getRegularSqueakContent(squeak)
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -238,10 +266,11 @@ import { ICON_ARROWBACK, ICON_HEART, ICON_REPLY, ICON_RETWEET, ICON_HEARTFULL,
|
|||
<div className="card-icon reply-icon"> <ICON_REPLY /> </div>
|
||||
{squeak.getNumReplies()}
|
||||
</div>
|
||||
<div onClick={()=>resqueak(squeak.getSqueakHash())} className="squeak-int-icon">
|
||||
<div onClick={()=>toggleResqueakModal()} className="squeak-int-icon">
|
||||
<div className="card-icon resqueak-icon">
|
||||
<ICON_RETWEET styles={false ? {stroke: 'rgb(23, 191, 99)'} : {fill:'rgb(101, 119, 134)'}}/>
|
||||
</div>
|
||||
{squeak.getNumResqueaks()}
|
||||
</div>
|
||||
<div onClick={()=>{
|
||||
squeak.getLikedTimeMs() ?
|
||||
|
|
@ -341,88 +370,106 @@ import { ICON_ARROWBACK, ICON_HEART, ICON_REPLY, ICON_RETWEET, ICON_HEARTFULL,
|
|||
</div>:null}
|
||||
|
||||
{squeak ?
|
||||
<div onClick={()=>toggleDeleteModal()} style={{display: deleteModalOpen ? 'block' : 'none'}} className="modal-edit">
|
||||
{deleteModalOpen ?
|
||||
<div onClick={()=>toggleResqueakModal()} style={{display: resqueakModalOpen ? 'block' : 'none'}} className="modal-edit">
|
||||
{resqueakModalOpen ?
|
||||
<div style={{minHeight: '379px', height: 'initial'}} onClick={(e)=>handleModalClick(e)} className="modal-content">
|
||||
<div className="modal-header">
|
||||
<div className="modal-closeIcon">
|
||||
<div onClick={()=>toggleDeleteModal()} className="modal-closeIcon-wrap">
|
||||
<div onClick={()=>toggleResqueakModal()} className="modal-closeIcon-wrap">
|
||||
<ICON_CLOSE />
|
||||
</div>
|
||||
</div>
|
||||
<p className="modal-title">Delete Squeak</p>
|
||||
<p className="modal-title">Resqueak</p>
|
||||
</div>
|
||||
<div style={{marginTop:'5px'}} className="modal-body">
|
||||
<DeleteSqueak squeakHash={squeak.getSqueakHash()} submittedCallback={toggleDeleteModal} />
|
||||
<MakeResqueak resqueakedSqueak={squeak} submittedCallback={toggleResqueakModal} />
|
||||
</div>
|
||||
</div> : null}
|
||||
</div>:null}
|
||||
|
||||
{squeak ?
|
||||
<div onClick={()=>toggleBuyModal()} style={{display: buyModalOpen ? 'block' : 'none'}} className="modal-edit">
|
||||
{buyModalOpen ?
|
||||
<div onClick={()=>toggleDeleteModal()} style={{display: deleteModalOpen ? 'block' : 'none'}} className="modal-edit">
|
||||
{deleteModalOpen ?
|
||||
<div style={{minHeight: '379px', height: 'initial'}} onClick={(e)=>handleModalClick(e)} className="modal-content">
|
||||
<div className="modal-header">
|
||||
<div className="modal-closeIcon">
|
||||
<div onClick={()=>toggleBuyModal()} className="modal-closeIcon-wrap">
|
||||
<div onClick={()=>toggleDeleteModal()} className="modal-closeIcon-wrap">
|
||||
<ICON_CLOSE />
|
||||
</div>
|
||||
</div>
|
||||
<p className="modal-title">Buy Squeak</p>
|
||||
<p className="modal-title">Delete Squeak</p>
|
||||
</div>
|
||||
<div style={{marginTop:'5px'}} className="modal-body">
|
||||
<BuySqueak squeak={squeak} submittedCallback={toggleBuyModal} />
|
||||
<DeleteSqueak squeakHash={squeak.getSqueakHash()} submittedCallback={toggleDeleteModal} />
|
||||
</div>
|
||||
</div> : null}
|
||||
</div>:null}
|
||||
|
||||
|
||||
{/* Modal for sent payments and received payments */}
|
||||
{squeak &&
|
||||
<div onClick={()=>toggleSpendingModal()} style={{display: spendingModalOpen ? 'block' : 'none'}} className="modal-edit">
|
||||
<div onClick={(e)=>handleModalClick(e)} className="modal-content">
|
||||
<div className="modal-header no-b-border">
|
||||
<div className="modal-closeIcon">
|
||||
<div onClick={()=>toggleSpendingModal()} className="modal-closeIcon-wrap">
|
||||
<ICON_CLOSE />
|
||||
{squeak ?
|
||||
<div onClick={()=>toggleBuyModal()} style={{display: buyModalOpen ? 'block' : 'none'}} className="modal-edit">
|
||||
{buyModalOpen ?
|
||||
<div style={{minHeight: '379px', height: 'initial'}} onClick={(e)=>handleModalClick(e)} className="modal-content">
|
||||
<div className="modal-header">
|
||||
<div className="modal-closeIcon">
|
||||
<div onClick={()=>toggleBuyModal()} className="modal-closeIcon-wrap">
|
||||
<ICON_CLOSE />
|
||||
</div>
|
||||
</div>
|
||||
<p className="modal-title">Buy Squeak</p>
|
||||
</div>
|
||||
<p className="modal-title">{null}</p>
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
<div className="explore-nav-menu">
|
||||
<div onClick={()=>setTab('Sent Payments')} className={tab =='Sent Payments' ? `explore-nav-item activeTab` : `explore-nav-item`}>
|
||||
Sent Payments
|
||||
</div>
|
||||
<div onClick={()=>setTab('Received Payments')} className={tab =='Received Payments' ? `explore-nav-item activeTab` : `explore-nav-item`}>
|
||||
Received Payments
|
||||
</div>
|
||||
<div style={{marginTop:'5px'}} className="modal-body">
|
||||
<BuySqueak squeak={squeak} submittedCallback={toggleBuyModal} />
|
||||
</div>
|
||||
<div className="modal-scroll">
|
||||
{tab === 'Sent Payments' ?
|
||||
<>
|
||||
<SentPayments squeakHash={props.id} />
|
||||
</>
|
||||
</div> : null}
|
||||
</div>:null}
|
||||
|
||||
:
|
||||
tab === 'Received Payments' ?
|
||||
<>
|
||||
<ReceivedPayments squeakHash={props.id} />
|
||||
</>
|
||||
: <div className="try-searching">
|
||||
Nothing to see here ..
|
||||
<div/>
|
||||
Try searching for people, usernames, or keywords
|
||||
|
||||
{/* Modal for sent payments and received payments */}
|
||||
{squeak &&
|
||||
<div onClick={()=>toggleSpendingModal()} style={{display: spendingModalOpen ? 'block' : 'none'}} className="modal-edit">
|
||||
<div onClick={(e)=>handleModalClick(e)} className="modal-content">
|
||||
<div className="modal-header no-b-border">
|
||||
<div className="modal-closeIcon">
|
||||
<div onClick={()=>toggleSpendingModal()} className="modal-closeIcon-wrap">
|
||||
<ICON_CLOSE />
|
||||
</div>
|
||||
</div>
|
||||
<p className="modal-title">{null}</p>
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
<div className="explore-nav-menu">
|
||||
<div onClick={()=>setTab('Sent Payments')} className={tab =='Sent Payments' ? `explore-nav-item activeTab` : `explore-nav-item`}>
|
||||
Sent Payments
|
||||
</div>
|
||||
<div onClick={()=>setTab('Received Payments')} className={tab =='Received Payments' ? `explore-nav-item activeTab` : `explore-nav-item`}>
|
||||
Received Payments
|
||||
</div>
|
||||
</div>
|
||||
<div className="modal-scroll">
|
||||
{tab === 'Sent Payments' ?
|
||||
<>
|
||||
<SentPayments squeakHash={props.id} />
|
||||
</>
|
||||
|
||||
:
|
||||
tab === 'Received Payments' ?
|
||||
<>
|
||||
<ReceivedPayments squeakHash={props.id} />
|
||||
</>
|
||||
: <div className="try-searching">
|
||||
Nothing to see here ..
|
||||
<div/>
|
||||
Try searching for people, usernames, or keywords
|
||||
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>}
|
||||
</div>}
|
||||
|
||||
|
||||
</>
|
||||
}
|
||||
</>
|
||||
}
|
||||
|
||||
export default withRouter(Squeak)
|
||||
export default withRouter(Squeak)
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import {
|
|||
getSearchSqueaks,
|
||||
getProfileSqueaks,
|
||||
makeSqueak,
|
||||
makeResqueak,
|
||||
deleteSqueak,
|
||||
getSqueakOffers,
|
||||
buySqueak,
|
||||
|
|
@ -165,6 +166,23 @@ export const setMakeSqueak = createAsyncThunk(
|
|||
}
|
||||
)
|
||||
|
||||
export const setMakeResqueak = createAsyncThunk(
|
||||
'squeaks/setMakeResqueak',
|
||||
async (values) => {
|
||||
console.log('Making resqueak');
|
||||
let profileId = values.signingProfile;
|
||||
let resqueakedHash = values.resqueakedHash;
|
||||
let replyTo = values.replyTo;
|
||||
|
||||
const response = await makeResqueak(
|
||||
profileId,
|
||||
resqueakedHash,
|
||||
replyTo,
|
||||
);
|
||||
return response.getSqueakHash();
|
||||
}
|
||||
)
|
||||
|
||||
export const fetchSqueakOffers = createAsyncThunk(
|
||||
'squeaks/fetchSqueakOffers',
|
||||
async (squeakHash) => {
|
||||
|
|
@ -202,10 +220,32 @@ const updatedSqueakInArray = (squeakArr, newSqueak) => {
|
|||
if (currentIndex != -1) {
|
||||
squeakArr[currentIndex] = newSqueak;
|
||||
}
|
||||
|
||||
// Update resqueaked squeaks
|
||||
for (let i = 0; i < squeakArr.length; i++) {
|
||||
const currentSqueak = squeakArr[i];
|
||||
const currentResqueakedSqueak = currentSqueak.getResqueakedSqueak();
|
||||
if (currentResqueakedSqueak && currentResqueakedSqueak.getSqueakHash() === newSqueak.getSqueakHash()) {
|
||||
const modifiedSqueak = currentSqueak.clone();
|
||||
modifiedSqueak.setResqueakedSqueak(newSqueak);
|
||||
squeakArr[i] = modifiedSqueak;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const removeSqueakInArray = (squeakArr, squeakHash) => {
|
||||
return squeakArr.filter(squeak => squeak.getSqueakHash() !== squeakHash);
|
||||
|
||||
// Remove resqueaked squeaks
|
||||
for (let i = 0; i < squeakArr.length; i++) {
|
||||
const currentSqueak = squeakArr[i];
|
||||
const currentResqueakedSqueak = currentSqueak.getResqueakedSqueak();
|
||||
if (currentResqueakedSqueak && currentResqueakedSqueak.getSqueakHash() === squeakHash) {
|
||||
const modifiedSqueak = currentSqueak.clone();
|
||||
modifiedSqueak.setResqueakedSqueak(null);
|
||||
squeakArr[i] = modifiedSqueak;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -342,6 +382,20 @@ const squeaksSlice = createSlice({
|
|||
state.makeSqueakStatus = 'idle';
|
||||
console.log('Go to new squeak');
|
||||
})
|
||||
.addCase(setMakeResqueak.pending, (state, action) => {
|
||||
console.log('setMakeResqueak pending');
|
||||
state.makeSqueakStatus = 'loading'
|
||||
})
|
||||
.addCase(setMakeResqueak.rejected, (state, action) => {
|
||||
state.makeSqueakStatus = 'idle'
|
||||
})
|
||||
.addCase(setMakeResqueak.fulfilled, (state, action) => {
|
||||
console.log('setMakeResqueak fulfilled');
|
||||
console.log(action);
|
||||
const newSqueakHash = action.payload;
|
||||
state.makeSqueakStatus = 'idle';
|
||||
console.log('Go to new squeak');
|
||||
})
|
||||
.addCase(fetchSqueakOffers.pending, (state, action) => {
|
||||
state.squeakOffers = [];
|
||||
state.squeakOffersStatus = 'loading'
|
||||
|
|
|
|||
|
|
@ -128,6 +128,10 @@ service SqueakAdmin {
|
|||
*/
|
||||
rpc MakeSqueak (MakeSqueakRequest) returns (MakeSqueakReply) {}
|
||||
|
||||
/** sqkadmin: `makeresqueak`
|
||||
*/
|
||||
rpc MakeResqueak (MakeResqueakRequest) returns (MakeResqueakReply) {}
|
||||
|
||||
/** sqkadmin: `getsqueakdisplay`
|
||||
*/
|
||||
rpc GetSqueakDisplay (GetSqueakDisplayRequest) returns (GetSqueakDisplayReply) {}
|
||||
|
|
@ -588,6 +592,22 @@ message MakeSqueakReply {
|
|||
string squeak_hash = 1;
|
||||
}
|
||||
|
||||
message MakeResqueakRequest {
|
||||
/// The profile id
|
||||
int32 profile_id = 1;
|
||||
|
||||
/// The content
|
||||
string resqueaked_hash = 2;
|
||||
|
||||
/// The replyto hash
|
||||
string replyto = 3;
|
||||
}
|
||||
|
||||
message MakeResqueakReply {
|
||||
/// Hash of the created resqueak.
|
||||
string squeak_hash = 1;
|
||||
}
|
||||
|
||||
message GetSqueakDisplayRequest {
|
||||
/// Hash of the squeak.
|
||||
string squeak_hash = 1;
|
||||
|
|
@ -658,6 +678,18 @@ message SqueakDisplayEntry {
|
|||
|
||||
/// Number of replies
|
||||
int32 num_replies = 20;
|
||||
|
||||
/// Is resqueak
|
||||
bool is_resqueak = 21;
|
||||
|
||||
/// Resqueaked hash
|
||||
string resqueaked_hash = 22;
|
||||
|
||||
/// Resqueaked squeak
|
||||
SqueakDisplayEntry resqueaked_squeak = 23;
|
||||
|
||||
/// Number of resqueaks
|
||||
int32 num_resqueaks = 24;
|
||||
}
|
||||
|
||||
message GetTimelineSqueakDisplaysRequest {
|
||||
|
|
|
|||
|
|
@ -3,4 +3,4 @@ grpcio==1.39.0
|
|||
grpcio-tools==1.39.0
|
||||
importlib_resources==1.4.0
|
||||
pytest==6.2.5
|
||||
squeaklib==0.12.0
|
||||
squeaklib==0.13.1
|
||||
|
|
|
|||
|
|
@ -12,6 +12,6 @@ python-bitcoinlib==0.11.0
|
|||
pyzmq==22.3.0
|
||||
requests[socks]==2.26.0
|
||||
SQLAlchemy==1.4.25
|
||||
squeaklib==0.12.0
|
||||
squeaklib==0.13.1
|
||||
typed-config==0.2.5
|
||||
Werkzeug==2.0.3
|
||||
|
|
|
|||
|
|
@ -67,12 +67,18 @@ def squeak_entry_to_message(squeak_entry: SqueakEntry) -> squeak_admin_pb2.Squea
|
|||
if squeak_entry.squeak_profile else None),
|
||||
liked_time_ms=squeak_entry.liked_time_ms, # type: ignore
|
||||
num_replies=squeak_entry.num_replies,
|
||||
num_resqueaks=squeak_entry.num_resqueaks,
|
||||
is_private=(squeak_entry.recipient_public_key is not None),
|
||||
recipient_pubkey=(squeak_entry.recipient_public_key.to_bytes(
|
||||
).hex() if squeak_entry.recipient_public_key else None),
|
||||
is_recipient_known=(squeak_entry.recipient_squeak_profile is not None),
|
||||
recipient=(squeak_profile_to_message(squeak_entry.recipient_squeak_profile)
|
||||
if squeak_entry.recipient_squeak_profile else None),
|
||||
is_resqueak=(squeak_entry.resqueaked_hash is not None),
|
||||
resqueaked_hash=(squeak_entry.resqueaked_hash.hex()
|
||||
if squeak_entry.resqueaked_hash else None), # type: ignore
|
||||
resqueaked_squeak=(squeak_entry_to_message(squeak_entry.resqueaked_squeak)
|
||||
if squeak_entry.resqueaked_squeak else None), # type: ignore
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -194,6 +200,7 @@ def message_to_squeak_entry(msg: squeak_admin_pb2.SqueakDisplayEntry) -> SqueakE
|
|||
recipient_squeak_profile=None, # TODO: message to squeak profile
|
||||
liked_time_ms=(msg.liked_time_ms if msg.liked_time_ms > 0 else None),
|
||||
num_replies=0,
|
||||
num_resqueaks=0,
|
||||
content=(msg.content_str if len(msg.content_str) > 0 else None),
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -309,6 +309,30 @@ class SqueakAdminServerHandler(object):
|
|||
squeak_hash=inserted_squeak_hash_str,
|
||||
)
|
||||
|
||||
def handle_make_resqueak(self, request):
|
||||
profile_id = request.profile_id
|
||||
resqueaked_hash_str = request.resqueaked_hash
|
||||
resqueaked_hash = bytes.fromhex(resqueaked_hash_str)
|
||||
replyto_hash_str = request.replyto
|
||||
replyto_hash = bytes.fromhex(
|
||||
replyto_hash_str) if replyto_hash_str else None
|
||||
logger.info(
|
||||
"Handle make resqueak with author profile id: {}".format(
|
||||
profile_id,
|
||||
)
|
||||
)
|
||||
inserted_resqueak_hash = self.squeak_controller.make_resqueak(
|
||||
profile_id,
|
||||
resqueaked_hash,
|
||||
replyto_hash,
|
||||
)
|
||||
inserted_resqueak_hash_str = optional_squeak_hash_to_hex(
|
||||
inserted_resqueak_hash,
|
||||
)
|
||||
return squeak_admin_pb2.MakeResqueakReply(
|
||||
squeak_hash=inserted_resqueak_hash_str,
|
||||
)
|
||||
|
||||
def handle_get_squeak_display_entry(self, request):
|
||||
squeak_hash_str = request.squeak_hash
|
||||
squeak_hash = bytes.fromhex(squeak_hash_str)
|
||||
|
|
|
|||
|
|
@ -140,6 +140,9 @@ class SqueakAdminServerServicer(squeak_admin_pb2_grpc.SqueakAdminServicer):
|
|||
def MakeSqueak(self, request, context):
|
||||
return self.handler.handle_make_squeak(request)
|
||||
|
||||
def MakeResqueak(self, request, context):
|
||||
return self.handler.handle_make_resqueak(request)
|
||||
|
||||
def GetSqueakDisplay(self, request, context):
|
||||
return self.handler.handle_get_squeak_display_entry(request)
|
||||
|
||||
|
|
|
|||
|
|
@ -313,6 +313,12 @@ def create_app(handler, username, password):
|
|||
def makesqueakrequest(msg):
|
||||
return handler.handle_make_squeak(msg)
|
||||
|
||||
@app.route("/makeresqueak", methods=["POST"])
|
||||
@login_required
|
||||
@protobuf_serialized(squeak_admin_pb2.MakeResqueakRequest())
|
||||
def makeresqueak(msg):
|
||||
return handler.handle_make_resqueak(msg)
|
||||
|
||||
@app.route("/getsqueakdisplay", methods=["POST"])
|
||||
@login_required
|
||||
@protobuf_serialized(squeak_admin_pb2.GetSqueakDisplayRequest())
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"files": {
|
||||
"main.css": "/static/css/main.206389cd.css",
|
||||
"main.js": "/static/js/main.618916f4.js",
|
||||
"main.js": "/static/js/main.bda102e3.js",
|
||||
"static/css/306.7f7715a0.chunk.css": "/static/css/306.7f7715a0.chunk.css",
|
||||
"static/js/306.90b2a66c.chunk.js": "/static/js/306.90b2a66c.chunk.js",
|
||||
"static/css/900.b66ac5fb.chunk.css": "/static/css/900.b66ac5fb.chunk.css",
|
||||
|
|
@ -11,7 +11,7 @@
|
|||
"static/media/icon.svg": "/static/media/icon.982210ec9275956e4d3cc77fa90167fd.svg",
|
||||
"index.html": "/index.html",
|
||||
"main.206389cd.css.map": "/static/css/main.206389cd.css.map",
|
||||
"main.618916f4.js.map": "/static/js/main.618916f4.js.map",
|
||||
"main.bda102e3.js.map": "/static/js/main.bda102e3.js.map",
|
||||
"306.7f7715a0.chunk.css.map": "/static/css/306.7f7715a0.chunk.css.map",
|
||||
"306.90b2a66c.chunk.js.map": "/static/js/306.90b2a66c.chunk.js.map",
|
||||
"900.b66ac5fb.chunk.css.map": "/static/css/900.b66ac5fb.chunk.css.map",
|
||||
|
|
@ -21,6 +21,6 @@
|
|||
},
|
||||
"entrypoints": [
|
||||
"static/css/main.206389cd.css",
|
||||
"static/js/main.618916f4.js"
|
||||
"static/js/main.bda102e3.js"
|
||||
]
|
||||
}
|
||||
|
|
@ -1 +1 @@
|
|||
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#1da1f2"/><meta nam="description" content="Squeaknode!"/><meta name="og:title" content="Squeaknode"/><meta name="og:description" content="check out my the Squeaknode I made"/><link href="https://fonts.googleapis.com/css2?family=Assistant&display=swap" rel="stylesheet"><link rel="apple-touch-icon" href="/logo192.png"/><link rel="manifest" href="/manifest.json"/><title>Squeaknode</title><script defer="defer" src="/static/js/main.618916f4.js"></script><link href="/static/css/main.206389cd.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#1da1f2"/><meta nam="description" content="Squeaknode!"/><meta name="og:title" content="Squeaknode"/><meta name="og:description" content="check out my the Squeaknode I made"/><link href="https://fonts.googleapis.com/css2?family=Assistant&display=swap" rel="stylesheet"><link rel="apple-touch-icon" href="/logo192.png"/><link rel="manifest" href="/manifest.json"/><title>Squeaknode</title><script defer="defer" src="/static/js/main.bda102e3.js"></script><link href="/static/css/main.206389cd.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -24,6 +24,7 @@ from typing import List
|
|||
from typing import Optional
|
||||
|
||||
import requests
|
||||
from squeak.core import CResqueak
|
||||
from squeak.core import CSqueak
|
||||
from squeak.core.keys import SqueakPublicKey
|
||||
|
||||
|
|
@ -97,8 +98,19 @@ class PeerClient:
|
|||
)
|
||||
if r.status_code != requests.codes.ok:
|
||||
return None
|
||||
squeak_bytes = r.content
|
||||
return CSqueak.deserialize(squeak_bytes)
|
||||
logger.info(r)
|
||||
squeak_json = r.json()
|
||||
# squeak_bytes = r.content
|
||||
squeak_type = squeak_json['squeak_type']
|
||||
squeak_bytes_hex = squeak_json['squeak_bytes']
|
||||
squeak_bytes = bytes.fromhex(squeak_bytes_hex)
|
||||
logger.info('Client downloaded: {}'.format(squeak_json))
|
||||
if squeak_type == 'squeak':
|
||||
return CSqueak.deserialize(squeak_bytes)
|
||||
elif squeak_type == 'resqueak':
|
||||
return CResqueak.deserialize(squeak_bytes)
|
||||
else:
|
||||
return None
|
||||
|
||||
def get_secret_key(self, squeak_hash: bytes) -> Optional[bytes]:
|
||||
squeak_hash_str = squeak_hash.hex()
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ from typing import Tuple
|
|||
|
||||
import grpc
|
||||
from bitcoin.core import CBlockHeader
|
||||
from squeak.core import CBaseSqueak
|
||||
from squeak.core import CSqueak
|
||||
|
||||
from squeaknode.bitcoin.bitcoin_client import BitcoinClient
|
||||
|
|
@ -46,6 +47,7 @@ from squeaknode.core.squeaks import check_squeak
|
|||
from squeaknode.core.squeaks import get_decrypted_content
|
||||
from squeaknode.core.squeaks import get_hash
|
||||
from squeaknode.core.squeaks import get_payment_point_of_secret_key
|
||||
from squeaknode.core.squeaks import make_resqueak_with_block
|
||||
from squeaknode.core.squeaks import make_squeak_with_block
|
||||
from squeaknode.lightning.lightning_client import LightningClient
|
||||
|
||||
|
|
@ -71,8 +73,6 @@ class SqueakCore:
|
|||
) -> Tuple[CSqueak, bytes]:
|
||||
"""Create a new squeak.
|
||||
|
||||
TODO: Include the block header in the result tuple.
|
||||
|
||||
Args:
|
||||
signing_profile: The profile of the author of the squeak.
|
||||
content_str: The content of the squeak as a string.
|
||||
|
|
@ -100,7 +100,40 @@ class SqueakCore:
|
|||
)
|
||||
return squeak, secret_key
|
||||
|
||||
def check_squeak(self, squeak: CSqueak) -> None:
|
||||
def make_resqueak(
|
||||
self,
|
||||
signing_profile: SqueakProfile,
|
||||
resqueak_hash: bytes,
|
||||
replyto_hash: Optional[bytes] = None,
|
||||
) -> Tuple[CSqueak, bytes]:
|
||||
"""Create a new resqueak.
|
||||
|
||||
Args:
|
||||
signing_profile: The profile of the author of the squeak.
|
||||
resqueak_hash: The hash of the squeak to resqueak.
|
||||
replyto_hash: The hash of the squeak to which this one is replying.
|
||||
|
||||
Returns:
|
||||
CResqueak: the resqueak that was created.
|
||||
|
||||
Raises:
|
||||
Exception: If the profile does not have a signing key.
|
||||
"""
|
||||
if signing_profile.private_key is None:
|
||||
raise Exception("Can't make squeak with a contact profile.")
|
||||
block_info = self.bitcoin_client.get_best_block_info()
|
||||
block_height = block_info.block_height
|
||||
block_hash = block_info.block_hash
|
||||
squeak = make_resqueak_with_block(
|
||||
signing_profile.private_key,
|
||||
resqueak_hash,
|
||||
block_height,
|
||||
block_hash,
|
||||
replyto_hash=replyto_hash,
|
||||
)
|
||||
return squeak
|
||||
|
||||
def check_squeak(self, base_squeak: CBaseSqueak) -> None:
|
||||
"""Checks if the squeak is valid and has a valid signature.
|
||||
|
||||
Args:
|
||||
|
|
@ -112,9 +145,9 @@ class SqueakCore:
|
|||
Raises:
|
||||
Exception: If the squeak is not valid.
|
||||
"""
|
||||
check_squeak(squeak)
|
||||
check_squeak(base_squeak)
|
||||
|
||||
def get_block_header(self, squeak: CSqueak) -> CBlockHeader:
|
||||
def get_block_header(self, base_squeak: CBaseSqueak) -> CBlockHeader:
|
||||
"""Checks if the embedded block hash in the squeak is valid for its
|
||||
block height and return the associtated block header.
|
||||
|
||||
|
|
@ -128,8 +161,8 @@ class SqueakCore:
|
|||
Exception: If the block hash is not valid.
|
||||
"""
|
||||
block_info = self.bitcoin_client.get_block_info_by_height(
|
||||
squeak.nBlockHeight)
|
||||
if squeak.hashBlock != block_info.block_hash:
|
||||
base_squeak.nBlockHeight)
|
||||
if base_squeak.hashBlock != block_info.block_hash:
|
||||
raise Exception("Block hash incorrect.")
|
||||
return block_info.block_header
|
||||
|
||||
|
|
|
|||
|
|
@ -42,5 +42,8 @@ class SqueakEntry(NamedTuple):
|
|||
squeak_profile: Optional[SqueakProfile]
|
||||
recipient_squeak_profile: Optional[SqueakProfile]
|
||||
num_replies: int
|
||||
num_resqueaks: int
|
||||
liked_time_ms: Optional[int] = None
|
||||
content: Optional[str] = None
|
||||
resqueaked_hash: Optional[bytes] = None
|
||||
resqueaked_squeak: Optional['SqueakEntry'] = None # type: ignore
|
||||
|
|
|
|||
|
|
@ -24,7 +24,9 @@ from typing import Optional
|
|||
from typing import Tuple
|
||||
|
||||
from squeak.core import CheckSqueak
|
||||
from squeak.core import CResqueak
|
||||
from squeak.core import CSqueak
|
||||
from squeak.core import MakeResqueak
|
||||
from squeak.core import MakeSqueakFromStr
|
||||
from squeak.core.elliptic import payment_point_bytes_from_scalar_bytes
|
||||
from squeak.core.keys import SqueakPrivateKey
|
||||
|
|
@ -87,6 +89,37 @@ def make_squeak_with_block(
|
|||
)
|
||||
|
||||
|
||||
def make_resqueak_with_block(
|
||||
private_key: SqueakPrivateKey,
|
||||
resqueak_hash: bytes,
|
||||
block_height: int,
|
||||
block_hash: bytes,
|
||||
replyto_hash: Optional[bytes] = None,
|
||||
) -> CResqueak:
|
||||
"""Create a new resqueak.
|
||||
|
||||
Args:
|
||||
private_key: The private key to sign the squeak.
|
||||
resqueak_hash: The hash of the squeak to resqueak.
|
||||
block_height: The height of the latest block in the bitcoin blockchain.
|
||||
block_hash: The hash of the latest block in the bitcoin blockchain.
|
||||
replyto_hash: The hash of the squeak to which this one is replying.
|
||||
recipient_public_key: The public key of the recipient of a private squeak.
|
||||
|
||||
Returns:
|
||||
CResqueak: the resqueak that was created.
|
||||
"""
|
||||
timestamp = int(time.time())
|
||||
return MakeResqueak(
|
||||
private_key,
|
||||
resqueak_hash,
|
||||
block_height,
|
||||
block_hash,
|
||||
timestamp,
|
||||
reply_to=replyto_hash,
|
||||
)
|
||||
|
||||
|
||||
def check_squeak(squeak: CSqueak) -> None:
|
||||
"""Checks if the squeak is valid and has a valid signature.
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,48 @@
|
|||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2020 Jonathan Zernik
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in all
|
||||
# copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
"""Add resqueak hash column in squeak table
|
||||
|
||||
Revision ID: b3d0395263c4
|
||||
Revises: 0e3b79a31b58
|
||||
Create Date: 2022-04-23 21:41:39.063229
|
||||
|
||||
"""
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'b3d0395263c4'
|
||||
down_revision = '0e3b79a31b58'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
with op.batch_alter_table('squeak', schema=None) as batch_op:
|
||||
batch_op.add_column(
|
||||
sa.Column('resqueak_hash', sa.LargeBinary(length=32), nullable=True))
|
||||
|
||||
|
||||
def downgrade():
|
||||
with op.batch_alter_table('squeak', schema=None) as batch_op:
|
||||
batch_op.drop_column('resqueak_hash')
|
||||
|
|
@ -82,6 +82,7 @@ class Models:
|
|||
Column("block_time_s", Integer, nullable=False),
|
||||
Column("liked_time_ms", SLBigInteger, default=None, nullable=True),
|
||||
Column("content", String(280), nullable=True),
|
||||
Column("resqueak_hash", LargeBinary(32), nullable=True),
|
||||
)
|
||||
|
||||
self.profiles = Table(
|
||||
|
|
|
|||
|
|
@ -28,12 +28,15 @@ from typing import Optional
|
|||
|
||||
import sqlalchemy
|
||||
from bitcoin.core import CBlockHeader
|
||||
from sqlalchemy import distinct
|
||||
from sqlalchemy import func
|
||||
from sqlalchemy import literal
|
||||
from sqlalchemy import not_
|
||||
from sqlalchemy import or_
|
||||
from sqlalchemy.sql import select
|
||||
from sqlalchemy.sql import tuple_
|
||||
from squeak.core import CBaseSqueak
|
||||
from squeak.core import CResqueak
|
||||
from squeak.core import CSqueak
|
||||
from squeak.core.keys import SqueakPrivateKey
|
||||
from squeak.core.keys import SqueakPublicKey
|
||||
|
|
@ -86,11 +89,16 @@ class SqueakDb:
|
|||
|
||||
# Create aliases for profiles
|
||||
self.author_profiles = self.profiles.alias()
|
||||
self.resqueaked_author_profiles = self.profiles.alias()
|
||||
self.recipient_profiles = self.profiles.alias()
|
||||
|
||||
# Create aliases for squeaks
|
||||
self.display_squeaks = self.squeaks.alias()
|
||||
self.reply_squeaks = self.squeaks.alias()
|
||||
self.resqueaked_squeaks = self.squeaks.alias()
|
||||
self.resqueaked_reply_squeaks = self.squeaks.alias()
|
||||
self.resqueaks = self.squeaks.alias()
|
||||
self.resqueaked_resqueaks = self.squeaks.alias()
|
||||
|
||||
def init_with_retries(
|
||||
self,
|
||||
|
|
@ -242,7 +250,36 @@ class SqueakDb:
|
|||
logger.debug("Failed to insert squeak.", exc_info=True)
|
||||
return None
|
||||
|
||||
def get_squeak(self, squeak_hash: bytes) -> Optional[CSqueak]:
|
||||
def insert_resqueak(self, resqueak: CResqueak, block_header: CBlockHeader) -> Optional[bytes]:
|
||||
""" Insert a new resqueak.
|
||||
|
||||
Return the hash (bytes) of the inserted resqueak.
|
||||
Return None if resqueak already exists.
|
||||
"""
|
||||
ins = self.squeaks.insert().values(
|
||||
created_time_ms=self.timestamp_now_ms,
|
||||
hash=get_hash(resqueak),
|
||||
squeak=resqueak.serialize(),
|
||||
reply_hash=(resqueak.hashReplySqk
|
||||
if resqueak.is_reply
|
||||
else None),
|
||||
block_hash=resqueak.hashBlock,
|
||||
block_height=resqueak.nBlockHeight,
|
||||
time_s=resqueak.nTime,
|
||||
author_public_key=resqueak.GetPubKey().to_bytes(),
|
||||
resqueak_hash=resqueak.hashResqueakSqk,
|
||||
block_time_s=block_header.nTime,
|
||||
)
|
||||
with self.get_connection() as connection:
|
||||
try:
|
||||
res = connection.execute(ins)
|
||||
squeak_hash = res.inserted_primary_key[0]
|
||||
return squeak_hash
|
||||
except sqlalchemy.exc.IntegrityError:
|
||||
logger.debug("Failed to insert squeak.", exc_info=True)
|
||||
return None
|
||||
|
||||
def get_squeak(self, squeak_hash: bytes) -> Optional[CBaseSqueak]:
|
||||
""" Get a squeak. """
|
||||
s = select([self.squeaks]).where(
|
||||
self.squeaks.c.hash == squeak_hash)
|
||||
|
|
@ -273,8 +310,17 @@ class SqueakDb:
|
|||
select([
|
||||
self.squeaks,
|
||||
self.author_profiles,
|
||||
self.resqueaked_author_profiles,
|
||||
self.recipient_profiles,
|
||||
func.count(self.reply_squeaks.c.hash).label("num_replies"),
|
||||
self.resqueaked_squeaks,
|
||||
func.count(distinct(self.reply_squeaks.c.hash)
|
||||
).label("num_replies"),
|
||||
func.count(distinct(self.resqueaked_reply_squeaks.c.hash)).label(
|
||||
"num_resqueak_replies"),
|
||||
func.count(distinct(self.resqueaks.c.hash)
|
||||
).label("num_resqueaks"),
|
||||
func.count(distinct(self.resqueaked_resqueaks.c.hash)).label(
|
||||
"num_resqueak_resqueaks"),
|
||||
])
|
||||
.select_from(
|
||||
self.squeaks
|
||||
|
|
@ -290,11 +336,33 @@ class SqueakDb:
|
|||
self.reply_squeaks,
|
||||
self.reply_squeaks.c.reply_hash == self.squeaks.c.hash,
|
||||
)
|
||||
.outerjoin(
|
||||
self.resqueaks,
|
||||
self.resqueaks.c.resqueak_hash == self.squeaks.c.hash,
|
||||
)
|
||||
.outerjoin(
|
||||
self.resqueaked_squeaks,
|
||||
self.resqueaked_squeaks.c.hash == self.squeaks.c.resqueak_hash,
|
||||
)
|
||||
.outerjoin(
|
||||
self.resqueaked_author_profiles,
|
||||
self.resqueaked_author_profiles.c.public_key == self.resqueaked_squeaks.c.author_public_key,
|
||||
)
|
||||
.outerjoin(
|
||||
self.resqueaked_reply_squeaks,
|
||||
self.resqueaked_reply_squeaks.c.reply_hash == self.resqueaked_squeaks.c.hash,
|
||||
)
|
||||
.outerjoin(
|
||||
self.resqueaked_resqueaks,
|
||||
self.resqueaked_resqueaks.c.resqueak_hash == self.resqueaked_squeaks.c.hash,
|
||||
)
|
||||
)
|
||||
.group_by(
|
||||
self.squeaks,
|
||||
self.author_profiles,
|
||||
self.resqueaked_author_profiles,
|
||||
self.recipient_profiles,
|
||||
self.resqueaked_squeaks,
|
||||
)
|
||||
.where(self.squeaks.c.hash == squeak_hash)
|
||||
)
|
||||
|
|
@ -334,8 +402,17 @@ class SqueakDb:
|
|||
select([
|
||||
self.squeaks,
|
||||
self.author_profiles,
|
||||
self.resqueaked_author_profiles,
|
||||
self.recipient_profiles,
|
||||
func.count(self.reply_squeaks.c.hash).label("num_replies"),
|
||||
self.resqueaked_squeaks,
|
||||
func.count(distinct(self.reply_squeaks.c.hash)
|
||||
).label("num_replies"),
|
||||
func.count(distinct(self.resqueaked_reply_squeaks.c.hash)).label(
|
||||
"num_resqueak_replies"),
|
||||
func.count(distinct(self.resqueaks.c.hash)
|
||||
).label("num_resqueaks"),
|
||||
func.count(distinct(self.resqueaked_resqueaks.c.hash)).label(
|
||||
"num_resqueak_resqueaks"),
|
||||
])
|
||||
.select_from(
|
||||
# self.squeaks.outerjoin(
|
||||
|
|
@ -355,11 +432,33 @@ class SqueakDb:
|
|||
self.reply_squeaks,
|
||||
self.reply_squeaks.c.reply_hash == self.squeaks.c.hash,
|
||||
)
|
||||
.outerjoin(
|
||||
self.resqueaks,
|
||||
self.resqueaks.c.resqueak_hash == self.squeaks.c.hash,
|
||||
)
|
||||
.outerjoin(
|
||||
self.resqueaked_squeaks,
|
||||
self.resqueaked_squeaks.c.hash == self.squeaks.c.resqueak_hash,
|
||||
)
|
||||
.outerjoin(
|
||||
self.resqueaked_author_profiles,
|
||||
self.resqueaked_author_profiles.c.public_key == self.resqueaked_squeaks.c.author_public_key,
|
||||
)
|
||||
.outerjoin(
|
||||
self.resqueaked_reply_squeaks,
|
||||
self.resqueaked_reply_squeaks.c.reply_hash == self.resqueaked_squeaks.c.hash,
|
||||
)
|
||||
.outerjoin(
|
||||
self.resqueaked_resqueaks,
|
||||
self.resqueaked_resqueaks.c.resqueak_hash == self.resqueaked_squeaks.c.hash,
|
||||
)
|
||||
)
|
||||
.group_by(
|
||||
self.squeaks,
|
||||
self.author_profiles,
|
||||
self.resqueaked_author_profiles,
|
||||
self.recipient_profiles,
|
||||
self.resqueaked_squeaks,
|
||||
)
|
||||
.where(self.profile_is_following(self.author_profiles))
|
||||
.where(
|
||||
|
|
@ -407,8 +506,17 @@ class SqueakDb:
|
|||
select([
|
||||
self.squeaks,
|
||||
self.author_profiles,
|
||||
self.resqueaked_author_profiles,
|
||||
self.recipient_profiles,
|
||||
func.count(self.reply_squeaks.c.hash).label("num_replies"),
|
||||
self.resqueaked_squeaks,
|
||||
func.count(distinct(self.reply_squeaks.c.hash)
|
||||
).label("num_replies"),
|
||||
func.count(distinct(self.resqueaked_reply_squeaks.c.hash)).label(
|
||||
"num_resqueak_replies"),
|
||||
func.count(distinct(self.resqueaks.c.hash)
|
||||
).label("num_resqueaks"),
|
||||
func.count(distinct(self.resqueaked_resqueaks.c.hash)).label(
|
||||
"num_resqueak_resqueaks"),
|
||||
])
|
||||
.select_from(
|
||||
# self.squeaks.outerjoin(
|
||||
|
|
@ -428,11 +536,33 @@ class SqueakDb:
|
|||
self.reply_squeaks,
|
||||
self.reply_squeaks.c.reply_hash == self.squeaks.c.hash,
|
||||
)
|
||||
.outerjoin(
|
||||
self.resqueaks,
|
||||
self.resqueaks.c.resqueak_hash == self.squeaks.c.hash,
|
||||
)
|
||||
.outerjoin(
|
||||
self.resqueaked_squeaks,
|
||||
self.resqueaked_squeaks.c.hash == self.squeaks.c.resqueak_hash,
|
||||
)
|
||||
.outerjoin(
|
||||
self.resqueaked_author_profiles,
|
||||
self.resqueaked_author_profiles.c.public_key == self.resqueaked_squeaks.c.author_public_key,
|
||||
)
|
||||
.outerjoin(
|
||||
self.resqueaked_reply_squeaks,
|
||||
self.resqueaked_reply_squeaks.c.reply_hash == self.resqueaked_squeaks.c.hash,
|
||||
)
|
||||
.outerjoin(
|
||||
self.resqueaked_resqueaks,
|
||||
self.resqueaked_resqueaks.c.resqueak_hash == self.resqueaked_squeaks.c.hash,
|
||||
)
|
||||
)
|
||||
.group_by(
|
||||
self.squeaks,
|
||||
self.author_profiles,
|
||||
self.resqueaked_author_profiles,
|
||||
self.recipient_profiles,
|
||||
self.resqueaked_squeaks,
|
||||
)
|
||||
.where(
|
||||
self.squeak_is_liked,
|
||||
|
|
@ -485,8 +615,17 @@ class SqueakDb:
|
|||
select([
|
||||
self.squeaks,
|
||||
self.author_profiles,
|
||||
self.resqueaked_author_profiles,
|
||||
self.recipient_profiles,
|
||||
func.count(self.reply_squeaks.c.hash).label("num_replies"),
|
||||
self.resqueaked_squeaks,
|
||||
func.count(distinct(self.reply_squeaks.c.hash)
|
||||
).label("num_replies"),
|
||||
func.count(distinct(self.resqueaked_reply_squeaks.c.hash)).label(
|
||||
"num_resqueak_replies"),
|
||||
func.count(distinct(self.resqueaks.c.hash)
|
||||
).label("num_resqueaks"),
|
||||
func.count(distinct(self.resqueaked_resqueaks.c.hash)).label(
|
||||
"num_resqueak_resqueaks"),
|
||||
])
|
||||
.select_from(
|
||||
# self.squeaks.outerjoin(
|
||||
|
|
@ -506,11 +645,33 @@ class SqueakDb:
|
|||
self.reply_squeaks,
|
||||
self.reply_squeaks.c.reply_hash == self.squeaks.c.hash,
|
||||
)
|
||||
.outerjoin(
|
||||
self.resqueaks,
|
||||
self.resqueaks.c.resqueak_hash == self.squeaks.c.hash,
|
||||
)
|
||||
.outerjoin(
|
||||
self.resqueaked_squeaks,
|
||||
self.resqueaked_squeaks.c.hash == self.squeaks.c.resqueak_hash,
|
||||
)
|
||||
.outerjoin(
|
||||
self.resqueaked_author_profiles,
|
||||
self.resqueaked_author_profiles.c.public_key == self.resqueaked_squeaks.c.author_public_key,
|
||||
)
|
||||
.outerjoin(
|
||||
self.resqueaked_reply_squeaks,
|
||||
self.resqueaked_reply_squeaks.c.reply_hash == self.resqueaked_squeaks.c.hash,
|
||||
)
|
||||
.outerjoin(
|
||||
self.resqueaked_resqueaks,
|
||||
self.resqueaked_resqueaks.c.resqueak_hash == self.resqueaked_squeaks.c.hash,
|
||||
)
|
||||
)
|
||||
.group_by(
|
||||
self.squeaks,
|
||||
self.author_profiles,
|
||||
self.resqueaked_author_profiles,
|
||||
self.recipient_profiles,
|
||||
self.resqueaked_squeaks,
|
||||
)
|
||||
.where(self.squeaks.c.author_public_key == public_key.to_bytes())
|
||||
.where(
|
||||
|
|
@ -564,8 +725,17 @@ class SqueakDb:
|
|||
select([
|
||||
self.squeaks,
|
||||
self.author_profiles,
|
||||
self.resqueaked_author_profiles,
|
||||
self.recipient_profiles,
|
||||
func.count(self.reply_squeaks.c.hash).label("num_replies"),
|
||||
self.resqueaked_squeaks,
|
||||
func.count(distinct(self.reply_squeaks.c.hash)
|
||||
).label("num_replies"),
|
||||
func.count(distinct(self.resqueaked_reply_squeaks.c.hash)).label(
|
||||
"num_resqueak_replies"),
|
||||
func.count(distinct(self.resqueaks.c.hash)
|
||||
).label("num_resqueaks"),
|
||||
func.count(distinct(self.resqueaked_resqueaks.c.hash)).label(
|
||||
"num_resqueak_resqueaks"),
|
||||
])
|
||||
.select_from(
|
||||
# self.squeaks.outerjoin(
|
||||
|
|
@ -585,11 +755,33 @@ class SqueakDb:
|
|||
self.reply_squeaks,
|
||||
self.reply_squeaks.c.reply_hash == self.squeaks.c.hash,
|
||||
)
|
||||
.outerjoin(
|
||||
self.resqueaks,
|
||||
self.resqueaks.c.resqueak_hash == self.squeaks.c.hash,
|
||||
)
|
||||
.outerjoin(
|
||||
self.resqueaked_squeaks,
|
||||
self.resqueaked_squeaks.c.hash == self.squeaks.c.resqueak_hash,
|
||||
)
|
||||
.outerjoin(
|
||||
self.resqueaked_author_profiles,
|
||||
self.resqueaked_author_profiles.c.public_key == self.resqueaked_squeaks.c.author_public_key,
|
||||
)
|
||||
.outerjoin(
|
||||
self.resqueaked_reply_squeaks,
|
||||
self.resqueaked_reply_squeaks.c.reply_hash == self.resqueaked_squeaks.c.hash,
|
||||
)
|
||||
.outerjoin(
|
||||
self.resqueaked_resqueaks,
|
||||
self.resqueaked_resqueaks.c.resqueak_hash == self.resqueaked_squeaks.c.hash,
|
||||
)
|
||||
)
|
||||
.group_by(
|
||||
self.squeaks,
|
||||
self.author_profiles,
|
||||
self.resqueaked_author_profiles,
|
||||
self.recipient_profiles,
|
||||
self.resqueaked_squeaks,
|
||||
)
|
||||
.where(self.squeaks.c.content.ilike(f'%{search_text}%'))
|
||||
.where(
|
||||
|
|
@ -645,8 +837,17 @@ class SqueakDb:
|
|||
select([
|
||||
self.squeaks,
|
||||
self.author_profiles,
|
||||
self.resqueaked_author_profiles,
|
||||
self.recipient_profiles,
|
||||
func.count(self.reply_squeaks.c.hash).label("num_replies"),
|
||||
self.resqueaked_squeaks,
|
||||
func.count(distinct(self.reply_squeaks.c.hash)
|
||||
).label("num_replies"),
|
||||
func.count(distinct(self.resqueaked_reply_squeaks.c.hash)).label(
|
||||
"num_resqueak_replies"),
|
||||
func.count(distinct(self.resqueaks.c.hash)
|
||||
).label("num_resqueaks"),
|
||||
func.count(distinct(self.resqueaked_resqueaks.c.hash)).label(
|
||||
"num_resqueak_resqueaks"),
|
||||
])
|
||||
.select_from(
|
||||
self.squeaks.join(
|
||||
|
|
@ -668,11 +869,33 @@ class SqueakDb:
|
|||
self.reply_squeaks,
|
||||
self.reply_squeaks.c.reply_hash == self.squeaks.c.hash,
|
||||
)
|
||||
.outerjoin(
|
||||
self.resqueaks,
|
||||
self.resqueaks.c.resqueak_hash == self.squeaks.c.hash,
|
||||
)
|
||||
.outerjoin(
|
||||
self.resqueaked_squeaks,
|
||||
self.resqueaked_squeaks.c.hash == self.squeaks.c.resqueak_hash,
|
||||
)
|
||||
.outerjoin(
|
||||
self.resqueaked_author_profiles,
|
||||
self.resqueaked_author_profiles.c.public_key == self.resqueaked_squeaks.c.author_public_key,
|
||||
)
|
||||
.outerjoin(
|
||||
self.resqueaked_reply_squeaks,
|
||||
self.resqueaked_reply_squeaks.c.reply_hash == self.resqueaked_squeaks.c.hash,
|
||||
)
|
||||
.outerjoin(
|
||||
self.resqueaked_resqueaks,
|
||||
self.resqueaked_resqueaks.c.resqueak_hash == self.resqueaked_squeaks.c.hash,
|
||||
)
|
||||
)
|
||||
.group_by(
|
||||
self.squeaks,
|
||||
self.author_profiles,
|
||||
self.resqueaked_author_profiles,
|
||||
self.recipient_profiles,
|
||||
self.resqueaked_squeaks,
|
||||
ancestors.c.depth,
|
||||
)
|
||||
.order_by(
|
||||
|
|
@ -731,8 +954,17 @@ class SqueakDb:
|
|||
select([
|
||||
self.squeaks,
|
||||
self.author_profiles,
|
||||
self.resqueaked_author_profiles,
|
||||
self.recipient_profiles,
|
||||
func.count(self.reply_squeaks.c.hash).label("num_replies"),
|
||||
self.resqueaked_squeaks,
|
||||
func.count(distinct(self.reply_squeaks.c.hash)
|
||||
).label("num_replies"),
|
||||
func.count(distinct(self.resqueaked_reply_squeaks.c.hash)).label(
|
||||
"num_resqueak_replies"),
|
||||
func.count(distinct(self.resqueaks.c.hash)
|
||||
).label("num_resqueaks"),
|
||||
func.count(distinct(self.resqueaked_resqueaks.c.hash)).label(
|
||||
"num_resqueak_resqueaks"),
|
||||
])
|
||||
.select_from(
|
||||
# self.squeaks.outerjoin(
|
||||
|
|
@ -752,11 +984,33 @@ class SqueakDb:
|
|||
self.reply_squeaks,
|
||||
self.reply_squeaks.c.reply_hash == self.squeaks.c.hash,
|
||||
)
|
||||
.outerjoin(
|
||||
self.resqueaks,
|
||||
self.resqueaks.c.resqueak_hash == self.squeaks.c.hash,
|
||||
)
|
||||
.outerjoin(
|
||||
self.resqueaked_squeaks,
|
||||
self.resqueaked_squeaks.c.hash == self.squeaks.c.resqueak_hash,
|
||||
)
|
||||
.outerjoin(
|
||||
self.resqueaked_author_profiles,
|
||||
self.resqueaked_author_profiles.c.public_key == self.resqueaked_squeaks.c.author_public_key,
|
||||
)
|
||||
.outerjoin(
|
||||
self.resqueaked_reply_squeaks,
|
||||
self.resqueaked_reply_squeaks.c.reply_hash == self.resqueaked_squeaks.c.hash,
|
||||
)
|
||||
.outerjoin(
|
||||
self.resqueaked_resqueaks,
|
||||
self.resqueaked_resqueaks.c.resqueak_hash == self.resqueaked_squeaks.c.hash,
|
||||
)
|
||||
)
|
||||
.group_by(
|
||||
self.squeaks,
|
||||
self.author_profiles,
|
||||
self.resqueaked_author_profiles,
|
||||
self.recipient_profiles,
|
||||
self.resqueaked_squeaks,
|
||||
)
|
||||
.where(self.squeaks.c.reply_hash == squeak_hash)
|
||||
.where(
|
||||
|
|
@ -2044,8 +2298,11 @@ class SqueakDb:
|
|||
with self.get_connection() as connection:
|
||||
connection.execute(delete_twitter_account_stmt)
|
||||
|
||||
def _parse_squeak(self, row) -> CSqueak:
|
||||
return CSqueak.deserialize(row["squeak"])
|
||||
def _parse_squeak(self, row) -> CBaseSqueak:
|
||||
if row["resqueak_hash"]:
|
||||
return CResqueak.deserialize(row["squeak"])
|
||||
else:
|
||||
return CSqueak.deserialize(row["squeak"])
|
||||
|
||||
# def _parse_squeak_entry(self, row, author_profiles_table=None, recipient_profiles_table=None) -> SqueakEntry:
|
||||
def _parse_squeak_entry(self, row) -> SqueakEntry:
|
||||
|
|
@ -2060,6 +2317,8 @@ class SqueakDb:
|
|||
row, profiles_table=self.author_profiles)
|
||||
recipient_profile = self._try_parse_squeak_profile(
|
||||
row, profiles_table=self.recipient_profiles)
|
||||
resqueaked_squeak = self._parse_resqueaked_squeak_entry(
|
||||
row) if row[self.resqueaked_squeaks.c.author_public_key] else None
|
||||
return SqueakEntry(
|
||||
squeak_hash=(row["hash"]),
|
||||
serialized_squeak=(row["squeak"]),
|
||||
|
|
@ -2075,7 +2334,45 @@ class SqueakDb:
|
|||
secret_key=(row["secret_key"]),
|
||||
liked_time_ms=liked_time_ms,
|
||||
num_replies=row["num_replies"],
|
||||
num_resqueaks=row["num_resqueaks"],
|
||||
content=row["content"],
|
||||
resqueaked_hash=row["resqueak_hash"],
|
||||
resqueaked_squeak=resqueaked_squeak,
|
||||
squeak_profile=profile,
|
||||
recipient_squeak_profile=recipient_profile,
|
||||
)
|
||||
|
||||
def _parse_resqueaked_squeak_entry(self, row) -> SqueakEntry:
|
||||
public_key_bytes = row[self.resqueaked_squeaks.c.author_public_key]
|
||||
recipient_public_key_bytes = row[self.resqueaked_squeaks.c.recipient_public_key]
|
||||
secret_key_column = row[self.resqueaked_squeaks.c.secret_key]
|
||||
is_locked = bool(secret_key_column)
|
||||
reply_to = (
|
||||
row[self.resqueaked_squeaks.c.reply_hash]) if row[self.resqueaked_squeaks.c.reply_hash] else None
|
||||
liked_time_ms = row[self.resqueaked_squeaks.c.liked_time_ms]
|
||||
profile = self._try_parse_squeak_profile(
|
||||
row, profiles_table=self.resqueaked_author_profiles)
|
||||
recipient_profile = self._try_parse_squeak_profile(
|
||||
row, profiles_table=self.recipient_profiles)
|
||||
return SqueakEntry(
|
||||
squeak_hash=(row[self.resqueaked_squeaks.c.hash]),
|
||||
serialized_squeak=(row[self.resqueaked_squeaks.c.squeak]),
|
||||
public_key=SqueakPublicKey.from_bytes(public_key_bytes),
|
||||
recipient_public_key=SqueakPublicKey.from_bytes(
|
||||
recipient_public_key_bytes) if recipient_public_key_bytes else None,
|
||||
block_height=row[self.resqueaked_squeaks.c.block_height],
|
||||
block_hash=(row[self.resqueaked_squeaks.c.block_hash]),
|
||||
block_time=row[self.resqueaked_squeaks.c.block_time_s],
|
||||
squeak_time=row[self.resqueaked_squeaks.c.time_s],
|
||||
reply_to=reply_to,
|
||||
is_unlocked=is_locked,
|
||||
secret_key=(row[self.resqueaked_squeaks.c.secret_key]),
|
||||
liked_time_ms=liked_time_ms,
|
||||
# TODO: needs another join. row[self.resqueaked_squeaks.c.num_replies],
|
||||
num_replies=row["num_resqueak_replies"],
|
||||
num_resqueaks=row["num_resqueak_resqueaks"],
|
||||
content=row[self.resqueaked_squeaks.c.content],
|
||||
resqueaked_hash=row[self.resqueaked_squeaks.c.resqueak_hash],
|
||||
squeak_profile=profile,
|
||||
recipient_squeak_profile=recipient_profile,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -84,6 +84,18 @@ class SqueakController:
|
|||
recipient_profile_id,
|
||||
)
|
||||
|
||||
def make_resqueak(
|
||||
self,
|
||||
profile_id: int,
|
||||
resqueaked_hash: bytes,
|
||||
replyto_hash: Optional[bytes],
|
||||
) -> Optional[bytes]:
|
||||
return self.squeak_store.make_resqueak(
|
||||
profile_id,
|
||||
resqueaked_hash,
|
||||
replyto_hash,
|
||||
)
|
||||
|
||||
def pay_offer(self, received_offer_id: int) -> int:
|
||||
return self.squeak_store.pay_offer(received_offer_id)
|
||||
|
||||
|
|
|
|||
|
|
@ -25,8 +25,10 @@ from typing import Iterator
|
|||
from typing import List
|
||||
from typing import Optional
|
||||
|
||||
from squeak.core import CBaseSqueak
|
||||
from squeak.core import CheckSqueak
|
||||
from squeak.core import CheckSqueakSecretKey
|
||||
from squeak.core import CResqueak
|
||||
from squeak.core import CSqueak
|
||||
from squeak.core.keys import SqueakPrivateKey
|
||||
from squeak.core.keys import SqueakPublicKey
|
||||
|
|
@ -119,32 +121,59 @@ class SqueakStore:
|
|||
)
|
||||
return inserted_squeak_hash
|
||||
|
||||
def save_squeak(self, squeak: CSqueak) -> Optional[bytes]:
|
||||
def make_resqueak(
|
||||
self,
|
||||
profile_id: int,
|
||||
resqueaked_hash: bytes,
|
||||
replyto_hash: Optional[bytes],
|
||||
) -> Optional[bytes]:
|
||||
squeak_profile = self.get_squeak_profile(profile_id)
|
||||
if squeak_profile is None:
|
||||
raise Exception("Profile with id {} not found.".format(
|
||||
profile_id,
|
||||
))
|
||||
resqueak = self.squeak_core.make_resqueak(
|
||||
squeak_profile,
|
||||
resqueaked_hash,
|
||||
replyto_hash,
|
||||
)
|
||||
inserted_resqueak_hash = self.save_squeak(resqueak)
|
||||
if inserted_resqueak_hash is None:
|
||||
raise Exception("Failed to save resqueak.")
|
||||
return inserted_resqueak_hash
|
||||
|
||||
def save_squeak(self, base_squeak: CBaseSqueak) -> Optional[bytes]:
|
||||
# Check if the squeak is valid context free.
|
||||
CheckSqueak(squeak)
|
||||
CheckSqueak(base_squeak)
|
||||
# Get the block header.
|
||||
block_header = self.squeak_core.get_block_header(squeak)
|
||||
block_header = self.squeak_core.get_block_header(base_squeak)
|
||||
# Check if limit exceeded.
|
||||
if self.squeak_db.get_number_of_squeaks() >= self.max_squeaks:
|
||||
raise Exception("Exceeded max number of squeaks.")
|
||||
# TODO: Check if limit per public key per block is exceeded.
|
||||
if self.squeak_db.number_of_squeaks_with_public_key_with_block_height(
|
||||
squeak.GetPubKey(),
|
||||
squeak.nBlockHeight,
|
||||
base_squeak.GetPubKey(),
|
||||
base_squeak.nBlockHeight,
|
||||
) >= self.max_squeaks_per_public_key_per_block:
|
||||
raise Exception(
|
||||
"Exceeded max number of squeaks per public key per block.")
|
||||
# Insert the squeak in db.
|
||||
inserted_squeak_hash = self.squeak_db.insert_squeak(
|
||||
squeak,
|
||||
block_header,
|
||||
)
|
||||
if isinstance(base_squeak, CSqueak):
|
||||
inserted_squeak_hash = self.squeak_db.insert_squeak(
|
||||
base_squeak,
|
||||
block_header,
|
||||
)
|
||||
elif isinstance(base_squeak, CResqueak):
|
||||
inserted_squeak_hash = self.squeak_db.insert_resqueak(
|
||||
base_squeak,
|
||||
block_header,
|
||||
)
|
||||
if inserted_squeak_hash is None:
|
||||
return None
|
||||
logger.info("Saved squeak: {}".format(
|
||||
inserted_squeak_hash.hex(),
|
||||
))
|
||||
self.new_squeak_listener.handle_new_item(squeak)
|
||||
self.new_squeak_listener.handle_new_item(base_squeak)
|
||||
return inserted_squeak_hash
|
||||
|
||||
def save_secret_key(self, squeak_hash: bytes, secret_key: bytes):
|
||||
|
|
@ -230,7 +259,11 @@ class SqueakStore:
|
|||
return sent_payment_id
|
||||
|
||||
def get_squeak(self, squeak_hash: bytes) -> Optional[CSqueak]:
|
||||
return self.squeak_db.get_squeak(squeak_hash)
|
||||
# TODO: remove this after squeak protocol struct stabilizes.
|
||||
try:
|
||||
return self.squeak_db.get_squeak(squeak_hash)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def get_squeak_secret_key(self, squeak_hash: bytes) -> Optional[bytes]:
|
||||
return self.squeak_db.get_squeak_secret_key(squeak_hash)
|
||||
|
|
|
|||
|
|
@ -59,10 +59,13 @@ def create_app(handler):
|
|||
@app.route('/squeak/<hash>')
|
||||
def squeak(hash):
|
||||
try:
|
||||
squeak_bytes = handler.handle_get_squeak_bytes(hash)
|
||||
squeak_type, squeak_bytes = handler.handle_get_squeak_bytes(hash)
|
||||
except NotFoundError:
|
||||
return "Not found", 404
|
||||
return squeak_bytes
|
||||
return jsonify({
|
||||
'squeak_type': squeak_type,
|
||||
'squeak_bytes': squeak_bytes.hex(),
|
||||
})
|
||||
|
||||
@app.route('/secretkey/<hash>')
|
||||
def secret_key(hash):
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@
|
|||
import logging
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Tuple
|
||||
|
||||
from squeak.core.keys import SqueakPublicKey
|
||||
|
||||
|
|
@ -54,12 +55,17 @@ class SqueakPeerServerHandler(object):
|
|||
self.node_settings = node_settings
|
||||
self.config = config
|
||||
|
||||
def handle_get_squeak_bytes(self, squeak_hash_str) -> bytes:
|
||||
def handle_get_squeak_bytes(self, squeak_hash_str) -> Tuple[str, bytes]:
|
||||
"""Return a tuple with the squeak type and the squeak bytes.
|
||||
|
||||
Returns: (str, bytes)
|
||||
"""
|
||||
squeak_hash = bytes.fromhex(squeak_hash_str)
|
||||
squeak = self.squeak_controller.get_squeak(squeak_hash)
|
||||
if not squeak:
|
||||
raise NotFoundError()
|
||||
return squeak.serialize()
|
||||
squeak_type = 'resqueak' if squeak.is_resqueak else 'squeak'
|
||||
return squeak_type, squeak.serialize()
|
||||
|
||||
def handle_get_secret_key(self, squeak_hash_str) -> bytes:
|
||||
squeak_hash = bytes.fromhex(squeak_hash_str)
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ from squeaknode.core.sent_payment_summary import SentPaymentSummary
|
|||
from squeaknode.core.squeak_entry import SqueakEntry
|
||||
from squeaknode.core.squeak_peer import SqueakPeer
|
||||
from squeaknode.core.squeaks import get_hash
|
||||
from squeaknode.core.squeaks import make_resqueak_with_block
|
||||
from squeaknode.core.squeaks import make_squeak_with_block
|
||||
from squeaknode.core.user_config import UserConfig
|
||||
from tests.utils import gen_contact_profile
|
||||
|
|
@ -216,6 +217,21 @@ def private_squeak(private_squeak_and_secret_key):
|
|||
yield squeak
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def resqueak(private_key, squeak_hash, block_info):
|
||||
yield make_resqueak_with_block(
|
||||
private_key,
|
||||
squeak_hash,
|
||||
block_info.block_height,
|
||||
block_info.block_hash,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def resqueak_hash(resqueak):
|
||||
yield get_hash(resqueak)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def peer_address():
|
||||
yield PeerAddress(
|
||||
|
|
@ -308,6 +324,7 @@ def squeak_entry_locked(
|
|||
recipient_squeak_profile=recipient_contact_profile,
|
||||
liked_time_ms=None,
|
||||
num_replies=0,
|
||||
num_resqueaks=0,
|
||||
content=None,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -344,6 +344,21 @@ def test_make_private_squeak(
|
|||
assert author_decrypted_content == squeak_content
|
||||
|
||||
|
||||
def test_make_resqueak(
|
||||
squeak_core,
|
||||
signing_profile,
|
||||
squeak_hash,
|
||||
block_header
|
||||
):
|
||||
created_resqueak = squeak_core.make_resqueak(
|
||||
signing_profile,
|
||||
squeak_hash,
|
||||
)
|
||||
|
||||
assert created_resqueak.hashResqueakSqk == squeak_hash
|
||||
squeak_core.check_squeak(created_resqueak)
|
||||
|
||||
|
||||
def test_get_block_header(
|
||||
squeak_core,
|
||||
squeak,
|
||||
|
|
|
|||
|
|
@ -61,6 +61,11 @@ def inserted_reply_squeak_hash(squeak_db, reply_squeak, block_header):
|
|||
yield squeak_db.insert_squeak(reply_squeak, block_header)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def inserted_resqueak_hash(squeak_db, resqueak, block_header):
|
||||
yield squeak_db.insert_resqueak(resqueak, block_header)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def unlocked_squeak_hash(squeak_db, squeak, inserted_squeak_hash, secret_key, squeak_content):
|
||||
squeak_db.set_squeak_secret_key(
|
||||
|
|
@ -488,6 +493,12 @@ def test_get_missing_squeak(squeak_db, squeak, squeak_hash):
|
|||
assert retrieved_squeak is None
|
||||
|
||||
|
||||
def test_get_resqueak(squeak_db, resqueak, inserted_resqueak_hash):
|
||||
retrieved_resqueak = squeak_db.get_squeak(inserted_resqueak_hash)
|
||||
|
||||
assert retrieved_resqueak == resqueak
|
||||
|
||||
|
||||
def test_get_squeak_entry(
|
||||
squeak_db,
|
||||
squeak,
|
||||
|
|
@ -584,6 +595,34 @@ def test_get_secret_key_missing_squeak(squeak_db, squeak, squeak_hash):
|
|||
assert retrieved_secret_key is None
|
||||
|
||||
|
||||
def test_get_resqueak_entry(
|
||||
squeak_db,
|
||||
resqueak,
|
||||
squeak,
|
||||
block_header,
|
||||
public_key,
|
||||
signing_profile,
|
||||
inserted_resqueak_hash,
|
||||
inserted_squeak_hash,
|
||||
inserted_signing_profile_id,
|
||||
):
|
||||
retrieved_resqueak_entry = squeak_db.get_squeak_entry(
|
||||
inserted_resqueak_hash)
|
||||
retrieved_squeak_entry = squeak_db.get_squeak_entry(inserted_squeak_hash)
|
||||
|
||||
assert retrieved_resqueak_entry.squeak_hash == inserted_resqueak_hash
|
||||
assert retrieved_resqueak_entry.public_key == public_key
|
||||
assert retrieved_resqueak_entry.content is None
|
||||
assert retrieved_resqueak_entry.block_time == block_header.nTime
|
||||
assert retrieved_resqueak_entry.squeak_profile._replace(
|
||||
profile_id=None) == signing_profile
|
||||
assert retrieved_resqueak_entry.resqueaked_hash == inserted_squeak_hash
|
||||
assert retrieved_resqueak_entry.resqueaked_squeak == retrieved_squeak_entry
|
||||
assert retrieved_resqueak_entry.resqueaked_squeak.public_key == public_key
|
||||
assert retrieved_resqueak_entry.num_resqueaks == 0
|
||||
assert retrieved_resqueak_entry.resqueaked_squeak.num_resqueaks == 1
|
||||
|
||||
|
||||
def test_get_timeline_squeak_entries(squeak_db, followed_squeak_hashes):
|
||||
timeline_squeak_entries = squeak_db.get_timeline_squeak_entries(
|
||||
limit=2,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue