diff --git a/docker-compose.yml b/docker-compose.yml index 128814f..8fcc2ea 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -15,6 +15,7 @@ services: - ./environment:/var/environment/pink_fox - ./.storage/go:/go environment: + - TZ=Europe/Moscow - ARG1 postgres: @@ -22,6 +23,8 @@ services: environment: - POSTGRES_USER=pink_fox - POSTGRES_PASSWORD=pink_fox_pass + - TZ=Europe/Moscow + - PGTZ=Europe/Moscow volumes: - ./.storage/postgres:/var/lib/postgresql/data - ./services/postgres/data:/var/backups/postgres diff --git a/pink_fox_app/.gitignore b/pink_fox_app/.gitignore index f5fb25c..d2cfe19 100644 --- a/pink_fox_app/.gitignore +++ b/pink_fox_app/.gitignore @@ -1,3 +1,5 @@ out.log err.log pink_fox +dlv.log + diff --git a/pink_fox_app/database/links.postgres.sql b/pink_fox_app/database/links.postgres.sql new file mode 100644 index 0000000..0be15da --- /dev/null +++ b/pink_fox_app/database/links.postgres.sql @@ -0,0 +1,11 @@ +DROP TABLE IF EXISTS links; + +CREATE TABLE links ( + id SERIAL PRIMARY KEY, + token VARCHAR(24) NOT NULL UNIQUE, + url VARCHAR(512) NOT NULL, + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + expires_at TIMESTAMP WITH TIME ZONE NOT NULL +); + +CREATE INDEX idx_links_token ON links (token); diff --git a/pink_fox_app/dlv.log b/pink_fox_app/dlv.log deleted file mode 100644 index d8690ef..0000000 --- a/pink_fox_app/dlv.log +++ /dev/null @@ -1,2 +0,0 @@ -2025-03-03T16:51:40Z warning layer=rpc Listening for remote connections (connections are not authenticated nor encrypted) -не удалось открыть файл лога: open : no such file or directory diff --git a/pink_fox_app/go.mod b/pink_fox_app/go.mod index feebd49..bd2507e 100644 --- a/pink_fox_app/go.mod +++ b/pink_fox_app/go.mod @@ -1,10 +1,10 @@ module pink_fox -go 1.24.0 +go 1.24.1 require ( - github.com/bytedance/sonic v1.12.9 // indirect - github.com/bytedance/sonic/loader v0.2.3 // indirect + github.com/bytedance/sonic v1.13.1 // indirect + github.com/bytedance/sonic/loader v0.2.4 // indirect github.com/cloudwego/base64x v0.1.5 // indirect github.com/cloudwego/iasm v0.2.0 // indirect github.com/gabriel-vasile/mimetype v1.4.8 // indirect @@ -27,11 +27,11 @@ require ( github.com/spf13/pflag v1.0.6 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.12 // indirect - golang.org/x/arch v0.14.0 // indirect - golang.org/x/crypto v0.35.0 // indirect - golang.org/x/net v0.35.0 // indirect - golang.org/x/sys v0.30.0 // indirect - golang.org/x/text v0.22.0 // indirect + golang.org/x/arch v0.15.0 // indirect + golang.org/x/crypto v0.36.0 // indirect + golang.org/x/net v0.37.0 // indirect + golang.org/x/sys v0.31.0 // indirect + golang.org/x/text v0.23.0 // indirect google.golang.org/protobuf v1.36.5 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/pink_fox_app/go.sum b/pink_fox_app/go.sum index b2e3c53..ef4135d 100644 --- a/pink_fox_app/go.sum +++ b/pink_fox_app/go.sum @@ -1,8 +1,8 @@ -github.com/bytedance/sonic v1.12.9 h1:Od1BvK55NnewtGaJsTDeAOSnLVO2BTSLOe0+ooKokmQ= -github.com/bytedance/sonic v1.12.9/go.mod h1:uVvFidNmlt9+wa31S1urfwwthTWteBgG0hWuoKAXTx8= +github.com/bytedance/sonic v1.13.1 h1:Jyd5CIvdFnkOWuKXr+wm4Nyk2h0yAFsr8ucJgEasO3g= +github.com/bytedance/sonic v1.13.1/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4= github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= -github.com/bytedance/sonic/loader v0.2.3 h1:yctD0Q3v2NOGfSWPLPvG2ggA2kV6TS6s4wioyEqssH0= -github.com/bytedance/sonic/loader v0.2.3/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= +github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY= +github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4= github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= @@ -67,17 +67,17 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= -golang.org/x/arch v0.14.0 h1:z9JUEZWr8x4rR0OU6c4/4t6E6jOZ8/QBS2bBYBm4tx4= -golang.org/x/arch v0.14.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= -golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs= -golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= -golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= -golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= +golang.org/x/arch v0.15.0 h1:QtOrQd0bTUnhNVNndMpLHNWrDmYzZ2KDqSrEymqInZw= +golang.org/x/arch v0.15.0/go.mod h1:JmwW7aLIoRUKgaTzhkiEFxvcEiQGyOg9BMonBJUS7EE= +golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= +golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= +golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= -golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= -golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/pink_fox_app/main.go b/pink_fox_app/main.go index 4a0add9..15ad3ab 100644 --- a/pink_fox_app/main.go +++ b/pink_fox_app/main.go @@ -6,6 +6,11 @@ import ( "pink_fox/src/app/cmd" ) +// FIXME +// перегрузка конфига +// отображение html страницы +// вынести перехват ошибок в middleware + func main() { err := cmd.Execute() if err != nil { diff --git a/pink_fox_app/src/app/config/config.go b/pink_fox_app/src/app/config/config.go index e42d6ca..7e62f72 100644 --- a/pink_fox_app/src/app/config/config.go +++ b/pink_fox_app/src/app/config/config.go @@ -18,6 +18,7 @@ type Config struct { Port int `yaml:"port"` Db DB `yaml:"db"` LogFile string `yaml:"logFile"` + Debug bool `yaml:"debug"` } func LoadConfig(defaultConfig string, defaultPort int) (*Config, error) { @@ -38,7 +39,7 @@ func setDefaultValue(conf *Config, defaultPort int) *Config { conf.Port = defaultPort } if conf.LogFile == "" { - conf.LogFile = "/var/logger/pink_fox/app.logger" + conf.LogFile = "/var/log/pink_fox/app.log" } return conf } diff --git a/pink_fox_app/src/app/http_server/request.go b/pink_fox_app/src/app/http_server/request.go new file mode 100644 index 0000000..ce44eab --- /dev/null +++ b/pink_fox_app/src/app/http_server/request.go @@ -0,0 +1,17 @@ +package http_server + +import "github.com/gin-gonic/gin" + +type Request struct { + ctx *gin.Context +} + +func NewRequest(ctx *gin.Context) *Request { + return &Request{ + ctx: ctx, + } +} + +func (it *Request) Param(key string) string { + return it.ctx.Param(key) +} diff --git a/pink_fox_app/src/app/http_server/response-factory.go b/pink_fox_app/src/app/http_server/response-factory.go index 5995894..cdd9094 100644 --- a/pink_fox_app/src/app/http_server/response-factory.go +++ b/pink_fox_app/src/app/http_server/response-factory.go @@ -12,6 +12,14 @@ func NewResponseFactory(ctx *gin.Context) *ResponseFactory { return &ResponseFactory{ctx: ctx} } -func (it *ResponseFactory) String(s string) Response { +func (it *ResponseFactory) String(s string) *StringResponse { return NewStringResponse(s, it.ctx) } + +func (it *ResponseFactory) HtmlError(code int) *HtmlErrorResponse { + return NewHtmlErrorResponse(code, it.ctx) +} + +func (it *ResponseFactory) Redirect(url string) *RedirectResponse { + return NewRedirectResponse(url, it.ctx) +} diff --git a/pink_fox_app/src/app/http_server/response.go b/pink_fox_app/src/app/http_server/response.go index 006b9a9..b9b4078 100644 --- a/pink_fox_app/src/app/http_server/response.go +++ b/pink_fox_app/src/app/http_server/response.go @@ -1,6 +1,7 @@ package http_server import ( + "fmt" "github.com/gin-gonic/gin" "net/http" ) @@ -21,3 +22,46 @@ func NewStringResponse(s string, ctx *gin.Context) *StringResponse { func (it *StringResponse) Render() { it.ctx.String(http.StatusOK, it.str) } + +type HtmlErrorResponse struct { + code int + msg string + ctx *gin.Context +} + +func NewHtmlErrorResponse(code int, ctx *gin.Context) *HtmlErrorResponse { + return &HtmlErrorResponse{code: code, ctx: ctx} +} + +func (it *HtmlErrorResponse) Msg(msg string) *HtmlErrorResponse { + it.msg = msg + return it +} + +func (it *HtmlErrorResponse) Render() { + message := "" + if it.msg != "" { + message = fmt.Sprintf("
%s
", it.msg) + } + it.ctx.Header("Content-Type", "text/html; charset=utf-8") + it.ctx.String(it.code, fmt.Sprintf("%s\n\n%s\n%s\n", err.String(), debugStack, strings.Join(err.GetTrace(), "\n"))
+ } else {
+ message = fmt.Sprintf("%s\n\n%s\n", err.String(), strings.Join(err.GetTrace(), "\n"))
+ }
+ }
+ ctx.Header("Content-Type", "text/html; charset=utf-8")
+ ctx.String(500, fmt.Sprintf("500 %s
\n%s\n", http.StatusText(500), message))
+}
diff --git a/pink_fox_app/src/controllers/Go.go b/pink_fox_app/src/controllers/Go.go
new file mode 100644
index 0000000..0827838
--- /dev/null
+++ b/pink_fox_app/src/controllers/Go.go
@@ -0,0 +1,33 @@
+package controllers
+
+import (
+ "fmt"
+ "pink_fox/src/app/http_server"
+ le "pink_fox/src/app/lerror"
+ "pink_fox/src/repositories"
+)
+
+type GoController struct {
+}
+
+func NewGoController() *GoController {
+ return &GoController{}
+}
+
+type IndexActionDependence interface {
+ MakeRequest() *http_server.Request
+ MakeResponseFactory() *http_server.ResponseFactory
+ MakeLinksRepository() repositories.LinksRepository
+}
+
+func (it *GoController) IndexAction(depend IndexActionDependence) (http_server.Response, *le.Error) {
+ token := depend.MakeRequest().Param("token")
+ link, err := depend.MakeLinksRepository().ByToken(token)
+ if err != nil {
+ return nil, err.Tap()
+ } else if link == nil {
+ return depend.MakeResponseFactory().HtmlError(404), nil
+ }
+
+ return depend.MakeResponseFactory().String(fmt.Sprintf("Ссылка %d, token \"%s\" to \"%v\", дата %v", link.ID, link.Token, link.Url, link.CreatedAt)), nil
+}
diff --git a/pink_fox_app/src/controllers/Index.go b/pink_fox_app/src/controllers/Index.go
index 993fcc6..3dbd1a0 100644
--- a/pink_fox_app/src/controllers/Index.go
+++ b/pink_fox_app/src/controllers/Index.go
@@ -2,22 +2,23 @@ package controllers
import (
"pink_fox/src/app/http_server"
+ le "pink_fox/src/app/lerror"
)
type IndexController struct {
responseFactory *http_server.ResponseFactory
}
-type IndexControllerContainer interface {
+type IndexControllerDependence interface {
MakeResponseFactory() *http_server.ResponseFactory
}
-func NewIndexController(di IndexControllerContainer) *IndexController {
+func NewIndexController(depend IndexControllerDependence) *IndexController {
return &IndexController{
- responseFactory: di.MakeResponseFactory(),
+ responseFactory: depend.MakeResponseFactory(),
}
}
-func (it *IndexController) ActionIndex() http_server.Response {
- return it.responseFactory.String("Hello world!")
+func (it *IndexController) IndexAction() (http_server.Response, *le.Error) {
+ return it.responseFactory.String("Hello world!"), nil
}
diff --git a/pink_fox_app/src/dependence/container.go b/pink_fox_app/src/dependence/container.go
index 4122a81..349ec0d 100644
--- a/pink_fox_app/src/dependence/container.go
+++ b/pink_fox_app/src/dependence/container.go
@@ -4,6 +4,7 @@ import (
"github.com/gin-gonic/gin"
"pink_fox/src/app"
"pink_fox/src/app/http_server"
+ repo "pink_fox/src/repositories"
)
type Container struct {
@@ -21,6 +22,14 @@ func NewContainer(application *app.Application, context *gin.Context) *Container
func (it *Container) Close() {
}
-func (it *Container) MakeResponseFactory() *http_server.ResponseFactory {
- return nil
+func (it *Container) MakeRequest() *http_server.Request {
+ return http_server.NewRequest(it.context)
+}
+
+func (it *Container) MakeResponseFactory() *http_server.ResponseFactory {
+ return http_server.NewResponseFactory(it.context)
+}
+
+func (it *Container) MakeLinksRepository() repo.LinksRepository {
+ return repo.NewLinksRepository(it.application.Conn)
}
diff --git a/pink_fox_app/src/repositories/links.go b/pink_fox_app/src/repositories/links.go
new file mode 100644
index 0000000..e35aeaf
--- /dev/null
+++ b/pink_fox_app/src/repositories/links.go
@@ -0,0 +1,48 @@
+package repositories
+
+import (
+ "database/sql"
+ "errors"
+ le "pink_fox/src/app/lerror"
+ "time"
+)
+
+type LinkData struct {
+ ID int
+ Token string
+ Url string
+ CreatedAt time.Time
+ ExpiresAt time.Time
+}
+
+type LinksRepository interface {
+ ByToken(string) (*LinkData, *le.Error)
+}
+
+type LinksRepositoryImpl struct {
+ db *sql.DB
+}
+
+func NewLinksRepository(conn *sql.DB) *LinksRepositoryImpl {
+ return &LinksRepositoryImpl{
+ db: conn,
+ }
+}
+
+// FIXME нужно настроить время
+// время для го
+// время для postgres
+
+func (it *LinksRepositoryImpl) ByToken(token string) (*LinkData, *le.Error) {
+ var link LinkData
+ query := `SELECT id, token, url, created_at, expires_at FROM links WHERE token = $1`
+ err := it.db.QueryRow(query, token).Scan(&link.ID, &link.Token, &link.Url, &link.CreatedAt, &link.ExpiresAt)
+ if err != nil {
+ if errors.Is(err, sql.ErrNoRows) {
+ return nil, nil
+ } else {
+ return nil, le.New(err)
+ }
+ }
+ return &link, nil
+}
diff --git a/pink_fox_app/src/routes/registration.go b/pink_fox_app/src/routes/registration.go
index 5efde75..a9f302e 100644
--- a/pink_fox_app/src/routes/registration.go
+++ b/pink_fox_app/src/routes/registration.go
@@ -2,6 +2,7 @@ package routes
import (
"pink_fox/src/app/http_server"
+ le "pink_fox/src/app/lerror"
"pink_fox/src/app/routes_manager"
"pink_fox/src/controllers"
di "pink_fox/src/dependence"
@@ -9,8 +10,14 @@ import (
func RegistrationRoutes(r *routes_manager.RoutesManager) {
r.GET("/", IndexController)
+
+ r.GET("/link/:token", GoController)
}
-func IndexController(dic *di.Container) http_server.Response {
- return controllers.NewIndexController(dic).ActionIndex()
+func IndexController(depend *di.Container) (http_server.Response, *le.Error) {
+ return controllers.NewIndexController(depend).IndexAction()
+}
+
+func GoController(depend *di.Container) (http_server.Response, *le.Error) {
+ return controllers.NewGoController().IndexAction(depend)
}
diff --git a/services/postgres/init/01-databases.sql b/services/postgres/init/01-databases.sql
index 9c10394..acfd7dd 100644
--- a/services/postgres/init/01-databases.sql
+++ b/services/postgres/init/01-databases.sql
@@ -1 +1 @@
-SELECT 'CREATE DATABASE pink_fox_db with owner pink_fox' WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'pink_fox_db')\gexec
+CREATE DATABASE pink_fox_db with owner pink_fox;