4
1
Fork 0
backend-core/conversation.go

469 lines
13 KiB
Go

package main
import (
"database/sql"
"encoding/json"
"log"
"net/http"
"github.com/julienschmidt/httprouter"
)
func (h *Handler) CreateConversation(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
// Parse
userID := r.Context().Value("user").(string)
conversation := Conversation{}
decoder := json.NewDecoder(r.Body)
err := decoder.Decode(&conversation)
if err != nil {
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
return
}
// Generate ID
id := "c-" + RandomHex()
conversation.ID = id
// Log
log.Print(conversation)
// Insert
tx, err := h.db.Begin()
if err != nil {
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
log.Print(err)
return
}
// Conversation
_, err1 := tx.Exec(`
INSERT INTO "conversation" (id, title, picture) VALUES ($1, $2, $3)
`, conversation.ID, conversation.Title, conversation.Picture)
// First member
_, err2 := tx.Exec(`
INSERT INTO member ("user", "conversation") VALUES ($1, $2)
`, userID, conversation.ID)
if err1 != nil || err2 != nil {
// likely 404...
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
log.Print(err1, err2)
return
}
err = tx.Commit()
if err != nil {
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
log.Print(err)
return
}
// Publish NATs
if h.nc != nil {
conversationString, err := json.Marshal(&conversation)
if err == nil {
updateMsg := UpdateMsg{
Type: "add",
Data: string(conversationString),
}
updateMsgString, err := json.Marshal(&updateMsg)
if err == nil {
h.nc.Publish("conversation", updateMsgString)
}
}
}
// Respond
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(conversation)
}
func (h *Handler) GetConversations(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
// Parse
userID := r.Context().Value("user").(string)
// Response object
conversations := make([]Conversation, 0)
// Select
rows, err := h.db.Query(`
SELECT "conversation".id, "conversation".title, "conversation".picture, member.pinned
FROM "conversation", member
WHERE member.conversation = "conversation".id AND member.user = $1
`, userID)
if err != nil {
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
log.Print(err)
return
}
defer rows.Close()
// Scan
for rows.Next() {
conversation := Conversation{}
if err := rows.Scan(&conversation.ID, &conversation.Title, &conversation.Picture, &conversation.Pinned); err != nil {
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
log.Print(err)
return
}
conversations = append(conversations, conversation)
}
// Respond
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(conversations)
}
func (h *Handler) GetConversation(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
// Parse
userID := r.Context().Value("user").(string)
conversationID := p.ByName("conversation")
// Response object
conversation := Conversation{}
// Select
err := h.db.QueryRow(`
SELECT "conversation".id, "conversation".title, "conversation".picture, member.pinned
FROM "conversation", member
WHERE member.conversation = "conversation".id AND member.user = $1 AND member.conversation = $2
`, userID, conversationID).Scan(&conversation.ID, &conversation.Title, &conversation.Picture, &conversation.Pinned)
switch {
case err == sql.ErrNoRows:
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
return
case err != nil:
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
log.Print(err)
return
}
// Respond
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(conversation)
}
func (h *Handler) UpdateConversation(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
// Parse
userID := r.Context().Value("user").(string)
conversationID := p.ByName("conversation")
conversation := Conversation{}
decoder := json.NewDecoder(r.Body)
err := decoder.Decode(&conversation)
if err != nil {
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
return
}
// Check
var conversationID2 string
err = h.db.QueryRow(`
SELECT id FROM "conversation"
INNER JOIN member
ON member.conversation = "conversation".id AND member.user = $1 AND member.conversation = $2
`, userID, conversationID).Scan(&conversationID2)
switch {
case err == sql.ErrNoRows:
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
return
case err != nil:
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
log.Print(err)
return
}
// Update
if conversation.Title.Valid {
_, err = h.db.Exec(`
UPDATE "conversation"
SET title = $2, picture = $3
WHERE id = $1
`, conversationID, conversation.Title, conversation.Picture)
if err != nil {
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
log.Print(err)
return
}
}
// Publish NATs
if h.nc != nil {
conversationString, err := json.Marshal(&conversation)
if err == nil {
updateMsg := UpdateMsg{
Type: "update",
Data: string(conversationString),
}
updateMsgString, err := json.Marshal(&updateMsg)
if err == nil {
h.nc.Publish("conversation", updateMsgString)
}
}
}
w.WriteHeader(200)
}
func (h *Handler) DeleteConversation(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
userID := r.Context().Value("user").(string)
conversationID := p.ByName("conversation")
// Delete
tx, err := h.db.Begin()
if err != nil {
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
log.Print(err)
return
}
// Check
var conversationID2 string
err = h.db.QueryRow(`
SELECT id FROM "conversation"
INNER JOIN member
ON member.conversation = "conversation".id AND member.user = $1 AND member.conversation = $2
`, userID, conversationID).Scan(&conversationID2)
switch {
case err == sql.ErrNoRows:
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
return
case err != nil:
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
log.Print(err)
return
}
// Users in Conversation
_, err1 := tx.Exec(`
DELETE FROM "member" WHERE "conversation" = $1
`, conversationID)
// Conversation
_, err2 := tx.Exec(`
DELETE FROM "conversation" WHERE "id" = $1
`, conversationID)
if err1 != nil || err2 != nil {
// likely 404...
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
log.Print(err1, err2)
return
}
err = tx.Commit()
if err != nil {
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
log.Print(err)
return
}
// Publish NATs
if h.nc != nil {
conversation := Conversation{
ID: conversationID,
}
conversationString, err := json.Marshal(&conversation)
if err == nil {
updateMsg := UpdateMsg{
Type: "delete",
Data: string(conversationString),
}
updateMsgString, err := json.Marshal(&updateMsg)
if err == nil {
h.nc.Publish("conversation", updateMsgString)
}
}
}
w.WriteHeader(200)
}
func (h *Handler) CreateConversationMember(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
// Parse
// We don't need the user ID here because when we first create a conversation, it should have no members
// TODO: conversations should have conversation owners?
//userID := r.Context().Value("user").(string)
conversationID := p.ByName("conversation")
member := User{}
decoder := json.NewDecoder(r.Body)
err := decoder.Decode(&member)
if err != nil {
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
return
}
// Validate
if len(member.ID) < 1 {
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
return
}
// Log
log.Print(member)
// TODO: When we need stronger constraints, add some policy around existing conversations with a title set
// Insert
_, err = h.db.Exec(`
INSERT INTO member ("user", "conversation") VALUES ($2, $1)
`, conversationID, member.ID)
if err != nil {
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
log.Print(err)
return
}
// Publish NATs
if h.nc != nil {
member := Member{
User: member.ID,
Conversation: conversationID,
Pinned: false, // default
}
memberString, err := json.Marshal(&member)
if err == nil {
updateMsg := UpdateMsg{
Type: "add",
Data: string(memberString),
}
updateMsgString, err := json.Marshal(&updateMsg)
if err == nil {
h.nc.Publish("member", updateMsgString)
}
}
}
// Respond
//w.Header().Set("Content-Type", "application/json")
//json.NewEncoder(w).Encode(member)
w.Write([]byte(conversationID))
}
func (h *Handler) GetConversationMembers(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
// Parse
userID := r.Context().Value("user").(string)
conversationID := p.ByName("conversation")
// Response object
users := make([]User, 0)
// Select
rows, err := h.db.Query(`
SELECT "user".id, "user".username, "user".bio, "user".profile_pic, "user".first_name, "user".last_name, "user".phone_number FROM "user"
INNER JOIN member m ON "user".id = m.user AND "user".id != $1
INNER JOIN conversation ON "conversation".id = m.conversation
INNER JOIN member
ON member.conversation = "conversation".id AND member.user = $1 AND member.conversation = $2
`, userID, conversationID)
if err != nil {
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
log.Print(err)
return
}
defer rows.Close()
// Scan
for rows.Next() {
user := User{}
if err := rows.Scan(&user.ID, &user.Username, &user.Bio, &user.ProfilePic, &user.FirstName, &user.LastName, &user.PhoneNumber); err != nil {
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
log.Print(err)
return
}
users = append(users, user)
}
// Respond
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(users)
}
func (h *Handler) PinConversation(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
conversationID := p.ByName("conversation")
userID := r.Context().Value("user").(string)
// Check relation exists
var exists int
err := h.db.QueryRow(`SELECT 1 FROM member WHERE "user" = $1 AND "conversation" = $2`, userID, conversationID).Scan(&exists)
if err == sql.ErrNoRows {
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
return
} else if err != nil {
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
// Update relation
_, err = h.db.Exec(`UPDATE "member" SET "pinned" = TRUE WHERE "user" = $1 AND "conversation" = $2`, userID, conversationID)
if err != nil {
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
// Publish NATs
if h.nc != nil {
member := Member{
User: userID,
Conversation: conversationID,
Pinned: true,
}
memberString, err := json.Marshal(&member)
if err == nil {
updateMsg := UpdateMsg{
Type: "update",
Data: string(memberString),
}
updateMsgString, err := json.Marshal(&updateMsg)
if err == nil {
h.nc.Publish("member", updateMsgString)
}
}
}
w.WriteHeader(200)
}
func (h *Handler) UnpinConversation(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
conversationID := p.ByName("conversation")
userID := r.Context().Value("user").(string)
// Check relation exists
var exists int
err := h.db.QueryRow(`SELECT 1 FROM member WHERE "user" = $1 AND "conversation" = $2`, userID, conversationID).Scan(&exists)
if err == sql.ErrNoRows {
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
return
} else if err != nil {
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
// Update relation
_, err = h.db.Exec(`UPDATE "member" SET "pinned" = FALSE WHERE "user" = $1 AND "conversation" = $2`, userID, conversationID)
if err != nil {
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
// Publish NATs
if h.nc != nil {
member := Member{
User: userID,
Conversation: conversationID,
Pinned: false,
}
memberString, err := json.Marshal(&member)
if err == nil {
updateMsg := UpdateMsg{
Type: "update",
Data: string(memberString),
}
updateMsgString, err := json.Marshal(&updateMsg)
if err == nil {
h.nc.Publish("member", updateMsgString)
}
}
}
w.WriteHeader(200)
}