first very primitive server

This commit is contained in:
Niko Abeler 2022-07-23 17:19:47 +02:00
parent d1819859a3
commit a74fd47710
11 changed files with 310 additions and 4 deletions

View File

@ -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)

151
cmd/kiss-web/main.go Normal file
View File

@ -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)
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
} }

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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
}

View File

@ -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
View File

@ -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) {

View File

@ -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