diff --git a/.github/workflows/push-docker-image.yml b/.github/workflows/push-docker-image.yml index 2c2b0c6..82e6118 100644 --- a/.github/workflows/push-docker-image.yml +++ b/.github/workflows/push-docker-image.yml @@ -26,7 +26,7 @@ jobs: with: registry: ghcr.io username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} + password: ${{ secrets.DOCKER_GH_TOKEN }} - name: Build and push Docker image uses: docker/build-push-action@v5 diff --git a/apimodels/create_event.go b/apimodels/create_event.go new file mode 100644 index 0000000..cad0253 --- /dev/null +++ b/apimodels/create_event.go @@ -0,0 +1,10 @@ +package apimodels + +type CreateEvent struct { + Name string `json:"name"` + Description string `json:"description"` + StartDate string `json:"startDate"` + EndDate string `json:"endDate"` + VolunteersRequired int `json:"volunteersRequired"` + Location string `json:"location"` +} diff --git a/db/schema.sql b/db/schema.sql index 78dac3e..70c802d 100644 --- a/db/schema.sql +++ b/db/schema.sql @@ -3,10 +3,11 @@ CREATE TABLE IF NOT EXISTS events ( id SERIAL PRIMARY KEY, name VARCHAR(255) NOT NULL, description TEXT, + location TEXT, start_date TIMESTAMP NOT NULL, end_date TIMESTAMP NOT NULL, volunteers_required INTEGER NOT NULL, - created_at TIMESTAMP NOT NULL DEFAULT NOW(), + created_at TIMESTAMP NOT NULL DEFAULT NOW() ); --- Users diff --git a/go.mod b/go.mod index 25583fc..4816b89 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,9 @@ require github.com/labstack/echo v3.3.10+incompatible require ( github.com/jmoiron/sqlx v1.3.5 // indirect + github.com/joho/godotenv v1.5.1 github.com/labstack/gommon v0.4.1 // indirect + github.com/lib/pq v1.10.9 github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect diff --git a/go.sum b/go.sum index cb6c8bf..da422eb 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +1,15 @@ github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/labstack/echo v3.3.10+incompatible h1:pGRcYk231ExFAyoAjAfD85kQzRJCRI8bbnE7CX5OEgg= github.com/labstack/echo v3.3.10+incompatible/go.mod h1:0INS7j/VjnFxD4E2wkz67b8cVwCLbBmJyDaka6Cmk1s= github.com/labstack/gommon v0.4.1 h1:gqEff0p/hTENGMABzezPoPSRtIh1Cvw0ueMOe0/dfOk= github.com/labstack/gommon v0.4.1/go.mod h1:TyTrpPqxR5KMk8LKVtLmfMjeQ5FEkBYdxLYPw/WfrOM= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= diff --git a/main.go b/main.go index 101ea41..ad7e539 100644 --- a/main.go +++ b/main.go @@ -1,15 +1,89 @@ package main import ( + "blood-for-life-backend/apimodels" + "blood-for-life-backend/store" + "fmt" "net/http" + "os" + "time" + "github.com/jmoiron/sqlx" + "github.com/joho/godotenv" "github.com/labstack/echo" + _ "github.com/lib/pq" ) func main() { e := echo.New() + + envErr := godotenv.Load(".env") + + if envErr != nil { + fmt.Println(envErr.Error()) + } + + dbConnection := os.Getenv("DB") + + db, err := sqlx.Connect("postgres", dbConnection) + + if err != nil { + fmt.Println(err.Error()) + } + + defer db.Close() + + if err := db.Ping(); err != nil { + fmt.Println(err.Error()) + } else { + fmt.Println("Successfully Connected") + } + + loadSchema(db) + + eventStore := store.NewPGEventStore(db) + bind(e, eventStore) + + e.Logger.Fatal(e.Start(":1323")) + +} + +func loadSchema(db *sqlx.DB) { + file, err := os.ReadFile("./db/schema.sql") + if err != nil { + fmt.Println(err.Error()) + } + + _, err = db.Exec(string(file)) + if err != nil { + fmt.Println(err.Error()) + } +} + +func bind(e *echo.Echo, eventStore store.EventStore) { e.GET("/", func(c echo.Context) error { return c.String(http.StatusOK, "Hello, World!") }) - e.Logger.Fatal(e.Start(":1323")) + + e.POST("/api/create", func(c echo.Context) error { + event := new(apimodels.CreateEvent) + + // parse request body + bindErr := c.Bind(event) + if bindErr != nil { + return c.JSON(http.StatusBadRequest, bindErr) + } + + // convert string to time.Time + start, _ := time.Parse("01/02/2006 03:04 PM", event.StartDate) + end, _ := time.Parse("01/02/2006 03:04 PM", event.EndDate) + + _, err := eventStore.Create(c.Request().Context(), event.Name, event.Description, start, end, event.VolunteersRequired, event.Location) + + if err != nil { + return c.JSON(http.StatusBadRequest, err) + } + + return c.JSON(http.StatusOK, event) + }) } diff --git a/store/event.go b/store/event.go index 2ccdb9c..a65cf65 100644 --- a/store/event.go +++ b/store/event.go @@ -1,91 +1,98 @@ package store + import ( - "context" - "fmt" - "time" - "github.com/jmoiron/sqlx" + "context" + "fmt" + "time" + + "github.com/jmoiron/sqlx" ) type Event struct { - ID int `db:"id" json:"id"` - Name string `db:"name" json:"name"` - Description string `db:"description" json:"description"` - StartDate time.Time `db:"start_date" json:"startDate"` - EndDate time.Time `db:"end_date" json:"endDate"` - VolunteersRequired int `db:"volunteers_required" json:"volunteersRequired"` - CreatedAt time.Time `db:"created_at" json:"createdAt"` + ID int `db:"id"` + Name string `db:"name"` + Description string `db:"description"` + StartDate time.Time `db:"start_date"` + EndDate time.Time `db:"end_date"` + VolunteersRequired int `db:"volunteers_required"` + Location string `db:"location"` + CreatedAt time.Time `db:"created_at"` } type EventStore interface { - GetAll(ctx context.Context) ([]Event, error) - GetOne(ctx context.Context, id int) (*Event, error) - GetOneByStartDate(ctx context.Context, startDate time.Time) (*Event, error) - GetOneByName(ctx context.Context, name string) (*Event, error) // ?? - Create(ctx context.Context, event Event) (*Event, error) - Update(ctx context.Context, event Event) (*Event, error) - Delete(ctx context.Context, id int) error + GetAll(ctx context.Context) ([]Event, error) + GetOne(ctx context.Context, id int) (*Event, error) + GetOneByStartDate(ctx context.Context, startDate time.Time) (*Event, error) + GetOneByName(ctx context.Context, name string) (*Event, error) // ?? + Create(ctx context.Context, name string, description string, start time.Time, end time.Time, volunteers int, location string) (*Event, error) + Update(ctx context.Context, event Event) (*Event, error) + Delete(ctx context.Context, id int) error } type pgEventStore struct { - db *sqlx.DB + db *sqlx.DB } func NewPGEventStore(db *sqlx.DB) EventStore { - return &pgEventStore{db} + return &pgEventStore{db} } func (s *pgEventStore) GetAll(ctx context.Context) ([]Event, error) { - var e []Event - err := s.db.SelectContext(ctx, &e, "SELECT * FROM events") - if err != nil { - return nil, fmt.Errorf("unable to retrieve events from database, error %w", err) - } - return e, nil + var e []Event + err := s.db.SelectContext(ctx, &e, "SELECT * FROM events") + if err != nil { + return nil, fmt.Errorf("unable to retrieve events from database, error %w", err) + } + return e, nil } func (s *pgEventStore) GetOne(ctx context.Context, id int) (*Event, error) { - var e Event - err := s.db.GetContext(ctx, &e, "SELECT * FROM events WHERE id = $1", id) - if err != nil { - return nil, fmt.Errorf("unable to find event with id, error %w", err) - } - return &e, nil + var e Event + err := s.db.GetContext(ctx, &e, "SELECT * FROM events WHERE id = $1", id) + if err != nil { + return nil, fmt.Errorf("unable to find event with id, error %w", err) + } + return &e, nil } - func (s *pgEventStore) GetOneByName(ctx context.Context, name string) (*Event, error) { - var e Event - err := s.db.GetContext(ctx, &e, "SELECT * FROM events WHERE LOWER(name) = LOWER($1)", name) - if err != nil { - return nil, fmt.Errorf("unable to find event with name %s, with error %w", name, err) - } - return &e, nil + var e Event + err := s.db.GetContext(ctx, &e, "SELECT * FROM events WHERE LOWER(name) = LOWER($1)", name) + if err != nil { + return nil, fmt.Errorf("unable to find event with name %s, with error %w", name, err) + } + return &e, nil } // Don't know if I handled date right here func (s *pgEventStore) GetOneByStartDate(ctx context.Context, date time.Time) (*Event, error) { - var e Event - formattedDate := date.Format("2006-01-02 15:04"); - err := s.db.GetContext(ctx, &e, "SELECT * FROM events WHERE TO_CHAR(start_date, 'YYYY-MM-DD HH24:MI') = $1", formattedDate) - if err != nil { - return nil, fmt.Errorf("unable to find event with start date %s, with error %w", date, err) - } - return &e, nil + var e Event + formattedDate := date.Format("2006-01-02 15:04") + err := s.db.GetContext(ctx, &e, "SELECT * FROM events WHERE TO_CHAR(start_date, 'YYYY-MM-DD HH24:MI') = $1", formattedDate) + if err != nil { + return nil, fmt.Errorf("unable to find event with start date %s, with error %w", date, err) + } + return &e, nil } -func (s *pgEventStore) Create(ctx context.Context, event Event) (*Event, error) { - query := "INSERT INTO events (name, description, start_date, end_date, volunteers_required) VALUES ($1, $2, $3, $4, $5) RETURNING id, created_at" - err := s.db.QueryRowContext(ctx, query, event.Name, event.Description, event.StartDate, event.EndDate, event.VolunteersRequired).Scan(&event.ID, &event.CreatedAt) - if err != nil { - return nil, fmt.Errorf("unable to create and store an event, error %w", err) - } - return &event, nil +func (s *pgEventStore) Create(ctx context.Context, name string, description string, start time.Time, end time.Time, volunteers int, location string) (*Event, error) { + id := 0 + createdAt := time.Time{} + + query := "INSERT INTO events (name, description, start_date, end_date, volunteers_required, location) VALUES ($1, $2, $3, $4, $5, $6) RETURNING id, created_at" + err := s.db.QueryRowContext(ctx, query, name, description, start, end, volunteers, location).Scan(&id, &createdAt) + if err != nil { + return nil, fmt.Errorf("unable to create and store an event, error %w", err) + } + + event := Event{id, name, description, start, end, volunteers, location, createdAt} + return &event, nil } func (s *pgEventStore) Update(ctx context.Context, event Event) (*Event, error) { - query := "UPDATE events SET name = $1, description = $2, start_date = $3, end_date = $4, volunteers_required = $5, WHERE id = $6" + query := "UPDATE events SET name = $1, description = $2, start_date = $3, end_date = $4, volunteers_required = $5, location = $6, WHERE id = $7" - _, err := s.db.ExecContext(ctx, query, event.Name, event.Description, event.StartDate, event.EndDate, event.VolunteersRequired, event.ID) + _, err := s.db.ExecContext(ctx, query, event.Name, event.Description, event.StartDate, event.EndDate, event.VolunteersRequired, event.Location, event.ID) if err != nil { return nil, fmt.Errorf("unable to update event, error %w", err) @@ -95,7 +102,7 @@ func (s *pgEventStore) Update(ctx context.Context, event Event) (*Event, error) return &event, nil } func (s *pgEventStore) Delete(ctx context.Context, id int) error { - query := "DELETE FROM events WHERE id = $1" + query := "DELETE FROM events WHERE id = $1" _, err := s.db.ExecContext(ctx, query, id) @@ -104,5 +111,5 @@ func (s *pgEventStore) Delete(ctx context.Context, id int) error { } - return nil -} \ No newline at end of file + return nil +}