Skip to content

Commit

Permalink
feat: create event api (#8)
Browse files Browse the repository at this point in the history
* implemented post request

* fix: added location to Event struct

* fix: follows convention of struct field tags

* fix: added apimodels package

* fix: edited Create()

* fix: parse string to time object

* fix: naming convention

* chore: rename token

* fix: overwritten changes to .gitignore

---------

Co-authored-by: Rustam Nassyrov <[email protected]>
  • Loading branch information
steph-feng and rustamch authored Mar 12, 2024
1 parent bb0f1ed commit c450524
Show file tree
Hide file tree
Showing 7 changed files with 159 additions and 61 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/push-docker-image.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 10 additions & 0 deletions apimodels/create_event.go
Original file line number Diff line number Diff line change
@@ -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"`
}
3 changes: 2 additions & 1 deletion db/schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -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=
Expand Down
76 changes: 75 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
@@ -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)
})
}
123 changes: 65 additions & 58 deletions store/event.go
Original file line number Diff line number Diff line change
@@ -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)
Expand All @@ -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)

Expand All @@ -104,5 +111,5 @@ func (s *pgEventStore) Delete(ctx context.Context, id int) error {

}

return nil
}
return nil
}

0 comments on commit c450524

Please sign in to comment.