mirror of
https://github.com/lyx0/nourybot.git
synced 2024-11-13 19:49:55 +01:00
start implementing an api for the commands
This commit is contained in:
parent
003a3737ac
commit
3843c76906
6
.gitignore
vendored
6
.gitignore
vendored
|
@ -15,4 +15,8 @@
|
|||
# vendor/
|
||||
|
||||
.env
|
||||
Nourybot
|
||||
Nourybot
|
||||
Nourybot-Api
|
||||
nourybot-api
|
||||
Nourybot-Web
|
||||
nourybot-web
|
5
Makefile
5
Makefile
|
@ -8,4 +8,7 @@ xd:
|
|||
cd cmd/bot && go build -o Nourybot && ./Nourybot
|
||||
|
||||
jq:
|
||||
cd cmd/bot && go build -o Nourybot && ./Nourybot | jq
|
||||
cd cmd/bot && go build -o Nourybot && ./Nourybot | jq
|
||||
|
||||
jqapi:
|
||||
cd cmd/api && go build -o Nourybot-Api && ./Nourybot-Api | jq
|
64
cmd/api/command.go
Normal file
64
cmd/api/command.go
Normal file
|
@ -0,0 +1,64 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"github.com/lyx0/nourybot/internal/data"
|
||||
)
|
||||
|
||||
func (app *application) showCommandHandler(w http.ResponseWriter, r *http.Request) {
|
||||
name, err := app.readCommandNameParam(r)
|
||||
if err != nil {
|
||||
// app.Logger.Errorf("showCommandHandler, Command not found", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Get the data for a specific movie from our helper method,
|
||||
// then check if an error was returned, and which.
|
||||
command, err := app.Models.Commands.Get(name)
|
||||
if err != nil {
|
||||
switch {
|
||||
case errors.Is(err, data.ErrRecordNotFound):
|
||||
app.Logger.Errorf("showCommandHandler, Command not found", err)
|
||||
return
|
||||
default:
|
||||
app.serverErrorResponse(w, r, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
app.Logger.Info("Command Name:", command.Name)
|
||||
err = app.writeJSON(w, http.StatusOK, envelope{"movie": command}, nil)
|
||||
// if err != nil {
|
||||
// app.serverErrorResponse(w, r, err)
|
||||
// }
|
||||
}
|
||||
|
||||
type envelope map[string]interface{}
|
||||
|
||||
func (app *application) writeJSON(w http.ResponseWriter, status int, data envelope, headers http.Header) error {
|
||||
// Encode the data into JSON and return any errors if there were any.
|
||||
// Use MarshalIndent instead of normal Marshal so it looks prettier on terminals.
|
||||
js, err := json.MarshalIndent(data, "", "\t")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Append a newline to make it prettier on terminals.
|
||||
js = append(js, '\n')
|
||||
|
||||
// Iterate over the header map and add each header to the
|
||||
// http.ResponseWriter header map.
|
||||
for key, value := range headers {
|
||||
w.Header()[key] = value
|
||||
}
|
||||
|
||||
// Set `Content-Type` to `application/json` because go
|
||||
// defaults to `text-plain; charset=utf8`.
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(status)
|
||||
w.Write(js)
|
||||
|
||||
return nil
|
||||
}
|
29
cmd/api/errors.go
Normal file
29
cmd/api/errors.go
Normal file
|
@ -0,0 +1,29 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func (app *application) serverErrorResponse(w http.ResponseWriter, r *http.Request, err error) {
|
||||
app.logError(r, err)
|
||||
|
||||
message := "the server encountered a problem and could not process your request"
|
||||
app.errorResponse(w, r, http.StatusInternalServerError, message)
|
||||
}
|
||||
|
||||
// The logError() method is a generic helper for logging an error message. Later in the
|
||||
// book we'll upgrade this to use structured logging, and record additional information
|
||||
// about the request including the HTTP method and URL.
|
||||
func (app *application) logError(r *http.Request, err error) {
|
||||
app.Logger.Infow("logError",
|
||||
"error", err)
|
||||
}
|
||||
|
||||
func (app *application) errorResponse(w http.ResponseWriter, r *http.Request, status int, message interface{}) {
|
||||
|
||||
// Write the response using the writeJSON() helper. If this happens to return an
|
||||
// error then log it, and fall back to sending the client an empty response with a
|
||||
// 500 Internal Server Error status code.
|
||||
fmt.Fprintf(w, "Error: %s", status)
|
||||
}
|
35
cmd/api/helpers.go
Normal file
35
cmd/api/helpers.go
Normal file
|
@ -0,0 +1,35 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/julienschmidt/httprouter"
|
||||
)
|
||||
|
||||
func (app *application) readIDParam(r *http.Request) (int64, error) {
|
||||
params := httprouter.ParamsFromContext(r.Context())
|
||||
|
||||
// Use `ByName()` function to get the value of the "id" parameter from the slice.
|
||||
// The value returned by `ByName()` is always a string so we try to convert it to
|
||||
// base64 with a bit size of 64.
|
||||
id, err := strconv.ParseInt(params.ByName("id"), 10, 64)
|
||||
if err != nil || id < 1 {
|
||||
return 0, errors.New("invalid id parameter")
|
||||
}
|
||||
|
||||
return id, nil
|
||||
}
|
||||
|
||||
func (app *application) readCommandNameParam(r *http.Request) (string, error) {
|
||||
params := httprouter.ParamsFromContext(r.Context())
|
||||
app.Logger.Info(r.Context())
|
||||
|
||||
// Use `ByName()` function to get the value of the "id" parameter from the slice.
|
||||
// The value returned by `ByName()` is always a string so we try to convert it to
|
||||
// base64 with a bit size of 64.
|
||||
name := params.ByName("name")
|
||||
|
||||
return name, nil
|
||||
}
|
111
cmd/api/main.go
Normal file
111
cmd/api/main.go
Normal file
|
@ -0,0 +1,111 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/joho/godotenv"
|
||||
_ "github.com/lib/pq"
|
||||
"github.com/lyx0/nourybot/internal/data"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type config struct {
|
||||
port int
|
||||
db struct {
|
||||
dsn string
|
||||
maxOpenConns int
|
||||
maxIdleConns int
|
||||
maxIdleTime string
|
||||
}
|
||||
}
|
||||
|
||||
type application struct {
|
||||
Logger *zap.SugaredLogger
|
||||
Db *sql.DB
|
||||
Models data.Models
|
||||
}
|
||||
|
||||
func main() {
|
||||
var cfg config
|
||||
|
||||
// Initialize a new sugared logger that we'll pass on
|
||||
// down through the application.
|
||||
sugar := zap.NewExample().Sugar()
|
||||
defer sugar.Sync()
|
||||
|
||||
err := godotenv.Load()
|
||||
if err != nil {
|
||||
log.Fatal("Error loading .env file")
|
||||
}
|
||||
|
||||
// Database
|
||||
cfg.db.dsn = os.Getenv("DB_DSN")
|
||||
cfg.port = 3000
|
||||
cfg.db.maxOpenConns = 25
|
||||
cfg.db.maxIdleConns = 25
|
||||
cfg.db.maxIdleTime = "15m"
|
||||
|
||||
// Establish database connection
|
||||
db, err := openDB(cfg)
|
||||
if err != nil {
|
||||
sugar.Fatal(err)
|
||||
}
|
||||
|
||||
// Initialize Application
|
||||
app := &application{
|
||||
Logger: sugar,
|
||||
Db: db,
|
||||
Models: data.NewModels(db),
|
||||
}
|
||||
|
||||
srv := &http.Server{
|
||||
Addr: fmt.Sprintf(":%d", cfg.port),
|
||||
Handler: app.routes(),
|
||||
IdleTimeout: time.Minute,
|
||||
ReadTimeout: 10 * time.Second,
|
||||
WriteTimeout: 10 * time.Second,
|
||||
}
|
||||
|
||||
err = srv.ListenAndServe()
|
||||
sugar.Fatal(err)
|
||||
|
||||
}
|
||||
|
||||
// openDB returns the sql.DB connection pool.
|
||||
func openDB(cfg config) (*sql.DB, error) {
|
||||
// sql.Open() creates an empty connection pool with the provided DSN
|
||||
db, err := sql.Open("postgres", cfg.db.dsn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Set database restraints.
|
||||
db.SetMaxOpenConns(cfg.db.maxOpenConns)
|
||||
db.SetMaxIdleConns(cfg.db.maxIdleConns)
|
||||
|
||||
// Parse the maxIdleTime string into an actual duration and set it.
|
||||
duration, err := time.ParseDuration(cfg.db.maxIdleTime)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
db.SetConnMaxIdleTime(duration)
|
||||
|
||||
// Create a new context with a 5 second timeout.
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// db.PingContext() is needed to actually check if the
|
||||
// connection to the database was successful.
|
||||
err = db.PingContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return db, nil
|
||||
}
|
14
cmd/api/routes.go
Normal file
14
cmd/api/routes.go
Normal file
|
@ -0,0 +1,14 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/julienschmidt/httprouter"
|
||||
)
|
||||
|
||||
func (app *application) routes() *httprouter.Router {
|
||||
router := httprouter.New()
|
||||
|
||||
router.HandlerFunc(http.MethodGet, "/v1/commands/:name", app.showCommandHandler)
|
||||
return router
|
||||
}
|
1
go.mod
1
go.mod
|
@ -7,6 +7,7 @@ require (
|
|||
github.com/dustin/go-humanize v1.0.0
|
||||
github.com/gempir/go-twitch-irc/v3 v3.2.0
|
||||
github.com/joho/godotenv v1.4.0
|
||||
github.com/julienschmidt/httprouter v1.3.0
|
||||
github.com/lib/pq v1.10.6
|
||||
go.uber.org/zap v1.21.0
|
||||
)
|
||||
|
|
2
go.sum
2
go.sum
|
@ -11,6 +11,8 @@ github.com/gempir/go-twitch-irc/v3 v3.2.0 h1:ENhsa7RgBE1GMmDqe0iMkvcSYfgw6ZsXilt
|
|||
github.com/gempir/go-twitch-irc/v3 v3.2.0/go.mod h1:/W9KZIiyizVecp4PEb7kc4AlIyXKiCmvlXrzlpPUytU=
|
||||
github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=
|
||||
github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
|
||||
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
|
|
|
@ -14,12 +14,11 @@ const Home: NextPage = () => {
|
|||
|
||||
<main className={styles.main}>
|
||||
<h1 className={styles.title}>
|
||||
Welcome to <a href="https://nextjs.org">Next.js!</a>
|
||||
Nourybot
|
||||
</h1>
|
||||
|
||||
<p className={styles.description}>
|
||||
Get started by editing{' '}
|
||||
<code className={styles.code}>pages/index.tsx</code>
|
||||
A lidl Twitch bot made by <a href="https://twitch.tv/nourylul">Noury</a>
|
||||
</p>
|
||||
|
||||
<div className={styles.grid}>
|
||||
|
|
Loading…
Reference in a new issue