diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..43c4ed3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,40 @@ +# Compiled binaries +*.bin +*.so +*.dylib +*.exe +*.log +*.logs +bin/logs/ + +# Environment files +.env +.env.local +.env.*.local + +# Bin directory exclusions +bin/*.bin +bin/main.bin +bin/.env +bin/projects/ +bin/project.json +bin/backups/ +bin/collections/ +bin/temp/ +bin/logs/ +bin/tmp/ +bin/system/ + + +# Allow specific Go and Odin source files +!main/main.odin + +# System and IDE files +.DS_Store +.ropeproject +*.key +*.ostrichdb +*project.json +*.claude +*rate_limits +*requests.json \ No newline at end of file diff --git a/OstrichDB b/OstrichDB new file mode 160000 index 0000000..2488c23 --- /dev/null +++ b/OstrichDB @@ -0,0 +1 @@ +Subproject commit 2488c23979d0a346156c3d2423bee96bc98fda44 diff --git a/client/client.go b/client/client.go deleted file mode 100644 index a624a72..0000000 --- a/client/client.go +++ /dev/null @@ -1,114 +0,0 @@ -package main - -import ( - "bufio" - "fmt" - "log" - "net" - - tea "github.com/charmbracelet/bubbletea" -) - -// "FEED" -func HandleFeed(pageNum int) (string, error) { - conn, _ := net.Dial("tcp", "localhost:8080") - defer conn.Close() - fmt.Fprintf(conn, "FEED %v\n", pageNum) - return bufio.NewReader(conn).ReadString('\n') -} - -// "POST" -func HandlePost(content string) (string, error) { - conn, _ := net.Dial("tcp", "localhost:8080") - defer conn.Close() - fmt.Fprintf(conn, "POST %v\n", content) - return bufio.NewReader(conn).ReadString('\n') -} - -// "FETCH" -func HandleFetch(post string) (string, error) { - conn, _ := net.Dial("tcp", "localhost:8080") - defer conn.Close() - fmt.Fprintf(conn, "FETCH %v\n", post) - return bufio.NewReader(conn).ReadString('\n') -} - -// "COMMENT" -func HandleComment(post, content string) (string, error) { - conn, _ := net.Dial("tcp", "localhost:8080") - defer conn.Close() - fmt.Fprintf(conn, "COMMENT %v %v\n", post, content) - return bufio.NewReader(conn).ReadString('\n') -} - -// "LIKE" -func HandleLike(post string) (string, error) { - conn, _ := net.Dial("tcp", "localhost:8080") - defer conn.Close() - fmt.Fprintf(conn, "LIKE %v\n", post) - return bufio.NewReader(conn).ReadString('\n') -} - -type model struct { - cursor int - width int - height int - text string -} - -func (m model) Init() tea.Cmd { - return nil -} - -func initialModel() model { - return model{ - text: "", - } -} - -func (m model) View() string { - s := fmt.Sprintf("cursor %v\n", m.cursor) - s += fmt.Sprintf("1. cobbcoding\nThis is a post.\n\n") - s += fmt.Sprintf("2. stam\nThis is a post 2.\n\n") - for range m.height - 8 { - s += "\n" - } - s += fmt.Sprintf(":%v|", m.text) - return s -} - -func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { - switch msg.(type) { - case tea.KeyMsg: - switch msg.(tea.KeyMsg).String() { - case "ctrl+c": - return m, tea.Quit - case "backspace": - if m.cursor > 0 { - m.cursor-- - m.text = m.text[:m.cursor] - } - break - case "enter": - m.text = "" - m.cursor = 0 - break - default: - m.text += msg.(tea.KeyMsg).String() - m.cursor += 1 - } - case tea.WindowSizeMsg: - m.width = msg.(tea.WindowSizeMsg).Width - m.height = msg.(tea.WindowSizeMsg).Height - break - } - return m, nil -} - -func main() { - p := tea.NewProgram(initialModel(), tea.WithAltScreen()) - _, err := p.Run() - if err != nil { - log.Fatal("Could not start TUI") - } -} diff --git a/main/go.mod b/main/go.mod new file mode 100644 index 0000000..730a203 --- /dev/null +++ b/main/go.mod @@ -0,0 +1,33 @@ +module twix-main + +go 1.25.3 + +require ostrichdb-go v0.0.0 + +require ( + client v0.0.0-00010101000000-000000000000 // indirect + github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect + github.com/charmbracelet/bubbletea v1.3.10 // indirect + github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect + github.com/charmbracelet/lipgloss v1.1.0 // indirect + github.com/charmbracelet/x/ansi v0.10.1 // indirect + github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect + github.com/charmbracelet/x/term v0.2.1 // indirect + github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect + github.com/joho/godotenv v1.5.1 // indirect + github.com/lucasb-eyer/go-colorful v1.2.0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-localereader v0.0.1 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect + github.com/muesli/cancelreader v0.2.2 // indirect + github.com/muesli/termenv v0.16.0 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect + golang.org/x/sys v0.36.0 // indirect + golang.org/x/text v0.3.8 // indirect +) + +replace client => ../src/client + +replace ostrichdb-go => ../ostrichdb-go diff --git a/client/go.sum b/main/go.sum similarity index 95% rename from client/go.sum rename to main/go.sum index cc7a40a..116af2d 100644 --- a/client/go.sum +++ b/main/go.sum @@ -14,6 +14,8 @@ github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQ github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= diff --git a/main/main.go b/main/main.go new file mode 100644 index 0000000..7b17b3a --- /dev/null +++ b/main/main.go @@ -0,0 +1,58 @@ +package main + +import ( + "fmt" + "ostrichdb-go/src/lib" + c "client" //uncomment when ready to use client functions + sdk "ostrichdb-go/src/sdk" +) + +var config lib.Config = *sdk.NewConfigBuilder() +var client lib.Client = *sdk.NewClientBuilder(&config) +var project lib.Project = *sdk.NewProjectBuilder(&client, "twix") + + +func main (){ + var err error + + // START | START | START | START | START | START | START | START | START + // The following code is an example of the flow for creating a + // Project(Dir of Users), Specific User Collection, and a first Post(Cluster) + // Once 'END' is reached an exmaple of another flow begins + + err= sdk.CreateProject(&project) + if err != nil { + fmt.Println("Error creating project") + fmt.Println("Error: ",err) + } + + // //Note: Whenever implementing user auth on client please replace "Marshall" with username input + collection:= sdk.NewCollectionBuilder(&project, "Marshall") + err = sdk.CreateCollection(collection) + + if err != nil { + fmt.Println("Error creating Collection") + fmt.Println("Error: ",err) + } + + //Testing things out + currentUser:= c.NewUserBuilder("@Marshall") + //TODO: If there is a space in the string value of Content is not stored. OstrichDB engine issue + newPost:= c.NewPostBuilder(¤tUser, "Hello World") + + err = c.HandlePost(*collection, newPost) + if err != nil { + fmt.Println("Error appending new post to a Collection") + fmt.Println("Error: ",err) + } + // END | END| END| END| END| END| END| END| END| END + + + + // Fetching a Post from OstrichDB + specificPost:= sdk.NewClusterBuilder(collection, "Marshall") + fmt.Println(c.FetchPostAuthor(specificPost, 1)) + // c.Run() + +} + diff --git a/ostrichdb-go b/ostrichdb-go new file mode 160000 index 0000000..6cdacee --- /dev/null +++ b/ostrichdb-go @@ -0,0 +1 @@ +Subproject commit 6cdacee989b60448fdc5d0492e1bb7b3465cd4cd diff --git a/server/go.mod b/server/go.mod deleted file mode 100644 index bc30072..0000000 --- a/server/go.mod +++ /dev/null @@ -1,3 +0,0 @@ -module twix - -go 1.25.3 diff --git a/server/main.go b/server/main.go deleted file mode 100644 index 7281793..0000000 --- a/server/main.go +++ /dev/null @@ -1,149 +0,0 @@ -package main - -import ( - "bufio" - "fmt" - "log" - "net" - "strconv" - "strings" -) - -const ( - PORT = ":8080" -) - -type Post struct { - id uint64 - content string - author string - likes uint64 - comments []Post -} - -var posts []Post - -func handleConnection(conn net.Conn) { - defer conn.Close() - msg, err := bufio.NewReader(conn).ReadString('\n') - if err != nil { - fmt.Println("Could not read message from connection: ", err) - return - } - res, ok := parseCommand(msg) - if !ok { - conn.Write([]byte("Could not parse message...\n")) - return - } - conn.Write([]byte(res)) -} - -func parsePostId(id_s string) *Post { - ids := strings.Split(id_s, "-") - id, err := strconv.Atoi(ids[0]) - if err != nil { - fmt.Println("Could not convert to integer: ", ids[0]) - return nil - } - if id >= len(posts) { - fmt.Printf("Post %v does not exist\n", id) - return nil - } - cur_post := &posts[id] - for id_i := 1; id_i < len(ids); id_i += 1 { - id := ids[id_i] - id_val, err := strconv.Atoi(id) - if err != nil { - fmt.Println("Could not convert to integer: ", id) - return nil - } - if id_val >= len(cur_post.comments) { - fmt.Printf("Post %v does not exist\n", id_val) - return nil - } - cur_post = &cur_post.comments[id_val] - } - return cur_post -} - -func parseCommand(command string) (string, bool) { - words := strings.Fields(command) - var res string - if len(words) < 2 { - fmt.Println("Not a valid command") - return "", false - } - switch words[0] { - // example: POST - case "POST": - msg := strings.Join(words[1:], " ") - post := Post{id: uint64(len(posts)), content: msg} - posts = append(posts, post) - res = "Post created successfully..." - break - // example: FEED - case "FEED": - res = fmt.Sprintf(res, "%v", posts) - break - // example: FETCH - case "FETCH": - if len(words) < 2 { - fmt.Println("Not enough arguments to command ", words[0]) - return "", false - } - post := parsePostId(words[1]) - if post == nil { - fmt.Println("Could not parse ", words[1]) - return "", false - } - res = fmt.Sprintf(res, "%v", post) - break - // example: COMMENT - case "COMMENT": - if len(words) < 3 { - fmt.Println("Not enough arguments to command ", words[0]) - return "", false - } - msg := strings.Join(words[2:], " ") - post := parsePostId(words[1]) - if post == nil { - fmt.Println("Could not parse ", words[1]) - return "", false - } - fmt.Println(post) - comment := Post{id: uint64(len(post.comments)), content: msg} - post.comments = append(post.comments, comment) - res = "Comment posted successfully..." - break - // example: LIKE - case "LIKE": - post := parsePostId(words[1]) - if post == nil { - fmt.Println("Could not parse ", words[1]) - return "", false - } - post.likes += 1 - res = "Post liked successfully..." - break - default: - log.Fatalf("Unknown command: %s\n", words[0]) - } - return res, true -} - -func main() { - list, err := net.Listen("tcp", PORT) - if err != nil { - log.Fatalf("Could not start server: %s\n", err) - } - defer list.Close() - fmt.Printf("Now accepting connections on port %v...\n", PORT) - for { - conn, err := list.Accept() - if err != nil { - fmt.Println("Could not accept incoming request: ", err) - continue - } - go handleConnection(conn) - } -} diff --git a/src/client/client.go b/src/client/client.go new file mode 100644 index 0000000..fb49a46 --- /dev/null +++ b/src/client/client.go @@ -0,0 +1,369 @@ +package client + +import ( + "bufio" + "fmt" + "log" + "net" + "strings" + "strconv" + "ostrichdb-go/src/lib" + sdk "ostrichdb-go/src/sdk" + + tea "github.com/charmbracelet/bubbletea" +) + +const ( + STR = "STRING" + INT = "INTEGER" + INT_ARR = "[]INTEGER" + STR_ARR = "[]STRING" +) + +var currentUser User + +type Post struct { + id uint64 + Author *User + Content string + NumOfLikes uint64 + NumOfComments uint64 + WhoLikedPost []string + WhoCommented []string + Comments []Post +} + +// OstrichDB cluster response structure +type ClusterResponse struct { + ClusterName string `json:"cluster_name"` + ClusterID int `json:"cluster_id"` + RecordCount int `json:"record_count"` + Records []RecordField `json:"records"` +} + +type RecordField struct { + Name string `json:"name"` + Type string `json:"type"` + Value interface{} `json:"value"` +} + +// Helper method to convert ClusterResponse to Post +func (cr *ClusterResponse) ToPost() (*Post, error) { + post := &Post{ + id: uint64(cr.ClusterID), + } + + // Parse records into Post fields + for _, record := range cr.Records { + switch record.Name { + case "author": + if authorStr, ok := record.Value.(string); ok { + post.Author = &User{Handle: authorStr} + } + case "content": + if content, ok := record.Value.(string); ok { + post.Content = content + } + case "numOfLikes": + if likes, ok := record.Value.(uint64); ok { + post.NumOfLikes = uint64(likes) + } + case "numOfComments": + if comments, ok := record.Value.(uint64); ok { + post.NumOfComments = uint64(comments) + } + case "whoLikedPost": + if arr, ok := record.Value.([]interface{}); ok { + post.WhoLikedPost = make([]string, len(arr)) + for i, v := range arr { + if str, ok := v.(string); ok { + post.WhoLikedPost[i] = str + } + } + } + case "whoCommented": + if arr, ok := record.Value.([]interface{}); ok { + post.WhoCommented = make([]string, len(arr)) + for i, v := range arr { + if str, ok := v.(string); ok { + post.WhoCommented[i] = str + } + } + } + case "comments": + if arr, ok := record.Value.([]interface{}); ok{ + post.Comments = make([]Post, len(arr)) + for i, v:= range arr { + if p, ok:= v.(Post); ok{ + post.Comments[i] = p + } + } + } + } + } + + return post, nil +} + +var postRecordNames = []string {"author","content","numOfLikes","numOfComments","whoLikedPost","whoCommented", "comments"} + +type User struct { + Handle string + Following []string + Followers []string + Posts []Post + //can add more here if needed +} + +func NewPostBuilder(user *User, content string) Post{ + return Post{ + id: 0, + Author: user, + Content: content, + NumOfLikes: 0, + NumOfComments: 0, + WhoLikedPost: nil, + WhoCommented: nil, + + } +} + +func NewUserBuilder(name string) User { + return User{ + Handle: name, + Following: nil, + Followers: nil, + } +} + +// "FEED" +func HandleFeed(pageNum int) (string, error) { + conn, _ := net.Dial("tcp", "localhost:8080") + defer conn.Close() + fmt.Fprintf(conn, "FEED %v\n", pageNum) + return bufio.NewReader(conn).ReadString('\n') +} + +//Creates a new Post (p) in a Collection (c) +//Upon creation each Post is stored as a Cluster within an OstrichDB Collection +func HandlePost(c lib.Collection, p Post) error { + newPost := sdk.NewClusterBuilder(&c, create_post_name(&c)) + sdk.CreateCluster(newPost) + var record *lib.Record + for _ , postName:= range postRecordNames{ + switch(postName){ + case "author": + record = sdk.NewRecordBuilder(newPost, postName, lib.STRING, p.Author.Handle) + break + case "content": + record = sdk.NewRecordBuilder(newPost, postName, lib.STRING, p.Content) + break + case "numOfLikes": + record = sdk.NewRecordBuilder(newPost, postName, lib.INTEGER, "0") + break + case "numOfComments": + record = sdk.NewRecordBuilder(newPost, postName, lib.INTEGER, "0") + break + case "whoLikedPost": + record = sdk.NewRecordBuilder(newPost, postName, lib.STRING_ARRAY, fmt.Sprintf("%v", p.WhoLikedPost)) + break + case "whoCommented": + record = sdk.NewRecordBuilder(newPost, postName, lib.STRING_ARRAY, fmt.Sprintf("%v", p.WhoCommented)) + } + + err:= sdk.CreateRecord(record) + if err != nil { + return err + } + } + + return nil +} + +// Fetches a specific Post (p) from a Collection (c) +// The Collection is based on the Author (a) that created the Post +// func HandleFetch(p Post, c *lib.Collection,) (string, error) { +// path:= +// post:= lib.Get() + +// } + +// "COMMENT" +// TODO: before working on commenets. Have to update OstrichDB src code +// and ensuring there is a way to append a new value to a record that is an array +// func HandleComment(post Post, comment string) (string, error) { +// conn, _ := net.Dial("tcp", "localhost:8080") +// defer conn.Close() +// fmt.Fprintf(conn, "COMMENT %v %v\n", post, content) +// return bufio.NewReader(conn).ReadString('\n') +// } + +// "LIKE" +func HandleLike(post string) (string, error) { + conn, _ := net.Dial("tcp", "localhost:8080") + defer conn.Close() + fmt.Fprintf(conn, "LIKE %v\n", post) + return bufio.NewReader(conn).ReadString('\n') +} + +type model struct { + cursor int + width int + height int + text string +} + +func (m model) Init() tea.Cmd { + return nil +} + +func initialModel() model { + return model{ + text: "", + } +} + +func (m model) View() string { + s := fmt.Sprintf("cursor %v\n", m.cursor) + s += fmt.Sprintf("1. cobbcoding\nThis is a post.\n\n") + s += fmt.Sprintf("2. stam\nThis is a post 2.\n\n") + for range m.height - 8 { + s += "\n" + } + s += fmt.Sprintf(":%v|", m.text) + return s +} + +func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg.(type) { + case tea.KeyMsg: + switch msg.(tea.KeyMsg).String() { + case "ctrl+c": + return m, tea.Quit + case "backspace": + if m.cursor > 0 { + m.cursor-- + m.text = m.text[:m.cursor] + } + break + case "enter": + m.text = "" + m.cursor = 0 + break + default: + m.text += msg.(tea.KeyMsg).String() + m.cursor += 1 + } + case tea.WindowSizeMsg: + m.width = msg.(tea.WindowSizeMsg).Width + m.height = msg.(tea.WindowSizeMsg).Height + break + } + return m, nil +} + +func Run() { + p := tea.NewProgram(initialModel(), tea.WithAltScreen()) + _, err := p.Run() + if err != nil { + log.Fatal("Could not start TUI") + } +} + +func parsePostId(id_s string) *Post { + ids := strings.Split(id_s, "-") + id, err := strconv.Atoi(ids[0]) + if err != nil { + fmt.Println("Could not convert to integer: ", ids[0]) + return nil + } + if id >= len(posts) { + fmt.Printf("Post %v does not exist\n", id) + return nil + } + cur_post := &posts[id] + for id_i := 1; id_i < len(ids); id_i += 1 { + id := ids[id_i] + id_val, err := strconv.Atoi(id) + if err != nil { + fmt.Println("Could not convert to integer: ", id) + return nil + } + if id_val >= len(cur_post.Comments) { + fmt.Printf("Post %v does not exist\n", id_val) + return nil + } + cur_post = &cur_post.Comments[id_val] + } + return cur_post +} + +var posts []Post + +func parseCommand(command string) (string, bool) { + currentUser:= NewUserBuilder("@Marshall") + words := strings.Fields(command) + var res string + if len(words) < 2 { + fmt.Println("Not a valid command") + return "", false + } + switch words[0] { + // example: POST + case "POST": + msg := strings.Join(words[1:], " ") + newPost := NewPostBuilder(¤tUser, msg) + newPost.id = uint64(len(posts)) + posts = append(posts, newPost) + res = fmt.Sprintf("%v", newPost) + + + break + // example: FEED + case "FEED": + res = fmt.Sprintf(res, "%v", posts) + break + // example: FETCH + case "FETCH": + if len(words) < 2 { + fmt.Println("Not enough arguments to command ", words[0]) + return "", false + } + post := parsePostId(words[1]) + if post == nil { + fmt.Println("Could not parse ", words[1]) + return "", false + } + res = fmt.Sprintf(res, "%v", post) + break + // example: COMMENT + case "COMMENT": + if len(words) < 3 { + fmt.Println("Not enough arguments to command ", words[0]) + return "", false + } + msg := strings.Join(words[2:], " ") + post := parsePostId(words[1]) + if post == nil { + fmt.Println("Could not parse ", words[1]) + return "", false + } + comment := Post{id: uint64(len(post.Comments)), Content: msg} + post.Comments = append(post.Comments, comment) + res = "Comment posted successfully..." + break + // example: LIKE + case "LIKE": + post := parsePostId(words[1]) + if post == nil { + fmt.Println("Could not parse ", words[1]) + return "", false + } + post.NumOfLikes += 1 + res = "Post liked successfully..." + break + default: + log.Fatalf("Unknown command: %s\n", words[0]) + } + return res, true +} \ No newline at end of file diff --git a/client/go.mod b/src/client/go.mod similarity index 85% rename from client/go.mod rename to src/client/go.mod index 6d4dd4e..496c7aa 100644 --- a/client/go.mod +++ b/src/client/go.mod @@ -1,16 +1,20 @@ -module twix-client +module client go 1.25.3 +require github.com/charmbracelet/bubbletea v1.3.10 + +require ostrichdb-go v0.0.0 + require ( github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect - github.com/charmbracelet/bubbletea v1.3.10 // indirect github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect github.com/charmbracelet/lipgloss v1.1.0 // indirect github.com/charmbracelet/x/ansi v0.10.1 // indirect github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect github.com/charmbracelet/x/term v0.2.1 // indirect github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect + github.com/joho/godotenv v1.5.1 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-localereader v0.0.1 // indirect @@ -23,3 +27,5 @@ require ( golang.org/x/sys v0.36.0 // indirect golang.org/x/text v0.3.8 // indirect ) + +replace ostrichdb-go => ../../ostrichdb-go diff --git a/src/client/go.sum b/src/client/go.sum new file mode 100644 index 0000000..6bfe030 --- /dev/null +++ b/src/client/go.sum @@ -0,0 +1,45 @@ +github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= +github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= +github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw= +github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4= +github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs= +github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk= +github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY= +github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30= +github.com/charmbracelet/x/ansi v0.10.1 h1:rL3Koar5XvX0pHGfovN03f5cxLbCF2YvLeyz7D2jVDQ= +github.com/charmbracelet/x/ansi v0.10.1/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE= +github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0GVL4jeHEwG5YOXDmi86oYw2yuYUGqz6a8sLwg0X8= +github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs= +github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= +github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= +github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= +github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= +github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= +github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= +github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= +github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= +github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= +github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= +github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= +golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E= +golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= +golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= diff --git a/src/client/helpers.go b/src/client/helpers.go new file mode 100644 index 0000000..ffe9384 --- /dev/null +++ b/src/client/helpers.go @@ -0,0 +1,108 @@ +package client + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "ostrichdb-go/src/lib" + sdk "ostrichdb-go/src/sdk" +) + +// Helper functions for client will move later - Marshalla +func increment_post_count(collection *lib.Collection) int{ + currentCount:= sdk.GetClusterCount(collection) + incremented := currentCount + 1 + return incremented +} + +// Only used to create a name for the post within OstrichDB e.g Post1, Post2, etc +func create_post_name(collection *lib.Collection) string{ + postCount:= increment_post_count(collection) + postName:= fmt.Sprintf("Post%d", postCount) + return postName +} + +//Used to fetch an entire Post (p) which is stored as a single cluster within OstrichDB +// Even though this function claims to fetch a post by id. its really looking for the post's name +// THis is the case becauser the ID is infact inside the name +func FetchPostByID(c *lib.Cluster, id uint64) Post{ + var post Post + client:= c.Collection.Project.Client + projName:= c.Collection.Project.Name + colName:= c.Collection.Name + cluName:= fmt.Sprintf("Post%d", id) + + path:= fmt.Sprintf("%s/projects/%s/collections/%s/clusters/%s", lib.OSTRICHDB_ADDRESS, projName, colName, cluName ) + + response, err:= lib.Get(client, path) + if err != nil { + return post + } + + defer response.Body.Close() + + if response.StatusCode != http.StatusOK { + return post + } + + // Read response body + body, err := io.ReadAll(response.Body) + if err != nil { + return post + } + + // Unmarshal the cluster JSON response + var clusterResp ClusterResponse + err = json.Unmarshal(body, &clusterResp) + if err != nil { + return post + } + + // Convert ClusterResponse to Post + fetchedPost, err := clusterResp.ToPost() + if err != nil { + return post + } + + return *fetchedPost +} + +// Fetch the number of likes for a specific post +func FetchNumOfLikes(c *lib.Cluster, id uint64) uint64 { + post := FetchPostByID(c,id) + return post.NumOfLikes +} + +// Fetch the number of comments for a specific post +func FetchNumOfComments(c *lib.Cluster, id uint64) uint64 { + post := FetchPostByID(c,id) + return post.NumOfComments +} + +// Fetch the list of users who liked a specific post +func FetchPostLikesList(c *lib.Cluster, id uint64) []string { + post := FetchPostByID(c,id) + return post.WhoLikedPost +} + +// Fetch the list of users who commented on a specific post +func FetchPostCommentersList(c *lib.Cluster, id uint64) []string { + post := FetchPostByID(c,id) + return post.WhoCommented +} + +// Fetch the author of a specific post +func FetchPostAuthor(c *lib.Cluster, id uint64) string { + post := FetchPostByID(c,id) + if post.Author != nil { + return post.Author.Handle + } + return "Unknown Author" +} + +// Fetch the actual comments (Post objects) for a specific post +func FetchPostCommnets(c *lib.Cluster, id uint64) []Post { + post := FetchPostByID(c,id) + return post.Comments +} \ No newline at end of file diff --git a/src/client/posts.go b/src/client/posts.go new file mode 100644 index 0000000..09f091a --- /dev/null +++ b/src/client/posts.go @@ -0,0 +1,30 @@ +package client + + +// type Post struct { +// id uint64 +// Author *User +// Content string +// NumOfLikes uint64 +// NumOfComments uint64 +// WhoLikedPost []string +// WhoCommented []string +// } + +// type User struct { +// Handle string +// Following []string +// Followers []string +// //can add more here if needed +// } + +// func NewPostBuilder(user *User, content string) Post{ +// return Post{ +// Author: user, +// Content: content, +// NumOfLikes: 0, +// NumOfComments: 0, +// WhoLikedPost: nil, +// WhoCommented: nil, +// } +// } diff --git a/src/library/go.mod b/src/library/go.mod new file mode 100644 index 0000000..28c16b9 --- /dev/null +++ b/src/library/go.mod @@ -0,0 +1,3 @@ +module library.com + +go 1.23.1 diff --git a/src/library/types.go b/src/library/types.go new file mode 100644 index 0000000..4008a13 --- /dev/null +++ b/src/library/types.go @@ -0,0 +1 @@ +// package library \ No newline at end of file diff --git a/src/server/go.mod b/src/server/go.mod new file mode 100644 index 0000000..805d151 --- /dev/null +++ b/src/server/go.mod @@ -0,0 +1,5 @@ +module twix + +go 1.25.3 + +replace lib => ../library diff --git a/src/server/main.go b/src/server/main.go new file mode 100644 index 0000000..556b8e7 --- /dev/null +++ b/src/server/main.go @@ -0,0 +1,53 @@ + +// package main + +// import ( +// "bufio" +// "fmt" +// "log" +// "net" +// "strconv" +// "strings" +// ) + +// const ( +// PORT = ":8080" +// ) + + + + + + +// func handleConnection(conn net.Conn) { +// defer conn.Close() +// msg, err := bufio.NewReader(conn).ReadString('\n') +// if err != nil { +// fmt.Println("Could not read message from connection: ", err) +// return +// } +// res, ok := parseCommand(msg) +// if !ok { +// conn.Write([]byte("Could not parse message...\n")) +// return +// } +// conn.Write([]byte(res)) +// } + + +// func main() { +// list, err := net.Listen("tcp", PORT) +// if err != nil { +// log.Fatalf("Could not start server: %s\n", err) +// } +// defer list.Close() +// fmt.Printf("Now accepting connections on port %v...\n", PORT) +// for { +// conn, err := list.Accept() +// if err != nil { +// fmt.Println("Could not accept incoming request: ", err) +// continue +// } +// go handleConnection(conn) +// } +// } \ No newline at end of file