diff --git a/services/demux/database/migrations/0050_messages.sql b/services/demux/database/migrations/0050_messages.sql new file mode 100644 index 0000000..9465a75 --- /dev/null +++ b/services/demux/database/migrations/0050_messages.sql @@ -0,0 +1,10 @@ +CREATE TABLE IF NOT EXISTS "${schema^}"."messages" ( + "id" SERIAL PRIMARY KEY, + "pet_id" INTEGER NOT NULL REFERENCES "${schema^}"."pets", + "message" TEXT NOT NULL, + "created_at" TIMESTAMP NOT NULL, + "created_block" BIGINT NOT NULL, + "created_trx" TEXT NOT NULL, + "created_eosacc" TEXT NOT NULL, + "_dmx_created_at" TIMESTAMP DEFAULT current_timestamp NOT NULL +); diff --git a/services/demux/src/updaters.ts b/services/demux/src/updaters.ts index 3cc9fc3..eb76dc9 100644 --- a/services/demux/src/updaters.ts +++ b/services/demux/src/updaters.ts @@ -295,6 +295,25 @@ const battleattack = async (db: any, payload: any, blockInfo: BlockInfo) => { } +const messagefrom = async (db: any, payload: any, blockInfo: BlockInfo) => { + + console.info("\n\n==== Message From ====") + console.info("\n\nUpdater Payload >>> \n", payload) + console.info("\n\nUpdater Block Info >>> \n", blockInfo) + + const data = { + pet_id: payload.data.pet_id, + message: payload.data.message, + created_block: blockInfo.blockNumber, + created_trx: payload.transactionId, + created_at: blockInfo.timestamp, + created_eosacc: payload.authorization[0].actor, + } + + const res = await db.messages.insert(data) + console.info("DB State Result >>> ", res) +} + const updaters = [ { actionType: "monstereosio::createpet", @@ -340,6 +359,10 @@ const updaters = [ actionType: "monstereosio::battleattack", updater: battleattack, }, + { + actionType: "monstereosio::messagefrom", + updater: messagefrom, + }, ] export { updaters } diff --git a/services/eos-dev/contracts/monstereosio/include/pet/pet.hpp b/services/eos-dev/contracts/monstereosio/include/pet/pet.hpp index 5631df4..6b75354 100644 --- a/services/eos-dev/contracts/monstereosio/include/pet/pet.hpp +++ b/services/eos-dev/contracts/monstereosio/include/pet/pet.hpp @@ -93,6 +93,9 @@ class pet : public eosio::contract { void signup ( name user ); void transfer ( uint64_t sender, uint64_t receiver ); + // messaging + void messagefrom (uuid petid, string message); + private: /* ****************************************** */ diff --git a/services/eos-dev/contracts/monstereosio/monstereosio.abi b/services/eos-dev/contracts/monstereosio/monstereosio.abi index dbc0e8b..673af62 100644 --- a/services/eos-dev/contracts/monstereosio/monstereosio.abi +++ b/services/eos-dev/contracts/monstereosio/monstereosio.abi @@ -273,6 +273,17 @@ "type": "uuid" } ] + },{ + "name": "messagefrom", + "base": "", + "fields": [{ + "name": "pet_id", + "type": "uuid" + },{ + "name": "message", + "type": "string" + } + ] },{ "name": "feedpet", "base": "", @@ -620,6 +631,10 @@ "name": "destroypet", "type": "destroypet", "ricardian_contract": "---\ntitle: Destroy Monster\nsummary: Delete and destroy a Monster forever\nicon: https://monstereos.io/favicon.png#e6479a7f15b9f19775b09703a5973af41e6e6c0eefbe0c09b9f032a286248b74\n---\n\n## Destroy Terms & Conditions\n\nI, the owner of the pet {{pet_id}}, am destroying this monster\nbecause I no longer have interest in taking care of it.\n\nI understand that this action cannot be undone, the monster will\nbe cruelty destroyed and no longer be accessible or recreated by\nanyone.\n\nI understand that monsters actions are not reversible after the\n{{$transaction.delay_sec}} seconds or other delay as configured\nby my own permissions.\n\nIf this action fails to be irreversibly confirmed for technical issues\nor failure, I agree that I need to attempt to submit this action again,\nand also the subsequent interactions that I could possibly being submitted\nin this interval.\n\n" + },{ + "name": "messagefrom", + "type": "messagefrom", + "ricardian_contract": "" },{ "name": "techrevive", "type": "techrevive", diff --git a/services/eos-dev/contracts/monstereosio/src/pet.cpp b/services/eos-dev/contracts/monstereosio/src/pet.cpp index 0cfdf7b..9370565 100644 --- a/services/eos-dev/contracts/monstereosio/src/pet.cpp +++ b/services/eos-dev/contracts/monstereosio/src/pet.cpp @@ -3,6 +3,7 @@ #include "pet.admin.cpp" #include "pet.battle.cpp" #include "pet.market.cpp" +#include "pet.messages.cpp" using namespace utils; using namespace types; diff --git a/services/eos-dev/contracts/monstereosio/src/pet.messages.cpp b/services/eos-dev/contracts/monstereosio/src/pet.messages.cpp new file mode 100644 index 0000000..54cbeb6 --- /dev/null +++ b/services/eos-dev/contracts/monstereosio/src/pet.messages.cpp @@ -0,0 +1,8 @@ + +void pet::messagefrom(uuid petid, string message) { + eosio_assert(message.length() <= 255, "E404|message longer than 255"); + auto pc = _get_pet_config(); + auto pet = pets.get(petid, "E404|Invalid petid"); + require_auth(pet.owner); + eosio_assert(_is_alive(pet, pc), "E404|dead monsters can't message"); +} \ No newline at end of file diff --git a/services/eos-node/config-full-node.ini b/services/eos-node/config-full-node.ini index 5419be5..430d9ab 100644 --- a/services/eos-node/config-full-node.ini +++ b/services/eos-node/config-full-node.ini @@ -85,6 +85,7 @@ mongodb-filter-on = monstereosio:changebatami: mongodb-filter-on = monstereosio:changebatama: mongodb-filter-on = monstereosio:migrate: mongodb-filter-on = monstereosio:transfer: +mongodb-filter-on = monstereosio:messagefrom: # peer-key = # peer-private-key = diff --git a/services/eos-node/config-main-node.ini b/services/eos-node/config-main-node.ini index 69e1acc..caba49a 100644 --- a/services/eos-node/config-main-node.ini +++ b/services/eos-node/config-main-node.ini @@ -85,6 +85,7 @@ mongodb-filter-on = monstereosio:changebatami: mongodb-filter-on = monstereosio:changebatama: mongodb-filter-on = monstereosio:migrate: mongodb-filter-on = monstereosio:transfer: +mongodb-filter-on = monstereosio:messagefrom: # peer-key = # peer-private-key = diff --git a/services/frontend/src/modules/pages/HomeScreen.tsx b/services/frontend/src/modules/pages/HomeScreen.tsx index dee9c05..09b3028 100644 --- a/services/frontend/src/modules/pages/HomeScreen.tsx +++ b/services/frontend/src/modules/pages/HomeScreen.tsx @@ -8,13 +8,38 @@ import get3dModel from "../monsters/monster3DMatrix" import { monsterModelSrc, } from "../monsters/monsters" +import MessageBoard from "../shared/MessageBoard" +import MessageSenderModal from "../shared/MessageSenderModal" const PAGE_WELCOME = process.env.REACT_APP_PAGE_WELCOME || "A Monster Tamagotchi and Battle game for EOS blockchain!" -const HomeScreen = (props: any) => { +interface Props { + scatter:any, + identity:any +} + +interface ReactState { + showMessageSender:boolean, + requiresMsgUpdate:boolean +} + +class HomeScreen extends React.Component { + public state = {showMessageSender:false, requiresMsgUpdate:true} + + public render() { + const {showMessageSender, requiresMsgUpdate} = this.state const monster3dModel = get3dModel(105) // DEVIL + const messageSenderClosed = (updateRequested:boolean) => { + this.setState({showMessageSender:false, requiresMsgUpdate:updateRequested}) + } + + const doShowMessageSender = () => { + this.setState({showMessageSender:true}) + } + + return (
{ />

{PAGE_WELCOME}

+
+ Send your own message + {showMessageSender && }
) + } } export default HomeScreen \ No newline at end of file diff --git a/services/frontend/src/modules/shared/MessageBoard.tsx b/services/frontend/src/modules/shared/MessageBoard.tsx new file mode 100644 index 0000000..d7b0eea --- /dev/null +++ b/services/frontend/src/modules/shared/MessageBoard.tsx @@ -0,0 +1,100 @@ +import * as React from "react" +import * as moment from "moment" +import { MonsterProps, monsterImageSrc } from "../monsters/monsters" +import { Query } from "react-apollo" +import gql from "graphql-tag" + +export interface Message { + monster:MonsterProps, + message:string +} + + +const MessageCard = (props:any) => { + const sinceText = moment.parseZone(props.message.createdAt).fromNow() + const url = "https://2d.monstereos.io" + monsterImageSrc(props.message.petByPetId.typeId) + const trxLink = "https://bloks.io/transaction/" + props.message.createdTrx + return( +
+
+ {props.message.message}
+ {props.message.petByPetId.petName} owned by {props.message.petByPetId.owner}
+ sent by {props.message.createdEosacc} {sinceText} +
+ ) +} + + +export const QUERY_MESSAGES = gql` +query LatestMessages($limit: Int!, $offset: Int!) { + allMessages( + first: $limit, + offset: $offset, + orderBy: ID_DESC, + ) { + edges { + node { + id + message + createdAt + createdTrx + createdEosacc + petByPetId { + petName + typeId + owner + } + } + } + } +} +` + +interface Props { + requiresMsgUpdate:boolean +} + +class MessageBoard extends React.Component { + + public render() { + const {requiresMsgUpdate} = this.props + const variables = { + limit: 3, + offset: 0 + } + + return
+ + {({error, data, loading, refetch}) => { + if (requiresMsgUpdate) { + // tslint:disable-next-line:no-console + console.log("refetching") + setTimeout(()=>refetch(variables), 500) + } + if (error) { + return ({error.toString()} {JSON.stringify(error)}) + } + if (loading || !data || !data.allMessages) { + return + Loading... Our servers are Syncing with the Chain + + } + const messages = data.allMessages.edges + + return
+ {messages.map(({node}:any, index:number) => { + return( + + )})} +
+ } + } +
+
+ } +} + +export default MessageBoard \ No newline at end of file diff --git a/services/frontend/src/modules/shared/MessageSenderModal.tsx b/services/frontend/src/modules/shared/MessageSenderModal.tsx new file mode 100644 index 0000000..c88200c --- /dev/null +++ b/services/frontend/src/modules/shared/MessageSenderModal.tsx @@ -0,0 +1,137 @@ +import * as React from "react" +import { MonsterProps } from "../monsters/monsters" +import { State, NOTIFICATION_ERROR, NOTIFICATION_SUCCESS, pushNotification } from "../../store" +import { connect } from "react-redux" +import Modal from "./Modal" +import { trxMessageFrom } from "../../utils/eos" + + +interface Props { + scatter: any, + closeModal: (msgSent:boolean) => void, + dispatchPushNotification: any, + myMonsters: MonsterProps[] +} + + +interface ReactState { + message:string, + monster:MonsterProps | undefined, + validMessage: boolean +} + +class MessageSenderModal extends React.Component { + + public state: ReactState = { + message: "", + monster: undefined, + validMessage: false + } + + public render() { + const { closeModal, myMonsters } = this.props + const { message, monster } = this.state + + const footerButtons = [ + closeModal(false)}> + Cancel + , + + Send Message + + ] + + return ( + closeModal(false)} + footerButtons={footerButtons}> +

Please follow the EOS constitution when you post a message to the public. It will be there for ever!

+
+ +
+
+ +
+ +
+
+
+
+ +
+
+
+ ) + } + + private handleChangeMonster = (event: any) => { + const {myMonsters} = this.props + const monster = myMonsters.find((m) => m.id === Number(event.target.value)) + if (monster) { + this.setState({monster}) + } + } + + private handleMessageChange = (event: any) => { + const message = event.target.value + const validMessage = message.length <= 255 + this.setState({validMessage, message}) + } + + private sendMessage = () => { + const { scatter, closeModal, dispatchPushNotification } = this.props + const { validMessage, monster, message } = this.state + + if (!monster) { + return dispatchPushNotification(`Monster is required to send a message`, NOTIFICATION_ERROR) + } + + if (monster && !validMessage) { + return dispatchPushNotification(`Message more than 255 characters`, NOTIFICATION_ERROR) + } + + trxMessageFrom(scatter, monster!!.id, message) + .then((res: any) => { + console.info(`Message from ${monster!!.id} was successfully sent`, res) + dispatchPushNotification(`Message from ${monster!!.name} was successfully sent`, NOTIFICATION_SUCCESS) + closeModal(true) + }).catch((err: any) => { + dispatchPushNotification(`Fail to send message from ${monster!!.name}: ${err.eosError}`, NOTIFICATION_ERROR) + }) + } +} + +const mapStateToProps = (state: State) => { + return { + scatter: state.scatter, + myMonsters: state.myMonsters + } +} + +const mapDispatchToProps = { + dispatchPushNotification: pushNotification +} + +export default connect(mapStateToProps, mapDispatchToProps)(MessageSenderModal) \ No newline at end of file diff --git a/services/frontend/src/utils/eos.ts b/services/frontend/src/utils/eos.ts index c6e70e6..8269877 100644 --- a/services/frontend/src/utils/eos.ts +++ b/services/frontend/src/utils/eos.ts @@ -153,6 +153,16 @@ export const trxPlaceBidMarket = async ( return contract.bidpet(petId, eosAuthorization.account.name, 0, amount, eosAuthorization.permission).catch(trxError) } +export const trxMessageFrom = async ( + scatter: any, + petId: number, + message: string +) => { + const eosAuthorization = getEosAuthorization(scatter.identity) + const contract = await getContract(scatter, network, MONSTERS_ACCOUNT) + return contract.messagefrom(petId, message, eosAuthorization.permission).catch(trxError) +} + // eos api const e2DefaultRpc = new e2Rpc.JsonRpc(CHAIN_URL, { fetch }) const e2HistoryRpc = new e2Rpc.JsonRpc(HISTORY_CHAIN_URL, { fetch })