diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 00000000..13566b81 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml new file mode 100644 index 00000000..37ebeea2 --- /dev/null +++ b/.idea/dataSources.xml @@ -0,0 +1,15 @@ + + + + + sqlite.xerial + true + org.sqlite.JDBC + jdbc:sqlite:$PROJECT_DIR$/tracker.db + + + + $ProjectFileDir$ + + + \ No newline at end of file diff --git a/.idea/go-db-sql-final.iml b/.idea/go-db-sql-final.iml new file mode 100644 index 00000000..5e764c4f --- /dev/null +++ b/.idea/go-db-sql-final.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 00000000..b152728e --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 00000000..35eb1ddf --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/1 b/1 new file mode 100644 index 00000000..58c9bdf9 --- /dev/null +++ b/1 @@ -0,0 +1 @@ +111 diff --git a/main.go b/main.go index 44c32b3f..3e2befd2 100644 --- a/main.go +++ b/main.go @@ -97,9 +97,14 @@ func (s ParcelService) Delete(number int) error { } func main() { - // настройте подключение к БД + db, err := sql.Open("sqlite", "tracker.db") + if err != nil { + fmt.Println(err) + return + } + defer db.Close() // настройте подключение к БД - store := // создайте объект ParcelStore функцией NewParcelStore + store := NewParcelStore(db) // создайте объект ParcelStore функцией NewParcelStore service := NewParcelService(store) // регистрация посылки diff --git a/parcel.go b/parcel.go index db6c815d..b1d2d8f4 100644 --- a/parcel.go +++ b/parcel.go @@ -2,6 +2,7 @@ package main import ( "database/sql" + "fmt" ) type ParcelStore struct { @@ -13,48 +14,125 @@ func NewParcelStore(db *sql.DB) ParcelStore { } func (s ParcelStore) Add(p Parcel) (int, error) { - // реализуйте добавление строки в таблицу parcel, используйте данные из переменной p - - // верните идентификатор последней добавленной записи - return 0, nil + // SQL-запрос для добавления новой записи в таблицу parcel + query := `INSERT INTO parcel (client, status, address, created_at) + VALUES (:client, :status, :address, :created_at)` + + // Выполнение запроса + result, err := s.db.Exec(query, 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 := result.LastInsertId() + if err != nil { + return 0, err + } + + return int(id), nil } func (s ParcelStore) Get(number int) (Parcel, error) { - // реализуйте чтение строки по заданному number - // здесь из таблицы должна вернуться только одна строка - - // заполните объект Parcel данными из таблицы - p := Parcel{} + // SQL-запрос для получения одной записи по номеру посылки + query := `SELECT number, client, status, address, created_at FROM parcel WHERE number = :number` + + // Выполнение запроса + row := s.db.QueryRow(query, sql.Named("number", number)) + + // Создание объекта для заполнения данными из БД + var p Parcel + err := row.Scan(&p.Number, &p.Client, &p.Status, &p.Address, &p.CreatedAt) + if err != nil { + if err == sql.ErrNoRows { + return p, fmt.Errorf("посылка с номером %d не найдена", number) + } + return p, err + } return p, nil } func (s ParcelStore) GetByClient(client int) ([]Parcel, error) { - // реализуйте чтение строк из таблицы parcel по заданному client - // здесь из таблицы может вернуться несколько строк - - // заполните срез Parcel данными из таблицы - var res []Parcel - - return res, nil + // SQL-запрос для получения всех посылок по клиенту + query := `SELECT number, client, status, address, created_at FROM parcel WHERE client = :client` + + // Выполнение запроса + rows, err := s.db.Query(query, sql.Named("client", client)) + if err != nil { + return nil, err + } + defer rows.Close() + + // Срез для хранения полученных посылок + var parcels []Parcel + + // Чтение всех строк из результата запроса + for rows.Next() { + var p Parcel + err := rows.Scan(&p.Number, &p.Client, &p.Status, &p.Address, &p.CreatedAt) + if err != nil { + return nil, err + } + parcels = append(parcels, p) + } + + // Проверка на наличие ошибок после завершения итерации + if err := rows.Err(); err != nil { + return nil, err + } + + return parcels, nil } func (s ParcelStore) SetStatus(number int, status string) error { - // реализуйте обновление статуса в таблице parcel + // SQL-запрос для обновления статуса посылки по её номеру + query := `UPDATE parcel SET status = :status WHERE number = :number` + + // Выполнение запроса + _, err := s.db.Exec(query, sql.Named("status", status), sql.Named("number", number)) + if err != nil { + return err + } return nil } func (s ParcelStore) SetAddress(number int, address string) error { - // реализуйте обновление адреса в таблице parcel - // менять адрес можно только если значение статуса registered + // Получаем текущий статус посылки + parcel, err := s.Get(number) + if err != nil { + return err + } + + // Если статус не "зарегистрирована", изменение адреса запрещено + if parcel.Status != ParcelStatusRegistered { + return fmt.Errorf("нельзя изменить адрес, так как статус посылки не 'зарегистрирован'") + } + + // SQL-запрос для обновления адреса посылки по её номеру + query := `UPDATE parcel SET address = :address WHERE number = :number` + _, err = s.db.Exec(query, sql.Named("address", address), sql.Named("number", number)) + if err != nil { + return err + } return nil } func (s ParcelStore) Delete(number int) error { - // реализуйте удаление строки из таблицы parcel - // удалять строку можно только если значение статуса registered - + // Выполняем запрос на удаление только если статус посылки 'registered' + _, err := s.db.Exec( + "DELETE FROM parcel WHERE number = :number AND status = :status", + sql.Named("number", number), + sql.Named("status", ParcelStatusRegistered), + ) + + if err != nil { + return fmt.Errorf("ошибка выполнения запроса на удаление: %w", err) + } + + // Если ошибка не возникла, но ни одна строка не была затронута, значит посылка не найдена или её статус не 'registered' return nil } diff --git a/parcel_test.go b/parcel_test.go index d1b93827..9d4d900d 100644 --- a/parcel_test.go +++ b/parcel_test.go @@ -22,7 +22,7 @@ var ( func getTestParcel() Parcel { return Parcel{ Client: 1000, - Status: ParcelStatusRegistered, + Status: ParcelStatusRegistered, // используем константу "registered" Address: "test", CreatedAt: time.Now().UTC().Format(time.RFC3339), } @@ -31,57 +31,97 @@ func getTestParcel() Parcel { // TestAddGetDelete проверяет добавление, получение и удаление посылки func TestAddGetDelete(t *testing.T) { // prepare - db, err := // настройте подключение к БД - store := NewParcelStore(db) - parcel := getTestParcel() + db, err := sql.Open("sqlite", "tracker.db") // подключаемся к файлу tracker.db + require.NoError(t, err) + defer db.Close() + + store := NewParcelStore(db) // создаем хранилище для работы с базой данных + parcel := getTestParcel() // создаем тестовую посылку // add - // добавьте новую посылку в БД, убедитесь в отсутствии ошибки и наличии идентификатора + id, err := store.Add(parcel) // добавляем новую посылку + require.NoError(t, err) + require.NotZero(t, id) + parcel.Number = id // сохраняем идентификатор посылки // get - // получите только что добавленную посылку, убедитесь в отсутствии ошибки - // проверьте, что значения всех полей в полученном объекте совпадают со значениями полей в переменной parcel + retrievedParcel, err := store.Get(parcel.Number) // получаем посылку по идентификатору + require.NoError(t, err) + require.Equal(t, parcel.Client, retrievedParcel.Client) + require.Equal(t, ParcelStatusRegistered, retrievedParcel.Status) // проверяем статус "registered" + require.Equal(t, parcel.Address, retrievedParcel.Address) + require.Equal(t, parcel.CreatedAt, retrievedParcel.CreatedAt) // delete - // удалите добавленную посылку, убедитесь в отсутствии ошибки - // проверьте, что посылку больше нельзя получить из БД + err = store.Delete(parcel.Number) // удаляем посылку + require.NoError(t, err) + + _, err = store.Get(parcel.Number) // проверяем, что посылка удалена + require.Error(t, err) } // TestSetAddress проверяет обновление адреса func TestSetAddress(t *testing.T) { // prepare - db, err := // настройте подключение к БД + db, err := sql.Open("sqlite", "tracker.db") // подключаемся к файлу tracker.db + require.NoError(t, err) + defer db.Close() + + store := NewParcelStore(db) + parcel := getTestParcel() // add - // добавьте новую посылку в БД, убедитесь в отсутствии ошибки и наличии идентификатора + id, err := store.Add(parcel) + require.NoError(t, err) + require.NotZero(t, id) + parcel.Number = id // set address - // обновите адрес, убедитесь в отсутствии ошибки newAddress := "new test address" + err = store.SetAddress(parcel.Number, newAddress) + require.NoError(t, err) // check - // получите добавленную посылку и убедитесь, что адрес обновился + updatedParcel, err := store.Get(parcel.Number) + require.NoError(t, err) + require.Equal(t, newAddress, updatedParcel.Address) } // TestSetStatus проверяет обновление статуса func TestSetStatus(t *testing.T) { // prepare - db, err := // настройте подключение к БД + db, err := sql.Open("sqlite", "tracker.db") // подключаемся к файлу tracker.db + require.NoError(t, err) + defer db.Close() + + store := NewParcelStore(db) + parcel := getTestParcel() // add - // добавьте новую посылку в БД, убедитесь в отсутствии ошибки и наличии идентификатора + id, err := store.Add(parcel) + require.NoError(t, err) + require.NotZero(t, id) + parcel.Number = id // set status - // обновите статус, убедитесь в отсутствии ошибки + newStatus := ParcelStatusSent // используем статус "sent" + err = store.SetStatus(parcel.Number, newStatus) + require.NoError(t, err) // check - // получите добавленную посылку и убедитесь, что статус обновился + updatedParcel, err := store.Get(parcel.Number) + require.NoError(t, err) + require.Equal(t, ParcelStatusSent, updatedParcel.Status) // проверяем статус "sent" } // TestGetByClient проверяет получение посылок по идентификатору клиента func TestGetByClient(t *testing.T) { // prepare - db, err := // настройте подключение к БД + db, err := sql.Open("sqlite", "tracker.db") // подключаемся к файлу tracker.db + require.NoError(t, err) + defer db.Close() + + store := NewParcelStore(db) parcels := []Parcel{ getTestParcel(), @@ -92,30 +132,31 @@ func TestGetByClient(t *testing.T) { // задаём всем посылкам один и тот же идентификатор клиента client := randRange.Intn(10_000_000) - parcels[0].Client = client - parcels[1].Client = client - parcels[2].Client = client + for i := range parcels { + parcels[i].Client = client + } // add for i := 0; i < len(parcels); i++ { - id, err := // добавьте новую посылку в БД, убедитесь в отсутствии ошибки и наличии идентификатора - - // обновляем идентификатор добавленной у посылки + id, err := store.Add(parcels[i]) + require.NoError(t, err) + require.NotZero(t, id) parcels[i].Number = id - - // сохраняем добавленную посылку в структуру map, чтобы её можно было легко достать по идентификатору посылки parcelMap[id] = parcels[i] } // get by client - storedParcels, err := // получите список посылок по идентификатору клиента, сохранённого в переменной client - // убедитесь в отсутствии ошибки - // убедитесь, что количество полученных посылок совпадает с количеством добавленных + storedParcels, err := store.GetByClient(client) + require.NoError(t, err) + require.Len(t, storedParcels, len(parcels)) // check for _, parcel := range storedParcels { - // в parcelMap лежат добавленные посылки, ключ - идентификатор посылки, значение - сама посылка - // убедитесь, что все посылки из storedParcels есть в parcelMap - // убедитесь, что значения полей полученных посылок заполнены верно + original, exists := parcelMap[parcel.Number] + require.True(t, exists) + require.Equal(t, original.Client, parcel.Client) + require.Equal(t, ParcelStatusRegistered, parcel.Status) // проверяем статус "registered" + require.Equal(t, original.Address, parcel.Address) + require.Equal(t, original.CreatedAt, parcel.CreatedAt) } } diff --git a/tracker.db b/tracker.db index b6ba48a1..7e80d28b 100644 Binary files a/tracker.db and b/tracker.db differ