Feed следва
Освен самото добавяне на нови емисии към базата данни, потребителите могат да посочат кои емисии искат да следват. Това ще бъде важно по-късно, когато искаме да покажем на потребителите списък с публикации от емисиите, които следват.
Добавете поддръжка за следните крайни точки и актуализирайте крайната точка „създаване на канал“, както е посочено по-долу.
Какво е „следване на емисии“?
Последването на емисия е просто връзка между потребител и емисия. Това е връзка „много към много“, така че потребителят може да следва много емисии, а една емисия може да бъде следвана от много потребители.
Създаването на проследяване на емисия показва, че потребител вече следва емисия. Изтриването му е същото като „прекратяване на следенето“ на емисия.
Важно е да разберете, че ID
на проследяване на емисия не е същото като ID
на самата емисия. Всяка двойка потребител/емисия ще има уникален идентификатор за следване на емисия.
Създайте проследяване на емисия
Крайна точка: POST /v1/feed_follows
Изисква удостоверяване
Примерно тяло на заявката:
{ "feed_id": "4a82b372-b0e2-45e3-956a-b9b83358f86b" }
Примерно тяло на отговора:
{ "id": "c834c69e-ee26-4c63-a677-a977432f9cfa", "feed_id": "4a82b372-b0e2-45e3-956a-b9b83358f86b", "user_id": "0e4fecc6-1354-47b8-8336-2077b307b20e", "created_at": "2017-01-01T00:00:00Z", "updated_at": "2017-01-01T00:00:00Z" }
Решения:
-- name: CreateFeedFollow :one INSERT INTO feed_follows (id, feed_id, user_id, created_at, updated_at) VALUES ( $1, $2, $3, $4, $5 ) RETURNING *; -- +goose Up CREATE TABLE feed_follows ( id UUID NOT NULL PRIMARY KEY, feed_id UUID NOT NULL REFERENCES feeds(id) ON DELETE CASCADE, user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, created_at TIMESTAMP NOT NULL, updated_at TIMESTAMP NOT NULL, UNIQUE (user_id, feed_id) ); -- +goose Down DROP TABLE feed_follows; package main import ( "encoding/json" "fmt" "net/http" "time" "github.com/google/uuid" "github.com/lordmoma/blog-aggregator/internal/database" ) type feedRequest struct { Name string `json:"name"` URL string `json:"url"` } type feedResponse struct { ID uuid.UUID `json:"id"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` Name string `json:"name"` URL string `json:"url"` UserID uuid.UUID `json:"user_id"` } type feedFollowRequest struct { FeedID uuid.UUID `json:"feed_id"` } type feedFollowResponse struct { ID uuid.UUID `json:"id"` FeedID uuid.UUID `json:"feed_id"` UserID uuid.UUID `json:"user_id"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } func (apiCfg *apiConfig) createFeedHandler(w http.ResponseWriter, r *http.Request, user database.User) { var req feedRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { respondWithError(w, http.StatusBadRequest, "Couldn't decode parameters") return } params := database.CreateFeedParams{ ID: uuid.New(), CreatedAt: time.Now().UTC(), UpdatedAt: time.Now().UTC(), Name: req.Name, Url: req.URL, UserID: user.ID, } feed, err := apiCfg.DB.CreateFeed(r.Context(), params) if err != nil { fmt.Println(err) respondWithError(w, http.StatusInternalServerError, "Couldn't create user") return } respondWithJSON(w, http.StatusOK, feedResponse{ ID: feed.ID, CreatedAt: feed.CreatedAt, UpdatedAt: feed.UpdatedAt, Name: feed.Name, URL: feed.Url, UserID: feed.UserID, }) } func (apiCfg *apiConfig) getFeedHandler(w http.ResponseWriter, r *http.Request) { feed, err := apiCfg.DB.GetFeeds(r.Context()) if err != nil { respondWithError(w, http.StatusInternalServerError, "Couldn't get feeds") return } respondWithJSON(w, http.StatusOK, feed) } func (apiCfg *apiConfig) createFeedFollowHandler(w http.ResponseWriter, r *http.Request, user database.User) { var req feedFollowRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { respondWithError(w, http.StatusBadRequest, "Couldn't decode parameters") return } params := database.CreateFeedFollowParams{ ID: uuid.New(), FeedID: req.FeedID, UserID: user.ID, CreatedAt: time.Now().UTC(), UpdatedAt: time.Now().UTC(), } feedFollow, err := apiCfg.DB.CreateFeedFollow(r.Context(), params) if err != nil { respondWithError(w, http.StatusInternalServerError, "Couldn't create feed follow") return } respondWithJSON(w, http.StatusOK, feedFollowResponse{ ID: feedFollow.ID, FeedID: feedFollow.FeedID, UserID: feedFollow.UserID, CreatedAt: feedFollow.CreatedAt, UpdatedAt: feedFollow.UpdatedAt, }) }
Не забравяйте да проверите репото, ако сте се изгубили в структурата на кода:
https://github.com/LordMoMA/blog-aggregator
Изтриване на проследяване на емисия
Крайна точка: DELETE /v1/feed_follows/{feedFollowID}
-- name: DeleteFeedFollow :exec DELETE FROM feed_follows WHERE id = $1; func (apiCfg *apiConfig) deleteFeedFollowHandler(w http.ResponseWriter, r *http.Request, user database.User) { idString := chi.URLParam(r, "feedFollowID") fmt.Println(idString) id, err := uuid.Parse(idString) if err != nil { respondWithError(w, http.StatusBadRequest, "Couldn't parse feed_follow id") return } if err := apiCfg.DB.DeleteFeedFollow(r.Context(), id); err != nil { respondWithError(w, http.StatusInternalServerError, "Couldn't delete feed follow") return } respondWithJSON(w, http.StatusOK, nil) }
Вземете всички последващи емисии за потребител
Крайна точка: GET /v1/feed_follows
Изисква удостоверяване
Примерен отговор:
[ { "id": "c834c69e-ee26-4c63-a677-a977432f9cfa", "feed_id": "4a82b372-b0e2-45e3-956a-b9b83358f86b", "user_id": "0e4fecc6-1354-47b8-8336-2077b307b20e", "created_at": "2017-01-01T00:00:00Z", "updated_at": "2017-01-01T00:00:00Z" }, { "id": "ad752167-f509-4ff3-8425-7781090b5c8f", "feed_id": "f71b842d-9fd1-4bc0-9913-dd96ba33bb15", "user_id": "0e4fecc6-1354-47b8-8336-2077b307b20e", "created_at": "2017-01-01T00:00:00Z", "updated_at": "2017-01-01T00:00:00Z" } ]
Решения:
-- name: CreateFeedFollow :one INSERT INTO feed_follows (id, feed_id, user_id, created_at, updated_at) VALUES ( $1, $2, $3, $4, $5 ) RETURNING *; -- name: DeleteFeedFollow :exec DELETE FROM feed_follows WHERE id = $1; -- name: GetFeedFollows :many SELECT * FROM feed_follows WHERE user_id = $1; func (apiCfg *apiConfig) getFeedFollowHandler(w http.ResponseWriter, r *http.Request, user database.User) { feedFollow, err := apiCfg.DB.GetFeedFollows(r.Context(), user.ID) if err != nil { respondWithError(w, http.StatusInternalServerError, "Couldn't get feed follows") return } respondWithJSON(w, http.StatusOK, feedFollow) }
Автоматично създаване на емисия, следвайте, когато създавате емисия
Когато потребител създаде нова емисия, той трябва автоматично да следва тази емисия. Те, разбира се, могат да изберат да спрат да го следват по-късно, но трябва да е там по подразбиране.
Отговорът на тази крайна точка вече трябва да съдържа и двата обекта:
{ "feed": { the feed object }, "feed_follow": { the feed follow object } }
Решения:
Трябва да намерим къде отива логиката тук, ще се занимаваме ли с createFeedHandler
или createFeedFollowHandler
?
Знаем, че в нашата предишна реализация feed
и feed_follow
имат своя собствена response
структура. Сега искаме да върнем и двете структури едновременно, когато се случи POST
`http://localhost:8080/v1/feeds`.
Засега горната крайна точка връща само следния отговор и знаем, че идентификаторът в JSON е feed_id
За да върнем и двете структури, трябва да използваме това feed_id
, за да направим POST
`http://localhost:8080/v1 /feed_follows`.
{ "id": "4fe179ef-e32a-4897-b903-4eeaf6c900ef", "created_at": "2023-04-08T09:17:31.318138Z", "updated_at": "2023-04-08T09:17:31.318138Z", "name": "huahua", "url": "https://canada/index.xml", "user_id": "1540ce78-6d90-41c4-beae-58bf97c47fad" }
Сега логиката е по-ясна, имаме нужда от сделката с createFeedHandler
и използваме част от createFeedFollowHandler
вътре в нея.
type combineResponse struct { Feed feedResponse `json:"feed"` FeedFollow feedFollowResponse `json:"feed_follow"` } func (apiCfg *apiConfig) createFeedHandler(w http.ResponseWriter, r *http.Request, user database.User) { var req feedRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { respondWithError(w, http.StatusBadRequest, "Couldn't decode parameters") return } params := database.CreateFeedParams{ ID: uuid.New(), CreatedAt: time.Now().UTC(), UpdatedAt: time.Now().UTC(), Name: req.Name, Url: req.URL, UserID: user.ID, } feed, err := apiCfg.DB.CreateFeed(r.Context(), params) if err != nil { fmt.Println(err) respondWithError(w, http.StatusInternalServerError, "Couldn't create user") return } params2 := database.CreateFeedFollowParams{ ID: uuid.New(), FeedID: feed.ID, UserID: user.ID, CreatedAt: time.Now().UTC(), UpdatedAt: time.Now().UTC(), } feedFollow, err := apiCfg.DB.CreateFeedFollow(r.Context(), params2) if err != nil { respondWithError(w, http.StatusInternalServerError, "Couldn't create feed follow") return } respondWithJSON(w, http.StatusOK, combineResponse{ Feed: feedResponse{ ID: feed.ID, CreatedAt: feed.CreatedAt, UpdatedAt: feed.UpdatedAt, Name: feed.Name, URL: feed.Url, UserID: feed.UserID, }, FeedFollow: feedFollowResponse{ ID: feedFollow.ID, FeedID: feedFollow.FeedID, UserID: feedFollow.UserID, CreatedAt: feedFollow.CreatedAt, UpdatedAt: feedFollow.UpdatedAt, }, }) }