From 9f11fbd8ae323b2ab0732014f83b6cfde23de628 Mon Sep 17 00:00:00 2001 From: Andrey Ivanov Date: Thu, 7 Jan 2021 15:33:06 -0500 Subject: [PATCH] Core app --- .gitignore | 3 ++ README.md | 42 ++++++++++++++++- cmd/main.go | 73 +++++++++++++++++++++++++++++ go.mod | 18 +++++++ go.sum | 60 ++++++++++++++++++++++++ internal/application/application.go | 47 +++++++++++++++++++ internal/handlers/handlers.go | 59 +++++++++++++++++++++++ internal/models/config.go | 19 ++++++++ internal/models/user.go | 58 +++++++++++++++++++++++ templates/404.tmpl | 0 templates/index.tmpl | 8 ++++ templates/list.tmpl | 0 templates/login.tmpl | 11 +++++ templates/signin.tmpl | 0 14 files changed, 397 insertions(+), 1 deletion(-) create mode 100644 cmd/main.go create mode 100644 go.mod create mode 100644 go.sum create mode 100755 internal/application/application.go create mode 100755 internal/handlers/handlers.go create mode 100755 internal/models/config.go create mode 100644 internal/models/user.go create mode 100644 templates/404.tmpl create mode 100644 templates/index.tmpl create mode 100644 templates/list.tmpl create mode 100644 templates/login.tmpl create mode 100644 templates/signin.tmpl diff --git a/.gitignore b/.gitignore index 66fd13c..717cb7b 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,6 @@ # Dependency directories (remove the comment below to include it) # vendor/ +.idea +application.conf +bin \ No newline at end of file diff --git a/README.md b/README.md index a215b3a..548660f 100644 --- a/README.md +++ b/README.md @@ -1 +1,41 @@ -# OTUS_HighLoad \ No newline at end of file +# Заготовка для социальной сети +Цель: В результате выполнения ДЗ вы создадите базовый скелет социальной сети, который будет развиваться в дальнейших ДЗ. + +###В данном задании тренируются навыки: +- декомпозиции предметной области; +- построения элементарной архитектуры проекта + Требуется разработать создание и просмотр анект в социальной сети. + +###Функциональные требования: +- Авторизация по паролю. +- Страница регистрации, где указывается следующая информация: + - Имя + - Фамилия + - Возраст + - Пол + - Интересы + - Город +- Страницы с анкетой. + +###Нефункциональные требования: +- Любой язык программирования +- В качестве базы данных использовать MySQL +- Не использовать ORM +- Программа должна представлять из себя монолитное приложение. +- Не рекомендуется использовать следующие технологии: + - Репликация + - Шардинг + - Индексы + - Кэширование + +Верстка не важна. Подойдет самая примитивная. +Разместить приложение на любом хостинге. Например, heroku. + +ДЗ принимается в виде исходного кода на github и демонстрации проекта на хостинге. + +Критерии оценки: Оценка происходит по принципу зачет/незачет. + +###Требования: +- Есть возможность регистрации, создавать персональные страницы, возможность подружиться, список друзей. +- Отсутствуют SQL-инъекции. +- Пароль хранится безопасно. \ No newline at end of file diff --git a/cmd/main.go b/cmd/main.go new file mode 100644 index 0000000..10ce156 --- /dev/null +++ b/cmd/main.go @@ -0,0 +1,73 @@ +package main + +import ( + "github.com/codegangsta/martini" + "github.com/codegangsta/martini-contrib/binding" + "github.com/codegangsta/martini-contrib/render" + "github.com/codegangsta/martini-contrib/sessionauth" + "github.com/codegangsta/martini-contrib/sessions" + _ "github.com/go-sql-driver/mysql" + "github.com/tiburon-777/OTUS_HighLoad/internal/application" + "github.com/tiburon-777/OTUS_HighLoad/internal/handlers" + "github.com/tiburon-777/OTUS_HighLoad/internal/models" + "log" + "net" + "net/http" + "os" + "time" +) + +func init() { + http.DefaultClient.Timeout = time.Second * 30 +} + +func main() { + log.Println("Starting...") + app, err := application.New("application.conf", "APP") + if err != nil{ + panic(err.Error()) + } + + m := martini.Classic() + + m.Map(log.New(os.Stdout, "[app]", log.Lshortfile)) + m.Map(app) + m.Use(sessions.Sessions("app", sessions.NewCookieStore([]byte("BfyfgIyngIOUgmOIUgt87thrg5RHn78b")))) + m.Use(sessionauth.SessionUser(models.GenerateAnonymousUser)) + m.Use(render.Renderer(render.Options{ + Directory: "templates", + Extensions: []string{".tmpl"}, + })) + + sessionauth.RedirectUrl = "/login" + sessionauth.RedirectParam = "next" + + m.Get("/404", func(r render.Render) { + r.HTML(200, "404", nil) + }) + m.Get("/login", func(r render.Render) { + r.HTML(200, "login", nil) + }) + m.Post("/login", binding.Bind(models.UserModel{}), handlers.PostLogin) + + m.Get("/logout", sessionauth.LoginRequired, func(session sessions.Session, user sessionauth.User, r render.Render) { + sessionauth.Logout(session, user) + r.Redirect("/") + }) + + // Регистрация пользователя, после которой нас перебрасывает на страницу логина + m.Get("/signup", handlers.GetSigned) + m.Post("/signup", handlers.PostSigned) + + //Анкета текущего пользователя + m.Get("/", sessionauth.LoginRequired, handlers.GetHome) + + m.Get("/list", sessionauth.LoginRequired, handlers.GetUserList) + + m.NotFound(func(r render.Render) { + r.HTML(404, "404", nil) + }) + if err := http.ListenAndServe(net.JoinHostPort(app.Config.Server.Address, app.Config.Server.Port), m); err != nil { + log.Fatalln(err.Error()) + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..3a2d553 --- /dev/null +++ b/go.mod @@ -0,0 +1,18 @@ +module github.com/tiburon-777/OTUS_HighLoad + +go 1.15 + +require ( + github.com/BurntSushi/toml v0.3.1 + github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0 // indirect + github.com/codegangsta/martini v0.0.0-20170121215854-22fa46961aab + github.com/codegangsta/martini-contrib v0.0.0-20140208234550-8ce6181c2609 + github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab // indirect + github.com/go-sql-driver/mysql v1.5.0 + github.com/gorilla/context v1.1.1 // indirect + github.com/gorilla/sessions v1.2.1 // indirect + github.com/martini-contrib/cors v0.0.0-20141016003011-553b9208d353 + github.com/stretchr/testify v1.6.1 + github.com/tiburon-777/modules v0.0.0-20201210103219-a0362a8da783 + gopkg.in/natefinch/lumberjack.v2 v2.0.0 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..cecdf1b --- /dev/null +++ b/go.sum @@ -0,0 +1,60 @@ +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= +github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= +github.com/amitrai48/logger v0.0.0-20190214092904-448001c055ec/go.mod h1:RZEHP3cxXvQlMuMjkpdh6qXA4b0CpjxnUBNxOpR0r30= +github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0 h1:sDMmm+q/3+BukdIpxwO365v/Rbspp2Nt5XntgQRXq8Q= +github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM= +github.com/codegangsta/martini v0.0.0-20170121215854-22fa46961aab h1:eFEFkK1s2OXNiIOvjNT5lk/AGXQodN2l8VgbkCK9r4c= +github.com/codegangsta/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:0SkifPRh0YknjZR6LxtP+eMvgPwUI6DG8hE9U/3dW9E= +github.com/codegangsta/martini-contrib v0.0.0-20140208234550-8ce6181c2609 h1:aRxx5sQikIjKQPDVpYbEjaUSrj58MM5kfxX2wS9nNNQ= +github.com/codegangsta/martini-contrib v0.0.0-20140208234550-8ce6181c2609/go.mod h1:Hr/9ecwnTZD7izDjg7HoB5wOJ01ZbNo+ntSTza2hqR4= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab h1:xveKWz2iaueeTaUgdetzel+U7exyigDYBryyVfV/rZk= +github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8= +github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= +github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= +github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= +github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI= +github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/lib/pq v1.9.0 h1:L8nSXQQzAYByakOFMTwpjRoHsMJklur4Gi59b6VivR8= +github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/martini-contrib/cors v0.0.0-20141016003011-553b9208d353 h1:kXDXsKbuZYwJXBVeLT49PwPvAfQg74/8mOZUqrfV3cg= +github.com/martini-contrib/cors v0.0.0-20141016003011-553b9208d353/go.mod h1:ZQqqR+M04limsCsRirtK4IAN2ioLKKNxhvl7f0U40yw= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/tiburon-777/modules v0.0.0-20201210103219-a0362a8da783 h1:Tf+F2s2H8NaCGTREuGFdzZgPRfUfPpjHloLgmd0iWiI= +github.com/tiburon-777/modules v0.0.0-20201210103219-a0362a8da783/go.mod h1:D0rjn8UFKInEmFmu/OCWCVLKJyVCHR7khJE1P0NO+Ao= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= +gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/application/application.go b/internal/application/application.go new file mode 100755 index 0000000..61c859c --- /dev/null +++ b/internal/application/application.go @@ -0,0 +1,47 @@ +package application + +import ( + "database/sql" + "fmt" + "github.com/codegangsta/martini" + "github.com/tiburon-777/OTUS_HighLoad/internal/models" + "github.com/tiburon-777/modules/core/config" +) + +type App struct { + Config *models.Configuration + DB *sql.DB + martini.ClassicMartini +} + +func New(configFile, envPrefix string) (App, error) { + conf, err := configure(configFile, envPrefix) + if err != nil{ + return App{}, fmt.Errorf("can't apply config: %w\n",err) + } + + db, err := sql.Open("mysql", conf.DSN.User+":"+conf.DSN.Pass+"@tcp("+conf.DSN.Host+":"+conf.DSN.Port+")/"+conf.DSN.Base) + if err != nil { + panic(err.Error()) + } + + return App{Config: conf, DB: db}, nil +} + +func configure(fileName string, envPrefix string) (*models.Configuration,error) { + var conf models.Configuration + s := config.New(&conf) + if fileName != "" { + fmt.Printf("try to apply config from file %s...\n", fileName) + if err := s.SetFromFile(fileName); err != nil { + return &models.Configuration{}, fmt.Errorf("can't apply config from file: %w", err) + } + } + if envPrefix != "" { + fmt.Printf("try to apply config from environment...\n") + if err := s.SetFromEnv(envPrefix); err != nil { + return &models.Configuration{}, fmt.Errorf("can't apply envvars to config:%w", err) + } + } + return &conf, nil +} \ No newline at end of file diff --git a/internal/handlers/handlers.go b/internal/handlers/handlers.go new file mode 100755 index 0000000..9f35d88 --- /dev/null +++ b/internal/handlers/handlers.go @@ -0,0 +1,59 @@ +package handlers + +import ( + "fmt" + "github.com/codegangsta/martini-contrib/render" + "github.com/codegangsta/martini-contrib/sessionauth" + "github.com/codegangsta/martini-contrib/sessions" + "github.com/tiburon-777/OTUS_HighLoad/internal/application" + "github.com/tiburon-777/OTUS_HighLoad/internal/models" + "net" + "net/http" +) + +func GetHome(r render.Render) { + doc := map[string]interface{}{ + "PageTitle": "Вы имеете доступ к проектам", + } + r.HTML(200, "index", doc) +} + +func GetSigned(r render.Render) { + doc := map[string]interface{}{ + "PageTitle": "page not exists", + } + r.HTML(200, "signin", doc) +} + +func PostSigned(app application.App, r render.Render) { + r.Redirect(net.JoinHostPort(app.Config.Server.Address, app.Config.Server.Port)+"/login") +} + + +func GetUserList(r render.Render) { + doc := map[string]interface{}{ + "PageTitle": "page not exists", + } + r.HTML(200, "list", doc) +} + +func PostLogin(app application.App, session sessions.Session, postedUser models.UserModel, r render.Render, req *http.Request) { + user := models.UserModel{} + query := fmt.Sprintf("SELECT * FROM users WHERE username=\"%s\" and password =\"%s\"", postedUser.Username, postedUser.Password) + err := app.DB.QueryRow(query).Scan(&user.Id, &user.Username, &user.Password) + + if err != nil || user.Id==0 { + r.Redirect(sessionauth.RedirectUrl) + return + } else { + err := sessionauth.AuthenticateSession(session, &user) + if err != nil { + r.JSON(500, err) + } + + params := req.URL.Query() + redirect := params.Get(sessionauth.RedirectParam) + r.Redirect(redirect) + return + } +} \ No newline at end of file diff --git a/internal/models/config.go b/internal/models/config.go new file mode 100755 index 0000000..acf38e1 --- /dev/null +++ b/internal/models/config.go @@ -0,0 +1,19 @@ +package models + +type Configuration struct { + Server Server + DSN DSN +} + +type Server struct { + Address string + Port string +} + +type DSN struct { + Host string + Port string + User string + Pass string + Base string +} \ No newline at end of file diff --git a/internal/models/user.go b/internal/models/user.go new file mode 100644 index 0000000..dd99ec4 --- /dev/null +++ b/internal/models/user.go @@ -0,0 +1,58 @@ +package models + +import ( + "database/sql" + "fmt" + "github.com/codegangsta/martini-contrib/sessionauth" + "time" +) + +type UserModel struct { + Id int64 `form:"id" db:"id"` + Username string `form:"name" db:"username"` + Password string `form:"password" db:"password"` + Name string `form:"name" db:"name"` + Surname string `form:"surname" db:"surname"` + BirthDate time.Time `form:"birthdate" db:"birthdate"` + Male bool `form:"male" db:"male"` + City string `form:"city" db:"city"` + Interests string `form:"interests" db:"interests"` + authenticated bool `form:"-" db:"-"` + Db *sql.DB +} + +func GenerateAnonymousUser() sessionauth.User { + return &UserModel{} +} + +func (u *UserModel) Login() { + // Update last login time + // Add to logged-in user's list + // etc ... + u.authenticated = true +} + +// Logout will preform any actions that are required to completely +// logout a user. +func (u *UserModel) Logout() { + // Remove from logged-in user's list + // etc ... + u.authenticated = false +} + +func (u *UserModel) IsAuthenticated() bool { + return u.authenticated +} + +func (u *UserModel) UniqueId() interface{} { + return u.Id +} + +func (u *UserModel) GetById(id interface{}) error { + query := fmt.Sprintf("SELECT username FROM users WHERE id=%d", id) + err := u.Db.QueryRow(query).Err() + if err != nil { + return err + } + return nil +} \ No newline at end of file diff --git a/templates/404.tmpl b/templates/404.tmpl new file mode 100644 index 0000000..e69de29 diff --git a/templates/index.tmpl b/templates/index.tmpl new file mode 100644 index 0000000..767208e --- /dev/null +++ b/templates/index.tmpl @@ -0,0 +1,8 @@ + + + +

This is a private link!

+

Hello {{ .Username }}

+ Logout
+ + \ No newline at end of file diff --git a/templates/list.tmpl b/templates/list.tmpl new file mode 100644 index 0000000..e69de29 diff --git a/templates/login.tmpl b/templates/login.tmpl new file mode 100644 index 0000000..8ba424a --- /dev/null +++ b/templates/login.tmpl @@ -0,0 +1,11 @@ + + + +

You must login!

+
+
+ + +
+ + \ No newline at end of file diff --git a/templates/signin.tmpl b/templates/signin.tmpl new file mode 100644 index 0000000..e69de29