528 lines
14 KiB
Go
528 lines
14 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"strings"
|
|
|
|
// "encoding/json"
|
|
// "fmt"
|
|
// "io"
|
|
"log"
|
|
"net/http"
|
|
|
|
// "os"
|
|
// "os/signal"
|
|
"strconv"
|
|
//"strings"
|
|
// "sync"
|
|
// "time"
|
|
"github.com/gorilla/mux"
|
|
"github.com/neo4j/neo4j-go-driver/v5/neo4j"
|
|
)
|
|
|
|
func create_new_post(driver neo4j.DriverWithContext, driver_ctx context.Context) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
|
|
|
err := r.ParseMultipartForm(10 << 20) // Limit to 10MB
|
|
if err != nil {
|
|
log.Println("Unable to parse form:", err)
|
|
http.Error(w, "Unable to parse form", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
space_id, err := strconv.Atoi(r.FormValue("space_id"))
|
|
if err != nil {
|
|
log.Println("Invalid space id:", err)
|
|
http.Error(w, "Invalid space id.", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// Required fields
|
|
user_id, ok := user_id_from_context(r.Context())
|
|
if !ok {
|
|
log.Println("Invalid user ID")
|
|
http.Error(w, "Invalid user ID", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
if !auth_user_in_space(driver, driver_ctx, user_id, space_id) {
|
|
log.Println("User", user_id, "attempted access to space", space_id, ", which is not allowed.")
|
|
http.Error(w, "You are not a member of that space.", http.StatusUnauthorized)
|
|
return
|
|
}
|
|
|
|
post_type := r.FormValue("post_type")
|
|
post_status := r.FormValue("post_status")
|
|
content := r.FormValue("content")
|
|
//create_space := r.FormValue("create_space")
|
|
|
|
if post_type == "" || content == "" || post_status == "" {
|
|
http.Error(w, "Missing required fields", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// Optional fields
|
|
title := r.FormValue("title")
|
|
excerpt := r.FormValue("excerpt")
|
|
location := r.FormValue("location")
|
|
raw_topics := r.FormValue("topics")
|
|
// files := r.FormFile("files") // Consider using r.FormFile for actual uploads
|
|
// images := r.Form("images") // Consider using r.Form for multiple URLs
|
|
// videos := r.Form("videos") // Consider using r.Form for multiple URLs
|
|
|
|
// TODO: Implement space retrieval or creation logic based on space_name and create_space
|
|
|
|
topics := []string{}
|
|
if raw_topics != "" {
|
|
topicList := strings.SplitSeq(raw_topics, ",")
|
|
for topic := range topicList {
|
|
trimmedTopic := strings.TrimSpace(topic)
|
|
if trimmedTopic != "" {
|
|
topics = append(topics, trimmedTopic)
|
|
}
|
|
}
|
|
}
|
|
|
|
topics_rel_stmt := ""
|
|
if len(topics) > 0 {
|
|
topics_rel_stmt = `
|
|
WITH p
|
|
FOREACH (topicName IN $topicsList |
|
|
MERGE (t:Topic {name: topicName})
|
|
CREATE (p)-[:TAGGED_WITH]->(t)
|
|
)
|
|
`
|
|
}
|
|
|
|
stmt := `
|
|
MATCH (u:User) WHERE id(u) = $user_id
|
|
MATCH (t:PostType) WHERE t.name = $post_type
|
|
MATCH (s:Space) WHERE id(s) = $space_id
|
|
MATCH (st:PostStatus) WHERE st.name = $post_status
|
|
|
|
CREATE
|
|
(u)-[:CREATED {time: datetime()}]->(p:Post {
|
|
content: $content,
|
|
title: $title,
|
|
excerpt: $excerpt,
|
|
location: $location
|
|
})
|
|
|
|
CREATE (p)-[:POST_TYPE]->(t)
|
|
CREATE (p)-[:IN_SPACE]->(s)
|
|
CREATE (p)-[:STATUS]->(st)
|
|
|
|
` + topics_rel_stmt
|
|
|
|
stmt_args := map[string]any{
|
|
"user_id": user_id,
|
|
"post_type": post_type,
|
|
"space_id": space_id,
|
|
"post_status": post_status,
|
|
"content": content,
|
|
"title": title,
|
|
"excerpt": excerpt,
|
|
"location": location,
|
|
"topicsList": topics,
|
|
}
|
|
|
|
_, err = neo4j.ExecuteQuery(driver_ctx, driver, stmt, stmt_args, neo4j.EagerResultTransformer, neo4j.ExecuteQueryWithDatabase("neo4j"))
|
|
|
|
if err != nil {
|
|
log.Println("Error creating post:", err)
|
|
http.Error(w, "Failed to create post", http.StatusInternalServerError)
|
|
return
|
|
} else {
|
|
w.WriteHeader(http.StatusCreated)
|
|
w.Write([]byte("Post created successfully!"))
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// TODO: Need to fix bug where only one topic/topic name are being returned in JSON response.
|
|
|
|
func retrieve_all_posts(driver neo4j.DriverWithContext, driver_ctx context.Context) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
|
|
|
// TODO: Handle anonymous users.
|
|
user_id, ok := user_id_from_context(r.Context())
|
|
if !ok {
|
|
http.Error(w, "Invalid user ID.", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
stmt := `
|
|
MATCH (p:Post)-[:IN_SPACE]->(s:Space)
|
|
MATCH (u:User)-[:SPACE_MEMBER]->(s)
|
|
MATCH (s)-[:SPACE_TYPE]->(st:SpaceType)
|
|
MATCH (p)-[:POST_TYPE]->(pt:PostType)
|
|
MATCH (p)-[:STATUS]->(ps:PostStatus)
|
|
MATCH (p)-[:TAGGED_WITH]->(t:Topic)
|
|
MATCH (a:User)-[:CREATED]->(p)
|
|
|
|
WHERE
|
|
id(u) = $user_id AND
|
|
ps.name = "Published"
|
|
|
|
RETURN
|
|
id(a) AS author_id,
|
|
a.username AS author_username,
|
|
a.profile_picture AS author_picture,
|
|
|
|
p.title AS title,
|
|
p.excerpt AS excerpt,
|
|
p.location AS location,
|
|
p.content AS content,
|
|
|
|
pt.name AS post_type,
|
|
id(t) AS topic_id,
|
|
t.name AS topic_name,
|
|
|
|
id(s) AS space_id,
|
|
s.name AS space_name,
|
|
st.name AS space_type
|
|
`
|
|
|
|
stmt_args := map[string]any{
|
|
"user_id": user_id,
|
|
}
|
|
|
|
result, err := neo4j.ExecuteQuery(driver_ctx, driver, stmt, stmt_args, neo4j.EagerResultTransformer, neo4j.ExecuteQueryWithDatabase("neo4j"))
|
|
|
|
if err != nil {
|
|
log.Println("Error retrieving all posts:", err)
|
|
http.Error(w, "Failed to retrieve all posts", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
posts_obj := map[int]any{}
|
|
|
|
for index, record := range result.Records {
|
|
space_id, _ := record.Get("space_id")
|
|
space_id = int(space_id.(int64))
|
|
if !auth_user_in_space(driver, driver_ctx, user_id, space_id.(int)) {
|
|
http.Error(w, "You are not allowed in this space.", http.StatusUnauthorized)
|
|
return
|
|
}
|
|
|
|
author_id, _ := record.Get("author_id")
|
|
author_username, _ := record.Get("author_username")
|
|
author_picture, _ := record.Get("author_picture")
|
|
|
|
title, _ := record.Get("title")
|
|
excerpt, _ := record.Get("excerpt")
|
|
location, _ := record.Get("location")
|
|
content, _ := record.Get("content")
|
|
|
|
post_type, _ := record.Get("post_type")
|
|
topic_id, _ := record.Get("topic_id")
|
|
topic_name, _ := record.Get("topic_name")
|
|
space_name, _ := record.Get("space_name")
|
|
|
|
record_obj := map[string]any{
|
|
"author_id": author_id,
|
|
"author_username": author_username,
|
|
"author_picture": author_picture,
|
|
"title": title,
|
|
"excerpt": excerpt,
|
|
"location": location,
|
|
"content": content,
|
|
"post_type": post_type,
|
|
"topic_id": topic_id,
|
|
"topic_name": topic_name,
|
|
"space_id": space_id,
|
|
"space_name": space_name,
|
|
}
|
|
|
|
posts_obj[index] = record_obj
|
|
}
|
|
|
|
obj, err := json.Marshal(posts_obj)
|
|
if err != nil {
|
|
http.Error(w, "Failed to parse into JSON", http.StatusInternalServerError)
|
|
log.Println("error marshalling json")
|
|
return
|
|
}
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Write(obj)
|
|
return
|
|
}
|
|
}
|
|
|
|
// TODO: Need to fix bug where only one topic/topic name are being returned in JSON response.
|
|
func retrieve_post(driver neo4j.DriverWithContext, driver_ctx context.Context) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
|
|
|
// TODO: Handle anonymous users.
|
|
user_id, ok := user_id_from_context(r.Context())
|
|
if !ok {
|
|
http.Error(w, "Invalid user ID.", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
post_id, err := strconv.Atoi(mux.Vars(r)["post_id"])
|
|
if err != nil {
|
|
http.Error(w, "Invalid Post ID.", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
stmt := `
|
|
MATCH (p:Post)-[:IN_SPACE]->(s:Space)
|
|
MATCH (s)-[:SPACE_TYPE]->(st:SpaceType)
|
|
MATCH (p)-[:POST_TYPE]->(pt:PostType)
|
|
MATCH (p)-[:STATUS]->(ps:PostStatus)
|
|
MATCH (p)-[:TAGGED_WITH]->(t:Topic)
|
|
MATCH (a:User)-[:CREATED]->(p)
|
|
|
|
WHERE
|
|
id(p) = $post_id
|
|
|
|
RETURN
|
|
id(a) AS author_id,
|
|
a.username AS author_username,
|
|
a.profile_picture AS author_picture,
|
|
|
|
p.title AS title,
|
|
p.excerpt AS excerpt,
|
|
p.location AS location,
|
|
p.content AS content,
|
|
|
|
pt.name AS post_type,
|
|
ps.name AS post_status,
|
|
id(t) AS topic_id,
|
|
t.name AS topic_name,
|
|
|
|
id(s) AS space_id,
|
|
st.name AS space_type
|
|
`
|
|
|
|
stmt_args := map[string]any{
|
|
"post_id": post_id,
|
|
}
|
|
|
|
result, err := neo4j.ExecuteQuery(driver_ctx, driver, stmt, stmt_args, neo4j.EagerResultTransformer, neo4j.ExecuteQueryWithDatabase("neo4j"))
|
|
if err != nil {
|
|
http.Error(w, "Failed to retrieve Post.", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
if len(result.Records) != 2 {
|
|
http.Error(w, "Post does not exist.", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
record := result.Records[0]
|
|
space_id, _ := record.Get("space_id")
|
|
space_id = int(space_id.(int64))
|
|
if !auth_user_in_space(driver, driver_ctx, user_id, space_id.(int)) {
|
|
http.Error(w, "You are not allowed in this space.", http.StatusUnauthorized)
|
|
return
|
|
}
|
|
|
|
author_id, _ := record.Get("author_id")
|
|
author_username, _ := record.Get("author_username")
|
|
author_picture, _ := record.Get("author_picture")
|
|
title, _ := record.Get("title")
|
|
excerpt, _ := record.Get("excerpt")
|
|
location, _ := record.Get("location")
|
|
content, _ := record.Get("content")
|
|
post_type, _ := record.Get("post_type")
|
|
post_status, _ := record.Get("post_status")
|
|
topic_id, _ := record.Get("topic_id")
|
|
topic_name, _ := record.Get("topic_name")
|
|
space_type, _ := record.Get("space_type")
|
|
|
|
record_obj := map[string]any{
|
|
"author_id": author_id,
|
|
"author_username": author_username,
|
|
"author_picture": author_picture,
|
|
"title": title,
|
|
"excerpt": excerpt,
|
|
"location": location,
|
|
"content": content,
|
|
"post_type": post_type,
|
|
"post_status": post_status,
|
|
"topic_id": topic_id,
|
|
"topic_name": topic_name,
|
|
"space_id": space_id,
|
|
"space_type": space_type,
|
|
}
|
|
|
|
obj, err := json.Marshal(record_obj)
|
|
if err != nil {
|
|
http.Error(w, "Failed to parse into JSON", http.StatusInternalServerError)
|
|
log.Println("error marshalling json")
|
|
return
|
|
}
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Write(obj)
|
|
return
|
|
}
|
|
}
|
|
|
|
// TODO: Need to test.
|
|
func update_post(driver neo4j.DriverWithContext, driver_ctx context.Context) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
|
|
|
err := r.ParseMultipartForm(10 << 20) // Limit to 10MB
|
|
if err != nil {
|
|
log.Println("Unable to parse form:", err)
|
|
http.Error(w, "Unable to parse form", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
space_id, err := strconv.Atoi(r.FormValue("space_id"))
|
|
if err != nil {
|
|
log.Println("Invalid space id:", err)
|
|
http.Error(w, "Invalid space id.", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
user_id, ok := user_id_from_context(r.Context())
|
|
if !ok {
|
|
http.Error(w, "Invalid user ID.", http.StatusUnauthorized)
|
|
return
|
|
}
|
|
|
|
if !auth_user_in_space(driver, driver_ctx, user_id, space_id) {
|
|
log.Println("User", user_id, "attempted access to space", space_id, ", which is not allowed.")
|
|
http.Error(w, "You are not a member of that space.", http.StatusUnauthorized)
|
|
return
|
|
}
|
|
|
|
post_id, err := strconv.Atoi(mux.Vars(r)["post_id"])
|
|
if err != nil {
|
|
http.Error(w, "Failed to get Post ID.", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
post_type := r.FormValue("post_type")
|
|
post_status := r.FormValue("post_status")
|
|
content := r.FormValue("content")
|
|
//create_space := r.FormValue("create_space")
|
|
|
|
if post_type == "" || content == "" || post_status == "" {
|
|
http.Error(w, "Missing required fields.", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// Optional fields
|
|
title := r.FormValue("title")
|
|
excerpt := r.FormValue("excerpt")
|
|
location := r.FormValue("location")
|
|
raw_topics := r.FormValue("topics")
|
|
// files := r.FormFile("files") // Consider using r.FormFile for actual uploads
|
|
// images := r.Form("images") // Consider using r.Form for multiple URLs
|
|
// videos := r.Form("videos") // Consider using r.Form for multiple URLs
|
|
|
|
// TODO: Implement space retrieval or creation logic based on space_name and create_space
|
|
|
|
topics := []string{}
|
|
if raw_topics != "" {
|
|
topicList := strings.SplitSeq(raw_topics, ",")
|
|
for topic := range topicList {
|
|
trimmedTopic := strings.TrimSpace(topic)
|
|
if trimmedTopic != "" {
|
|
topics = append(topics, trimmedTopic)
|
|
}
|
|
}
|
|
}
|
|
|
|
topics_rel_stmt := ""
|
|
if len(topics) > 0 {
|
|
topics_rel_stmt = `
|
|
WITH p
|
|
FOREACH (topicName IN $topicsList |
|
|
MERGE (t:Topic {name: topicName})
|
|
MERGE (p)-[:TAGGED_WITH]->(t)
|
|
)
|
|
`
|
|
}
|
|
|
|
stmt := `
|
|
MATCH (u:User) WHERE id(u) = $user_id
|
|
MATCH (t:PostType) WHERE t.name = $post_type
|
|
MATCH (s:Space) WHERE id(s) = $space_id
|
|
MATCH (st:PostStatus) WHERE st.name = $post_status
|
|
|
|
SET
|
|
p.content = $content,
|
|
p.title = $title,
|
|
p.excerpt = $excerpt,
|
|
p.location = $location,
|
|
p.updated_at = datetime()
|
|
|
|
|
|
MERGE (p)-[:POST_TYPE]->(t)
|
|
MERGE (p)-[:IN_SPACE]->(s)
|
|
MERGE (p)-[:STATUS]->(st)
|
|
|
|
` + topics_rel_stmt
|
|
|
|
stmt_args := map[string]any{
|
|
"user_id": user_id,
|
|
"post_type": post_type,
|
|
"space_id": space_id,
|
|
"post_status": post_status,
|
|
"content": content,
|
|
"title": title,
|
|
"excerpt": excerpt,
|
|
"location": location,
|
|
"topicsList": topics,
|
|
}
|
|
|
|
_, err = neo4j.ExecuteQuery(driver_ctx, driver, stmt, stmt_args, neo4j.EagerResultTransformer, neo4j.ExecuteQueryWithDatabase("neo4j"))
|
|
if err != nil {
|
|
log.Println("Attempted to update Post", post_id, "which failed.")
|
|
http.Error(w, "Unable to update Post.", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
return
|
|
}
|
|
}
|
|
|
|
func delete_post(driver neo4j.DriverWithContext, driver_ctx context.Context) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
|
|
|
user_id, ok := user_id_from_context(r.Context())
|
|
if !ok {
|
|
http.Error(w, "Invalid user ID.", http.StatusUnauthorized)
|
|
return
|
|
}
|
|
|
|
post_id, err := strconv.Atoi(mux.Vars(r)["post_id"])
|
|
if err != nil {
|
|
http.Error(w, "Invalid Post ID.", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
stmt := "MATCH (u:User)-[:CREATED]->(p:Post) WHERE id(u) = $user_id AND id(p) = $post_id DETACH DELETE p"
|
|
|
|
stmt_args := map[string]any{
|
|
"user_id": user_id,
|
|
"post_id": post_id,
|
|
}
|
|
|
|
_, err = neo4j.ExecuteQuery(driver_ctx, driver, stmt, stmt_args, neo4j.EagerResultTransformer, neo4j.ExecuteQueryWithDatabase("neo4j"))
|
|
|
|
if err != nil {
|
|
http.Error(w, "Unable to delete Post.", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
return
|
|
}
|
|
}
|