This commit is contained in:
Martin Pander
2023-12-09 19:34:45 +01:00
parent 32346e0aa9
commit 8addda35ea
144 changed files with 7247 additions and 3268 deletions

1
backend/.dockerignore Normal file
View File

@ -0,0 +1 @@
__debug*

30
backend/Dockerfile Normal file
View File

@ -0,0 +1,30 @@
# Start by building the application.
FROM golang:1.21 as builder
# Set the Current Working Directory inside the container
WORKDIR /app
# Copy go mod and sum files
COPY go.mod go.sum ./
# Download all dependencies. Dependencies will be cached if the go.mod and go.sum files are not changed
RUN go mod download
# Copy the source from the current directory to the Working Directory inside the container
COPY . .
# Build the Go app
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -installsuffix cgo -o main .
# Start a new stage from scratch
# Alternatively, you could use a very small base image like alpine, but scratch will be smaller
FROM scratch
# Copy the Pre-built binary file from the previous stage
COPY --from=builder /app/main .
# Expose the port the app runs on
EXPOSE 8080
# Command to run the executable
CMD ["./main"]

BIN
backend/__debug_bin3928492043 Executable file

Binary file not shown.

View File

@ -1,16 +1,10 @@
// package main
// import "fmt"
// func main() {
// fmt.Println("Hello!")
// }
package main
import (
"log"
"net/http"
"os"
"strconv"
"github.com/gorilla/handlers"
@ -21,7 +15,18 @@ import (
)
func main() {
db, err := database.NewPgDatabase("localhost", "dash", "dash", "dash", 15432)
dbHost := os.Getenv("DB_HOST")
dbName := os.Getenv("DB_NAME")
dbUser := os.Getenv("DB_USER")
dbPassword := os.Getenv("DB_PASSWORD")
dbPort := os.Getenv("DB_PORT")
// Convert dbPort to uint16
dbPortUint16, err := strconv.ParseUint(dbPort, 10, 16)
if err != nil {
log.Fatal(err)
}
db, err := database.NewPgDatabase(dbHost, dbUser, dbPassword, dbName, uint16(dbPortUint16))
if err != nil {
log.Fatal(err)
}
@ -34,16 +39,21 @@ func main() {
PlanApiService := service.NewPlanApiService(db, mapper)
PlanApiController := dashapi.NewPlanApiController(PlanApiService)
TrackingApiService := service.NewTrackingApiService(db, mapper)
TrackingApiController := dashapi.NewTrackingApiController(TrackingApiService)
InboxApiService := service.NewInboxApiService(db, mapper)
InboxApiController := dashapi.NewInboxApiController(InboxApiService)
cors := handlers.CORS(
// handlers.AllowedMethods([]string{"GET", "POST", "DELETE"}),
handlers.AllowedMethods([]string{"GET", "POST", "DELETE"}),
handlers.AllowedHeaders([]string{"Accept", "Accept-Language", "Content-Type", "Content-Language", "Origin"}),
handlers.AllowedOrigins([]string{"*"}),
)
router := dashapi.NewRouter(JournalApiController, PlanApiController)
router.Methods("GET", "POST", "DELETE", "OPTIONS")
router := dashapi.NewRouter(JournalApiController, PlanApiController, TrackingApiController, InboxApiController)
// router.Methods("GET", "POST", "DELETE", "OPTIONS")
log.Printf("Starting server.")
// TODO remove listening on all interfaces
log.Fatal(http.ListenAndServe("0.0.0.0:8080", cors(router)))
}

View File

@ -16,6 +16,14 @@ import (
// InboxApiRouter defines the required methods for binding the api requests to a responses for the InboxApi
// The InboxApiRouter implementation should parse necessary information from the http request,
// pass the data to a InboxApiServicer to perform the required actions, then write the service results to the http response.
type InboxApiRouter interface {
AddInboxItem(http.ResponseWriter, *http.Request)
DeleteInboxItem(http.ResponseWriter, *http.Request)
GetInboxItems(http.ResponseWriter, *http.Request)
}
// JournalApiRouter defines the required methods for binding the api requests to a responses for the JournalApi
// The JournalApiRouter implementation should parse necessary information from the http request,
// pass the data to a JournalApiServicer to perform the required actions, then write the service results to the http response.
@ -35,6 +43,25 @@ type PlanApiRouter interface {
SavePlanForMonth(http.ResponseWriter, *http.Request)
SavePlanForWeek(http.ResponseWriter, *http.Request)
}
// TrackingApiRouter defines the required methods for binding the api requests to a responses for the TrackingApi
// The TrackingApiRouter implementation should parse necessary information from the http request,
// pass the data to a TrackingApiServicer to perform the required actions, then write the service results to the http response.
type TrackingApiRouter interface {
GetTrackingCategories(http.ResponseWriter, *http.Request)
GetTrackingEntryForDate(http.ResponseWriter, *http.Request)
WriteTrackingEntry(http.ResponseWriter, *http.Request)
}
// InboxApiServicer defines the api actions for the InboxApi service
// This interface intended to stay up to date with the openapi yaml used to generate it,
// while the service implementation can be ignored with the .openapi-generator-ignore file
// and updated with the logic required for the API.
type InboxApiServicer interface {
AddInboxItem(context.Context, InboxItem) (ImplResponse, error)
DeleteInboxItem(context.Context, int32) (ImplResponse, error)
GetInboxItems(context.Context) (ImplResponse, error)
}
// JournalApiServicer defines the api actions for the JournalApi service
@ -60,3 +87,14 @@ type PlanApiServicer interface {
SavePlanForMonth(context.Context, PlanMonth) (ImplResponse, error)
SavePlanForWeek(context.Context, PlanWeek) (ImplResponse, error)
}
// TrackingApiServicer defines the api actions for the TrackingApi service
// This interface intended to stay up to date with the openapi yaml used to generate it,
// while the service implementation can be ignored with the .openapi-generator-ignore file
// and updated with the logic required for the API.
type TrackingApiServicer interface {
GetTrackingCategories(context.Context) (ImplResponse, error)
GetTrackingEntryForDate(context.Context, string) (ImplResponse, error)
WriteTrackingEntry(context.Context, TrackingEntry) (ImplResponse, error)
}

View File

@ -0,0 +1,129 @@
/*
* Dash API
*
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* API version: 0.1
* Generated by: OpenAPI Generator (https://openapi-generator.tech)
*/
package dashapi
import (
"encoding/json"
"net/http"
"strings"
"github.com/gorilla/mux"
)
// InboxApiController binds http requests to an api service and writes the service results to the http response
type InboxApiController struct {
service InboxApiServicer
errorHandler ErrorHandler
}
// InboxApiOption for how the controller is set up.
type InboxApiOption func(*InboxApiController)
// WithInboxApiErrorHandler inject ErrorHandler into controller
func WithInboxApiErrorHandler(h ErrorHandler) InboxApiOption {
return func(c *InboxApiController) {
c.errorHandler = h
}
}
// NewInboxApiController creates a default api controller
func NewInboxApiController(s InboxApiServicer, opts ...InboxApiOption) Router {
controller := &InboxApiController{
service: s,
errorHandler: DefaultErrorHandler,
}
for _, opt := range opts {
opt(controller)
}
return controller
}
// Routes returns all the api routes for the InboxApiController
func (c *InboxApiController) Routes() Routes {
return Routes{
{
"AddInboxItem",
strings.ToUpper("Post"),
"/api/v1/inbox/",
c.AddInboxItem,
},
{
"DeleteInboxItem",
strings.ToUpper("Delete"),
"/api/v1/inbox/{item}",
c.DeleteInboxItem,
},
{
"GetInboxItems",
strings.ToUpper("Get"),
"/api/v1/inbox/",
c.GetInboxItems,
},
}
}
// AddInboxItem -
func (c *InboxApiController) AddInboxItem(w http.ResponseWriter, r *http.Request) {
inboxItemParam := InboxItem{}
d := json.NewDecoder(r.Body)
d.DisallowUnknownFields()
if err := d.Decode(&inboxItemParam); err != nil {
c.errorHandler(w, r, &ParsingError{Err: err}, nil)
return
}
if err := AssertInboxItemRequired(inboxItemParam); err != nil {
c.errorHandler(w, r, err, nil)
return
}
result, err := c.service.AddInboxItem(r.Context(), inboxItemParam)
// If an error occurred, encode the error with the status code
if err != nil {
c.errorHandler(w, r, err, &result)
return
}
// If no error, encode the body and the result code
EncodeJSONResponse(result.Body, &result.Code, w)
}
// DeleteInboxItem -
func (c *InboxApiController) DeleteInboxItem(w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
itemParam, err := parseInt32Parameter(params["item"], true)
if err != nil {
c.errorHandler(w, r, &ParsingError{Err: err}, nil)
return
}
result, err := c.service.DeleteInboxItem(r.Context(), itemParam)
// If an error occurred, encode the error with the status code
if err != nil {
c.errorHandler(w, r, err, &result)
return
}
// If no error, encode the body and the result code
EncodeJSONResponse(result.Body, &result.Code, w)
}
// GetInboxItems -
func (c *InboxApiController) GetInboxItems(w http.ResponseWriter, r *http.Request) {
result, err := c.service.GetInboxItems(r.Context())
// If an error occurred, encode the error with the status code
if err != nil {
c.errorHandler(w, r, err, &result)
return
}
// If no error, encode the body and the result code
EncodeJSONResponse(result.Body, &result.Code, w)
}

View File

@ -0,0 +1,60 @@
/*
* Dash API
*
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* API version: 0.1
* Generated by: OpenAPI Generator (https://openapi-generator.tech)
*/
package dashapi
import (
"context"
"net/http"
"errors"
)
// InboxApiService is a service that implements the logic for the InboxApiServicer
// This service should implement the business logic for every endpoint for the InboxApi API.
// Include any external packages or services that will be required by this service.
type InboxApiService struct {
}
// NewInboxApiService creates a default api service
func NewInboxApiService() InboxApiServicer {
return &InboxApiService{}
}
// AddInboxItem -
func (s *InboxApiService) AddInboxItem(ctx context.Context, inboxItem InboxItem) (ImplResponse, error) {
// TODO - update AddInboxItem with the required logic for this service method.
// Add api_inbox_service.go to the .openapi-generator-ignore to avoid overwriting this service implementation when updating open api generation.
//TODO: Uncomment the next line to return response Response(200, {}) or use other options such as http.Ok ...
//return Response(200, nil),nil
return Response(http.StatusNotImplemented, nil), errors.New("AddInboxItem method not implemented")
}
// DeleteInboxItem -
func (s *InboxApiService) DeleteInboxItem(ctx context.Context, item int32) (ImplResponse, error) {
// TODO - update DeleteInboxItem with the required logic for this service method.
// Add api_inbox_service.go to the .openapi-generator-ignore to avoid overwriting this service implementation when updating open api generation.
//TODO: Uncomment the next line to return response Response(200, {}) or use other options such as http.Ok ...
//return Response(200, nil),nil
return Response(http.StatusNotImplemented, nil), errors.New("DeleteInboxItem method not implemented")
}
// GetInboxItems -
func (s *InboxApiService) GetInboxItems(ctx context.Context) (ImplResponse, error) {
// TODO - update GetInboxItems with the required logic for this service method.
// Add api_inbox_service.go to the .openapi-generator-ignore to avoid overwriting this service implementation when updating open api generation.
//TODO: Uncomment the next line to return response Response(200, []InboxItem{}) or use other options such as http.Ok ...
//return Response(200, []InboxItem{}), nil
return Response(http.StatusNotImplemented, nil), errors.New("GetInboxItems method not implemented")
}

View File

@ -0,0 +1,125 @@
/*
* Dash API
*
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* API version: 0.1
* Generated by: OpenAPI Generator (https://openapi-generator.tech)
*/
package dashapi
import (
"encoding/json"
"net/http"
"strings"
"github.com/gorilla/mux"
)
// TrackingApiController binds http requests to an api service and writes the service results to the http response
type TrackingApiController struct {
service TrackingApiServicer
errorHandler ErrorHandler
}
// TrackingApiOption for how the controller is set up.
type TrackingApiOption func(*TrackingApiController)
// WithTrackingApiErrorHandler inject ErrorHandler into controller
func WithTrackingApiErrorHandler(h ErrorHandler) TrackingApiOption {
return func(c *TrackingApiController) {
c.errorHandler = h
}
}
// NewTrackingApiController creates a default api controller
func NewTrackingApiController(s TrackingApiServicer, opts ...TrackingApiOption) Router {
controller := &TrackingApiController{
service: s,
errorHandler: DefaultErrorHandler,
}
for _, opt := range opts {
opt(controller)
}
return controller
}
// Routes returns all the api routes for the TrackingApiController
func (c *TrackingApiController) Routes() Routes {
return Routes{
{
"GetTrackingCategories",
strings.ToUpper("Get"),
"/api/v1/tracking/categories",
c.GetTrackingCategories,
},
{
"GetTrackingEntryForDate",
strings.ToUpper("Get"),
"/api/v1/tracking/entry/{date}",
c.GetTrackingEntryForDate,
},
{
"WriteTrackingEntry",
strings.ToUpper("Post"),
"/api/v1/tracking/entry",
c.WriteTrackingEntry,
},
}
}
// GetTrackingCategories -
func (c *TrackingApiController) GetTrackingCategories(w http.ResponseWriter, r *http.Request) {
result, err := c.service.GetTrackingCategories(r.Context())
// If an error occurred, encode the error with the status code
if err != nil {
c.errorHandler(w, r, err, &result)
return
}
// If no error, encode the body and the result code
EncodeJSONResponse(result.Body, &result.Code, w)
}
// GetTrackingEntryForDate -
func (c *TrackingApiController) GetTrackingEntryForDate(w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
dateParam := params["date"]
result, err := c.service.GetTrackingEntryForDate(r.Context(), dateParam)
// If an error occurred, encode the error with the status code
if err != nil {
c.errorHandler(w, r, err, &result)
return
}
// If no error, encode the body and the result code
EncodeJSONResponse(result.Body, &result.Code, w)
}
// WriteTrackingEntry -
func (c *TrackingApiController) WriteTrackingEntry(w http.ResponseWriter, r *http.Request) {
trackingEntryParam := TrackingEntry{}
d := json.NewDecoder(r.Body)
d.DisallowUnknownFields()
if err := d.Decode(&trackingEntryParam); err != nil {
c.errorHandler(w, r, &ParsingError{Err: err}, nil)
return
}
if err := AssertTrackingEntryRequired(trackingEntryParam); err != nil {
c.errorHandler(w, r, err, nil)
return
}
result, err := c.service.WriteTrackingEntry(r.Context(), trackingEntryParam)
// If an error occurred, encode the error with the status code
if err != nil {
c.errorHandler(w, r, err, &result)
return
}
// If no error, encode the body and the result code
EncodeJSONResponse(result.Body, &result.Code, w)
}

View File

@ -0,0 +1,60 @@
/*
* Dash API
*
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* API version: 0.1
* Generated by: OpenAPI Generator (https://openapi-generator.tech)
*/
package dashapi
import (
"context"
"net/http"
"errors"
)
// TrackingApiService is a service that implements the logic for the TrackingApiServicer
// This service should implement the business logic for every endpoint for the TrackingApi API.
// Include any external packages or services that will be required by this service.
type TrackingApiService struct {
}
// NewTrackingApiService creates a default api service
func NewTrackingApiService() TrackingApiServicer {
return &TrackingApiService{}
}
// GetTrackingCategories -
func (s *TrackingApiService) GetTrackingCategories(ctx context.Context) (ImplResponse, error) {
// TODO - update GetTrackingCategories with the required logic for this service method.
// Add api_tracking_service.go to the .openapi-generator-ignore to avoid overwriting this service implementation when updating open api generation.
//TODO: Uncomment the next line to return response Response(200, TrackingCategories{}) or use other options such as http.Ok ...
//return Response(200, TrackingCategories{}), nil
return Response(http.StatusNotImplemented, nil), errors.New("GetTrackingCategories method not implemented")
}
// GetTrackingEntryForDate -
func (s *TrackingApiService) GetTrackingEntryForDate(ctx context.Context, date string) (ImplResponse, error) {
// TODO - update GetTrackingEntryForDate with the required logic for this service method.
// Add api_tracking_service.go to the .openapi-generator-ignore to avoid overwriting this service implementation when updating open api generation.
//TODO: Uncomment the next line to return response Response(200, TrackingEntry{}) or use other options such as http.Ok ...
//return Response(200, TrackingEntry{}), nil
return Response(http.StatusNotImplemented, nil), errors.New("GetTrackingEntryForDate method not implemented")
}
// WriteTrackingEntry -
func (s *TrackingApiService) WriteTrackingEntry(ctx context.Context, trackingEntry TrackingEntry) (ImplResponse, error) {
// TODO - update WriteTrackingEntry with the required logic for this service method.
// Add api_tracking_service.go to the .openapi-generator-ignore to avoid overwriting this service implementation when updating open api generation.
//TODO: Uncomment the next line to return response Response(200, {}) or use other options such as http.Ok ...
//return Response(200, nil),nil
return Response(http.StatusNotImplemented, nil), errors.New("WriteTrackingEntry method not implemented")
}

View File

@ -0,0 +1,43 @@
/*
* Dash API
*
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* API version: 0.1
* Generated by: OpenAPI Generator (https://openapi-generator.tech)
*/
package dashapi
type InboxItem struct {
Id int32 `json:"id,omitempty"`
Item string `json:"item"`
}
// AssertInboxItemRequired checks if the required fields are not zero-ed
func AssertInboxItemRequired(obj InboxItem) error {
elements := map[string]interface{}{
"item": obj.Item,
}
for name, el := range elements {
if isZero := IsZeroValue(el); isZero {
return &RequiredError{Field: name}
}
}
return nil
}
// AssertRecurseInboxItemRequired recursively checks if required fields are not zero-ed in a nested slice.
// Accepts only nested slice of InboxItem (e.g. [][]InboxItem), otherwise ErrTypeAssertionError is thrown.
func AssertRecurseInboxItemRequired(objSlice interface{}) error {
return AssertRecurseInterfaceRequired(objSlice, func(obj interface{}) error {
aInboxItem, ok := obj.(InboxItem)
if !ok {
return ErrTypeAssertionError
}
return AssertInboxItemRequired(aInboxItem)
})
}

View File

@ -13,7 +13,7 @@ type PlanWeekItem struct {
Item string `json:"item"`
NumTodos int32 `json:"numTodos,omitempty"`
NumTodo int32 `json:"numTodo,omitempty"`
NumDone int32 `json:"numDone,omitempty"`
}

View File

@ -0,0 +1,37 @@
/*
* Dash API
*
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* API version: 0.1
* Generated by: OpenAPI Generator (https://openapi-generator.tech)
*/
package dashapi
type TrackingCategories struct {
Categories []TrackingCategory `json:"categories,omitempty"`
}
// AssertTrackingCategoriesRequired checks if the required fields are not zero-ed
func AssertTrackingCategoriesRequired(obj TrackingCategories) error {
for _, el := range obj.Categories {
if err := AssertTrackingCategoryRequired(el); err != nil {
return err
}
}
return nil
}
// AssertRecurseTrackingCategoriesRequired recursively checks if required fields are not zero-ed in a nested slice.
// Accepts only nested slice of TrackingCategories (e.g. [][]TrackingCategories), otherwise ErrTypeAssertionError is thrown.
func AssertRecurseTrackingCategoriesRequired(objSlice interface{}) error {
return AssertRecurseInterfaceRequired(objSlice, func(obj interface{}) error {
aTrackingCategories, ok := obj.(TrackingCategories)
if !ok {
return ErrTypeAssertionError
}
return AssertTrackingCategoriesRequired(aTrackingCategories)
})
}

View File

@ -0,0 +1,46 @@
/*
* Dash API
*
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* API version: 0.1
* Generated by: OpenAPI Generator (https://openapi-generator.tech)
*/
package dashapi
type TrackingCategory struct {
Type string `json:"type"`
Name string `json:"name"`
Items []string `json:"items,omitempty"`
}
// AssertTrackingCategoryRequired checks if the required fields are not zero-ed
func AssertTrackingCategoryRequired(obj TrackingCategory) error {
elements := map[string]interface{}{
"type": obj.Type,
"name": obj.Name,
}
for name, el := range elements {
if isZero := IsZeroValue(el); isZero {
return &RequiredError{Field: name}
}
}
return nil
}
// AssertRecurseTrackingCategoryRequired recursively checks if required fields are not zero-ed in a nested slice.
// Accepts only nested slice of TrackingCategory (e.g. [][]TrackingCategory), otherwise ErrTypeAssertionError is thrown.
func AssertRecurseTrackingCategoryRequired(objSlice interface{}) error {
return AssertRecurseInterfaceRequired(objSlice, func(obj interface{}) error {
aTrackingCategory, ok := obj.(TrackingCategory)
if !ok {
return ErrTypeAssertionError
}
return AssertTrackingCategoryRequired(aTrackingCategory)
})
}

View File

@ -0,0 +1,49 @@
/*
* Dash API
*
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* API version: 0.1
* Generated by: OpenAPI Generator (https://openapi-generator.tech)
*/
package dashapi
type TrackingEntry struct {
Date string `json:"date"`
Items []TrackingItem `json:"items"`
}
// AssertTrackingEntryRequired checks if the required fields are not zero-ed
func AssertTrackingEntryRequired(obj TrackingEntry) error {
elements := map[string]interface{}{
"date": obj.Date,
"items": obj.Items,
}
for name, el := range elements {
if isZero := IsZeroValue(el); isZero {
return &RequiredError{Field: name}
}
}
for _, el := range obj.Items {
if err := AssertTrackingItemRequired(el); err != nil {
return err
}
}
return nil
}
// AssertRecurseTrackingEntryRequired recursively checks if required fields are not zero-ed in a nested slice.
// Accepts only nested slice of TrackingEntry (e.g. [][]TrackingEntry), otherwise ErrTypeAssertionError is thrown.
func AssertRecurseTrackingEntryRequired(objSlice interface{}) error {
return AssertRecurseInterfaceRequired(objSlice, func(obj interface{}) error {
aTrackingEntry, ok := obj.(TrackingEntry)
if !ok {
return ErrTypeAssertionError
}
return AssertTrackingEntryRequired(aTrackingEntry)
})
}

View File

@ -0,0 +1,47 @@
/*
* Dash API
*
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* API version: 0.1
* Generated by: OpenAPI Generator (https://openapi-generator.tech)
*/
package dashapi
type TrackingItem struct {
Date string `json:"date"`
Type string `json:"type"`
Value string `json:"value"`
}
// AssertTrackingItemRequired checks if the required fields are not zero-ed
func AssertTrackingItemRequired(obj TrackingItem) error {
elements := map[string]interface{}{
"date": obj.Date,
"type": obj.Type,
"value": obj.Value,
}
for name, el := range elements {
if isZero := IsZeroValue(el); isZero {
return &RequiredError{Field: name}
}
}
return nil
}
// AssertRecurseTrackingItemRequired recursively checks if required fields are not zero-ed in a nested slice.
// Accepts only nested slice of TrackingItem (e.g. [][]TrackingItem), otherwise ErrTypeAssertionError is thrown.
func AssertRecurseTrackingItemRequired(objSlice interface{}) error {
return AssertRecurseInterfaceRequired(objSlice, func(obj interface{}) error {
aTrackingItem, ok := obj.(TrackingItem)
if !ok {
return ErrTypeAssertionError
}
return AssertTrackingItemRequired(aTrackingItem)
})
}

View File

@ -0,0 +1,6 @@
package models
type Inbox struct {
Id int32 `gorm:"primaryKey"`
Item string
}

View File

@ -17,8 +17,15 @@ type PlanDay struct {
type PlanWeek struct {
Date datatypes.Date `gorm:"primaryKey"`
Items datatypes.JSON
// Items []PlanWeekItem
}
// type PlanWeekItem struct {
// Item string
// NumTodo int32
// NumDone int32
// }
type PlanMonth struct {
Date datatypes.Date `gorm:"primaryKey"`
Items datatypes.JSON

View File

@ -0,0 +1,19 @@
package models
import (
"time"
"gorm.io/datatypes"
)
type TrackingCategory struct {
Name string `gorm:"primaryKey"`
Type string
Items datatypes.JSON
}
type TrackingItem struct {
Date time.Time `gorm:"primaryKey"`
Type string `gorm:"primaryKey"`
Value string `json:"value"`
}

View File

@ -42,6 +42,9 @@ func (db *PgDatabase) migrate() {
db.Db.AutoMigrate(&models.PlanDay{})
db.Db.AutoMigrate(&models.PlanWeek{})
db.Db.AutoMigrate(&models.PlanMonth{})
db.Db.AutoMigrate(&models.TrackingCategory{})
db.Db.AutoMigrate(&models.TrackingItem{})
db.Db.AutoMigrate(&models.Inbox{})
}
func (db *PgDatabase) WriteJournalEntry(entry interface{}) error {
@ -111,7 +114,80 @@ func (db *PgDatabase) WritePlanMonth(entry interface{}) error {
func (db *PgDatabase) GetPlanMonthForDate(date time.Time) (interface{}, error) {
entry := models.PlanMonth{Date: datatypes.Date(date)}
db.Db.First(&entry)
err := db.Db.First(&entry).Error
if err != nil {
log.Print("Error getting plan month from database.")
return nil, err
}
return entry, nil
}
func (db *PgDatabase) GetTrackingItemsForDate(date time.Time) (interface{}, error) {
entries := []models.TrackingItem{}
err := db.Db.Where("date = ?", datatypes.Date(date)).Find(&entries).Error
if err != nil {
log.Print("Error getting tracking items from database.")
return nil, err
}
return entries, nil
}
func (db *PgDatabase) WriteTrackingItems(entries interface{}) error {
trackingItems := entries.([]models.TrackingItem)
if len(trackingItems) == 0 {
return nil
}
err := db.Db.Clauses(clause.OnConflict{UpdateAll: true}).Create(&trackingItems).Error
if err != nil {
log.Print("Error writing tracking items to database.")
return err
}
return nil
}
func (db *PgDatabase) GetTrackingCategories() (interface{}, error) {
categories := []models.TrackingCategory{}
err := db.Db.Find(&categories).Error
if err != nil {
log.Print("Error getting tracking categories from database.")
return nil, err
}
return categories, nil
}
func (db *PgDatabase) WriteInboxItem(entry interface{}) error {
inboxItem := entry.(models.Inbox)
err := db.Db.Clauses(clause.OnConflict{UpdateAll: true}).Create(&inboxItem).Error
if err != nil {
log.Print("Error writing inbox item to database.")
return err
}
return nil
}
func (db *PgDatabase) DeleteInboxItem(id int32) error {
err := db.Db.Delete(&models.Inbox{}, id).Error
if err != nil {
log.Print("Error deleting inbox item from database.")
return err
}
return nil
}
func (db *PgDatabase) GetInboxItems() ([]interface{}, error) {
var inboxItems []models.Inbox
db.Db.Find(&inboxItems)
var interfaceSlice []interface{} = make([]interface{}, len(inboxItems))
for i, d := range inboxItems {
interfaceSlice[i] = d
}
return interfaceSlice, nil
}

View File

@ -17,6 +17,13 @@ type Mapper interface {
PlanMonthApiToDs(api interface{}) (db interface{})
PlanMonthDsToApi(db interface{}) (api interface{})
TrackingCategoriesDbToApi(db interface{}) (api interface{})
TrackingEntryDbToApi(db interface{}) (api interface{})
TrackingEntryApiToDb(api interface{}) (db interface{})
InboxApiToDs(api interface{}) (db interface{})
InboxDsToApi(db interface{}) (api interface{})
StringToDate(string) (time.Time, error)
DateToString(time.Time) string
}

View File

@ -206,20 +206,172 @@ func (mapper MapperImpl) PlanDayDsToApi(dm interface{}) interface{} {
}
}
func (mapper MapperImpl) PlanWeekApiToDs(api interface{}) (db interface{}) {
return new(interface{})
func (mapper MapperImpl) PlanWeekApiToDs(am interface{}) (dm interface{}) {
apimodel := am.(api.PlanWeek)
date, err := mapper.StringToDate(apimodel.Date)
if err != nil {
log.Printf("[ERROR] Could not parse date `%s`", apimodel.Date)
}
items, err := json.Marshal(apimodel.Items)
if err != nil {
items = nil
}
return db.PlanWeek{
Date: datatypes.Date(date),
Items: items,
}
}
func (mapper MapperImpl) PlanWeekDsToApi(api interface{}) (db interface{}) {
return new(interface{})
func (mapper MapperImpl) PlanWeekDsToApi(dm interface{}) (am interface{}) {
dbmodel := dm.(db.PlanWeek)
dateValue, err := dbmodel.Date.Value()
var date string
if err != nil {
date = ""
} else {
date = mapper.DateToString(dateValue.(time.Time))
}
var items []api.PlanWeekItem
err = json.Unmarshal(dbmodel.Items, &items)
if err != nil {
items = nil
}
return api.PlanWeek{
Date: date,
Items: items,
}
}
func (mapper MapperImpl) PlanMonthApiToDs(api interface{}) (db interface{}) {
return new(interface{})
// write a function that takes a month and returns a plan month
func (mapper MapperImpl) PlanMonthApiToDs(am interface{}) (dm interface{}) {
apimodel := am.(api.PlanMonth)
date, err := mapper.StringToDate(apimodel.Date)
if err != nil {
log.Printf("[ERROR] Could not parse date `%s`", apimodel.Date)
}
items, err := json.Marshal(apimodel.Items)
if err != nil {
items = nil
}
return db.PlanMonth{
Date: datatypes.Date(date),
Items: items,
}
}
func (mapper MapperImpl) PlanMonthDsToApi(api interface{}) (db interface{}) {
return new(interface{})
// write a function that takes a plan month and returns a month
func (mapper MapperImpl) PlanMonthDsToApi(dm interface{}) (am interface{}) {
dbmodel := dm.(db.PlanMonth)
dateValue, err := dbmodel.Date.Value()
var date string
if err != nil {
date = ""
} else {
date = mapper.DateToString(dateValue.(time.Time))
}
var items []string
err = json.Unmarshal(dbmodel.Items, &items)
if err != nil {
items = nil
}
return api.PlanMonth{
Date: date,
Items: items,
}
}
func (mapper MapperImpl) TrackingCategoriesDbToApi(dm interface{}) (am interface{}) {
dbmodel := dm.([]db.TrackingCategory)
var categories []api.TrackingCategory
for _, category := range dbmodel {
var items []string
err := json.Unmarshal(category.Items, &items)
if err != nil {
items = nil
}
categories = append(categories, api.TrackingCategory{
Name: category.Name,
Type: category.Type,
Items: items,
})
}
return api.TrackingCategories{
Categories: categories,
}
}
func (mapper MapperImpl) TrackingEntryDbToApi(dm interface{}) (am interface{}) {
dbmodel := dm.([]db.TrackingItem)
var items []api.TrackingItem
for _, item := range dbmodel {
items = append(items, api.TrackingItem{
Date: mapper.DateToString(item.Date),
Type: item.Type,
Value: item.Value,
})
}
if len(items) == 0 {
return api.TrackingEntry{}
} else {
return api.TrackingEntry{
Date: mapper.DateToString(dbmodel[0].Date),
Items: items,
}
}
}
func (mapper MapperImpl) TrackingEntryApiToDb(am interface{}) (dm interface{}) {
apimodel := am.(api.TrackingEntry)
date, err := mapper.StringToDate(apimodel.Date)
if err != nil {
log.Printf("[ERROR] Could not parse date `%s`", apimodel.Date)
}
var items []db.TrackingItem
for _, item := range apimodel.Items {
items = append(items, db.TrackingItem{
Date: date,
Type: item.Type,
Value: item.Value,
})
}
return items
}
func (mapper MapperImpl) InboxApiToDs(am interface{}) (dm interface{}) {
apimodel := am.(api.InboxItem)
return db.Inbox{
Id: apimodel.Id,
Item: apimodel.Item,
}
}
func (mapper MapperImpl) InboxDsToApi(dm interface{}) (am interface{}) {
dbmodel := dm.(db.Inbox)
return api.InboxItem{
Id: dbmodel.Id,
Item: dbmodel.Item,
}
}
func (mapper MapperImpl) StringToDate(dateString string) (time.Time, error) {

View File

@ -407,6 +407,150 @@ func TestMapperImpl_PlanDayDsToApi(t *testing.T) {
}
}
func TestMapperImpl_PlanWeekApiToDs(t *testing.T) {
date, _ := time.Parse("2006-01-02", "2022-02-18")
items, _ := json.Marshal([]dashapi.PlanWeekItem{
{
Item: "test1",
NumTodo: 3,
NumDone: 1,
},
{
Item: "test2",
NumTodo: 3,
NumDone: 3,
}})
empty, _ := json.Marshal(nil)
type args struct {
am interface{}
}
tests := []struct {
name string
mapper MapperImpl
args args
wantDm interface{}
}{
{
name: "Full Object",
mapper: MapperImpl{},
args: args{
am: dashapi.PlanWeek{
Date: "2022-02-18",
Items: []dashapi.PlanWeekItem{
{
Item: "test1",
NumTodo: 3,
NumDone: 1,
},
{
Item: "test2",
NumTodo: 3,
NumDone: 3,
},
}}},
wantDm: models.PlanWeek{
Date: datatypes.Date(date),
Items: datatypes.JSON(items),
},
},
{
name: "Empty Object",
mapper: MapperImpl{},
args: args{
am: dashapi.PlanWeek{
Date: "2022-02-18",
Items: nil,
}},
wantDm: models.PlanWeek{
Date: datatypes.Date(date),
Items: datatypes.JSON(empty),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mapper := MapperImpl{}
if gotDm := mapper.PlanWeekApiToDs(tt.args.am); !reflect.DeepEqual(gotDm, tt.wantDm) {
t.Errorf("MapperImpl.PlanWeekApiToDs() = %v, want %v", gotDm, tt.wantDm)
}
})
}
}
// write a test for PlanMonthApiToDs
func TestMapperImpl_PlanWeekDsToApi(t *testing.T) {
date, _ := time.Parse("2006-01-02", "2022-02-18")
items, _ := json.Marshal([]dashapi.PlanWeekItem{
{
Item: "test1",
NumTodo: 3,
NumDone: 1,
},
{
Item: "test2",
NumTodo: 3,
NumDone: 3,
}})
empty, _ := json.Marshal(nil)
type args struct {
dm interface{}
}
tests := []struct {
name string
mapper MapperImpl
args args
wantAm interface{}
}{
{
name: "Full Object",
mapper: MapperImpl{},
args: args{
dm: models.PlanWeek{
Date: datatypes.Date(date),
Items: datatypes.JSON(items),
}},
wantAm: dashapi.PlanWeek{
Date: "2022-02-18",
Items: []dashapi.PlanWeekItem{
{
Item: "test1",
NumTodo: 3,
NumDone: 1,
},
{
Item: "test2",
NumTodo: 3,
NumDone: 3,
}},
},
},
{
name: "Empty Object",
mapper: MapperImpl{},
args: args{
dm: models.PlanWeek{
Date: datatypes.Date(date),
Items: datatypes.JSON(empty),
}},
wantAm: dashapi.PlanWeek{
Date: "2022-02-18",
Items: nil,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mapper := MapperImpl{}
if gotAm := mapper.PlanWeekDsToApi(tt.args.dm); !reflect.DeepEqual(gotAm, tt.wantAm) {
t.Errorf("MapperImpl.PlanWeekDsToApi() = %v, want %v", gotAm, tt.wantAm)
}
})
}
}
func TestMapperImpl_StringToDate(t *testing.T) {
type args struct {
dateString string
@ -480,3 +624,25 @@ func TestMapperImpl_DateToString(t *testing.T) {
})
}
}
func TestMapperImpl_InboxApiToDs(t *testing.T) {
type args struct {
am interface{}
}
tests := []struct {
name string
mapper MapperImpl
args args
wantDm interface{}
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mapper := MapperImpl{}
if gotDm := mapper.InboxApiToDs(tt.args.am); !reflect.DeepEqual(gotDm, tt.wantDm) {
t.Errorf("MapperImpl.InboxApiToDs() = %v, want %v", gotDm, tt.wantDm)
}
})
}
}

View File

@ -0,0 +1,65 @@
/*
* Dash API
*
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* API version: 0.1
* Generated by: OpenAPI Generator (https://openapi-generator.tech)
*/
package service
import (
"context"
"errors"
"net/http"
dashapi "github.com/moustachioed/dash/backend/dashapi"
"github.com/moustachioed/dash/backend/mapping"
)
// InboxApiService is a service that implements the logic for the InboxApiServicer
// This service should implement the business logic for every endpoint for the InboxApi API.
// Include any external packages or services that will be required by this service.
type InboxApiService struct {
ds DataStore
mapper mapping.Mapper
}
// NewInboxApiService creates a default api service
func NewInboxApiService(ds DataStore, mapper mapping.Mapper) dashapi.InboxApiServicer {
return &InboxApiService{
ds: ds,
mapper: mapper,
}
}
// AddInboxItem -
func (s *InboxApiService) AddInboxItem(ctx context.Context, inboxItem dashapi.InboxItem) (dashapi.ImplResponse, error) {
item := s.mapper.InboxApiToDs(inboxItem)
s.ds.WriteInboxItem(item)
return dashapi.Response(http.StatusOK, nil), nil
}
// DeleteInboxItem -
func (s *InboxApiService) DeleteInboxItem(ctx context.Context, item int32) (dashapi.ImplResponse, error) {
s.ds.DeleteInboxItem(item)
return dashapi.Response(http.StatusOK, nil), nil
}
// GetInboxItems -
func (s *InboxApiService) GetInboxItems(ctx context.Context) (dashapi.ImplResponse, error) {
items, err := s.ds.GetInboxItems()
if err != nil {
return dashapi.Response(http.StatusInternalServerError, nil), errors.New("Could not get inbox items")
}
apiItems := make([]dashapi.InboxItem, len(items))
for i, item := range items {
apiItems[i] = s.mapper.InboxDsToApi(item).(dashapi.InboxItem)
}
return dashapi.Response(http.StatusOK, apiItems), nil
}

View File

@ -45,8 +45,8 @@ func (s *PlanApiService) GetPlanDayForDate(ctx context.Context, date string) (da
}
dbEntry, _ := s.ds.GetPlanDayForDate(d)
planDay := s.mapper.PlanDayDsToApi(dbEntry)
return dashapi.Response(200, planDay), nil
plan := s.mapper.PlanDayDsToApi(dbEntry)
return dashapi.Response(200, plan), nil
}
// GetPlanMonthForDate -
@ -62,13 +62,14 @@ func (s *PlanApiService) GetPlanMonthForDate(ctx context.Context, date string) (
// GetPlanWeekForDate -
func (s *PlanApiService) GetPlanWeekForDate(ctx context.Context, date string) (dashapi.ImplResponse, error) {
// TODO - update GetPlanWeekForDate with the required logic for this service method.
// Add api_plan_service.go to the .openapi-generator-ignore to avoid overwriting this service implementation when updating open api generation.
d, err := s.mapper.StringToDate(date)
if err != nil {
log.Fatal(err)
}
//TODO: Uncomment the next line to return response Response(200, PlanWeek{}) or use other options such as http.Ok ...
//return Response(200, PlanWeek{}), nil
return dashapi.Response(http.StatusNotImplemented, nil), errors.New("GetPlanWeekForDate method not implemented")
dbEntry, _ := s.ds.GetPlanWeekForDate(d)
plan := s.mapper.PlanWeekDsToApi(dbEntry)
return dashapi.Response(200, plan), nil
}
// SavePlanForDay -
@ -92,11 +93,8 @@ func (s *PlanApiService) SavePlanForMonth(ctx context.Context, planMonth dashapi
// SavePlanForWeek -
func (s *PlanApiService) SavePlanForWeek(ctx context.Context, planWeek dashapi.PlanWeek) (dashapi.ImplResponse, error) {
// TODO - update SavePlanForWeek with the required logic for this service method.
// Add api_plan_service.go to the .openapi-generator-ignore to avoid overwriting this service implementation when updating open api generation.
plan := s.mapper.PlanWeekApiToDs(planWeek)
s.ds.WritePlanWeek(plan)
//TODO: Uncomment the next line to return response Response(200, {}) or use other options such as http.Ok ...
//return Response(200, nil),nil
return dashapi.Response(http.StatusNotImplemented, nil), errors.New("SavePlanForWeek method not implemented")
return dashapi.Response(200, nil), nil
}

View File

@ -0,0 +1,79 @@
/*
* Dash API
*
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* API version: 0.1
* Generated by: OpenAPI Generator (https://openapi-generator.tech)
*/
package service
import (
"context"
"net/http"
dashapi "github.com/moustachioed/dash/backend/dashapi"
"github.com/moustachioed/dash/backend/mapping"
)
// TrackingApiService is a service that implements the logic for the TrackingApiServicer
// This service should implement the business logic for every endpoint for the TrackingApi API.
// Include any external packages or services that will be required by this service.
type TrackingApiService struct {
ds DataStore
mapper mapping.Mapper
}
// NewTrackingApiService creates a default api service
func NewTrackingApiService(ds DataStore, mapper mapping.Mapper) dashapi.TrackingApiServicer {
return &TrackingApiService{
ds: ds,
mapper: mapper,
}
}
// GetTrackingCategories -
func (s *TrackingApiService) GetTrackingCategories(ctx context.Context) (dashapi.ImplResponse, error) {
dbcategories, err := s.ds.GetTrackingCategories()
if err != nil {
return dashapi.Response(http.StatusInternalServerError, nil), err
}
categories := s.mapper.TrackingCategoriesDbToApi(dbcategories).(dashapi.TrackingCategories)
return dashapi.Response(http.StatusOK, categories), nil
}
// GetTrackingEntryForDate -
func (s *TrackingApiService) GetTrackingEntryForDate(ctx context.Context, date string) (dashapi.ImplResponse, error) {
d, err := s.mapper.StringToDate(date)
if err != nil {
return dashapi.Response(http.StatusInternalServerError, nil), err
}
dbentry, err := s.ds.GetTrackingItemsForDate(d)
if err != nil {
return dashapi.Response(http.StatusInternalServerError, nil), err
}
entry := s.mapper.TrackingEntryDbToApi(dbentry).(dashapi.TrackingEntry)
if entry.Date == "" {
entry.Date = date
entry.Items = []dashapi.TrackingItem{}
}
return dashapi.Response(http.StatusOK, entry), nil
}
// WriteTrackingEntry -
func (s *TrackingApiService) WriteTrackingEntry(ctx context.Context, trackingEntry dashapi.TrackingEntry) (dashapi.ImplResponse, error) {
entry := s.mapper.TrackingEntryApiToDb(trackingEntry)
err := s.ds.WriteTrackingItems(entry)
if err != nil {
return dashapi.Response(http.StatusInternalServerError, nil), err
}
return dashapi.Response(http.StatusOK, nil), nil
}

View File

@ -16,4 +16,12 @@ type DataStore interface {
WritePlanMonth(interface{}) error
GetPlanMonthForDate(time.Time) (interface{}, error)
WriteTrackingItems(interface{}) error
GetTrackingItemsForDate(time.Time) (interface{}, error)
GetTrackingCategories() (interface{}, error)
WriteInboxItem(interface{}) error
DeleteInboxItem(int32) error
GetInboxItems() ([]interface{}, error)
}