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", "http://localhost:2025") 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", "http://localhost:2025") // 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", "http://localhost:2025") // 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", "http://localhost:2025") 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", "http://localhost:2025") 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 } }