From da7ebdee0d698ed54f416807f5aa4dda52371735 Mon Sep 17 00:00:00 2001 From: bear-brown-beard Date: Wed, 15 Jan 2025 19:53:32 +0500 Subject: [PATCH 1/8] 1th commit --- go.mod | 27 ++++++++ go.sum | 47 +++++++++++++ main.go | 177 +++++++++++++++++++++++++++++++++++++++++++++++++ parcel.go | 96 +++++++++++++++++++++++++++ parcel_test.go | 167 ++++++++++++++++++++++++++++++++++++++++++++++ tracker.db | Bin 0 -> 61440 bytes 6 files changed, 514 insertions(+) create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go create mode 100644 parcel.go create mode 100644 parcel_test.go create mode 100644 tracker.db diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..ccf4e5f --- /dev/null +++ b/go.mod @@ -0,0 +1,27 @@ +module github.com/Yandex-Practicum/42-docker-final + +go 1.22.0 + +require ( + github.com/stretchr/testify v1.8.4 + modernc.org/sqlite v1.29.1 +) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/google/uuid v1.3.0 // indirect + github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect + github.com/mattn/go-isatty v0.0.16 // indirect + github.com/ncruces/go-strftime v0.1.9 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + golang.org/x/sys v0.16.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 // indirect + modernc.org/libc v1.41.0 // indirect + modernc.org/mathutil v1.6.0 // indirect + modernc.org/memory v1.7.2 // indirect + modernc.org/strutil v1.2.0 // indirect + modernc.org/token v1.1.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..1a4926c --- /dev/null +++ b/go.sum @@ -0,0 +1,47 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= +github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= +github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= +github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= +golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= +golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 h1:5D53IMaUuA5InSeMu9eJtlQXS2NxAhyWQvkKEgXZhHI= +modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4= +modernc.org/libc v1.41.0 h1:g9YAc6BkKlgORsUWj+JwqoB1wU3o4DE3bM3yvA3k+Gk= +modernc.org/libc v1.41.0/go.mod h1:w0eszPsiXoOnoMJgrXjglgLuDy/bt5RR4y3QzUUeodY= +modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= +modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= +modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E= +modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E= +modernc.org/sqlite v1.29.1 h1:19GY2qvWB4VPw0HppFlZCPAbmxFU41r+qjKZQdQ1ryA= +modernc.org/sqlite v1.29.1/go.mod h1:hG41jCYxOAOoO6BRK66AdRlmOcDzXf7qnwlwjUIOqa0= +modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= +modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= +modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= +modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= diff --git a/main.go b/main.go new file mode 100644 index 0000000..a406215 --- /dev/null +++ b/main.go @@ -0,0 +1,177 @@ +package main + +import ( + "database/sql" + "fmt" + "time" + + _ "modernc.org/sqlite" +) + +const ( + ParcelStatusRegistered = "registered" + ParcelStatusSent = "sent" + ParcelStatusDelivered = "delivered" +) + +type Parcel struct { + Number int + Client int + Status string + Address string + CreatedAt string +} + +type ParcelService struct { + store ParcelStore +} + +func NewParcelService(store ParcelStore) ParcelService { + return ParcelService{store: store} +} + +func (s ParcelService) Register(client int, address string) (Parcel, error) { + parcel := Parcel{ + Client: client, + Status: ParcelStatusRegistered, + Address: address, + CreatedAt: time.Now().UTC().Format(time.RFC3339), + } + + id, err := s.store.Add(parcel) + if err != nil { + return parcel, err + } + + parcel.Number = id + + fmt.Printf("Новая посылка № %d на адрес %s от клиента с идентификатором %d зарегистрирована %s\n", + parcel.Number, parcel.Address, parcel.Client, parcel.CreatedAt) + + return parcel, nil +} + +func (s ParcelService) PrintClientParcels(client int) error { + parcels, err := s.store.GetByClient(client) + if err != nil { + return err + } + + fmt.Printf("Посылки клиента %d:\n", client) + for _, parcel := range parcels { + fmt.Printf("Посылка № %d на адрес %s от клиента с идентификатором %d зарегистрирована %s, статус %s\n", + parcel.Number, parcel.Address, parcel.Client, parcel.CreatedAt, parcel.Status) + } + fmt.Println() + + return nil +} + +func (s ParcelService) NextStatus(number int) error { + parcel, err := s.store.Get(number) + if err != nil { + return err + } + + var nextStatus string + switch parcel.Status { + case ParcelStatusRegistered: + nextStatus = ParcelStatusSent + case ParcelStatusSent: + nextStatus = ParcelStatusDelivered + case ParcelStatusDelivered: + return nil + } + + fmt.Printf("У посылки № %d новый статус: %s\n", number, nextStatus) + + return s.store.SetStatus(number, nextStatus) +} + +func (s ParcelService) ChangeAddress(number int, address string) error { + return s.store.SetAddress(number, address) +} + +func (s ParcelService) Delete(number int) error { + return s.store.Delete(number) +} + +func main() { + db, err := sql.Open("sqlite", "tracker.db") + if err != nil { + fmt.Println(err) + return + } + defer db.Close() + + store := NewParcelStore(db) + service := NewParcelService(store) + + // регистрация посылки + client := 1 + address := "Псков, д. Пушкина, ул. Колотушкина, д. 5" + p, err := service.Register(client, address) + if err != nil { + fmt.Println(err) + return + } + + // изменение адреса + newAddress := "Саратов, д. Верхние Зори, ул. Козлова, д. 25" + err = service.ChangeAddress(p.Number, newAddress) + if err != nil { + fmt.Println(err) + return + } + + // изменение статуса + err = service.NextStatus(p.Number) + if err != nil { + fmt.Println(err) + return + } + + // вывод посылок клиента + err = service.PrintClientParcels(client) + if err != nil { + fmt.Println(err) + return + } + + // попытка удаления отправленной посылки + err = service.Delete(p.Number) + if err != nil { + fmt.Println(err) + return + } + + // вывод посылок клиента + // предыдущая посылка не должна удалиться, т.к. её статус НЕ «зарегистрирована» + err = service.PrintClientParcels(client) + if err != nil { + fmt.Println(err) + return + } + + // регистрация новой посылки + p, err = service.Register(client, address) + if err != nil { + fmt.Println(err) + return + } + + // удаление новой посылки + err = service.Delete(p.Number) + if err != nil { + fmt.Println(err) + return + } + + // вывод посылок клиента + // здесь не должно быть последней посылки, т.к. она должна была успешно удалиться + err = service.PrintClientParcels(client) + if err != nil { + fmt.Println(err) + return + } +} diff --git a/parcel.go b/parcel.go new file mode 100644 index 0000000..783b05c --- /dev/null +++ b/parcel.go @@ -0,0 +1,96 @@ +package main + +import ( + "database/sql" +) + +type ParcelStore struct { + db *sql.DB +} + +func NewParcelStore(db *sql.DB) ParcelStore { + return ParcelStore{db: db} +} + +func (s ParcelStore) Add(p Parcel) (int, error) { + res, err := s.db.Exec("INSERT INTO parcel (client, status, address, created_at) VALUES (:client, :status, :address, :created_at)", + sql.Named("client", p.Client), + sql.Named("status", p.Status), + sql.Named("address", p.Address), + sql.Named("created_at", p.CreatedAt)) + if err != nil { + return 0, err + } + + id, err := res.LastInsertId() + if err != nil { + return 0, err + } + + return int(id), nil +} + +func (s ParcelStore) Get(number int) (Parcel, error) { + p := Parcel{} + + row := s.db.QueryRow("SELECT number, client, status, address, created_at FROM parcel WHERE number = :number", + sql.Named("number", number)) + err := row.Scan(&p.Number, &p.Client, &p.Status, &p.Address, &p.CreatedAt) + if err != nil { + return p, err + } + + return p, nil +} + +func (s ParcelStore) GetByClient(client int) ([]Parcel, error) { + rows, err := s.db.Query("SELECT number, client, status, address, created_at FROM parcel WHERE client = :client", + sql.Named("client", client)) + if err != nil { + return nil, err + } + defer rows.Close() + + var res []Parcel + for rows.Next() { + p := Parcel{} + + err := rows.Scan(&p.Number, &p.Client, &p.Status, &p.Address, &p.CreatedAt) + if err != nil { + return nil, err + } + + res = append(res, p) + } + + if err := rows.Err(); err != nil { + return nil, err + } + + return res, nil +} + +func (s ParcelStore) SetStatus(number int, status string) error { + _, err := s.db.Exec("UPDATE parcel SET status = :status WHERE number = :number", + sql.Named("status", status), + sql.Named("number", number)) + + return err +} + +func (s ParcelStore) SetAddress(number int, address string) error { + _, err := s.db.Exec("UPDATE parcel SET address = :address WHERE number = :number AND status = :status", + sql.Named("address", address), + sql.Named("number", number), + sql.Named("status", ParcelStatusRegistered)) + + return err +} + +func (s ParcelStore) Delete(number int) error { + _, err := s.db.Exec("DELETE FROM parcel WHERE number = :number AND status = :status", + sql.Named("number", number), + sql.Named("status", ParcelStatusRegistered)) + + return err +} diff --git a/parcel_test.go b/parcel_test.go new file mode 100644 index 0000000..cd46e1c --- /dev/null +++ b/parcel_test.go @@ -0,0 +1,167 @@ +package main + +import ( + "database/sql" + "math/rand" + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +var ( + // randSource источник псевдо случайных чисел. + // Для повышения уникальности в качестве seed + // используется текущее время в unix формате (в виде числа) + randSource = rand.NewSource(time.Now().UnixNano()) + // randRange использует randSource для генерации случайных чисел + randRange = rand.New(randSource) +) + +// getTestParcel возвращает тестовую посылку +func getTestParcel() Parcel { + return Parcel{ + Client: 1000, + Status: ParcelStatusRegistered, + Address: "test", + CreatedAt: time.Now().UTC().Format(time.RFC3339), + } +} + +// TestAddGetDelete проверяет добавление, получение и удаление посылку +func TestAddGetDelete(t *testing.T) { + // prepare + db, err := sql.Open("sqlite", "tracker.db") + if err != nil { + require.NoError(t, err) + } + defer db.Close() + store := NewParcelStore(db) + parcel := getTestParcel() + + // add + parcel.Number, err = store.Add(parcel) + + require.NoError(t, err) + require.NotEmpty(t, parcel.Number) + + // get + stored, err := store.Get(parcel.Number) + + require.NoError(t, err) + require.Equal(t, parcel, stored) + + // delete + err = store.Delete(parcel.Number) + + stored, err = store.Get(parcel.Number) + require.Equal(t, sql.ErrNoRows, err) +} + +// TestSetAddress проверяет обновление адреса +func TestSetAddress(t *testing.T) { + // prepare + db, err := sql.Open("sqlite", "tracker.db") + if err != nil { + require.NoError(t, err) + } + defer db.Close() + store := NewParcelStore(db) + parcel := getTestParcel() + + // add + parcel.Number, err = store.Add(parcel) + + require.NoError(t, err) + require.NotEmpty(t, parcel.Number) + + // set address + newAddress := "new test address" + err = store.SetAddress(parcel.Number, newAddress) + + require.NoError(t, err) + + // check + stored, err := store.Get(parcel.Number) + + require.NoError(t, err) + require.Equal(t, newAddress, stored.Address) +} + +// TestSetStatus проверяет обновление статуса +func TestSetStatus(t *testing.T) { + // prepare + db, err := sql.Open("sqlite", "tracker.db") + if err != nil { + require.NoError(t, err) + } + defer db.Close() + store := NewParcelStore(db) + parcel := getTestParcel() + + // add + parcel.Number, err = store.Add(parcel) + + require.NoError(t, err) + require.NotEmpty(t, parcel.Number) + + // set status + err = store.SetStatus(parcel.Number, ParcelStatusSent) + + require.NoError(t, err) + + // check + stored, err := store.Get(parcel.Number) + + require.NoError(t, err) + require.Equal(t, ParcelStatusSent, stored.Status) +} + +// TestGetByClient проверяет получение посылок по идентификатору клиента +func TestGetByClient(t *testing.T) { + // prepare + db, err := sql.Open("sqlite", "tracker.db") + if err != nil { + require.NoError(t, err) + } + defer db.Close() + store := NewParcelStore(db) + + parcels := []Parcel{ + getTestParcel(), + getTestParcel(), + getTestParcel(), + } + parcelMap := map[int]Parcel{} + + // задаём всем посылкам одного клиента + client := randRange.Intn(10_000_000) + parcels[0].Client = client + parcels[1].Client = client + parcels[2].Client = client + + // add + for i := 0; i < len(parcels); i++ { + id, err := store.Add(parcels[i]) + + require.NoError(t, err) + require.NotEmpty(t, id) + + parcels[i].Number = id + parcelMap[id] = parcels[i] + } + + // get by client + storedParcels, err := store.GetByClient(client) + + require.NoError(t, err) + require.Len(t, storedParcels, len(parcels)) + + // check + for _, parcel := range storedParcels { + expectedParcel, ok := parcelMap[parcel.Number] + + require.True(t, ok) + require.Equal(t, expectedParcel, parcel) + } +} diff --git a/tracker.db b/tracker.db new file mode 100644 index 0000000000000000000000000000000000000000..04ab843de974eb0007dc422e6ded9d587585ab9a GIT binary patch literal 61440 zcmeIb3z%e8l_r|;%FKw2%s7#Ph!T>#EQ&`F=fqp2p#BUbPKgZ!GjvxP@i2uv~T;mVfi4HS9O!bO%YiNjx*X_opv!^ZL=Lnn$-d)Ot%}@!_I!QYWMg-Ic6(#; zhS8lj%unrZdx&4S?&7uUFCJOH_N`Z3Jks{3)aj`a{4>2UwXHGB-$ur#=Nmg4vqI+c z_L=Fq`Pn+ie#zYv!5{g;?%DCF`t0J!L}PKJzA!&CKD~XmG1Zu!KTBTQJ~@ux`J!L7 z{5~>0GmmB`C-tql`TG3Aocv|O+I1IRx^~^^u2)^%@m76wbha@kubH=6p5ehMYuInd?6zmEfB(Y~JKNzK=kW*a-l z=jI!;jnSo#A8jr@xAfA|Q)i7V9a%Gi=i83n3CgoeFD@NEYvkx{OV5M)Al`U>>7}Es z_P?@3$;*4iv-95BZgIU^esiJrX0NzuEP|GfZJt<~!`YLiN0tsB-H)G^)y}<3UpTt| z=pAU~*`+UxEIokA_b)x$*5EU2a_K2{gAI8rwv+%1A{jaSFqHXa=DEzLGY2xaW%gvY zWUkDdn>izMTqZvBv!U+|eQoIZq0bB*9J*s@c4+I+RYPwXI%DW{Ly5sx2mf|(Y4D}N z&klZM@V$czgWCqL9z1XGtijh0_NRZI{$cuP`uEbGOCL(#nckajq_0h{O`nrKAw4kg zuLD0B_~yV@2A&*vc;N1Vn+J9cTsLs(KzZQQf!7Q~Qm>@Gllqg?GpSFe?oYK+yHi_I zSEkOzX_sSDvEitgdAN4-n`*80EdiVA2=)Jc0g5I-wkMB(+ewO%t;%kZL5|1bDPwY=jCaz0doNyDl z#6bKP@gK+kEdFx*v+;xRJL0qPt??`4wfL&|G4V+3mDpd${y6r<*kiGKWABOWioG*- zQOu3KF_wz{Jo>}vH=-{_e>eI-^!Dg%^oHo$qqXRpqQ^udk)KAs9r=UE;m9FO%W(5j z+?x=I&5J=xqI9!YXcf1RZC+&F z+aEI(x=!=L-ku2FyH!c$d?nL-D@n=Rt@={F=rq@Yf(CBY`pYg}y1;(P_43)~`Fx2r z^_yPgo#t=xtw`=YNg%3*KV7P;;7Z(U)%4VOHIW(>#}TC381PgKj>Lj=s5# z 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 G4*cKPR1d8H literal 0 HcmV?d00001 From 76ecd6031b74380a955a96c5e4824c740926798d Mon Sep 17 00:00:00 2001 From: bear-brown-beard Date: Wed, 15 Jan 2025 20:50:53 +0500 Subject: [PATCH 2/8] 2th commit Dockerfile --- Dockerfile | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 Dockerfile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..9776eca --- /dev/null +++ b/Dockerfile @@ -0,0 +1,24 @@ +FROM golang:1.23.4 AS builder + +WORKDIR /app + +COPY go.mod go.sum ./ + +RUN go mod tidy + +COPY . . + +RUN go build -o tracker . + +FROM debian:bullseye-slim + +RUN apt-get update && apt-get install -y sqlite3 libsqlite3-dev && rm -rf /var/lib/apt/lists/* + +WORKDIR /app + +COPY --from=builder /app/tracker /app/ +COPY --from=builder /app/tracker.db /app/ + +EXPOSE 8080 + +CMD ["./tracker"] From 66692ae24219144e7d29fe77405143ff012a6757 Mon Sep 17 00:00:00 2001 From: bear-brown-beard Date: Wed, 2 Apr 2025 20:14:36 +0500 Subject: [PATCH 3/8] Update README.md --- README.md | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d52c399..1ad580c 100644 --- a/README.md +++ b/README.md @@ -1 +1,26 @@ -# 42_final \ No newline at end of file +Данный сервис позволяет управлять посылками клиентов, храня их в базе данных SQLite. + +Основные возможности: + +Регистрация посылки (номер, клиент, статус, адрес, дата регистрации) +Изменение статуса (Registered -> Sent -> Delivered) +Изменение адреса доставки (только для статуса registered) +Получение списка посылок клиента +Удаление посылки (возможно только в статусе registered) + +Сервис написан на Go и взаимодействует с базой данных через database/sql. + +Технологии: + +Go – серверная часть +SQLite – база данных +database/sql – работа с БД +Git – контроль версий +Testify – тестирование + +Покрываемые тесты: + +TestAddGetDelete – проверяет добавление, получение и удаление посылки +TestSetAddress – проверяет обновление адреса +TestSetStatus – проверяет обновление статуса +TestGetByClient – проверяет получение посылок по клиенту From ad27883ac94cf01271c44da5a25ab52328852c06 Mon Sep 17 00:00:00 2001 From: bear-brown-beard Date: Wed, 2 Apr 2025 20:15:14 +0500 Subject: [PATCH 4/8] Update README.md --- README.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 1ad580c..bedc0f9 100644 --- a/README.md +++ b/README.md @@ -2,25 +2,25 @@ Основные возможности: -Регистрация посылки (номер, клиент, статус, адрес, дата регистрации) -Изменение статуса (Registered -> Sent -> Delivered) -Изменение адреса доставки (только для статуса registered) -Получение списка посылок клиента -Удаление посылки (возможно только в статусе registered) +#Регистрация посылки (номер, клиент, статус, адрес, дата регистрации) +#Изменение статуса (Registered -> Sent -> Delivered) +#Изменение адреса доставки (только для статуса registered) +#Получение списка посылок клиента +#Удаление посылки (возможно только в статусе registered) Сервис написан на Go и взаимодействует с базой данных через database/sql. Технологии: -Go – серверная часть -SQLite – база данных -database/sql – работа с БД -Git – контроль версий -Testify – тестирование +#Go – серверная часть +#SQLite – база данных +#database/sql – работа с БД +#Git – контроль версий +#Testify – тестирование Покрываемые тесты: -TestAddGetDelete – проверяет добавление, получение и удаление посылки -TestSetAddress – проверяет обновление адреса -TestSetStatus – проверяет обновление статуса -TestGetByClient – проверяет получение посылок по клиенту +#TestAddGetDelete – проверяет добавление, получение и удаление посылки +#TestSetAddress – проверяет обновление адреса +#TestSetStatus – проверяет обновление статуса +#TestGetByClient – проверяет получение посылок по клиенту From b7a36f7247c34a29c4d9aca6931f2a1613d79420 Mon Sep 17 00:00:00 2001 From: bear-brown-beard Date: Wed, 2 Apr 2025 20:15:58 +0500 Subject: [PATCH 5/8] Update README.md --- README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/README.md b/README.md index bedc0f9..03b71ec 100644 --- a/README.md +++ b/README.md @@ -3,24 +3,38 @@ Основные возможности: #Регистрация посылки (номер, клиент, статус, адрес, дата регистрации) + #Изменение статуса (Registered -> Sent -> Delivered) + #Изменение адреса доставки (только для статуса registered) + #Получение списка посылок клиента + #Удаление посылки (возможно только в статусе registered) + Сервис написан на Go и взаимодействует с базой данных через database/sql. Технологии: #Go – серверная часть + #SQLite – база данных + #database/sql – работа с БД + #Git – контроль версий + #Testify – тестирование + Покрываемые тесты: + #TestAddGetDelete – проверяет добавление, получение и удаление посылки + #TestSetAddress – проверяет обновление адреса + #TestSetStatus – проверяет обновление статуса + #TestGetByClient – проверяет получение посылок по клиенту From f418da956c03c6ef3501041d057aa382bee6ddd8 Mon Sep 17 00:00:00 2001 From: bear-brown-beard Date: Wed, 2 Apr 2025 20:19:24 +0500 Subject: [PATCH 6/8] Update README.md --- README.md | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 03b71ec..a344b24 100644 --- a/README.md +++ b/README.md @@ -1,40 +1,42 @@ Данный сервис позволяет управлять посылками клиентов, храня их в базе данных SQLite. -Основные возможности: -#Регистрация посылки (номер, клиент, статус, адрес, дата регистрации) +**Основные возможности:** -#Изменение статуса (Registered -> Sent -> Delivered) +# Регистрация посылки (номер, клиент, статус, адрес, дата регистрации) -#Изменение адреса доставки (только для статуса registered) +# Изменение статуса (Registered -> Sent -> Delivered) -#Получение списка посылок клиента +# Изменение адреса доставки (только для статуса registered) -#Удаление посылки (возможно только в статусе registered) +# Получение списка посылок клиента + +# Удаление посылки (возможно только в статусе registered) Сервис написан на Go и взаимодействует с базой данных через database/sql. -Технологии: -#Go – серверная часть +**Технологии:** + +# Go – серверная часть -#SQLite – база данных +# SQLite – база данных -#database/sql – работа с БД +# database/sql – работа с БД -#Git – контроль версий +# Git – контроль версий -#Testify – тестирование +# Testify – тестирование -Покрываемые тесты: +**Покрываемые тесты:** -#TestAddGetDelete – проверяет добавление, получение и удаление посылки +# TestAddGetDelete – проверяет добавление, получение и удаление посылки -#TestSetAddress – проверяет обновление адреса +# TestSetAddress – проверяет обновление адреса -#TestSetStatus – проверяет обновление статуса +# TestSetStatus – проверяет обновление статуса -#TestGetByClient – проверяет получение посылок по клиенту +# TestGetByClient – проверяет получение посылок по клиенту From 8acf627d8a011a4ddbb2bf8691b0200c7d164598 Mon Sep 17 00:00:00 2001 From: bear-brown-beard Date: Wed, 2 Apr 2025 20:20:30 +0500 Subject: [PATCH 7/8] Update README.md --- README.md | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index a344b24..ed984f2 100644 --- a/README.md +++ b/README.md @@ -1,42 +1,42 @@ Данный сервис позволяет управлять посылками клиентов, храня их в базе данных SQLite. -**Основные возможности:** +# **Основные возможности:** -# Регистрация посылки (номер, клиент, статус, адрес, дата регистрации) +Регистрация посылки (номер, клиент, статус, адрес, дата регистрации) -# Изменение статуса (Registered -> Sent -> Delivered) +Изменение статуса (Registered -> Sent -> Delivered) -# Изменение адреса доставки (только для статуса registered) +Изменение адреса доставки (только для статуса registered) -# Получение списка посылок клиента +Получение списка посылок клиента -# Удаление посылки (возможно только в статусе registered) +Удаление посылки (возможно только в статусе registered) Сервис написан на Go и взаимодействует с базой данных через database/sql. -**Технологии:** +# **Технологии:** -# Go – серверная часть +Go – серверная часть -# SQLite – база данных +SQLite – база данных -# database/sql – работа с БД +database/sql – работа с БД -# Git – контроль версий +Git – контроль версий -# Testify – тестирование +Testify – тестирование -**Покрываемые тесты:** +# **Покрываемые тесты:** -# TestAddGetDelete – проверяет добавление, получение и удаление посылки +TestAddGetDelete – проверяет добавление, получение и удаление посылки -# TestSetAddress – проверяет обновление адреса +TestSetAddress – проверяет обновление адреса -# TestSetStatus – проверяет обновление статуса +TestSetStatus – проверяет обновление статуса -# TestGetByClient – проверяет получение посылок по клиенту +TestGetByClient – проверяет получение посылок по клиенту From bc802f82ca6e63634bf19fe257509fe9d4d58563 Mon Sep 17 00:00:00 2001 From: bear-brown-beard Date: Wed, 2 Apr 2025 20:22:09 +0500 Subject: [PATCH 8/8] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index ed984f2..5b5f5f2 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,8 @@ Git – контроль версий Testify – тестирование +Docker – контейнеризация + # **Покрываемые тесты:**