Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Task management: Added SQL tables for storing todo lists and tasks #271

Draft
wants to merge 17 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,8 @@ cd $(go env GOPATH)/src/github.com/cryptag/leapchat/db
chmod a+rx ~/
createdb
sudo -u $USER bash init_sql.sh
wget https://github.com/PostgREST/postgrest/releases/download/v7.0.0/postgrest-v7.0.0-osx.tar.xz
tar xvf postgrest-v7.0.0-osx.tar.xz
wget https://github.com/PostgREST/postgrest/releases/download/v10.1.1/postgrest-v10.1.1-macos-x64.tar.xz
tar xvf postgrest-v10.1.1-macos-x64.tar.xz
./postgrest postgrest.conf
```

Expand Down Expand Up @@ -157,8 +157,8 @@ and have `postgrest` connect to Postgres:
cd $(go env GOPATH)/src/github.com/cryptag/leapchat/db
chmod a+rx ~/
sudo -u postgres bash init_sql.sh
wget https://github.com/PostgREST/postgrest/releases/download/v7.0.0/postgrest-v7.0.0-ubuntu.tar.xz
tar xvf postgrest-v7.0.0-ubuntu.tar.xz
wget https://github.com/PostgREST/postgrest/releases/download/v10.1.1/postgrest-v10.1.1-linux-static-x64.tar.xz
tar xvf postgrest-v10.1.1-linux-static-x64.tar.xz
./postgrest postgrest.conf
```

Expand Down
13 changes: 13 additions & 0 deletions db/sql/table03_todo_lists.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
CREATE TABLE todo_lists (
id uuid NOT NULL UNIQUE PRIMARY KEY DEFAULT uuid_generate_v4(),
room_id text NOT NULL REFERENCES rooms ON DELETE CASCADE,

-- This is called 'title_enc' (encrypted title) but stores
-- miniLock ciphertext, which can also store metadata
title_enc text NOT NULL -- base64-encoded ciphertext

-- ASSUMPTION: todo lists don't expire and must be manually deleted by the user

-- ASSUMPTION: no timestamp needed; better for user metadata privacy to not store it
);
ALTER TABLE todo_lists OWNER TO superuser;
16 changes: 16 additions & 0 deletions db/sql/table04_tasks.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
CREATE TABLE tasks (
id uuid NOT NULL UNIQUE PRIMARY KEY DEFAULT uuid_generate_v4(),
room_id text NOT NULL REFERENCES rooms ON DELETE CASCADE,

-- This is called 'title_enc' (encrypted title) but stores
-- miniLock ciphertext, which can also store metadata
title_enc text NOT NULL, -- base64-encoded ciphertext

list_id uuid NOT NULL REFERENCES todo_lists ON DELETE CASCADE,
index double precision NOT NULL -- index in the todo list with list_id

-- ASSUMPTION: tasks don't expire and must be manually deleted by the user

-- ASSUMPTION: no timestamp needed; better for user metadata privacy to not store it
);
ALTER TABLE tasks OWNER TO superuser;
37 changes: 31 additions & 6 deletions messages.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ import (

type Message []byte

type TodoList struct {
TitleEnc string `json:"title_enc"` // base64-encoded, encrypted title
}

type OutgoingPayload struct {
Ephemeral []Message `json:"ephemeral"`
FromServer FromServer `json:"from_server,omitempty"`
Expand All @@ -26,8 +30,10 @@ type ToServer struct {
}

type IncomingPayload struct {
Ephemeral []Message `json:"ephemeral"`
ToServer ToServer `json:"to_server"`
Ephemeral []Message `json:"ephemeral"`
TodoLists []TodoList `json:"todo_lists"`
// Tasks []string `json:"tasks"`
ToServer ToServer `json:"to_server"`
}

func WSMessagesHandler(rooms *RoomManager) func(w http.ResponseWriter, r *http.Request) {
Expand Down Expand Up @@ -92,12 +98,31 @@ func messageReader(room *Room, client *Client) {
continue
}

err = room.AddMessages(payload.Ephemeral, payload.ToServer.TTL)
if err != nil {
log.Debugf("Error from AddMessages: %v", err)
continue
if len(payload.Ephemeral) > 0 {
err = room.AddMessages(payload.Ephemeral, payload.ToServer.TTL)
if err != nil {
log.Debugf("Error from AddMessages: %v", err)
continue
}
}

if len(payload.TodoLists) > 0 {
jsonToBroadcast, err := room.AddTodoLists(payload.TodoLists)
if err != nil {
log.Debugf("Error from AddTodoLists: %v", err)
continue
}
room.BroadcastJSON(client, jsonToBroadcast)
}

// if len(payload.Tasks) > 0 {
// err = room.AddTasks(payload.Tasks)
// if err != nil {
// log.Debugf("Error from AddTasks: %v", err)
// continue
// }
// }

room.BroadcastMessages(client, payload.Ephemeral...)

case websocket.BinaryMessage:
Expand Down
6 changes: 6 additions & 0 deletions pg_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,12 @@ type PGMessage struct {
Created *time.Time `json:"created,omitempty"`
}

type PGTodoList struct {
ID *string `json:"id,omitempty"`
RoomID string `json:"room_id"`
TitleEnc string `json:"title_enc"`
}

type pgPostMessage PGMessage

func (msg *PGMessage) MarshalJSON() ([]byte, error) {
Expand Down
77 changes: 69 additions & 8 deletions room.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package main

import (
"bytes"
"encoding/base64"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"sync"
"time"

Expand Down Expand Up @@ -110,17 +113,57 @@ func (r *Room) GetMessages() ([]Message, error) {
}

func (r *Room) AddMessages(msgs []Message, ttlSecs *int) error {
post := make(PGMessages, len(msgs))
toPost := make(PGMessages, len(msgs))

for i := 0; i < len(msgs); i++ {
post[i] = &PGMessage{
toPost[i] = &PGMessage{
RoomID: r.ID,
MessageEnc: string(msgs[i]),
TTL: ttlSecs,
}
}

return post.Create(r.pgClient)
return toPost.Create(r.pgClient)
}

func (r *Room) AddTodoLists(lists []TodoList) (toBroadcast []byte, err error) {
toPost := make([]PGTodoList, len(lists))

for i := 0; i < len(lists); i++ {
toPost[i].RoomID = r.ID
toPost[i].TitleEnc = lists[i].TitleEnc
}

return MarshalToPostgrestResp("/todo_lists", toPost, http.StatusCreated)
}

func MarshalToPostgrestResp(urlSuffix string, toPost interface{}, wantedCode int) (respBytes []byte, err error) {
toPostBytes, err := json.Marshal(toPost)
if err != nil {
return nil, err
}

r := bytes.NewReader(toPostBytes)
req, _ := http.NewRequest("POST", POSTGREST_BASE_URL+urlSuffix, r)
req.Header.Add("Prefer", "return=representation")
req.Header.Add("Content-Type", "application/json")

resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()

respBytes, err = ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}

if resp.StatusCode != wantedCode {
return nil, errors.New(string(respBytes))
}

return
}

func byteaToBytes(hexdata string) ([]byte, error) {
Expand Down Expand Up @@ -183,6 +226,20 @@ func (r *Room) BroadcastMessages(sender *Client, msgs ...Message) {
}
}

func (r *Room) BroadcastJSON(sender *Client, jsonToBroadcast []byte) {
r.clientLock.RLock()
defer r.clientLock.RUnlock()

for _, client := range r.Clients {
go func(client *Client) {
err := client.SendJSON(jsonToBroadcast)
if err != nil {
log.Debugf("Error sending message. Err: %s", err)
}
}(client)
}
}

func (r *Room) DeleteAllMessages() error {
resp, err := r.pgClient.Delete("/messages?room_id=eq." + r.ID)
if err != nil {
Expand Down Expand Up @@ -224,19 +281,23 @@ type Client struct {
}

func (c *Client) SendMessages(msgs ...Message) error {
c.writeLock.Lock()
defer c.writeLock.Unlock()

outgoing := OutgoingPayload{Ephemeral: msgs}

body, err := json.Marshal(outgoing)
if err != nil {
return err
}

err = c.wsConn.WriteMessage(websocket.TextMessage, body)
return c.SendJSON(body)
}

func (c *Client) SendJSON(body []byte) error {
c.writeLock.Lock()
defer c.writeLock.Unlock()

err := c.wsConn.WriteMessage(websocket.TextMessage, body)
if err != nil {
log.Debugf("Error sending message to client. Removing client from room. Err: %s", err)
log.Debugf("Error sending JSON to client. Removing client from room. Err: %s", err)
c.room.RemoveClient(c)
return err
}
Expand Down
3 changes: 3 additions & 0 deletions src/components/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
} from '../actions/alertActions';

import Header from './layout/Header';
import RightPanel from './layout/RightPanel';

import ChatContainer from './chat/ChatContainer';

Expand Down Expand Up @@ -170,6 +171,8 @@ class App extends Component {
isAudioEnabled={isAudioEnabled}
onSetIsAudioEnabled={this.onSetIsAudioEnabled} />

<RightPanel />

<InfoModal
showModal={showInfoModal}
onToggleInfoModal={this.onToggleInfoModal} />
Expand Down
43 changes: 43 additions & 0 deletions src/components/layout/RightPanel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React, { Component } from 'react';
import { PropTypes } from 'prop-types';
import { connect } from 'react-redux';
import { chatHandler } from '../../epics/chatEpics';

import TodoListInput from '../right_panel/TodoListInput';


class RightPanel extends Component {
constructor(props) {
super(props);

// So we can create todo lists and tasks without 9 layers of
// abstraction
this.getWsConn = chatHandler.getWsConn;
this.getCryptoInfo = chatHandler.getCryptoInfo;
}

render() {
return (
<div style={styleRightPanel}>
<h2>Todo Lists</h2>

{/* TODO: Iterate over this.props.task.todoLists */}

<TodoListInput
chat={this.props.chat}
task={this.props.task}
getWsConn={this.getWsConn}
getCryptoInfo={this.getCryptoInfo}
/>
</div>
);
}
}

const styleRightPanel = {
padding: '16px',
width: '30vw',
minWidth: '300px'
};

export default connect(({ chat, task }) => ({ chat, task }))(RightPanel);
Loading