Skip to content

Commit a9168bf

Browse files
talgat-yandextalgat-ruby
authored andcommitted
add lesson4
1 parent ea2ef68 commit a9168bf

File tree

22 files changed

+509
-0
lines changed

22 files changed

+509
-0
lines changed

lesson4/example1/.env.example

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
PORT=
2+
AI_API_KEY=

lesson4/example1/.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.env

lesson4/example1/configs/conf.go

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package configs
2+
3+
import (
4+
"context"
5+
"flag"
6+
)
7+
8+
type Config struct {
9+
Api *ApiConfig
10+
}
11+
12+
func NewConfig(ctx context.Context) (*Config, error) {
13+
conf := &Config{}
14+
15+
_ = conf.loadDotEnvFiles()
16+
17+
// Api config
18+
if c, err := newApiConfig(ctx); err != nil {
19+
return nil, err
20+
} else {
21+
conf.Api = c
22+
}
23+
24+
flag.Parse()
25+
26+
return conf, nil
27+
}

lesson4/example1/configs/env.go

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package configs
2+
3+
import (
4+
"github.com/joho/godotenv"
5+
)
6+
7+
func (c *Config) loadDotEnvFiles() error {
8+
return godotenv.Load(".env", ".env.local")
9+
}
+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package configs
2+
3+
import (
4+
"context"
5+
"flag"
6+
7+
"github.com/sethvargo/go-envconfig"
8+
)
9+
10+
type ApiConfig struct {
11+
Port int `env:"PORT"`
12+
AIApiKey string `env:"AI_API_KEY"`
13+
}
14+
15+
func newApiConfig(ctx context.Context) (*ApiConfig, error) {
16+
c := &ApiConfig{}
17+
18+
if err := envconfig.Process(ctx, c); err != nil {
19+
return nil, err
20+
}
21+
22+
flag.IntVar(&c.Port, "port", c.Port, "server port [PORT]")
23+
flag.StringVar(&c.AIApiKey, "token-key", c.AIApiKey, "Open AI API key [AI_API_KEY]")
24+
25+
return c, nil
26+
}

lesson4/example1/go.mod

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
module github.com/talgat-ruby/lessons-go/lesson4/example1
2+
3+
go 1.22.5
4+
5+
require (
6+
github.com/joho/godotenv v1.5.1
7+
github.com/sethvargo/go-envconfig v1.1.0
8+
)

lesson4/example1/go.sum

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
2+
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
3+
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
4+
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
5+
github.com/sethvargo/go-envconfig v1.1.0 h1:cWZiJxeTm7AlCvzGXrEXaSTCNgip5oJepekh/BOQuog=
6+
github.com/sethvargo/go-envconfig v1.1.0/go.mod h1:JLd0KFWQYzyENqnEPWWZ49i4vzZo/6nRidxI8YvGiHw=
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package ai
2+
3+
type Handler struct {
4+
apiKey string
5+
}
6+
7+
func New(apiKey string) *Handler {
8+
return &Handler{apiKey: apiKey}
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package ai
2+
3+
import (
4+
"encoding/json"
5+
"log/slog"
6+
"net/http"
7+
8+
"github.com/talgat-ruby/lessons-go/lesson4/example1/internal/gemini"
9+
"github.com/talgat-ruby/lessons-go/lesson4/example1/pkg/response"
10+
)
11+
12+
type promptReqBody struct {
13+
Prompt string `json:"prompt"`
14+
}
15+
16+
func (h *Handler) Prompt(w http.ResponseWriter, r *http.Request) {
17+
ctx := r.Context()
18+
slog.InfoContext(ctx, "aiHandler::Prompt::start")
19+
20+
var b promptReqBody
21+
err := json.NewDecoder(r.Body).Decode(&b)
22+
if err != nil {
23+
slog.ErrorContext(
24+
ctx,
25+
"aiHandler::Prompt::fail",
26+
"error", err,
27+
)
28+
http.Error(w, err.Error(), http.StatusBadRequest)
29+
return
30+
}
31+
32+
g := gemini.NewGemini(h.apiKey)
33+
text, err := g.SendPrompt(b.Prompt)
34+
if err != nil {
35+
slog.ErrorContext(
36+
ctx,
37+
"aiHandler::Prompt::fail",
38+
"error", err,
39+
)
40+
http.Error(w, err.Error(), http.StatusBadRequest)
41+
return
42+
}
43+
44+
data := &promptResponseBody{
45+
Message: text,
46+
}
47+
if err := response.JSON(w, data); err != nil {
48+
slog.ErrorContext(
49+
ctx,
50+
"aiHandler::Prompt::fail",
51+
"error", err,
52+
)
53+
return
54+
}
55+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package ai
2+
3+
type promptResponseBody struct {
4+
Message string `json:"message"`
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package pingHandler
2+
3+
type Handler struct{}
4+
5+
func New() *Handler {
6+
return &Handler{}
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package pingHandler
2+
3+
import (
4+
"log/slog"
5+
"net/http"
6+
7+
"github.com/talgat-ruby/lessons-go/lesson4/example1/pkg/response"
8+
)
9+
10+
func (h *Handler) Ping(w http.ResponseWriter, r *http.Request) {
11+
ctx := r.Context()
12+
slog.InfoContext(ctx, "pingHandler::Ping::start")
13+
14+
data := &pingResponseBody{
15+
Message: "pong",
16+
}
17+
if err := response.JSON(w, data); err != nil {
18+
slog.ErrorContext(
19+
ctx,
20+
"pingHandler::Ping::fail",
21+
"error", err,
22+
)
23+
return
24+
}
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package pingHandler
2+
3+
type pingResponseBody struct {
4+
Message string `json:"message"`
5+
}

lesson4/example1/internal/api/main.go

+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package api
2+
3+
import (
4+
"context"
5+
"errors"
6+
"fmt"
7+
"log/slog"
8+
"net"
9+
"net/http"
10+
"os"
11+
"os/signal"
12+
13+
"github.com/talgat-ruby/lessons-go/lesson4/example1/configs"
14+
"github.com/talgat-ruby/lessons-go/lesson4/example1/internal/api/router"
15+
apiT "github.com/talgat-ruby/lessons-go/lesson4/example1/internal/api/types"
16+
)
17+
18+
type server struct {
19+
conf *configs.ApiConfig
20+
}
21+
22+
func New(conf *configs.ApiConfig) apiT.Api {
23+
s := &server{
24+
conf: conf,
25+
}
26+
27+
return s
28+
}
29+
30+
func (s *server) Config() *configs.ApiConfig {
31+
return s.conf
32+
}
33+
34+
func (s *server) Start(ctx context.Context, cancel context.CancelFunc) {
35+
mux := http.NewServeMux()
36+
router.SetupRoutes(mux, s)
37+
38+
// start up HTTP
39+
srv := &http.Server{
40+
Addr: fmt.Sprintf(":%d", s.conf.Port),
41+
Handler: mux,
42+
BaseContext: func(_ net.Listener) context.Context {
43+
return ctx
44+
},
45+
}
46+
47+
// Listen from s different goroutine
48+
go func() {
49+
if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
50+
slog.ErrorContext(ctx, "server error", "error", err)
51+
}
52+
53+
cancel()
54+
}()
55+
56+
slog.InfoContext(
57+
ctx,
58+
"starting api service",
59+
"port", s.conf.Port,
60+
"playground", fmt.Sprintf("http://localhost:%d/", s.conf.Port),
61+
)
62+
63+
shutdown := make(chan os.Signal, 1) // Create channel to signify s signal being sent
64+
signal.Notify(shutdown, os.Interrupt) // When an interrupt is sent, notify the channel
65+
66+
go func() {
67+
sig := <-shutdown
68+
69+
slog.WarnContext(ctx, "signal received - shutting down...", "signal", sig)
70+
if err := srv.Shutdown(ctx); err != nil {
71+
slog.ErrorContext(ctx, "server shutdown error", "error", err)
72+
}
73+
}()
74+
}
+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package router
2+
3+
import (
4+
"net/http"
5+
6+
aiHandler "github.com/talgat-ruby/lessons-go/lesson4/example1/internal/api/handler/ai"
7+
)
8+
9+
func ai(mux *http.ServeMux, apiKey string) {
10+
h := aiHandler.New(apiKey)
11+
12+
mux.Handle("POST /prompt", http.HandlerFunc(h.Prompt))
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package router
2+
3+
import (
4+
"net/http"
5+
6+
apiT "github.com/talgat-ruby/lessons-go/lesson4/example1/internal/api/types"
7+
)
8+
9+
// SetupRoutes setup router api
10+
func SetupRoutes(mux *http.ServeMux, api apiT.Api) {
11+
ping(mux)
12+
ai(mux, api.Config().AIApiKey)
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package router
2+
3+
import (
4+
"net/http"
5+
6+
pingHandler "github.com/talgat-ruby/lessons-go/lesson4/example1/internal/api/handler/ping"
7+
)
8+
9+
func ping(mux *http.ServeMux) {
10+
h := pingHandler.New()
11+
12+
mux.Handle("GET /ping", http.HandlerFunc(h.Ping))
13+
}
+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package types
2+
3+
import (
4+
"context"
5+
6+
"github.com/talgat-ruby/lessons-go/lesson4/example1/configs"
7+
)
8+
9+
type Api interface {
10+
Start(ctx context.Context, cancel context.CancelFunc)
11+
Config() *configs.ApiConfig
12+
}
+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package gemini
2+
3+
type Gemini struct {
4+
apiKey string
5+
}
6+
7+
func NewGemini(apiKey string) *Gemini {
8+
return &Gemini{apiKey}
9+
}

0 commit comments

Comments
 (0)