diff --git a/comments/Makefile b/comments/Makefile index e288495..5a99bc0 100644 --- a/comments/Makefile +++ b/comments/Makefile @@ -27,10 +27,12 @@ db-create-migration: $(BINDIR)/goose -dir $(MIGRATIONS_FOLDER) create -s $(n) sql db-migrate: - $(BINDIR)/goose -dir $(MIGRATIONS_FOLDER) postgres "$(LOCAL_DB_DSN)" up + $(BINDIR)/goose -dir $(MIGRATIONS_FOLDER) postgres "postgresql://$(PROD_USER)-1:$(PROD_PASS)-1@192.168.64.5:5438/comments_db?sslmode=disable" up + $(BINDIR)/goose -dir $(MIGRATIONS_FOLDER) postgres "postgresql://$(PROD_USER)-2:$(PROD_PASS)-2@192.168.64.5:5439/comments_db?sslmode=disable" up db-migrate-down: - $(BINDIR)/goose -dir $(MIGRATIONS_FOLDER) postgres "$(LOCAL_DB_DSN)" down + $(BINDIR)/goose -dir $(MIGRATIONS_FOLDER) postgres "postgresql://$(PROD_USER)-1:$(PROD_PASS)-1@192.168.64.5:5438/comments_db?sslmode=disable" down + $(BINDIR)/goose -dir $(MIGRATIONS_FOLDER) postgres "postgresql://$(PROD_USER)-2:$(PROD_PASS)-2@192.168.64.5:5439/comments_db?sslmode=disable" down db-reset-local: psql -c "drop database if exists \"$(LOCAL_DB_NAME)\"" diff --git a/comments/configs/values_local.yaml b/comments/configs/values_local.yaml index eafe322..becea89 100644 --- a/comments/configs/values_local.yaml +++ b/comments/configs/values_local.yaml @@ -7,14 +7,14 @@ service: http_port: 8086 db_shards: - - host: localhost - port: 5434 + - host: 192.168.64.5 + port: 5438 user: comments-user-1 password: comments-password-1 db_name: comments_db - - host: localhost - port: 5435 + - host: 192.168.64.5 + port: 5439 user: comments-user-2 password: comments-password-2 db_name: comments_db diff --git a/comments/db/migrations/00001_create_comments.sql b/comments/db/migrations/00001_create_comments.sql index 5113b4a..2e52b3c 100644 --- a/comments/db/migrations/00001_create_comments.sql +++ b/comments/db/migrations/00001_create_comments.sql @@ -1,6 +1,6 @@ -- +goose Up CREATE TABLE comments ( - id BIGSERIAL PRIMARY KEY, + id BIGINT PRIMARY KEY, user_id BIGINT NOT NULL, sku BIGINT NOT NULL, text VARCHAR(255) NOT NULL, @@ -12,5 +12,3 @@ CREATE INDEX user_id_idx ON comments(user_id, created_at DESC); -- +goose Down DROP TABLE comments; -DROP INDEX sku_idx; -DROP INDEX user_id_idx; \ No newline at end of file diff --git a/comments/infra/repository/sqlc/query.sql b/comments/infra/repository/sqlc/query.sql index e1fd8f1..20601e0 100644 --- a/comments/infra/repository/sqlc/query.sql +++ b/comments/infra/repository/sqlc/query.sql @@ -1,5 +1,5 @@ -- name: InsertComment :one -INSERT INTO comments (user_id, sku, text) VALUES ($1, $2, $3) +INSERT INTO comments (id, user_id, sku, text) VALUES ($1, $2, $3, $4) RETURNING id, user_id, sku, text, created_at; -- name: GetCommentByID :one diff --git a/comments/infra/repository/sqlc/query.sql.go b/comments/infra/repository/sqlc/query.sql.go index 601f3c9..d804178 100644 --- a/comments/infra/repository/sqlc/query.sql.go +++ b/comments/infra/repository/sqlc/query.sql.go @@ -27,18 +27,24 @@ func (q *Queries) GetCommentByID(ctx context.Context, id int64) (*Comment, error } const insertComment = `-- name: InsertComment :one -INSERT INTO comments (user_id, sku, text) VALUES ($1, $2, $3) +INSERT INTO comments (id, user_id, sku, text) VALUES ($1, $2, $3, $4) RETURNING id, user_id, sku, text, created_at ` type InsertCommentParams struct { + ID int64 UserID int64 Sku int64 Text string } func (q *Queries) InsertComment(ctx context.Context, arg *InsertCommentParams) (*Comment, error) { - row := q.db.QueryRow(ctx, insertComment, arg.UserID, arg.Sku, arg.Text) + row := q.db.QueryRow(ctx, insertComment, + arg.ID, + arg.UserID, + arg.Sku, + arg.Text, + ) var i Comment err := row.Scan( &i.ID, diff --git a/comments/infra/repository/sqlc/repository.go b/comments/infra/repository/sqlc/repository.go index c4266d2..c7cc404 100644 --- a/comments/infra/repository/sqlc/repository.go +++ b/comments/infra/repository/sqlc/repository.go @@ -3,6 +3,7 @@ package sqlc import ( "context" "errors" + "sync" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgxpool" @@ -15,21 +16,25 @@ import ( type commentsRepo struct { shard1 *pgxpool.Pool shard2 *pgxpool.Pool + + curID int64 + idMx sync.Mutex } func NewCommentsRepository(shard1, shard2 *pgxpool.Pool) *commentsRepo { return &commentsRepo{ shard1: shard1, shard2: shard2, + curID: 1, } } -func (r *commentsRepo) pickShard(sku int64) *pgxpool.Pool { +func (r *commentsRepo) pickShard(sku int64) (*pgxpool.Pool, int64) { if sku%2 == 0 { - return r.shard1 + return r.shard1, 0 } - return r.shard2 + return r.shard2, 1 } func mapComment(c *Comment) *entity.Comment { @@ -67,13 +72,19 @@ func (r *commentsRepo) GetCommentByID(ctx context.Context, id int64) (*entity.Co } func (r *commentsRepo) InsertComment(ctx context.Context, comment *entity.Comment) (*entity.Comment, error) { - shard := r.pickShard(comment.SKU) + shard, shardID := r.pickShard(comment.SKU) q := New(shard) + r.idMx.Lock() + id := r.curID*1000 + shardID + r.curID++ + r.idMx.Unlock() + req := &InsertCommentParams{ UserID: comment.UserID, Sku: comment.SKU, Text: comment.Text, + ID: id, } c, err := q.InsertComment(ctx, req) @@ -85,7 +96,7 @@ func (r *commentsRepo) InsertComment(ctx context.Context, comment *entity.Commen } func (r *commentsRepo) ListCommentsBySku(ctx context.Context, sku int64) ([]*entity.Comment, error) { - shard := r.pickShard(sku) + shard, _ := r.pickShard(sku) q := New(shard) list, err := q.ListCommentsBySku(ctx, sku) diff --git a/docker-compose.yaml b/docker-compose.yaml index 036daf9..e108035 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -5,57 +5,57 @@ volumes: shard2-data: {} services: - cart: - build: - context: . - dockerfile: cart/Dockerfile - ports: - - "8080:8080" - depends_on: - - product-service + # cart: + # build: + # context: . + # dockerfile: cart/Dockerfile + # ports: + # - "8080:8080" + # depends_on: + # - product-service - product-service: - image: gitlab-registry.ozon.dev/go/classroom-18/students/homework-draft/products:latest - ports: - - "8082:8082" + # product-service: + # image: gitlab-registry.ozon.dev/go/classroom-18/students/homework-draft/products:latest + # ports: + # - "8082:8082" - loms: - build: - context: . - dockerfile: loms/Dockerfile - depends_on: - postgres-master: - condition: service_started - init-kafka: - condition: service_completed_successfully - ports: - - "8083:8083" - - "8084:8084" - - "8085:8085" + # loms: + # build: + # context: . + # dockerfile: loms/Dockerfile + # depends_on: + # postgres-master: + # condition: service_started + # init-kafka: + # condition: service_completed_successfully + # ports: + # - "8083:8083" + # - "8084:8084" + # - "8085:8085" - notifier: - build: - context: . - dockerfile: notifier/Dockerfile - depends_on: - init-kafka: - condition: service_completed_successfully - deploy: - replicas: 3 + # notifier: + # build: + # context: . + # dockerfile: notifier/Dockerfile + # depends_on: + # init-kafka: + # condition: service_completed_successfully + # deploy: + # replicas: 3 - comments: - build: - context: . - dockerfile: comments/Dockerfile - depends_on: - postgres-comments-shard-1: - condition: service_started - postgres-comments-shard-2: - condition: service_started - ports: - - "8083:8083" - - "8084:8084" - - "8085:8085" + # comments: + # build: + # context: . + # dockerfile: comments/Dockerfile + # depends_on: + # postgres-comments-shard-1: + # condition: service_started + # postgres-comments-shard-2: + # condition: service_started + # ports: + # - "8083:8083" + # - "8084:8084" + # - "8085:8085" postgres-master: image: gitlab-registry.ozon.dev/go/classroom-18/students/base/postgres:16 @@ -98,7 +98,7 @@ services: POSTGRES_USER: comments-user-1 POSTGRES_PASSWORD: comments-password-1 ports: - - "5432:5432" + - "5438:5432" volumes: - shard1-data:/var/lib/postgresql/data @@ -109,7 +109,7 @@ services: POSTGRES_USER: comments-user-2 POSTGRES_PASSWORD: comments-password-2 ports: - - "5432:5432" + - "5439:5432" volumes: - shard2-data:/var/lib/postgresql/data diff --git a/docs/homework-8/comment.http b/docs/homework-8/comment.http new file mode 100644 index 0000000..d6b552b --- /dev/null +++ b/docs/homework-8/comment.http @@ -0,0 +1,593 @@ +########################## add + +### add valid comment +POST http://localhost:8086/comment/add +Content-Type: application/json + +{ + "user_id": 42, + "sku": 1004005, + "comment": "hello world" +} + +### Expect: +### { +### "id": "1001" +### } + + +### add invalid comment, miss user_id +POST http://localhost:8086/comment/add +Content-Type: application/json + +{ + "sku": 1004005, + "comment": "hello world" +} + +### Expect: +### { +### "code": 3, +### "message": "invalid CommentAddRequest.UserId: value must be greater than 0", +### "details": [] +### } + + +### add invalid comment, negative user_id +POST http://localhost:8086/comment/add +Content-Type: application/json + +{ + "user_id": -42, + "sku": 1004005, + "comment": "hello world" +} + +### Expect: +### { +### "code": 3, +### "message": "invalid CommentAddRequest.UserId: value must be greater than 0", +### "details": [] +### } + + +### add invalid comment, miss sku +POST http://localhost:8086/comment/add +Content-Type: application/json + +{ + "user_id": 42, + "comment": "hello world" +} + +### Expect: +### { +### "code": 3, +### "message": "invalid CommentAddRequest.Sku: value must be greater than 0", +### "details": [] +### } + + +### add invalid comment, negative sku +POST http://localhost:8086/comment/add +Content-Type: application/json + +{ + "user_id": 42, + "sku": -10, + "comment": "hello world" +} + +### Expect: +### { +### "code": 3, +### "message": "invalid CommentAddRequest.Sku: value must be greater than 0", +### "details": [] +### } + + +### add invalid comment, miss comment +POST http://localhost:8086/comment/add +Content-Type: application/json + +{ + "user_id": 42, + "sku": 1004005 +} + +### Expect: +### { +### "code": 3, +### "message": "invalid CommentAddRequest.Comment: value length must be between 1 and 255 runes, inclusive", +### "details": [] +### } + + +### add invalid comment, empty comment +POST http://localhost:8086/comment/add +Content-Type: application/json + +{ + "user_id": 42, + "sku": 1004005, + "comment": "" +} + + +### Expect: +### { +### "code": 3, +### "message": "invalid CommentAddRequest.Comment: value length must be between 1 and 255 runes, inclusive", +### "details": [] +### } + + +### add invalid comment, too large comment +POST http://localhost:8086/comment/add +Content-Type: application/json + +{ + "user_id": 42, + "sku": 1004005, + "comment": "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456" +} + +### Expect: +### { +### "code": 3, +### "message": "invalid CommentAddRequest.Comment: value length must be between 1 and 255 runes, inclusive", +### "details": [] +### } + + + + + +########################## get by id + +### set precondition BEGIN ### +### save comment_id from response + +### add valid comment +POST http://localhost:8086/comment/add +Content-Type: application/json + +{ + "user_id": 123, + "sku": 456005, + "comment": "hello world 123" +} + +### Expect: +### { +### "id": "8000" +### } + +### set precondition END ### + +### get valid, previously created, comment +POST http://localhost:8086/comment/get-by-id +Content-Type: application/json + +{ + "id": 8000 +} + +### Expect: +### { +### "id": "8000", +### "user_id": "123", +### "sku": "456005", +### "comment": "hello world 123", +### "created_at": "2025-01-13T17:15:17.252417Z" +### } + + +### get comment by invalid id (negative) +POST http://localhost:8086/comment/get-by-id +Content-Type: application/json + +{ + "id": -8000 +} + +### Expect: +### { +### "code": 3, +### "message": "invalid CommentGetByIDRequest.Id: value must be greater than 0", +### "details": [] +### } + + +### get comment by id, which not exists +POST http://localhost:8086/comment/get-by-id +Content-Type: application/json + +{ + "id": 404000 +} + +### Expect: +### { +### "code": 5, +### "message": "comment with id=404000 not found", +### "details": [] +### } + + + + + +########################## edit + +### edit valid comment +POST http://localhost:8086/comment/edit +Content-Type: application/json + +{ + "user_id": 42, + "comment_id": 1001, + "new_comment": "hello world changed" +} + +### Expect +### {} + + +### edit invalid comment, user is not an author +POST http://localhost:8086/comment/edit +Content-Type: application/json + +{ + "user_id": 555555, + "comment_id": 1001, + "new_comment": "hello world changed" +} + +### Expect +### { +### "code": 7, +### "message": "unable to edit comment: given user is not an author of modified comment", +### "details": [] +### } + + +### edit invalid comment, comment is not exists +POST http://localhost:8086/comment/edit +Content-Type: application/json + +{ + "user_id": 42, + "comment_id": 555551001, + "new_comment": "hello world changed" +} + +### Expect +### { +### "code": 5, +### "message": "editing comment is not found", +### "details": [] +### } + + +### edit invalid comment, miss user_id +POST http://localhost:8086/comment/edit +Content-Type: application/json + +{ + "comment_id": 1001, + "new_comment": "hello world changed" +} + +### Expect +### { +### "code": 3, +### "message": "invalid CommentEditRequest.UserId: value must be greater than 0", +### "details": [] +### } + + +### edit invalid comment, negative user_id +POST http://localhost:8086/comment/edit +Content-Type: application/json + +{ + "user_id": -42, + "comment_id": 1001, + "new_comment": "hello world changed" +} + +### Expect +### { +### "code": 3, +### "message": "invalid CommentEditRequest.UserId: value must be greater than 0", +### "details": [] +### } + + +### edit invalid comment, miss comment_id +POST http://localhost:8086/comment/edit +Content-Type: application/json + +{ + "user_id": 42, + "new_comment": "hello world changed" +} + +### Expect +### { +### "code": 3, +### "message": "invalid CommentEditRequest.CommentId: value must be greater than 0", +### "details": [] +### } + + +### edit invalid comment, negative comment_id +POST http://localhost:8086/comment/edit +Content-Type: application/json + +{ + "user_id": 42, + "comment_id": -1001, + "new_comment": "hello world changed" +} + +### Expect +### { +### "code": 3, +### "message": "invalid CommentEditRequest.CommentId: value must be greater than 0", +### "details": [] +### } + + +### edit invalid comment, miss new_comment +POST http://localhost:8086/comment/edit +Content-Type: application/json + +{ + "user_id": 42, + "comment_id": 1001 +} + +### Expect +### { +### "code": 3, +### "message": "invalid CommentEditRequest.NewComment: value length must be between 1 and 255 runes, inclusive", +### "details": [] +### } + + +### edit invalid comment, empty new_comment +POST http://localhost:8086/comment/edit +Content-Type: application/json + +{ + "user_id": 42, + "comment_id": 1001, + "new_comment": "" +} + +### Expect +### { +### "code": 3, +### "message": "invalid CommentEditRequest.NewComment: value length must be between 1 and 255 runes, inclusive", +### "details": [] +### } + + +### edit invalid comment, too long new_comment +POST http://localhost:8086/comment/edit +Content-Type: application/json + +{ + "user_id": 42, + "comment_id": 1001, + "new_comment": "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456" +} + +### Expect +### { +### "code": 3, +### "message": "invalid CommentEditRequest.NewComment: value length must be between 1 and 255 runes, inclusive", +### "details": [] +### } + + +### edit valid comment, but editable time expired (CHANGE EditInterval!!!) +POST http://localhost:8086/comment/edit +Content-Type: application/json + +{ + "user_id": 42, + "comment_id": 1001, + "new_comment": "hello world changed 3" +} + +### Expect +### { +### "code": 9, +### "message": "unable to edit comment: editing time interval expired", +### "details": [] +### } + + + + + +########################## list by sku + +### set precondition BEGIN ### + +### add valid comment 1 +POST http://localhost:8086/comment/add +Content-Type: application/json + +{"user_id": 71, "sku": 123, "comment": "hello world 1"} + +### add valid comment 2 +POST http://localhost:8086/comment/add +Content-Type: application/json + +{"user_id": 71, "sku": 123, "comment": "hello world 2"} + +### add valid comment 3 +POST http://localhost:8086/comment/add +Content-Type: application/json + +{"user_id": 71, "sku": 123, "comment": "hello world 3"} + +### set precondition END ### + +### list comments by sku +POST http://localhost:8086/comment/list-by-sku +Content-Type: application/json + +{"sku": 123} + +### Expected (order created_at DESC, IMPORTANT!!!): +### { +### "comments": [ +### { +### "id": "5001", +### "user_id": "71", +### "comment": "hello world 3", +### "created_at": "2024-12-24T22:32:54.301507Z" +### }, +### { +### "id": "4001", +### "user_id": "71", +### "comment": "hello world 2", +### "created_at": "2024-12-24T22:32:52.656693Z" +### }, +### { +### "id": "3001", +### "user_id": "71", +### "comment": "hello world 1", +### "created_at": "2024-12-24T22:32:50.302224Z" +### } +### ] +### } + + +### list comments by sku, sku have no comments yet +POST http://localhost:8086/comment/list-by-sku +Content-Type: application/json + +{"sku": 444444} + +### Expected: +### { +### "comments": [] +### } + + +### list comments by sku, invalid, miss sku +POST http://localhost:8086/comment/list-by-sku +Content-Type: application/json + +{} + +### Expected: +### { +### "code": 3, +### "message": "invalid CommentListBySKURequest.Sku: value must be greater than 0", +### "details": [] +### } + + +### list comments by sku, invalid, negative sku +POST http://localhost:8086/comment/list-by-sku +Content-Type: application/json + +{ + "sku": -1234 +} + +### Expected: +### { +### "code": 3, +### "message": "invalid CommentListBySKURequest.Sku: value must be greater than 0", +### "details": [] +### } + + + + + +########################## list by user +### set precondition BEGIN ### + +### add valid comment 1 +POST http://localhost:8086/comment/add +Content-Type: application/json + +{"user_id": 54, "sku": 789, "comment": "comment #1 54-789 (shard-2)"} + +### add valid comment 1 +POST http://localhost:8086/comment/add +Content-Type: application/json + +{"user_id": 54, "sku": 456, "comment": "comment #1 54-456 (shard-1)"} + +### add valid comment 2 +POST http://localhost:8086/comment/add +Content-Type: application/json + +{"user_id": 54, "sku": 220, "comment": "comment #2 54-220 (shard-1)"} + +### add valid comment 3 +POST http://localhost:8086/comment/add +Content-Type: application/json + +{"user_id": 54, "sku": 567, "comment": "comment #2 54-567 (shard-2)"} + +### set precondition END ### + + +### list comments by user +POST http://localhost:8086/comment/list-by-user +Content-Type: application/json + +{"user_id": 54} + +### Expected (order by created_at DESC, IMPORTANT!!!): +### { +### "comments": [ +### { +### "id": "13001", +### "sku": "567", +### "comment": "comment #2 54-567 (shard-2)", +### "created_at": "2024-12-24T22:45:52.284861Z" +### }, +### { +### "id": "5000", +### "sku": "220", +### "comment": "comment #2 54-220 (shard-1)", +### "created_at": "2024-12-24T22:45:49.050342Z" +### }, +### { +### "id": "4000", +### "sku": "456", +### "comment": "comment #1 54-456 (shard-1)", +### "created_at": "2024-12-24T22:45:45.511675Z" +### }, +### { +### "id": "12001", +### "sku": "789", +### "comment": "comment #1 54-789 (shard-2)", +### "created_at": "2024-12-24T22:45:39.449233Z" +### } +### ] +### } + + +### list comments by user, no comments for user +POST http://localhost:8086/comment/list-by-user +Content-Type: application/json + +{"user_id": 32423423} + +### Expected: +### { +### "comments": [] +### }