first very primitive server
This commit is contained in:
parent
d1819859a3
commit
a74fd47710
|
@ -10,6 +10,7 @@ func main() {
|
||||||
println("Commands")
|
println("Commands")
|
||||||
println("init <repo> - Creates a new repository")
|
println("init <repo> - Creates a new repository")
|
||||||
println("<repo> new-user <name> - Creates a new user")
|
println("<repo> new-user <name> - Creates a new user")
|
||||||
|
println("<repo> new-post <user> <title> - Creates a new post")
|
||||||
|
|
||||||
if len(os.Args) < 3 {
|
if len(os.Args) < 3 {
|
||||||
println("Please specify a repository and command")
|
println("Please specify a repository and command")
|
||||||
|
@ -45,6 +46,24 @@ func main() {
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
println("User created: ", user.Name())
|
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:
|
default:
|
||||||
println("Unknown command: ", os.Args[2])
|
println("Unknown command: ", os.Args[2])
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
}
|
|
@ -1,15 +1,35 @@
|
||||||
package kiss
|
package kiss
|
||||||
|
|
||||||
import "os"
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
func dirExists(path string) bool {
|
func dirExists(path string) bool {
|
||||||
_, err := os.Stat(path)
|
_, err := os.Stat(path)
|
||||||
return err == nil
|
return err == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// lists all files/dirs in a directory, not recursive
|
||||||
func listDir(path string) []string {
|
func listDir(path string) []string {
|
||||||
dir, _ := os.Open(path)
|
dir, _ := os.Open(path)
|
||||||
defer dir.Close()
|
defer dir.Close()
|
||||||
files, _ := dir.Readdirnames(-1)
|
files, _ := dir.Readdirnames(-1)
|
||||||
return files
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -7,3 +7,12 @@ func getTestUser() kiss.User {
|
||||||
user, _ := repo.CreateUser(randomUserName())
|
user, _ := repo.CreateUser(randomUserName())
|
||||||
return user
|
return user
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func contains(s []string, e string) bool {
|
||||||
|
for _, a := range s {
|
||||||
|
if a == e {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
4
post.go
4
post.go
|
@ -20,6 +20,10 @@ func (post Post) Dir() string {
|
||||||
return path.Join(post.user.Dir(), "public", post.id)
|
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 {
|
func (post Post) Title() string {
|
||||||
return post.title
|
return post.title
|
||||||
}
|
}
|
||||||
|
|
11
renderer.go
11
renderer.go
|
@ -9,3 +9,14 @@ func RenderPost(post Post) (string, error) {
|
||||||
postHtml += buf.String()
|
postHtml += buf.String()
|
||||||
return strings.Replace(template, "{{content}}", postHtml, -1), nil
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -24,3 +24,16 @@ func TestRendererUsesBaseTemplate(t *testing.T) {
|
||||||
t.Error("Base template not used. Got: " + result)
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -31,7 +31,7 @@ func OpenRepository(name string) (Repository, error) {
|
||||||
|
|
||||||
repo := Repository{name: name}
|
repo := Repository{name: name}
|
||||||
if !dirExists(repo.Dir()) {
|
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
|
return repo, nil
|
||||||
|
@ -71,3 +71,11 @@ func (repo Repository) CreateUser(name string) (User, error) {
|
||||||
|
|
||||||
return new_user, nil
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -129,3 +129,26 @@ func TestCannotOpenNonExisitingRepo(t *testing.T) {
|
||||||
t.Error("No error returned when opening non-existing repository")
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
19
user.go
19
user.go
|
@ -5,6 +5,7 @@ import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -17,13 +18,27 @@ func (user User) Dir() string {
|
||||||
return path.Join(user.repo.Dir(), "users", user.name)
|
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 {
|
func (user User) Name() string {
|
||||||
return user.name
|
return user.name
|
||||||
}
|
}
|
||||||
|
|
||||||
func (user User) Posts() ([]string, error) {
|
func (user User) Posts() ([]string, error) {
|
||||||
postIds := listDir(path.Join(user.Dir(), "public"))
|
postFiles := walkDir(path.Join(user.Dir(), "public"))
|
||||||
return postIds, nil
|
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) {
|
func (user User) GetPost(id string) (Post, error) {
|
||||||
|
|
33
user_test.go
33
user_test.go
|
@ -4,6 +4,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"h4kor/kiss-social"
|
"h4kor/kiss-social"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"testing"
|
"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) {
|
func TestCanLoadPost(t *testing.T) {
|
||||||
user := getTestUser()
|
user := getTestUser()
|
||||||
// Create a new post
|
// Create a new post
|
||||||
|
|
Loading…
Reference in New Issue