1
0
Fork 0

Init simple Go React blog with Goxygen

This commit is contained in:
Aroy-Art 2024-07-19 22:13:45 +02:00
parent 634a24fe19
commit 1ada5abed1
Signed by: Aroy
GPG key ID: 583642324A1D2070
31 changed files with 873 additions and 0 deletions

View file

@ -0,0 +1,36 @@
package db
import (
"database/sql"
"simple-blog/model"
)
type DB interface {
GetTechnologies() ([]*model.Technology, error)
}
type PostgresDB struct {
db *sql.DB
}
func NewDB(db *sql.DB) DB {
return PostgresDB{db: db}
}
func (d PostgresDB) GetTechnologies() ([]*model.Technology, error) {
rows, err := d.db.Query("select name, details from technologies")
if err != nil {
return nil, err
}
defer rows.Close()
var tech []*model.Technology
for rows.Next() {
t := new(model.Technology)
err = rows.Scan(&t.Name, &t.Details)
if err != nil {
return nil, err
}
tech = append(tech, t)
}
return tech, nil
}

View file

@ -0,0 +1,5 @@
module simple-blog
go 1.22
require github.com/lib/pq v1.10.9

View file

@ -0,0 +1,2 @@
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=

View file

@ -0,0 +1,6 @@
package model
type Technology struct {
Name string `json:"name"`
Details string `json:"details"`
}

View file

@ -0,0 +1,35 @@
package main
import (
"database/sql"
"log"
"os"
"simple-blog/db"
"simple-blog/web"
_ "github.com/lib/pq"
)
func main() {
d, err := sql.Open("postgres", dataSource())
if err != nil {
log.Fatal(err)
}
defer d.Close()
// CORS is enabled only in prod profile
cors := os.Getenv("profile") == "prod"
app := web.NewApp(db.NewDB(d), cors)
err = app.Serve()
log.Println("Error", err)
}
func dataSource() string {
host := "localhost"
pass := "pass"
if os.Getenv("profile") == "prod" {
host = "db"
pass = os.Getenv("db_pass")
}
return "postgresql://" + host + ":5432/goxygen" +
"?user=goxygen&sslmode=disable&password=" + pass
}

View file

@ -0,0 +1,63 @@
package web
import (
"encoding/json"
"log"
"net/http"
"simple-blog/db"
)
type App struct {
d db.DB
handlers map[string]http.HandlerFunc
}
func NewApp(d db.DB, cors bool) App {
app := App{
d: d,
handlers: make(map[string]http.HandlerFunc),
}
techHandler := app.GetTechnologies
if !cors {
techHandler = disableCors(techHandler)
}
app.handlers["/api/technologies"] = techHandler
app.handlers["/"] = http.FileServer(http.Dir("/webapp")).ServeHTTP
return app
}
func (a *App) Serve() error {
for path, handler := range a.handlers {
http.Handle(path, handler)
}
log.Println("Web server is available on port 8080")
return http.ListenAndServe(":8080", nil)
}
func (a *App) GetTechnologies(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
technologies, err := a.d.GetTechnologies()
if err != nil {
sendErr(w, http.StatusInternalServerError, err.Error())
return
}
err = json.NewEncoder(w).Encode(technologies)
if err != nil {
sendErr(w, http.StatusInternalServerError, err.Error())
}
}
func sendErr(w http.ResponseWriter, code int, message string) {
resp, _ := json.Marshal(map[string]string{"error": message})
http.Error(w, string(resp), code)
}
// Needed in order to disable CORS for local development
func disableCors(h http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "*")
w.Header().Set("Access-Control-Allow-Headers", "*")
h(w, r)
}
}

View file

@ -0,0 +1,57 @@
package web
import (
"errors"
"net/http"
"net/http/httptest"
"simple-blog/model"
"testing"
)
type MockDb struct {
tech []*model.Technology
err error
}
func (m *MockDb) GetTechnologies() ([]*model.Technology, error) {
return m.tech, m.err
}
func TestApp_GetTechnologies(t *testing.T) {
app := App{d: &MockDb{
tech: []*model.Technology{
{"Tech1", "Details1"},
{"Tech2", "Details2"},
},
}}
r, _ := http.NewRequest("GET", "/api/technologies", nil)
w := httptest.NewRecorder()
app.GetTechnologies(w, r)
if w.Code != http.StatusOK {
t.Errorf("handler returned wrong status code: got %v want %v", w.Code, http.StatusOK)
}
want := `[{"name":"Tech1","details":"Details1"},{"name":"Tech2","details":"Details2"}]` + "\n"
if got := w.Body.String(); got != want {
t.Errorf("handler returned unexpected body: got %v want %v", got, want)
}
}
func TestApp_GetTechnologies_WithDBError(t *testing.T) {
app := App{d: &MockDb{
tech: nil,
err: errors.New("unknown error"),
}}
r, _ := http.NewRequest("GET", "/api/technologies", nil)
w := httptest.NewRecorder()
app.GetTechnologies(w, r)
if w.Code != http.StatusInternalServerError {
t.Errorf("handler returned wrong status code: got %v want %v", w.Code, http.StatusOK)
}
}