diff --git a/cmd/kiss-cli/main.go b/cmd/kiss-cli/main.go index 5570a28..b0bac68 100644 --- a/cmd/kiss-cli/main.go +++ b/cmd/kiss-cli/main.go @@ -10,6 +10,7 @@ func main() { println("Commands") println("init - Creates a new repository") println(" new-user - Creates a new user") + println(" new-post - Creates a new post") if len(os.Args) < 3 { println("Please specify a repository and command") @@ -45,6 +46,24 @@ func main() { os.Exit(1) } println("User created: ", user.Name()) + case "new-post": + if len(os.Args) < 5 { + println("Please specify a user name and a title") + os.Exit(1) + } + userName := os.Args[3] + user, err := repo.GetUser(userName) + if err != nil { + println("Error finding user: ", err.Error()) + os.Exit(1) + } + title := os.Args[4] + post, err := user.CreateNewPost(title) + if err != nil { + println("Error creating post: ", err.Error()) + os.Exit(1) + } + println("Post created: ", post.Title()) default: println("Unknown command: ", os.Args[2]) os.Exit(1) diff --git a/cmd/kiss-web/main.go b/cmd/kiss-web/main.go new file mode 100644 index 0000000..f850842 --- /dev/null +++ b/cmd/kiss-web/main.go @@ -0,0 +1,151 @@ +package main + +import ( + "h4kor/kiss-social" + "net/http" + "os" + "strconv" + "strings" +) + +func handler(repo kiss.Repository) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + // normalize the path + path := r.URL.Path + // remove leading '/' + if len(path) > 0 && path[0] == '/' { + path = path[1:] + } + // remove trailing '/' + if len(path) > 0 && path[len(path)-1] == '/' { + path = path[:len(path)-1] + } + + // index page + if path == "" { + println("Index page") + indexHandler(repo)(w, r) + return + } + + // parse the path + parts := strings.Split(path, "/") + userName := parts[0] + + // only one part -> user page + if len(parts) == 1 { + println("User page") + userHandler(repo, userName)(w, r) + return + } + + // multiple parts -> post page + println("Post page") + postId := strings.Join(parts[1:], "/") + postHandler(repo, userName, postId)(w, r) + + } +} + +func indexHandler(repo kiss.Repository) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + users, err := repo.Users() + if err != nil { + println("Error getting users: ", err.Error()) + w.Write([]byte("Error getting users")) + return + } + w.Write([]byte("Index")) + w.Write([]byte("<ul>")) + for _, user := range users { + w.Write([]byte("<li>")) + w.Write([]byte(user.Name())) + w.Write([]byte("</li>")) + } + } +} + +func userHandler(repo kiss.Repository, userName string) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + user, err := repo.GetUser(userName) + if err != nil { + println("Error getting user: ", err.Error()) + w.WriteHeader(http.StatusNotFound) + w.Write([]byte("User not found")) + return + } + html, err := kiss.RenderIndexPage(user) + if err != nil { + println("Error rendering index page: ", err.Error()) + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("Internal server error")) + return + } + println("Rendering index page for user", userName) + w.Write([]byte(html)) + } +} + +func postHandler(repo kiss.Repository, userName string, postId string) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + user, err := repo.GetUser(userName) + if err != nil { + println("Error getting user: ", err.Error()) + w.WriteHeader(http.StatusNotFound) + w.Write([]byte("User not found")) + return + } + post, err := user.GetPost(postId) + if err != nil { + println("Error getting post: ", err.Error()) + w.WriteHeader(http.StatusNotFound) + w.Write([]byte("Post not found")) + return + } + html, err := kiss.RenderPost(post) + if err != nil { + println("Error rendering post: ", err.Error()) + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("Internal server error")) + return + } + println("Rendering post", postId) + w.Write([]byte(html)) + + } +} + +func main() { + println("KISS Web Server") + println("Parameters") + println("-repo <repo> - Specify the repository to use. Defaults to '.'") + println("-port <port> - Specify the port to use, Default is '8080'") + var repoName string + var port int + for i, arg := range os.Args[0 : len(os.Args)-1] { + if arg == "-port" { + port, _ = strconv.Atoi(os.Args[i+1]) + } + if arg == "-repo" { + repoName = os.Args[i+1] + } + } + if repoName == "" { + repoName = "." + } + if port == 0 { + port = 8080 + } + + repo, err := kiss.OpenRepository(repoName) + if err != nil { + println("Error opening repository: ", err.Error()) + os.Exit(1) + } + + http.HandleFunc("/", handler(repo)) + + println("Listening on port", port) + http.ListenAndServe(":"+strconv.Itoa(port), nil) + +} diff --git a/directories.go b/directories.go index 6abc2df..9d2c48f 100644 --- a/directories.go +++ b/directories.go @@ -1,15 +1,35 @@ package kiss -import "os" +import ( + "os" + "path/filepath" +) func dirExists(path string) bool { _, err := os.Stat(path) return err == nil } +// lists all files/dirs in a directory, not recursive func listDir(path string) []string { dir, _ := os.Open(path) defer dir.Close() files, _ := dir.Readdirnames(-1) return files } + +// recursive list of all files in a directory +func walkDir(path string) []string { + files := make([]string, 0) + filepath.Walk(path, func(subPath string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if info.IsDir() { + return nil + } + files = append(files, subPath[len(path)+1:]) + return nil + }) + return files +} diff --git a/kiss_test.go b/kiss_test.go index 94c26ac..66e6b50 100644 --- a/kiss_test.go +++ b/kiss_test.go @@ -7,3 +7,12 @@ func getTestUser() kiss.User { user, _ := repo.CreateUser(randomUserName()) return user } + +func contains(s []string, e string) bool { + for _, a := range s { + if a == e { + return true + } + } + return false +} diff --git a/post.go b/post.go index 85e6d46..2230314 100644 --- a/post.go +++ b/post.go @@ -20,6 +20,10 @@ func (post Post) Dir() string { return path.Join(post.user.Dir(), "public", post.id) } +func (post Post) Path() string { + return post.user.Path() + "/" + post.id +} + func (post Post) Title() string { return post.title } diff --git a/renderer.go b/renderer.go index 4b9b17d..7a40820 100644 --- a/renderer.go +++ b/renderer.go @@ -9,3 +9,14 @@ func RenderPost(post Post) (string, error) { postHtml += buf.String() return strings.Replace(template, "{{content}}", postHtml, -1), nil } + +func RenderIndexPage(user User) (string, error) { + template, _ := user.Template() + posts, _ := user.Posts() + postHtml := "" + for _, postId := range posts { + post, _ := user.GetPost(postId) + postHtml += "<h2><a href=\"" + post.Path() + "\">" + post.Title() + "</a></h2>\n" + } + return strings.Replace(template, "{{content}}", postHtml, -1), nil +} diff --git a/renderer_test.go b/renderer_test.go index 3d0715a..01c19d0 100644 --- a/renderer_test.go +++ b/renderer_test.go @@ -24,3 +24,16 @@ func TestRendererUsesBaseTemplate(t *testing.T) { t.Error("Base template not used. Got: " + result) } } + +func TestCanRenderIndexPage(t *testing.T) { + user := getTestUser() + user.CreateNewPost("testpost1") + user.CreateNewPost("testpost2") + result, _ := kiss.RenderIndexPage(user) + if !strings.Contains(result, "testpost1") { + t.Error("Post title not rendered as h1. Got: " + result) + } + if !strings.Contains(result, "testpost2") { + t.Error("Post title not rendered as h1. Got: " + result) + } +} diff --git a/repository.go b/repository.go index e828f41..537e8a4 100644 --- a/repository.go +++ b/repository.go @@ -31,7 +31,7 @@ func OpenRepository(name string) (Repository, error) { repo := Repository{name: name} if !dirExists(repo.Dir()) { - return Repository{}, fmt.Errorf("Repository does not exist") + return Repository{}, fmt.Errorf("Repository does not exist: " + repo.Dir()) } return repo, nil @@ -71,3 +71,11 @@ func (repo Repository) CreateUser(name string) (User, error) { return new_user, nil } + +func (repo Repository) GetUser(name string) (User, error) { + user := User{repo: repo, name: name} + if !dirExists(user.Dir()) { + return User{}, fmt.Errorf("User does not exist") + } + return user, nil +} diff --git a/repository_test.go b/repository_test.go index d784078..e1523ab 100644 --- a/repository_test.go +++ b/repository_test.go @@ -129,3 +129,26 @@ func TestCannotOpenNonExisitingRepo(t *testing.T) { t.Error("No error returned when opening non-existing repository") } } + +func TestGetUser(t *testing.T) { + // Create a new user + repo, _ := kiss.CreateRepository(testRepoName()) + user, _ := repo.CreateUser(randomUserName()) + // Get the user + user2, err := repo.GetUser(user.Name()) + if err != nil { + t.Error("Error getting user: ", err.Error()) + } + if user2.Name() != user.Name() { + t.Error("User names do not match") + } +} + +func TestCannotGetNonexistingUser(t *testing.T) { + // Create a new user + repo, _ := kiss.CreateRepository(testRepoName()) + _, err := repo.GetUser(randomUserName()) + if err == nil { + t.Error("No error returned when getting non-existing user") + } +} diff --git a/user.go b/user.go index d02a00c..22577cc 100644 --- a/user.go +++ b/user.go @@ -5,6 +5,7 @@ import ( "io/ioutil" "os" "path" + "strings" "time" ) @@ -17,13 +18,27 @@ func (user User) Dir() string { return path.Join(user.repo.Dir(), "users", user.name) } +func (user User) Path() string { + return "/" + user.name +} + +func (user User) PostDir() string { + return path.Join(user.Dir(), "public") +} + func (user User) Name() string { return user.name } func (user User) Posts() ([]string, error) { - postIds := listDir(path.Join(user.Dir(), "public")) - return postIds, nil + postFiles := walkDir(path.Join(user.Dir(), "public")) + posts := make([]string, 0) + for _, id := range postFiles { + if strings.HasSuffix(id, "/index.md") { + posts = append(posts, id[:len(id)-9]) + } + } + return posts, nil } func (user User) GetPost(id string) (Post, error) { diff --git a/user_test.go b/user_test.go index 00fa68e..b531996 100644 --- a/user_test.go +++ b/user_test.go @@ -4,6 +4,7 @@ import ( "fmt" "h4kor/kiss-social" "io/ioutil" + "os" "path" "testing" ) @@ -57,6 +58,38 @@ func TestCanListUserPosts(t *testing.T) { } } +func TestCanListUserPostsWithSubdirectories(t *testing.T) { + // Create a new user + repo, _ := kiss.CreateRepository(testRepoName()) + user, _ := repo.CreateUser(randomUserName()) + // Create a new post + user.CreateNewPost("testpost") + os.Mkdir(path.Join(user.PostDir(), "foo"), 0755) + os.Mkdir(path.Join(user.PostDir(), "foo/bar"), 0755) + content := "" + content += "---\n" + content += "title: test\n" + content += "---\n" + content += "\n" + content += "Write your post here.\n" + + os.WriteFile(path.Join(user.PostDir(), "foo/bar/index.md"), []byte(content), 0644) + posts, _ := user.Posts() + if contains(posts, "foo") { + t.Error("Contains non-post name: foo") + for _, p := range posts { + t.Error("\t" + p) + } + } + + if !contains(posts, "foo/bar") { + t.Error("Post not found. Found: ") + for _, p := range posts { + t.Error("\t" + p) + } + } +} + func TestCanLoadPost(t *testing.T) { user := getTestUser() // Create a new post