first very primitive server
This commit is contained in:
parent
d1819859a3
commit
a74fd47710
|
@ -10,6 +10,7 @@ func main() {
|
|||
println("Commands")
|
||||
println("init <repo> - Creates a new repository")
|
||||
println("<repo> new-user <name> - Creates a new user")
|
||||
println("<repo> new-post <user> <title> - 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)
|
||||
|
|
|
@ -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
|
||||
|
||||
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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
4
post.go
4
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
|
||||
}
|
||||
|
|
11
renderer.go
11
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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
19
user.go
19
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) {
|
||||
|
|
33
user_test.go
33
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
|
||||
|
|
Loading…
Reference in New Issue