Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/bin
/generated
/.idea
/mocks-generated

/cover.out
/cover.out.tmp
Expand Down
7 changes: 6 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,8 @@ bin-deps: .bin-deps
go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28.1 && \
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.2.0 && \
go install golang.org/x/tools/cmd/goimports@v0.19.0 && \
go install github.com/envoyproxy/protoc-gen-validate@v1.2.1
go install github.com/envoyproxy/protoc-gen-validate@v1.2.1 && \
go install go.uber.org/mock/mockgen@latest

.create-bin:
rm -rf ./bin
Expand All @@ -106,11 +107,15 @@ fast-generate: .generate
rm -rf ./docs/spec
mkdir -p ./docs/spec

rm -rf ./mocks-generated
mkdir ./mocks-generated

rm -rf ~/.easyp/

(PATH="$(PATH):$(LOCAL_BIN)" && $(EASYP_BIN) mod download && $(EASYP_BIN) generate)

$(GOIMPORTS_BIN) -w .
go generate ./...

build:
go mod tidy
Expand Down
170 changes: 170 additions & 0 deletions api/library/library.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
syntax = "proto3";

import "google/api/annotations.proto";
import "validate/validate.proto";
import "google/protobuf/timestamp.proto";

package library;

option go_package = "github.com/project/library/pkg/api/library;library";

service Library {
rpc AddBook(AddBookRequest) returns (AddBookResponse) {
option (google.api.http) = {
post: "/v1/library/book"
body: "*"
};
}

rpc UpdateBook(UpdateBookRequest) returns (UpdateBookResponse) {
option (google.api.http) = {
put: "/v1/library/book"
body: "*"
};
}

rpc GetBookInfo(GetBookInfoRequest) returns (GetBookInfoResponse) {
option (google.api.http) = {
get: "/v1/library/book/{id}"
};
}

rpc RegisterAuthor(RegisterAuthorRequest) returns (RegisterAuthorResponse) {
option (google.api.http) = {
post: "/v1/library/author"
body: "*"
};
}

rpc ChangeAuthorInfo(ChangeAuthorInfoRequest) returns (ChangeAuthorInfoResponse) {
option (google.api.http) = {
put: "/v1/library/author"
body: "*"
};
}

rpc GetAuthorInfo(GetAuthorInfoRequest) returns (GetAuthorInfoResponse) {
option (google.api.http) = {
get: "/v1/library/author/{id}"
};
}

rpc GetAuthorBooks(GetAuthorBooksRequest) returns (stream Book) {
option (google.api.http) = {
get: "/v1/library/author_books/{author_id}"
};
}
}

message Book {
string id = 1 [
(validate.rules).string.uuid = true
];

string name = 2;

repeated string author_id = 3 [
(validate.rules).repeated = {
min_items: 0,
items: {
string: {uuid: true}
}
}
];

google.protobuf.Timestamp created_at = 4;
google.protobuf.Timestamp updated_at = 5;
}

message AddBookRequest {
string name = 1;

repeated string author_ids = 2 [
(validate.rules).repeated = {
min_items: 0,
items: {
string: {uuid: true}
}
}
];
}

message AddBookResponse {
Book book = 1;
}

message UpdateBookRequest {
string id = 1 [
(validate.rules).string.uuid = true
];

string name = 2;

repeated string author_ids = 3 [
(validate.rules).repeated = {
min_items: 0,
items: {
string: {uuid: true}
}
}
];
}

message UpdateBookResponse {}

message GetBookInfoRequest {
string id = 1 [
(validate.rules).string.uuid = true
];
}

message GetBookInfoResponse {
Book book = 1;
}

message RegisterAuthorRequest {
string name = 1 [
(validate.rules).string = {
pattern: "^[A-Za-z0-9]+( [A-Za-z0-9]+)*$",
min_len: 1,
max_len: 512
}
];
}

message RegisterAuthorResponse {
string id = 1;
}

message ChangeAuthorInfoRequest {
string id = 1 [
(validate.rules).string.uuid = true
];
string name = 2 [
(validate.rules).string = {
pattern: "^[A-Za-z0-9]+( [A-Za-z0-9]+)*$",
min_len: 1,
max_len: 512
}
];
}

message ChangeAuthorInfoResponse {}


message GetAuthorInfoRequest {
string id = 1 [
(validate.rules).string.uuid = true
];
}

message GetAuthorInfoResponse {
string id = 1;
string name = 2;
}

message GetAuthorBooksRequest {
string author_id = 1 [
(validate.rules).string.uuid = true
];
}
2 changes: 1 addition & 1 deletion cmd/library/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
)

func main() {
cfg, err := config.NewConfig()
cfg, err := config.New()

if err != nil {
log.Fatalf("can not get application config: %s", err)
Expand Down
136 changes: 135 additions & 1 deletion config/config.go
Original file line number Diff line number Diff line change
@@ -1,18 +1,152 @@
package config

import (
"errors"
"fmt"
"net"
"net/url"
"os"
)

type (
Config struct {
GRPC
PG
}

GRPC struct {
Port string `env:"GRPC_PORT"`
GatewayPort string `env:"GRPC_GATEWAY_PORT"`
}

PG struct {
URL string
Host string `env:"POSTGRES_HOST"`
Port string `env:"POSTGRES_PORT"`
DB string `env:"POSTGRES_DB"`
User string `env:"POSTGRES_USER"`
Password string `env:"POSTGRES_PASSWORD"`
MaxConn string `env:"POSTGRES_MAX_CONN"`
}
)

var (
ErrGRPCPortNotSet = errors.New("GRPC_PORT environment variable not set")
ErrGRPCGatewayPortNotSet = errors.New("GRPC_GATEWAY_PORT environment variable not set")
ErrPostgresHostNotSet = errors.New("POSTGRES_HOST environment variable not set")
ErrPostgresPortNotSet = errors.New("POSTGRES_PORT environment variable not set")
ErrPostgresDBNotSet = errors.New("POSTGRES_DB environment variable not set")
ErrPostgresUserNotSet = errors.New("POSTGRES_USER environment variable not set")
ErrPostgresPasswordNotSet = errors.New("POSTGRES_PASSWORD environment variable not set")
ErrPostgresMaxConnNotSet = errors.New("POSTGRES_MAX_CONN environment variable not set")
)

func NewConfig() (*Config, error) {
func New() (*Config, error) {
cfg := &Config{}

if err := cfg.readGrpcPort(); err != nil {
return nil, err
}
if err := cfg.readGrpcGatewayPort(); err != nil {
return nil, err
}
if err := cfg.readPGHost(); err != nil {
return nil, err
}
if err := cfg.readPGUser(); err != nil {
return nil, err
}
if err := cfg.readPGPort(); err != nil {
return nil, err
}
if err := cfg.readPGPassword(); err != nil {
return nil, err
}
if err := cfg.readPGDB(); err != nil {
return nil, err
}
if err := cfg.readPGMaxConn(); err != nil {
return nil, err
}

cfg.PG.URL = fmt.Sprintf("postgres://%s:%s@%s/%s?sslmode=disable",
url.PathEscape(cfg.PG.User),
url.PathEscape(cfg.PG.Password),
net.JoinHostPort(cfg.PG.Host, cfg.PG.Port),
cfg.PG.DB,
)

return cfg, nil
}

func (config *Config) readGrpcPort() error {
var ok bool
config.GRPC.Port, ok = os.LookupEnv("GRPC_PORT")
if !ok || config.GRPC.Port == "" {
return ErrGRPCPortNotSet
}
return nil
}

func (config *Config) readGrpcGatewayPort() error {
var ok bool
config.GRPC.GatewayPort, ok = os.LookupEnv("GRPC_GATEWAY_PORT")
if !ok || config.GRPC.GatewayPort == "" {
return ErrGRPCGatewayPortNotSet
}
return nil
}

func (config *Config) readPGHost() error {
var ok bool
config.PG.Host, ok = os.LookupEnv("POSTGRES_HOST")
if !ok || config.PG.Host == "" {
return ErrPostgresHostNotSet
}
return nil
}

func (config *Config) readPGPort() error {
var ok bool
config.PG.Port, ok = os.LookupEnv("POSTGRES_PORT")
if !ok || config.PG.Port == "" {
return ErrPostgresPortNotSet
}
return nil
}

func (config *Config) readPGUser() error {
var ok bool
config.PG.User, ok = os.LookupEnv("POSTGRES_USER")
if !ok || config.PG.User == "" {
return ErrPostgresUserNotSet
}
return nil
}

func (config *Config) readPGDB() error {
var ok bool
config.PG.DB, ok = os.LookupEnv("POSTGRES_DB")
if !ok || config.PG.DB == "" {
return ErrPostgresDBNotSet
}
return nil
}

func (config *Config) readPGPassword() error {
var ok bool
config.PG.Password, ok = os.LookupEnv("POSTGRES_PASSWORD")
if !ok || config.PG.Password == "" {
return ErrPostgresPasswordNotSet
}
return nil
}

func (config *Config) readPGMaxConn() error {
var ok bool
config.PG.MaxConn, ok = os.LookupEnv("POSTGRES_MAX_CONN")
if !ok || config.PG.MaxConn == "" {
return ErrPostgresMaxConnNotSet
}
return nil
}
Loading