2023-02-06 02:51:05 +09:00
|
|
|
package app
|
|
|
|
|
|
|
|
import (
|
|
|
|
"database/sql"
|
2023-06-24 09:59:05 +09:00
|
|
|
"encoding/json"
|
2023-02-06 02:51:05 +09:00
|
|
|
"fmt"
|
|
|
|
"log"
|
|
|
|
"net/http"
|
|
|
|
"os"
|
|
|
|
"time"
|
|
|
|
|
2023-06-24 09:59:05 +09:00
|
|
|
"github.com/syumai/workers/_examples/d1-blog-server/app/model"
|
2023-02-26 12:26:48 +09:00
|
|
|
"github.com/syumai/workers/cloudflare/d1"
|
2023-02-06 02:51:05 +09:00
|
|
|
_ "github.com/syumai/workers/cloudflare/d1" // register driver
|
|
|
|
)
|
|
|
|
|
|
|
|
type articleHandler struct{}
|
|
|
|
|
|
|
|
var _ http.Handler = (*articleHandler)(nil)
|
|
|
|
|
|
|
|
func NewArticleHandler() http.Handler {
|
|
|
|
return &articleHandler{}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (h *articleHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
2023-02-26 12:26:48 +09:00
|
|
|
// initialize DB.
|
|
|
|
// D1 connector requires request's context to initialize DB.
|
2024-04-17 00:56:47 +09:00
|
|
|
c, err := d1.OpenConnector("BlogDB")
|
2023-02-06 02:51:05 +09:00
|
|
|
if err != nil {
|
|
|
|
h.handleErr(w, http.StatusInternalServerError, fmt.Sprintf("failed to initialize DB: %v", err))
|
|
|
|
}
|
2023-02-26 12:26:48 +09:00
|
|
|
// use sql.OpenDB instead of sql.Open.
|
|
|
|
db := sql.OpenDB(c)
|
|
|
|
|
2023-02-06 02:51:05 +09:00
|
|
|
switch req.Method {
|
|
|
|
case http.MethodGet:
|
2023-02-12 01:57:04 +09:00
|
|
|
h.listArticles(w, req, db)
|
2023-02-06 02:51:05 +09:00
|
|
|
return
|
|
|
|
case http.MethodPost:
|
|
|
|
h.createArticle(w, req, db)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
w.WriteHeader(http.StatusNotFound)
|
|
|
|
w.Header().Set("Content-Type", "text/plain")
|
|
|
|
w.Write([]byte("not found"))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (h *articleHandler) handleErr(w http.ResponseWriter, status int, msg string) {
|
|
|
|
w.WriteHeader(status)
|
|
|
|
w.Header().Set("Content-Type", "text/plain")
|
|
|
|
w.Write([]byte(msg))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (h *articleHandler) createArticle(w http.ResponseWriter, req *http.Request, db *sql.DB) {
|
|
|
|
var createArticleReq model.CreateArticleRequest
|
2023-06-24 09:59:05 +09:00
|
|
|
if err := json.NewDecoder(req.Body).Decode(&createArticleReq); err != nil {
|
2023-02-06 02:51:05 +09:00
|
|
|
h.handleErr(w, http.StatusBadRequest,
|
|
|
|
"request format is invalid")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
now := time.Now().Unix()
|
|
|
|
article := model.Article{
|
|
|
|
Title: createArticleReq.Title,
|
|
|
|
Body: createArticleReq.Body,
|
|
|
|
CreatedAt: uint64(now),
|
|
|
|
}
|
|
|
|
|
2023-02-26 17:40:24 +09:00
|
|
|
result, err := db.Exec(`
|
|
|
|
INSERT INTO articles (title, body, created_at)
|
|
|
|
VALUES (?, ?, ?)
|
|
|
|
`, article.Title, article.Body, article.CreatedAt)
|
2023-02-06 02:51:05 +09:00
|
|
|
if err != nil {
|
|
|
|
log.Println(err)
|
|
|
|
h.handleErr(w, http.StatusInternalServerError,
|
|
|
|
"failed to save article")
|
|
|
|
return
|
|
|
|
}
|
2023-02-26 17:40:24 +09:00
|
|
|
id, err := result.LastInsertId()
|
|
|
|
if err != nil {
|
|
|
|
log.Println(err)
|
|
|
|
h.handleErr(w, http.StatusInternalServerError,
|
|
|
|
"failed to get ID of inserted article")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
article.ID = uint64(id)
|
2023-02-06 02:51:05 +09:00
|
|
|
|
|
|
|
res := model.CreateArticleResponse{
|
|
|
|
Article: article,
|
|
|
|
}
|
|
|
|
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
2023-06-24 09:59:05 +09:00
|
|
|
if err := json.NewEncoder(w).Encode(res); err != nil {
|
2023-02-06 02:51:05 +09:00
|
|
|
fmt.Fprintf(os.Stderr, "failed to encode response: %w\n", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-12 01:57:04 +09:00
|
|
|
func (h *articleHandler) listArticles(w http.ResponseWriter, req *http.Request, db *sql.DB) {
|
2023-02-26 12:26:48 +09:00
|
|
|
rows, err := db.Query(`
|
2023-02-06 02:51:05 +09:00
|
|
|
SELECT id, title, body, created_at FROM articles
|
|
|
|
ORDER BY created_at DESC;
|
|
|
|
`)
|
|
|
|
if err != nil {
|
|
|
|
h.handleErr(w, http.StatusInternalServerError,
|
|
|
|
"failed to load article")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
articles := []model.Article{}
|
|
|
|
for rows.Next() {
|
2023-02-26 18:16:16 +09:00
|
|
|
var a model.Article
|
|
|
|
err = rows.Scan(&a.ID, &a.Title, &a.Body, &a.CreatedAt)
|
2023-02-06 02:51:05 +09:00
|
|
|
if err != nil {
|
2023-02-26 18:16:16 +09:00
|
|
|
log.Println(err)
|
|
|
|
h.handleErr(w, http.StatusInternalServerError,
|
|
|
|
"failed to scan article")
|
|
|
|
return
|
2023-02-06 02:51:05 +09:00
|
|
|
}
|
2023-02-26 18:16:16 +09:00
|
|
|
articles = append(articles, a)
|
2023-02-06 02:51:05 +09:00
|
|
|
}
|
|
|
|
res := model.ListArticlesResponse{
|
|
|
|
Articles: articles,
|
|
|
|
}
|
|
|
|
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
2023-06-24 09:59:05 +09:00
|
|
|
if err := json.NewEncoder(w).Encode(res); err != nil {
|
2023-02-06 02:51:05 +09:00
|
|
|
fmt.Fprintf(os.Stderr, "failed to encode response: %w\n", err)
|
|
|
|
}
|
|
|
|
}
|