From f766c1dd6e742683e5cae8a7c1886102d3aa4c05 Mon Sep 17 00:00:00 2001 From: Andrey Ivanov Date: Mon, 1 Mar 2021 03:23:06 -0500 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=974=20=D0=BD=D0=B0=D1=87=D0=B0=D1=82?= =?UTF-8?q?=D0=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmd/main.go | 7 +- internal/application/application.go | 12 ++ internal/handlers/friends.go | 48 +++++++ internal/handlers/handlers.go | 186 ++++----------------------- internal/handlers/postAdd.go | 29 +++++ internal/handlers/userList.go | 133 +++++++++++++++++++ internal/handlers/userSubscribing.go | 46 +++++++ templates/feed.tmpl | 31 +++++ templates/index.tmpl | 15 ++- templates/postadd.tmpl | 23 ++++ test/dz004/README.md | 9 +- 11 files changed, 373 insertions(+), 166 deletions(-) create mode 100644 internal/handlers/friends.go create mode 100644 internal/handlers/postAdd.go create mode 100644 internal/handlers/userList.go create mode 100644 internal/handlers/userSubscribing.go create mode 100644 templates/feed.tmpl create mode 100644 templates/postadd.tmpl diff --git a/cmd/main.go b/cmd/main.go index 8dd5c1b..6ab9dba 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -25,7 +25,7 @@ func init() { func main() { log.Println("Starting...") m := martini.Classic() - app, err := application.New("", "APP") + app, err := application.New("application.conf", "APP") if err != nil { log.Fatal(fmt.Errorf("can't build app: %w", err).Error()) } @@ -72,6 +72,11 @@ func main() { m.Get("/search", handlers.GetUserList) m.Post("/search", handlers.PostUserSearch) + m.Get("/addPost", auth.LoginRequired, handlers.GetAddPost) + m.Post("/addPost", auth.LoginRequired, handlers.PostAddPost) + + m.Get("/feed", auth.LoginRequired, handlers.GetFeed) + m.NotFound(func(r render.Render) { r.HTML(404, "404", nil) }) diff --git a/internal/application/application.go b/internal/application/application.go index e4b05f1..e00f3c6 100755 --- a/internal/application/application.go +++ b/internal/application/application.go @@ -84,6 +84,18 @@ func dbInit(db *sql.DB) error { ) ENGINE=InnoDB DEFAULT CHARSET=utf8`); err != nil { return err } + if _, err := db.Exec(`CREATE TABLE IF NOT EXISTS posts ( + Id INT(11) NOT NULL AUTO_INCREMENT, + Author INT(11) NULL DEFAULT NULL, + Created TIMESTAMP NULL DEFAULT NULL, + Subject VARCHAR(50) NULL DEFAULT NULL, + Body MEDIUMTEXT NULL DEFAULT NULL, + PRIMARY KEY (Id) USING BTREE, + INDEX AuthorID (author) USING BTREE, + CONSTRAINT AuthorID FOREIGN KEY (author) REFERENCES app.users (Id) ON UPDATE RESTRICT ON DELETE RESTRICT + ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_unicode_ci`); err != nil { + return err + } log.Println("All tables exists") return nil } diff --git a/internal/handlers/friends.go b/internal/handlers/friends.go new file mode 100644 index 0000000..02e8168 --- /dev/null +++ b/internal/handlers/friends.go @@ -0,0 +1,48 @@ +package handlers + +import ( + "github.com/codegangsta/martini-contrib/render" + "github.com/tiburon-777/OTUS_HighLoad/internal/application" + "github.com/tiburon-777/OTUS_HighLoad/internal/auth" + "time" +) + +func GetFeed(app application.App, r render.Render, user auth.User) { + h := user.(*auth.UserModel).BirthDate + user.(*auth.UserModel).YearsOld = int(time.Since(h).Hours() / 8760) + doc := make(map[string]interface{}) + doc["user"] = user.(*auth.UserModel) + var tmpTime string + var post Post + var posts []Post + var results, err = app.DBMaster.Query(`SELECT + posts.Id AS Id, + users.Username AS Author, + posts.Created AS Created, + posts.Subject AS Subject, + posts.Body AS Body + FROM + users JOIN relations JOIN posts + WHERE + relations.friendId=users.Id + AND posts.Author=relations.friendId + AND relations.userId=? + ORDER by Created DESC`, + user.(*auth.UserModel).Id) + if err != nil || results == nil { + err500("can't get feed from DB: ", err, r) + } + defer results.Close() + for results.Next() { + err = results.Scan(&post.Id, &post.Author, &tmpTime, &post.Subject, &post.Body) + if err != nil { + err500("can't scan result from DB: ", err, r) + } + post.Created = str2Time(tmpTime, r) + posts = append(posts, post) + } + doc["posts"] = posts + + r.HTML(200, "feed", doc) +} + diff --git a/internal/handlers/handlers.go b/internal/handlers/handlers.go index eb8d5b0..afb4e99 100755 --- a/internal/handlers/handlers.go +++ b/internal/handlers/handlers.go @@ -10,10 +10,17 @@ import ( "golang.org/x/crypto/bcrypt" "log" "net/http" - "strconv" "time" ) +type Post struct{ + Id int `db:"Id"` + Author string `db:"Author"` + Created time.Time `db:"Created"` + Subject string `db:"Subject"` + Body string `db:"Body"` +} + func GetHome(app application.App, r render.Render, user auth.User) { h := user.(*auth.UserModel).BirthDate user.(*auth.UserModel).YearsOld = int(time.Since(h).Hours() / 8760) @@ -51,6 +58,23 @@ func GetHome(app application.App, r render.Render, user auth.User) { } doc["table"] = users + var post Post + var posts []Post + results, err = app.DBMaster.Query(`SELECT Id, Created, Subject, Body FROM posts WHERE Author=? ORDER BY Created DESC;`, user.(*auth.UserModel).Id) + if err != nil || results == nil { + err500("can't get user list from DB: ", err, r) + } + defer results.Close() + for results.Next() { + err = results.Scan(&post.Id, &tmpTime, &post.Subject, &post.Body) + if err != nil { + err500("can't scan result from DB: ", err, r) + } + post.Created = str2Time(tmpTime, r) + posts = append(posts, post) + } + doc["posts"] = posts + r.HTML(200, "index", doc) } @@ -96,129 +120,6 @@ func PostSignup(app application.App, postedUser auth.UserModel, r render.Render) r.Redirect("/login") } -func GetUserList(app application.App, r render.Render) { - doc := make(map[string]interface{}) - doc["UsersFound"] = 0 - var tmp int - if err := app.DBMaster.QueryRow(`SELECT COUNT(*) FROM users`).Scan(&tmp); err != nil { - err500("can't get total of user profiles from DB: ", err, r) - } - doc["UsersTotal"] = tmp - r.HTML(200, "list", doc) -} - -func PostUserList(app application.App, user auth.User, r render.Render, req *http.Request) { - postName := req.FormValue("name") - postSurname := req.FormValue("surname") - doc := make(map[string]interface{}) - doc["user"] = user.(*auth.UserModel) - var users []auth.UserModel - var tmp auth.UserModel - var tmpTime string - var results, err = app.DBMaster.Query(`SELECT - users.id as id, - users.name as name, - users.surname as surname, - users.birthdate as birthdate, - users.gender as gender, - users.city as city - FROM - users - WHERE - NOT users.id=? - AND users.id NOT IN ( - SELECT - relations.friendId - FROM - relations - WHERE - relations.userId=?) - AND ( users.Name LIKE concat(?, '%') AND users.Surname LIKE concat(?, '%') )`, - user.(*auth.UserModel).Id, - user.(*auth.UserModel).Id, - postName, - postSurname, - ) - if err != nil || results == nil { - err500("can't get user list from DB: ", err, r) - } - defer results.Close() - for results.Next() { - err = results.Scan(&tmp.Id, &tmp.Name, &tmp.Surname, &tmpTime, &tmp.Gender, &tmp.City) - if err != nil { - err500("can't scan result from DB: ", err, r) - } - tmp.BirthDate = str2Time(tmpTime, r) - tmp.YearsOld = int(time.Since(tmp.BirthDate).Hours() / 8760) - users = append(users, tmp) - if len(users) >= 100 { - doc["msg"] = "( Too much rows in result. We will display only the first 100. )" - break - } - } - doc["table"] = users - doc["UsersFound"] = len(users) - var uTotal int - if err := app.DBMaster.QueryRow(`SELECT COUNT(*) FROM users`).Scan(&uTotal); err != nil { - err500("can't get total of user profiles from DB: ", err, r) - } - doc["UsersTotal"] = uTotal - r.HTML(200, "list", doc) -} - -func PostUserSearch(app application.App, r render.Render, req *http.Request) { - db := app.DBMaster - if app.Config.DSN.Slave1!="" { - db = app.DBSlave1 - } - - postName := req.FormValue("name") - postSurname := req.FormValue("surname") - doc := make(map[string]interface{}) - var users []auth.UserModel - var tmp auth.UserModel - var tmpTime string - var results, err = db.Query(`SELECT - users.id as id, - users.name as name, - users.surname as surname, - users.birthdate as birthdate, - users.gender as gender, - users.city as city - FROM - users - WHERE - ( users.Name LIKE concat(?, '%') AND users.Surname LIKE concat(?, '%') )`, - postName, - postSurname, - ) - if err != nil || results == nil { - err500("can't get user list from DB: ", err, r) - } - defer results.Close() - for results.Next() { - err = results.Scan(&tmp.Id, &tmp.Name, &tmp.Surname, &tmpTime, &tmp.Gender, &tmp.City) - if err != nil { - err500("can't scan result from DB: ", err, r) - } - tmp.BirthDate = str2Time(tmpTime, r) - tmp.YearsOld = int(time.Since(tmp.BirthDate).Hours() / 8760) - users = append(users, tmp) - if len(users) >= 100 { - doc["msg"] = "( Too much rows in result. We will display only the first 100. )" - break - } - } - doc["table"] = users - doc["UsersFound"] = len(users) - var uTotal int - if err := db.QueryRow(`SELECT COUNT(*) FROM users`).Scan(&uTotal); err != nil { - err500("can't get total of user profiles from DB: ", err, r) - } - doc["UsersTotal"] = uTotal - r.HTML(200, "list", doc) -} - func PostLogin(app application.App, session sessions.Session, postedUser auth.UserModel, r render.Render, req *http.Request) { user := auth.UserModel{} err1 := app.DBMaster.QueryRow("SELECT id, password FROM users WHERE username=?", postedUser.Username).Scan(&user.Id, &user.Password) @@ -242,43 +143,6 @@ func PostLogin(app application.App, session sessions.Session, postedUser auth.Us } } -func GetSubscribe(app application.App, r render.Render, user auth.User, req *http.Request) { - sid, ok := req.URL.Query()["id"] - if !ok { - err500("can't parce URL query", nil, r) - } - did, err := strconv.Atoi(sid[0]) - if err != nil { - err500("can't convert URL query value: ", err, r) - } - _, err = app.DBMaster.Exec(`REPLACE INTO relations (userId, friendId) values (?, ?)`, user.(*auth.UserModel).Id, did) - if err != nil { - err500("can't create relation in DB: ", err, r) - } - _, err = app.DBMaster.Exec(`REPLACE INTO relations (userId, friendId) values (?, ?)`, did, user.(*auth.UserModel).Id) - if err != nil { - err500("can't create relation in DB: ", err, r) - } - r.Redirect("/list") -} - -func GetUnSubscribe(app application.App, r render.Render, user auth.User, req *http.Request) { - sid, ok := req.URL.Query()["id"] - if !ok { - err500("can't parce URL query", nil, r) - } - did, err := strconv.Atoi(sid[0]) - if err != nil { - err500("can't convert URL query value: ", err, r) - } - _, err = app.DBMaster.Exec(`DELETE FROM relations WHERE (userId,friendId) IN ((?, ?),(?, ?))`, user.(*auth.UserModel).Id, did, did, user.(*auth.UserModel).Id) - if err != nil { - err500("can't remove relation from DB: ", err, r) - } - r.Redirect("/") - -} - func str2Time(s string, r render.Render) time.Time { t, err := time.Parse("2006-01-02 15:04:05", s) if err != nil { diff --git a/internal/handlers/postAdd.go b/internal/handlers/postAdd.go new file mode 100644 index 0000000..2343cf8 --- /dev/null +++ b/internal/handlers/postAdd.go @@ -0,0 +1,29 @@ +package handlers + +import ( + "github.com/codegangsta/martini-contrib/render" + "github.com/tiburon-777/OTUS_HighLoad/internal/application" + "github.com/tiburon-777/OTUS_HighLoad/internal/auth" + "net/http" + "time" +) + +func GetAddPost(app application.App, r render.Render) { + r.HTML(200, "postadd", nil) +} + +func PostAddPost(app application.App, user auth.User, r render.Render, req *http.Request) { + postSubj := req.FormValue("subj") + postBody := req.FormValue("body") + var results, err = app.DBMaster.Query(`INSERT INTO posts (Author, Created, Subject, Body) VALUES ( + ?, ?, ?, ?)`, + user.(*auth.UserModel).Id, + time.Now().Format("2006-01-02 15:04:05"), + postSubj, + postBody, + ) + if err != nil || results == nil { + err500("can't add new post: ", err, r) + } + r.Redirect("/",302) +} diff --git a/internal/handlers/userList.go b/internal/handlers/userList.go new file mode 100644 index 0000000..1f0a44e --- /dev/null +++ b/internal/handlers/userList.go @@ -0,0 +1,133 @@ +package handlers + +import ( + "github.com/codegangsta/martini-contrib/render" + "github.com/tiburon-777/OTUS_HighLoad/internal/application" + "github.com/tiburon-777/OTUS_HighLoad/internal/auth" + "net/http" + "time" +) + +func GetUserList(app application.App, r render.Render) { + doc := make(map[string]interface{}) + doc["UsersFound"] = 0 + var tmp int + if err := app.DBMaster.QueryRow(`SELECT COUNT(*) FROM users`).Scan(&tmp); err != nil { + err500("can't get total of user profiles from DB: ", err, r) + } + doc["UsersTotal"] = tmp + r.HTML(200, "list", doc) +} + +func PostUserList(app application.App, user auth.User, r render.Render, req *http.Request) { + postName := req.FormValue("name") + postSurname := req.FormValue("surname") + doc := make(map[string]interface{}) + doc["user"] = user.(*auth.UserModel) + var users []auth.UserModel + var tmp auth.UserModel + var tmpTime string + var results, err = app.DBMaster.Query(`SELECT + users.id as id, + users.name as name, + users.surname as surname, + users.birthdate as birthdate, + users.gender as gender, + users.city as city + FROM + users + WHERE + NOT users.id=? + AND users.id NOT IN ( + SELECT + relations.friendId + FROM + relations + WHERE + relations.userId=?) + AND ( users.Name LIKE concat(?, '%') AND users.Surname LIKE concat(?, '%') )`, + user.(*auth.UserModel).Id, + user.(*auth.UserModel).Id, + postName, + postSurname, + ) + if err != nil || results == nil { + err500("can't get user list from DB: ", err, r) + } + defer results.Close() + for results.Next() { + err = results.Scan(&tmp.Id, &tmp.Name, &tmp.Surname, &tmpTime, &tmp.Gender, &tmp.City) + if err != nil { + err500("can't scan result from DB: ", err, r) + } + tmp.BirthDate = str2Time(tmpTime, r) + tmp.YearsOld = int(time.Since(tmp.BirthDate).Hours() / 8760) + users = append(users, tmp) + if len(users) >= 100 { + doc["msg"] = "( Too much rows in result. We will display only the first 100. )" + break + } + } + doc["table"] = users + doc["UsersFound"] = len(users) + var uTotal int + if err := app.DBMaster.QueryRow(`SELECT COUNT(*) FROM users`).Scan(&uTotal); err != nil { + err500("can't get total of user profiles from DB: ", err, r) + } + doc["UsersTotal"] = uTotal + r.HTML(200, "list", doc) +} + +func PostUserSearch(app application.App, r render.Render, req *http.Request) { + db := app.DBMaster + if app.Config.DSN.Slave1!="" { + db = app.DBSlave1 + } + + postName := req.FormValue("name") + postSurname := req.FormValue("surname") + doc := make(map[string]interface{}) + var users []auth.UserModel + var tmp auth.UserModel + var tmpTime string + var results, err = db.Query(`SELECT + users.id as id, + users.name as name, + users.surname as surname, + users.birthdate as birthdate, + users.gender as gender, + users.city as city + FROM + users + WHERE + ( users.Name LIKE concat(?, '%') AND users.Surname LIKE concat(?, '%') )`, + postName, + postSurname, + ) + if err != nil || results == nil { + err500("can't get user list from DB: ", err, r) + } + defer results.Close() + for results.Next() { + err = results.Scan(&tmp.Id, &tmp.Name, &tmp.Surname, &tmpTime, &tmp.Gender, &tmp.City) + if err != nil { + err500("can't scan result from DB: ", err, r) + } + tmp.BirthDate = str2Time(tmpTime, r) + tmp.YearsOld = int(time.Since(tmp.BirthDate).Hours() / 8760) + users = append(users, tmp) + if len(users) >= 100 { + doc["msg"] = "( Too much rows in result. We will display only the first 100. )" + break + } + } + doc["table"] = users + doc["UsersFound"] = len(users) + var uTotal int + if err := db.QueryRow(`SELECT COUNT(*) FROM users`).Scan(&uTotal); err != nil { + err500("can't get total of user profiles from DB: ", err, r) + } + doc["UsersTotal"] = uTotal + r.HTML(200, "list", doc) +} + diff --git a/internal/handlers/userSubscribing.go b/internal/handlers/userSubscribing.go new file mode 100644 index 0000000..6613220 --- /dev/null +++ b/internal/handlers/userSubscribing.go @@ -0,0 +1,46 @@ +package handlers + +import ( + "github.com/codegangsta/martini-contrib/render" + "github.com/tiburon-777/OTUS_HighLoad/internal/application" + "github.com/tiburon-777/OTUS_HighLoad/internal/auth" + "net/http" + "strconv" +) + +func GetSubscribe(app application.App, r render.Render, user auth.User, req *http.Request) { + sid, ok := req.URL.Query()["id"] + if !ok { + err500("can't parce URL query", nil, r) + } + did, err := strconv.Atoi(sid[0]) + if err != nil { + err500("can't convert URL query value: ", err, r) + } + _, err = app.DBMaster.Exec(`REPLACE INTO relations (userId, friendId) values (?, ?)`, user.(*auth.UserModel).Id, did) + if err != nil { + err500("can't create relation in DB: ", err, r) + } + _, err = app.DBMaster.Exec(`REPLACE INTO relations (userId, friendId) values (?, ?)`, did, user.(*auth.UserModel).Id) + if err != nil { + err500("can't create relation in DB: ", err, r) + } + r.Redirect("/list") +} + +func GetUnSubscribe(app application.App, r render.Render, user auth.User, req *http.Request) { + sid, ok := req.URL.Query()["id"] + if !ok { + err500("can't parce URL query", nil, r) + } + did, err := strconv.Atoi(sid[0]) + if err != nil { + err500("can't convert URL query value: ", err, r) + } + _, err = app.DBMaster.Exec(`DELETE FROM relations WHERE (userId,friendId) IN ((?, ?),(?, ?))`, user.(*auth.UserModel).Id, did, did, user.(*auth.UserModel).Id) + if err != nil { + err500("can't remove relation from DB: ", err, r) + } + r.Redirect("/") + +} diff --git a/templates/feed.tmpl b/templates/feed.tmpl new file mode 100644 index 0000000..71726a9 --- /dev/null +++ b/templates/feed.tmpl @@ -0,0 +1,31 @@ + + + + + + +

Feed of your friend's news

+ {{ range $post:=.posts }} + {{ $post.Author }} wrote at {{ $post.Created }}:
+ {{ $post.Subject }}
{{ $post.Body }} +

+ {{ end }} + +

Available actions:

+ + + \ No newline at end of file diff --git a/templates/index.tmpl b/templates/index.tmpl index 75cc10e..6948312 100644 --- a/templates/index.tmpl +++ b/templates/index.tmpl @@ -25,6 +25,12 @@

You now live in {{ .user.City }}

You interests is: {{ .user.Interests }}

+

Available actions:

+ + + +
+

You have friends:

@@ -49,8 +55,11 @@
-

Available actions:

- -
+

Your posts:

+ {{ range $post:=.posts }} + {{ $post.Subject }}
{{ $post.Body }} +

+ {{ end }} + \ No newline at end of file diff --git a/templates/postadd.tmpl b/templates/postadd.tmpl new file mode 100644 index 0000000..6e422e1 --- /dev/null +++ b/templates/postadd.tmpl @@ -0,0 +1,23 @@ + + + + + + +

Create post

+

{{ .msg }}

+
+ + + + + + + + + +
Тема
Текст
+ +
+ + \ No newline at end of file diff --git a/test/dz004/README.md b/test/dz004/README.md index 246208e..8900811 100644 --- a/test/dz004/README.md +++ b/test/dz004/README.md @@ -7,7 +7,14 @@ - работа с очередями; - проектирование масштабируемых архитектур. -Разработать ленту новостей. Создается отдельная страница, куда пишутся все обновления друзей. Для этого нужно хранить подписчиков. Лента формируется на уровне кешей. Формирование ленты производить через постановку задачи в очередь на часть подписчиков, чтобы избежать эффекта леди Гаги. В ленте держать последние 1000 обновлений друзей. Лента должна кешироваться. +# План выполнения: +1) Разработать страницу добавления поста. +2) Разработать ленту новостей, содержащую посты пользователей на которых подписан текущий пользователь. + - Создается отдельная страница, куда пишутся все обновления друзей. Для этого нужно хранить подписчиков. + - Лента формируется на уровне кешей. + - Формирование ленты производить через постановку задачи в очередь на часть подписчиков, чтобы избежать эффекта леди Гаги. + - В ленте держать последние 1000 обновлений друзей. + - Лента должна кешироваться. ДЗ сдается в виде ссылки на гитлаб и демонстрации работающего проекта, развернутого в интернете. Критерии оценки: Оценка происходит по принципу зачет/незачет.