ДЗ4 начато

main
Andrey Ivanov 2021-03-01 03:23:06 -05:00 committed by Andrey Ivanov
parent 4b6be61eea
commit f766c1dd6e
11 changed files with 373 additions and 166 deletions

View File

@ -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)
})

View File

@ -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
}

View File

@ -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)
}

View File

@ -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 {

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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("/")
}

31
templates/feed.tmpl Normal file
View File

@ -0,0 +1,31 @@
<!DOCTYPE html>
<html>
<head>
<style>
table {
border-collapse: collapse;
border: 1px solid black;
width: 100%;
background-color: azure;
}
table th {
border: 1px solid black;
background-color: aquamarine;
}
table td {
border: 1px solid black;
}
</style>
</head>
<body>
<h2>Feed of your friend's news</h2>
{{ range $post:=.posts }}
<a><b>{{ $post.Author }} wrote at {{ $post.Created }}:</b></a><br/>
<a><b>{{ $post.Subject }}</b><br/><a>{{ $post.Body }}</a>
<br/><br/>
{{ end }}
<h2>Available actions:</h2>
<input type="button" onclick="location.href='/';" value="Home" />
</body>
</html>

View File

@ -25,6 +25,12 @@
<p>You now live in <b>{{ .user.City }}</b></p>
<p>You interests is: <b>{{ .user.Interests }}</b></p>
<h2>Available actions:</h2>
<input type="button" onclick="location.href='/addPost';" value="Create Post" />
<input type="button" onclick="location.href='/feed';" value="Friend Feed" />
<input type="button" onclick="location.href='/list';" value="User list" />
<input type="button" onclick="location.href='/logout';" value="Logout" /><br />
<h2> You have friends:</h2>
<table>
<thead>
@ -49,8 +55,11 @@
</tbody>
</table>
<h2>Available actions:</h2>
<input type="button" onclick="location.href='/list';" value="User list" />
<input type="button" onclick="location.href='/logout';" value="Logout" /><br />
<h2> Your posts:</h2>
{{ range $post:=.posts }}
<a><b>{{ $post.Subject }}</b><br/><a>{{ $post.Body }}</a>
<br/><br/>
{{ end }}
</body>
</html>

23
templates/postadd.tmpl Normal file
View File

@ -0,0 +1,23 @@
<!DOCTYPE html>
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.10.3/moment.min.js"></script>
</head>
<body>
<h2>Create post</h2>
<p style="color: red;"><b>{{ .msg }}</b></p>
<form method="POST">
<table>
<tr>
<td>Тема</td>
<td><input type="text" name="subj" /></td>
</tr>
<tr>
<td>Текст</td>
<td><textarea rows="10" cols="45" name="body"></textarea></td>
</tr>
</table>
<button>Отправить</button>
</form>
</body>
</html>

View File

@ -7,7 +7,14 @@
- работа с очередями;
- проектирование масштабируемых архитектур.
Разработать ленту новостей. Создается отдельная страница, куда пишутся все обновления друзей. Для этого нужно хранить подписчиков. Лента формируется на уровне кешей. Формирование ленты производить через постановку задачи в очередь на часть подписчиков, чтобы избежать эффекта леди Гаги. В ленте держать последние 1000 обновлений друзей. Лента должна кешироваться.
# План выполнения:
1) Разработать страницу добавления поста.
2) Разработать ленту новостей, содержащую посты пользователей на которых подписан текущий пользователь.
- Создается отдельная страница, куда пишутся все обновления друзей. Для этого нужно хранить подписчиков.
- Лента формируется на уровне кешей.
- Формирование ленты производить через постановку задачи в очередь на часть подписчиков, чтобы избежать эффекта леди Гаги.
- В ленте держать последние 1000 обновлений друзей.
- Лента должна кешироваться.
ДЗ сдается в виде ссылки на гитлаб и демонстрации работающего проекта, развернутого в интернете.
Критерии оценки: Оценка происходит по принципу зачет/незачет.