V2
@ -164,6 +164,99 @@ paths:
|
||||
type: string
|
||||
format: date
|
||||
example: 2022-02-18
|
||||
|
||||
/tracking/entry:
|
||||
post:
|
||||
operationId: writeTrackingEntry
|
||||
tags:
|
||||
- tracking
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/TrackingEntry'
|
||||
responses:
|
||||
'200':
|
||||
description: Tracking entry successfully written.
|
||||
|
||||
/tracking/entry/{date}:
|
||||
get:
|
||||
operationId: getTrackingEntryForDate
|
||||
tags:
|
||||
- tracking
|
||||
responses:
|
||||
'200':
|
||||
description: A tracking entry for a specific date.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/TrackingEntry'
|
||||
parameters:
|
||||
- name: date
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: date
|
||||
example: 2022-02-18
|
||||
|
||||
/tracking/categories:
|
||||
get:
|
||||
operationId: getTrackingCategories
|
||||
tags:
|
||||
- tracking
|
||||
responses:
|
||||
'200':
|
||||
description: Tracking categories successfully retrieved.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/TrackingCategories'
|
||||
|
||||
/inbox/:
|
||||
get:
|
||||
operationId: getInboxItems
|
||||
tags:
|
||||
- inbox
|
||||
responses:
|
||||
'200':
|
||||
description: Inbox items successfully retrieved.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/InboxItem'
|
||||
post:
|
||||
operationId: addInboxItem
|
||||
tags:
|
||||
- inbox
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/InboxItem'
|
||||
responses:
|
||||
'200':
|
||||
description: Journal entry successfully written.
|
||||
/inbox/{item}:
|
||||
delete:
|
||||
operationId: deleteInboxItem
|
||||
tags:
|
||||
- inbox
|
||||
responses:
|
||||
'200':
|
||||
description: Inbox item successfully deleted.
|
||||
parameters:
|
||||
- name: item
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
format: int32
|
||||
example: 1
|
||||
|
||||
components:
|
||||
schemas:
|
||||
@ -293,7 +386,7 @@ components:
|
||||
item:
|
||||
type: string
|
||||
example: 'planWeekItem'
|
||||
numTodos:
|
||||
numTodo:
|
||||
type: integer
|
||||
format: int32
|
||||
example: 3
|
||||
@ -315,4 +408,78 @@ components:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
example: '["mitem1", "mitem2", "mitem3"]'
|
||||
example: '["mitem1", "mitem2", "mitem3"]'
|
||||
|
||||
TrackingItem:
|
||||
type: object
|
||||
required:
|
||||
- date
|
||||
- type
|
||||
- value
|
||||
properties:
|
||||
date:
|
||||
type: string
|
||||
format: date
|
||||
example: 2022-02-18
|
||||
type:
|
||||
type: string
|
||||
example: 'trackingItem'
|
||||
value:
|
||||
type: string
|
||||
example: 'trackingItemValue'
|
||||
|
||||
TrackingEntry:
|
||||
type: object
|
||||
required:
|
||||
- date
|
||||
- items
|
||||
properties:
|
||||
date:
|
||||
type: string
|
||||
format: date
|
||||
example: 2022-02-18
|
||||
items:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/TrackingItem'
|
||||
example: '[{"type": "titem1", "value": "titem1value"}, {"type": "titem2", "value": "titem2value"}]'
|
||||
|
||||
TrackingCategory:
|
||||
type: object
|
||||
required:
|
||||
- type
|
||||
- name
|
||||
properties:
|
||||
type:
|
||||
type: string
|
||||
example: 'trackingCategory'
|
||||
name:
|
||||
type: string
|
||||
example: 'trackingCategoryName'
|
||||
items:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
example: '["titem1", "titem2", "titem3"]'
|
||||
|
||||
TrackingCategories:
|
||||
type: object
|
||||
properties:
|
||||
categories:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/TrackingCategory'
|
||||
example: '[{"type": "tcat1", "name": "tcat1name", "items": ["titem1", "titem2", "titem3"]}, {"type": "tcat2", "name": "tcat2name", "items": ["titem1", "titem2", "titem3"]}]'
|
||||
|
||||
InboxItem:
|
||||
type: object
|
||||
required:
|
||||
- item
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
format: int32
|
||||
example: 1
|
||||
item:
|
||||
type: string
|
||||
example: 'inboxItem'
|
||||
1
backend/.dockerignore
Normal file
@ -0,0 +1 @@
|
||||
__debug*
|
||||
30
backend/Dockerfile
Normal 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
@ -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)))
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
129
backend/dashapi/api_inbox.go
Normal 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)
|
||||
|
||||
}
|
||||
60
backend/dashapi/api_inbox_service.go
Normal 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")
|
||||
}
|
||||
125
backend/dashapi/api_tracking.go
Normal 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)
|
||||
|
||||
}
|
||||
60
backend/dashapi/api_tracking_service.go
Normal 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")
|
||||
}
|
||||
43
backend/dashapi/model_inbox_item.go
Normal 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)
|
||||
})
|
||||
}
|
||||
@ -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"`
|
||||
}
|
||||
|
||||
37
backend/dashapi/model_tracking_categories.go
Normal 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)
|
||||
})
|
||||
}
|
||||
46
backend/dashapi/model_tracking_category.go
Normal 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)
|
||||
})
|
||||
}
|
||||
49
backend/dashapi/model_tracking_entry.go
Normal 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)
|
||||
})
|
||||
}
|
||||
47
backend/dashapi/model_tracking_item.go
Normal 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)
|
||||
})
|
||||
}
|
||||
6
backend/database/models/inbox.go
Normal file
@ -0,0 +1,6 @@
|
||||
package models
|
||||
|
||||
type Inbox struct {
|
||||
Id int32 `gorm:"primaryKey"`
|
||||
Item string
|
||||
}
|
||||
@ -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
|
||||
|
||||
19
backend/database/models/tracking.go
Normal 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"`
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
65
backend/service/api_inbox_service.go
Normal 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
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
|
||||
79
backend/service/api_tracking_service.go
Normal 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
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
|
||||
36
docker-compose.yml
Normal file
@ -0,0 +1,36 @@
|
||||
version: '3'
|
||||
services:
|
||||
backend:
|
||||
build: ./backend
|
||||
container_name: dash-backend
|
||||
ports:
|
||||
- "8088:8080"
|
||||
environment:
|
||||
- DB_HOST=db
|
||||
- DB_NAME=dash
|
||||
- DB_USER=dash
|
||||
- DB_PASSWORD=dash
|
||||
- DB_PORT=5432
|
||||
depends_on:
|
||||
- db
|
||||
frontend:
|
||||
build:
|
||||
context: ./frontend
|
||||
args:
|
||||
VITE_API_HOST: localhost
|
||||
VITE_API_PORT: 8088
|
||||
container_name: dash-frontend
|
||||
ports:
|
||||
- "30012:3000"
|
||||
depends_on:
|
||||
- backend
|
||||
db:
|
||||
image: postgres:latest
|
||||
environment:
|
||||
- POSTGRES_DB=dash
|
||||
- POSTGRES_USER=dash
|
||||
- POSTGRES_PASSWORD=dash
|
||||
volumes:
|
||||
- db_data:/var/lib/postgresql/data
|
||||
volumes:
|
||||
db_data:
|
||||
1
frontend/.dockerignore
Normal file
@ -0,0 +1 @@
|
||||
node_modules
|
||||
13
frontend/.eslintignore
Normal file
@ -0,0 +1,13 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
/build
|
||||
/.svelte-kit
|
||||
/package
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
|
||||
# Ignore files for PNPM, NPM and YARN
|
||||
pnpm-lock.yaml
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
31
frontend/.eslintrc.cjs
Normal file
@ -0,0 +1,31 @@
|
||||
/** @type { import("eslint").Linter.FlatConfig } */
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:svelte/recommended',
|
||||
'prettier'
|
||||
],
|
||||
parser: '@typescript-eslint/parser',
|
||||
plugins: ['@typescript-eslint'],
|
||||
parserOptions: {
|
||||
sourceType: 'module',
|
||||
ecmaVersion: 2020,
|
||||
extraFileExtensions: ['.svelte']
|
||||
},
|
||||
env: {
|
||||
browser: true,
|
||||
es2017: true,
|
||||
node: true
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: ['*.svelte'],
|
||||
parser: 'svelte-eslint-parser',
|
||||
parserOptions: {
|
||||
parser: '@typescript-eslint/parser'
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
10
frontend/.gitignore
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
/build
|
||||
/.svelte-kit
|
||||
/package
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
vite.config.js.timestamp-*
|
||||
vite.config.ts.timestamp-*
|
||||
1
frontend/.npmrc
Normal file
@ -0,0 +1 @@
|
||||
engine-strict=true
|
||||
13
frontend/.prettierignore
Normal file
@ -0,0 +1,13 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
/build
|
||||
/.svelte-kit
|
||||
/package
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
|
||||
# Ignore files for PNPM, NPM and YARN
|
||||
pnpm-lock.yaml
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
8
frontend/.prettierrc
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"useTabs": true,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "none",
|
||||
"printWidth": 100,
|
||||
"plugins": ["prettier-plugin-svelte"],
|
||||
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
|
||||
}
|
||||
45
frontend/Dockerfile
Normal file
@ -0,0 +1,45 @@
|
||||
# Step 1: Build the application
|
||||
FROM node:18 AS build
|
||||
|
||||
ARG VITE_API_HOST
|
||||
ARG VITE_API_PORT
|
||||
|
||||
# Use ARG to Set ENV (if needed)
|
||||
ENV VITE_API_HOST=${VITE_API_HOST}
|
||||
ENV VITE_API_PORT=${VITE_API_PORT}
|
||||
|
||||
# Set the working directory
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
# Copy package.json and package-lock.json
|
||||
COPY package*.json ./
|
||||
|
||||
# Install dependencies
|
||||
RUN npm install
|
||||
|
||||
# Copy project files
|
||||
COPY . .
|
||||
|
||||
# Build the SvelteKit application
|
||||
RUN npm run build
|
||||
|
||||
# Step 2: Run the application
|
||||
FROM node:18
|
||||
|
||||
# Set the working directory
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
# Copy package.json and package-lock.json
|
||||
COPY package*.json ./
|
||||
|
||||
# Install production dependencies only
|
||||
RUN npm install --production
|
||||
|
||||
# Copy built files from the build stage
|
||||
COPY --from=build /usr/src/app/build ./build
|
||||
|
||||
# Expose the port the app runs on
|
||||
EXPOSE 3000
|
||||
|
||||
# Define the command to run the app
|
||||
CMD ["node", "build"]
|
||||
@ -1,48 +1,38 @@
|
||||
# Svelte + TS + Vite
|
||||
# create-svelte
|
||||
|
||||
This template should help get you started developing with Svelte and TypeScript in Vite.
|
||||
Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte).
|
||||
|
||||
## Recommended IDE Setup
|
||||
## Creating a project
|
||||
|
||||
[VS Code](https://code.visualstudio.com/) + [Svelte](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode).
|
||||
If you're seeing this, you've probably already done this step. Congrats!
|
||||
|
||||
## Need an official Svelte framework?
|
||||
```bash
|
||||
# create a new project in the current directory
|
||||
npm create svelte@latest
|
||||
|
||||
Check out [SvelteKit](https://github.com/sveltejs/kit#readme), which is also powered by Vite. Deploy anywhere with its serverless-first approach and adapt to various platforms, with out of the box support for TypeScript, SCSS, and Less, and easily-added support for mdsvex, GraphQL, PostCSS, Tailwind CSS, and more.
|
||||
|
||||
## Technical considerations
|
||||
|
||||
**Why use this over SvelteKit?**
|
||||
|
||||
- It brings its own routing solution which might not be preferable for some users.
|
||||
- It is first and foremost a framework that just happens to use Vite under the hood, not a Vite app.
|
||||
`vite dev` and `vite build` wouldn't work in a SvelteKit environment, for example.
|
||||
|
||||
This template contains as little as possible to get started with Vite + TypeScript + Svelte, while taking into account the developer experience with regards to HMR and intellisense. It demonstrates capabilities on par with the other `create-vite` templates and is a good starting point for beginners dipping their toes into a Vite + Svelte project.
|
||||
|
||||
Should you later need the extended capabilities and extensibility provided by SvelteKit, the template has been structured similarly to SvelteKit so that it is easy to migrate.
|
||||
|
||||
**Why `global.d.ts` instead of `compilerOptions.types` inside `jsconfig.json` or `tsconfig.json`?**
|
||||
|
||||
Setting `compilerOptions.types` shuts out all other types not explicitly listed in the configuration. Using triple-slash references keeps the default TypeScript setting of accepting type information from the entire workspace, while also adding `svelte` and `vite/client` type information.
|
||||
|
||||
**Why include `.vscode/extensions.json`?**
|
||||
|
||||
Other templates indirectly recommend extensions via the README, but this file allows VS Code to prompt the user to install the recommended extension upon opening the project.
|
||||
|
||||
**Why enable `allowJs` in the TS template?**
|
||||
|
||||
While `allowJs: false` would indeed prevent the use of `.js` files in the project, it does not prevent the use of JavaScript syntax in `.svelte` files. In addition, it would force `checkJs: false`, bringing the worst of both worlds: not being able to guarantee the entire codebase is TypeScript, and also having worse typechecking for the existing JavaScript. In addition, there are valid use cases in which a mixed codebase may be relevant.
|
||||
|
||||
**Why is HMR not preserving my local component state?**
|
||||
|
||||
HMR state preservation comes with a number of gotchas! It has been disabled by default in both `svelte-hmr` and `@sveltejs/vite-plugin-svelte` due to its often surprising behavior. You can read the details [here](https://github.com/rixo/svelte-hmr#svelte-hmr).
|
||||
|
||||
If you have state that's important to retain within a component, consider creating an external store which would not be replaced by HMR.
|
||||
|
||||
```ts
|
||||
// store.ts
|
||||
// An extremely simple external store
|
||||
import { writable } from 'svelte/store'
|
||||
export default writable(0)
|
||||
# create a new project in my-app
|
||||
npm create svelte@latest my-app
|
||||
```
|
||||
|
||||
## Developing
|
||||
|
||||
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
|
||||
# or start the server and open the app in a new browser tab
|
||||
npm run dev -- --open
|
||||
```
|
||||
|
||||
## Building
|
||||
|
||||
To create a production version of your app:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
You can preview the production build with `npm run preview`.
|
||||
|
||||
> To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment.
|
||||
|
||||
@ -1,13 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Vite + Svelte + TS</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
6338
frontend/package-lock.json
generated
@ -1,24 +1,37 @@
|
||||
{
|
||||
"name": "dash_app",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite --host 0.0.0.0",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"check": "svelte-check --tsconfig ./tsconfig.json"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/vite-plugin-svelte": "^1.0.1",
|
||||
"@tsconfig/svelte": "^3.0.0",
|
||||
"shortid": "^2.2.16",
|
||||
"svelte": "^3.49.0",
|
||||
"svelte-check": "^2.8.0",
|
||||
"svelte-dnd-action": "^0.9.21",
|
||||
"svelte-preprocess": "^4.10.7",
|
||||
"tslib": "^2.4.0",
|
||||
"typescript": "^4.6.4",
|
||||
"vite": "^3.0.7"
|
||||
}
|
||||
"name": "frontend",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite dev --host 0.0.0.0",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||
"lint": "prettier --check . && eslint .",
|
||||
"format": "prettier --write ."
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/adapter-auto": "^2.0.0",
|
||||
"@sveltejs/adapter-node": "^1.3.1",
|
||||
"@sveltejs/kit": "^1.27.4",
|
||||
"@typescript-eslint/eslint-plugin": "^6.0.0",
|
||||
"@typescript-eslint/parser": "^6.0.0",
|
||||
"autoprefixer": "^10.4.16",
|
||||
"eslint": "^8.28.0",
|
||||
"eslint-config-prettier": "^9.0.0",
|
||||
"eslint-plugin-svelte": "^2.30.0",
|
||||
"postcss": "^8.4.32",
|
||||
"prettier": "^3.0.0",
|
||||
"prettier-plugin-svelte": "^3.0.0",
|
||||
"shortid": "^2.2.16",
|
||||
"svelte": "^4.2.7",
|
||||
"svelte-check": "^3.6.0",
|
||||
"svelte-dnd-action": "^0.9.33",
|
||||
"tailwindcss": "^3.3.6",
|
||||
"tslib": "^2.4.1",
|
||||
"typescript": "^5.0.0",
|
||||
"vite": "^4.4.2"
|
||||
},
|
||||
"type": "module"
|
||||
}
|
||||
|
||||
6
frontend/postcss.config.js
Normal file
@ -0,0 +1,6 @@
|
||||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||
|
Before Width: | Height: | Size: 1.5 KiB |
@ -1,55 +0,0 @@
|
||||
<script lang="ts">
|
||||
import Main from './components/Main.svelte'
|
||||
</script>
|
||||
|
||||
<main>
|
||||
<Main/>
|
||||
</main>
|
||||
|
||||
<!--
|
||||
<script>
|
||||
import svelteLogo from './assets/svelte.svg'
|
||||
import Counter from './lib/Counter.svelte'
|
||||
</script>
|
||||
|
||||
<main>
|
||||
<div>
|
||||
<a href="https://vitejs.dev" target="_blank">
|
||||
<img src="/vite.svg" class="logo" alt="Vite Logo" />
|
||||
</a>
|
||||
<a href="https://svelte.dev" target="_blank">
|
||||
<img src={svelteLogo} class="logo svelte" alt="Svelte Logo" />
|
||||
</a>
|
||||
</div>
|
||||
<h1>Vite + Svelte</h1>
|
||||
|
||||
<div class="card">
|
||||
<Counter />
|
||||
</div>
|
||||
|
||||
<p>
|
||||
Check out <a href="https://github.com/sveltejs/kit#readme" target="_blank">SvelteKit</a>, the official Svelte app framework powered by Vite!
|
||||
</p>
|
||||
|
||||
<p class="read-the-docs">
|
||||
Click on the Vite and Svelte logos to learn more
|
||||
</p>
|
||||
</main>
|
||||
|
||||
<style>
|
||||
.logo {
|
||||
height: 6em;
|
||||
padding: 1.5em;
|
||||
will-change: filter;
|
||||
}
|
||||
.logo:hover {
|
||||
filter: drop-shadow(0 0 2em #646cffaa);
|
||||
}
|
||||
.logo.svelte:hover {
|
||||
filter: drop-shadow(0 0 2em #ff3e00aa);
|
||||
}
|
||||
.read-the-docs {
|
||||
color: #888;
|
||||
}
|
||||
</style>
|
||||
-->
|
||||
@ -1,81 +1,19 @@
|
||||
:root {
|
||||
font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
font-weight: 400;
|
||||
|
||||
color-scheme: light dark;
|
||||
color: rgba(255, 255, 255, 0.87);
|
||||
background-color: #242424;
|
||||
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
a {
|
||||
font-weight: 500;
|
||||
color: #646cff;
|
||||
text-decoration: inherit;
|
||||
}
|
||||
a:hover {
|
||||
color: #535bf2;
|
||||
}
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
display: flex;
|
||||
place-items: top;
|
||||
min-width: 320px;
|
||||
min-height: 100vh;
|
||||
@apply bg-slate-50;
|
||||
@apply text-slate-900;
|
||||
@apply dark:bg-slate-800;
|
||||
@apply dark:text-slate-50;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2.6em;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.card {
|
||||
padding: 2em;
|
||||
}
|
||||
|
||||
#app {
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
button {
|
||||
border-radius: 8px;
|
||||
border: 1px solid transparent;
|
||||
padding: 0.6em 1.2em;
|
||||
font-size: 1em;
|
||||
font-weight: 500;
|
||||
font-family: inherit;
|
||||
background-color: #1a1a1a;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.25s;
|
||||
}
|
||||
button:hover {
|
||||
border-color: #646cff;
|
||||
}
|
||||
button:focus,
|
||||
button:focus-visible {
|
||||
outline: 4px auto -webkit-focus-ring-color;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root {
|
||||
color: #213547;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
a:hover {
|
||||
color: #747bff;
|
||||
}
|
||||
button {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
}
|
||||
svg {
|
||||
@apply text-slate-900;
|
||||
@apply dark:text-slate-50;
|
||||
@apply stroke-slate-900;
|
||||
@apply dark:stroke-slate-50;
|
||||
@apply fill-slate-900;
|
||||
@apply dark:fill-slate-50;
|
||||
}
|
||||
12
frontend/src/app.d.ts
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
// See https://kit.svelte.dev/docs/types#app
|
||||
// for information about these interfaces
|
||||
declare global {
|
||||
namespace App {
|
||||
// interface Error {}
|
||||
// interface Locals {}
|
||||
// interface PageData {}
|
||||
// interface Platform {}
|
||||
}
|
||||
}
|
||||
|
||||
export {};
|
||||
12
frontend/src/app.html
Normal file
@ -0,0 +1,12 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
<body data-sveltekit-preload-data="hover">
|
||||
<div style="display: contents">%sveltekit.body%</div>
|
||||
</body>
|
||||
</html>
|
||||
@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!-- Font Awesome Pro 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M448 360V24c0-13.3-10.7-24-24-24H96C43 0 0 43 0 96v320c0 53 43 96 96 96h328c13.3 0 24-10.7 24-24v-16c0-7.5-3.5-14.3-8.9-18.7-4.2-15.4-4.2-59.3 0-74.7 5.4-4.3 8.9-11.1 8.9-18.6zM128 134c0-3.3 2.7-6 6-6h212c3.3 0 6 2.7 6 6v20c0 3.3-2.7 6-6 6H134c-3.3 0-6-2.7-6-6v-20zm0 64c0-3.3 2.7-6 6-6h212c3.3 0 6 2.7 6 6v20c0 3.3-2.7 6-6 6H134c-3.3 0-6-2.7-6-6v-20zm253.4 250H96c-17.7 0-32-14.3-32-32 0-17.6 14.4-32 32-32h285.4c-1.9 17.1-1.9 46.9 0 64z"/></svg>
|
||||
|
Before Width: | Height: | Size: 655 B |
@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!-- Font Awesome Pro 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M400 64h-48V12c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v52H160V12c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v52H48C21.49 64 0 85.49 0 112v352c0 26.51 21.49 48 48 48h352c26.51 0 48-21.49 48-48V112c0-26.51-21.49-48-48-48zm-6 400H54a6 6 0 0 1-6-6V160h352v298a6 6 0 0 1-6 6zm-52.849-200.65L198.842 404.519c-4.705 4.667-12.303 4.637-16.971-.068l-75.091-75.699c-4.667-4.705-4.637-12.303.068-16.971l22.719-22.536c4.705-4.667 12.303-4.637 16.97.069l44.104 44.461 111.072-110.181c4.705-4.667 12.303-4.637 16.971.068l22.536 22.718c4.667 4.705 4.636 12.303-.069 16.97z"/></svg>
|
||||
|
Before Width: | Height: | Size: 792 B |
@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!-- Font Awesome Pro 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M436 160H12c-6.627 0-12-5.373-12-12v-36c0-26.51 21.49-48 48-48h48V12c0-6.627 5.373-12 12-12h40c6.627 0 12 5.373 12 12v52h128V12c0-6.627 5.373-12 12-12h40c6.627 0 12 5.373 12 12v52h48c26.51 0 48 21.49 48 48v36c0 6.627-5.373 12-12 12zM12 192h424c6.627 0 12 5.373 12 12v260c0 26.51-21.49 48-48 48H48c-26.51 0-48-21.49-48-48V204c0-6.627 5.373-12 12-12zm333.296 95.947l-28.169-28.398c-4.667-4.705-12.265-4.736-16.97-.068L194.12 364.665l-45.98-46.352c-4.667-4.705-12.266-4.736-16.971-.068l-28.397 28.17c-4.705 4.667-4.736 12.265-.068 16.97l82.601 83.269c4.667 4.705 12.265 4.736 16.97.068l142.953-141.805c4.705-4.667 4.736-12.265.068-16.97z"/></svg>
|
||||
|
Before Width: | Height: | Size: 851 B |
@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!-- Font Awesome Pro 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M496 384H64V80c0-8.84-7.16-16-16-16H16C7.16 64 0 71.16 0 80v336c0 17.67 14.33 32 32 32h464c8.84 0 16-7.16 16-16v-32c0-8.84-7.16-16-16-16zM464 96H345.94c-21.38 0-32.09 25.85-16.97 40.97l32.4 32.4L288 242.75l-73.37-73.37c-12.5-12.5-32.76-12.5-45.25 0l-68.69 68.69c-6.25 6.25-6.25 16.38 0 22.63l22.62 22.62c6.25 6.25 16.38 6.25 22.63 0L192 237.25l73.37 73.37c12.5 12.5 32.76 12.5 45.25 0l96-96 32.4 32.4c15.12 15.12 40.97 4.41 40.97-16.97V112c.01-8.84-7.15-16-15.99-16z"/></svg>
|
||||
|
Before Width: | Height: | Size: 683 B |
@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><!-- Font Awesome Pro 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M34.52 239.03L228.87 44.69c9.37-9.37 24.57-9.37 33.94 0l22.67 22.67c9.36 9.36 9.37 24.52.04 33.9L131.49 256l154.02 154.75c9.34 9.38 9.32 24.54-.04 33.9l-22.67 22.67c-9.37 9.37-24.57 9.37-33.94 0L34.52 272.97c-9.37-9.37-9.37-24.57 0-33.94z"/></svg>
|
||||
|
Before Width: | Height: | Size: 455 B |
@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><!-- Font Awesome Pro 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z"/></svg>
|
||||
|
Before Width: | Height: | Size: 498 B |
@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 512"><!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path d="M64 360c30.9 0 56 25.1 56 56s-25.1 56-56 56s-56-25.1-56-56s25.1-56 56-56zm0-160c30.9 0 56 25.1 56 56s-25.1 56-56 56s-56-25.1-56-56s25.1-56 56-56zM120 96c0 30.9-25.1 56-56 56S8 126.9 8 96S33.1 40 64 40s56 25.1 56 56z"/></svg>
|
||||
|
Before Width: | Height: | Size: 463 B |
@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!-- Font Awesome Pro 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M224 136V0H24C10.7 0 0 10.7 0 24v464c0 13.3 10.7 24 24 24h336c13.3 0 24-10.7 24-24V160H248c-13.2 0-24-10.8-24-24zm64 236c0 6.6-5.4 12-12 12H108c-6.6 0-12-5.4-12-12v-8c0-6.6 5.4-12 12-12h168c6.6 0 12 5.4 12 12v8zm0-64c0 6.6-5.4 12-12 12H108c-6.6 0-12-5.4-12-12v-8c0-6.6 5.4-12 12-12h168c6.6 0 12 5.4 12 12v8zm0-72v8c0 6.6-5.4 12-12 12H108c-6.6 0-12-5.4-12-12v-8c0-6.6 5.4-12 12-12h168c6.6 0 12 5.4 12 12zm96-114.1v6.1H256V0h6.1c6.4 0 12.5 2.5 17 7l97.9 98c4.5 4.5 7 10.6 7 16.9z"/></svg>
|
||||
|
Before Width: | Height: | Size: 694 B |
@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Pro 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path d="M421.6 379.9c-.6641 0-1.35 .0625-2.049 .1953c-11.24 2.143-22.37 3.17-33.32 3.17c-94.81 0-174.1-77.14-174.1-175.5c0-63.19 33.79-121.3 88.73-152.6c8.467-4.812 6.339-17.66-3.279-19.44c-11.2-2.078-29.53-3.746-40.9-3.746C132.3 31.1 32 132.2 32 256c0 123.6 100.1 224 223.8 224c69.04 0 132.1-31.45 173.8-82.93C435.3 389.1 429.1 379.9 421.6 379.9zM255.8 432C158.9 432 80 353 80 256c0-76.32 48.77-141.4 116.7-165.8C175.2 125 163.2 165.6 163.2 207.8c0 99.44 65.13 183.9 154.9 212.8C298.5 428.1 277.4 432 255.8 432z"/></svg>
|
||||
|
Before Width: | Height: | Size: 752 B |
@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--! Font Awesome Pro 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path d="M223.5 32C100 32 0 132.3 0 256S100 480 223.5 480c60.6 0 115.5-24.2 155.8-63.4c5-4.9 6.3-12.5 3.1-18.7s-10.1-9.7-17-8.5c-9.8 1.7-19.8 2.6-30.1 2.6c-96.9 0-175.5-78.8-175.5-176c0-65.8 36-123.1 89.3-153.3c6.1-3.5 9.2-10.5 7.7-17.3s-7.3-11.9-14.3-12.5c-6.3-.5-12.6-.8-19-.8z"/></svg>
|
||||
|
Before Width: | Height: | Size: 518 B |
@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path d="M560 160c44.2 0 80-35.8 80-80s-35.8-80-80-80s-80 35.8-80 80s35.8 80 80 80zM55.9 512H381.1h75H578.9c33.8 0 61.1-27.4 61.1-61.1c0-11.2-3.1-22.2-8.9-31.8l-132-216.3C495 196.1 487.8 192 480 192s-15 4.1-19.1 10.7l-48.2 79L286.8 81c-6.6-10.6-18.3-17-30.8-17s-24.1 6.4-30.8 17L8.6 426.4C3 435.3 0 445.6 0 456.1C0 487 25 512 55.9 512z"/></svg>
|
||||
|
Before Width: | Height: | Size: 574 B |
@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!-- Font Awesome Pro 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M290.74 93.24l128.02 128.02-277.99 277.99-114.14 12.6C11.35 513.54-1.56 500.62.14 485.34l12.7-114.22 277.9-277.88zm207.2-19.06l-60.11-60.11c-18.75-18.75-49.16-18.75-67.91 0l-56.55 56.55 128.02 128.02 56.55-56.55c18.75-18.76 18.75-49.16 0-67.91z"/></svg>
|
||||
|
Before Width: | Height: | Size: 461 B |
@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Pro 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path d="M505.2 324.8l-47.73-68.78l47.75-68.81c7.359-10.62 8.797-24.12 3.844-36.06c-4.969-11.94-15.52-20.44-28.22-22.72l-82.39-14.88l-14.89-82.41c-2.281-12.72-10.76-23.25-22.69-28.22c-11.97-4.936-25.42-3.498-36.12 3.844L256 54.49L187.2 6.709C176.5-.6016 163.1-2.039 151.1 2.896c-11.92 4.971-20.4 15.5-22.7 28.19l-14.89 82.44L31.15 128.4C18.42 130.7 7.854 139.2 2.9 151.2C-2.051 163.1-.5996 176.6 6.775 187.2l47.73 68.78l-47.75 68.81c-7.359 10.62-8.795 24.12-3.844 36.06c4.969 11.94 15.52 20.44 28.22 22.72l82.39 14.88l14.89 82.41c2.297 12.72 10.78 23.25 22.7 28.22c11.95 4.906 25.44 3.531 36.09-3.844L256 457.5l68.83 47.78C331.3 509.7 338.8 512 346.3 512c4.906 0 9.859-.9687 14.56-2.906c11.92-4.969 20.4-15.5 22.7-28.19l14.89-82.44l82.37-14.88c12.73-2.281 23.3-10.78 28.25-22.75C514.1 348.9 512.6 335.4 505.2 324.8zM456.8 339.2l-99.61 18l-18 99.63L256 399.1L172.8 456.8l-18-99.63l-99.61-18L112.9 255.1L55.23 172.8l99.61-18l18-99.63L256 112.9l83.15-57.75l18.02 99.66l99.61 18L399.1 255.1L456.8 339.2zM256 143.1c-61.85 0-111.1 50.14-111.1 111.1c0 61.85 50.15 111.1 111.1 111.1s111.1-50.14 111.1-111.1C367.1 194.1 317.8 143.1 256 143.1zM256 319.1c-35.28 0-63.99-28.71-63.99-63.99S220.7 192 256 192s63.99 28.71 63.99 63.1S291.3 319.1 256 319.1z"/></svg>
|
||||
|
Before Width: | Height: | Size: 1.4 KiB |
@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Pro 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path d="M361.5 1.2c5 2.1 8.6 6.6 9.6 11.9L391 121l107.9 19.8c5.3 1 9.8 4.6 11.9 9.6s1.5 10.7-1.6 15.2L446.9 256l62.3 90.3c3.1 4.5 3.7 10.2 1.6 15.2s-6.6 8.6-11.9 9.6L391 391 371.1 498.9c-1 5.3-4.6 9.8-9.6 11.9s-10.7 1.5-15.2-1.6L256 446.9l-90.3 62.3c-4.5 3.1-10.2 3.7-15.2 1.6s-8.6-6.6-9.6-11.9L121 391 13.1 371.1c-5.3-1-9.8-4.6-11.9-9.6s-1.5-10.7 1.6-15.2L65.1 256 2.8 165.7c-3.1-4.5-3.7-10.2-1.6-15.2s6.6-8.6 11.9-9.6L121 121 140.9 13.1c1-5.3 4.6-9.8 9.6-11.9s10.7-1.5 15.2 1.6L256 65.1 346.3 2.8c4.5-3.1 10.2-3.7 15.2-1.6zM352 256c0 53-43 96-96 96s-96-43-96-96s43-96 96-96s96 43 96 96zm32 0c0-70.7-57.3-128-128-128s-128 57.3-128 128s57.3 128 128 128s128-57.3 128-128z"/></svg>
|
||||
|
Before Width: | Height: | Size: 910 B |
@ -1,33 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { curTab } from '../stores/appStore';
|
||||
import Journal from './journal/Journal.svelte';
|
||||
import Plan from './plan/Plan.svelte';
|
||||
</script>
|
||||
|
||||
<div class="main">
|
||||
<!--
|
||||
<svelte:component this={$curTab}/> -->
|
||||
{#if $curTab==="Journal"}
|
||||
<Journal/>
|
||||
{:else if $curTab=="Plan"}
|
||||
<Plan/>
|
||||
<!-- {:else if $curTab=="Tracking"} -->
|
||||
<!-- <Tracking/> -->
|
||||
<!-- {:else if $curTab=="Inbox"} -->
|
||||
<!-- <Note/> -->
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.main {
|
||||
/* margin: 1rem; */
|
||||
}
|
||||
</style>
|
||||
<!--
|
||||
<script lang="ts">
|
||||
import { curTab } from '../stores/appStore';
|
||||
import Tracking from './tracking/Tracking.svelte';
|
||||
import Note from './note/Note.svelte';
|
||||
</script>
|
||||
|
||||
-->
|
||||
@ -1,10 +0,0 @@
|
||||
<script lang="ts">
|
||||
let count: number = 0
|
||||
const increment = () => {
|
||||
count += 1
|
||||
}
|
||||
</script>
|
||||
|
||||
<button on:click={increment}>
|
||||
count is {count}
|
||||
</button>
|
||||
@ -1,33 +0,0 @@
|
||||
<script lang="ts">
|
||||
import leftImg from '../assets/chevron-left-solid.svg'
|
||||
import rightImg from '../assets/chevron-right-solid.svg'
|
||||
|
||||
import { currentDate, dateUpdate } from '../stores/appStore';
|
||||
import { getClientDateRep } from '../modules/dateHelpers';
|
||||
import InvertableButton from './inputs/InvertableButton.svelte';
|
||||
|
||||
async function setDate(day: number) {
|
||||
var newDate = new Date($currentDate);
|
||||
newDate.setDate(newDate.getDate() + day);
|
||||
currentDate.set(newDate);
|
||||
$dateUpdate();
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="DatePicker">
|
||||
<InvertableButton label="left" icon={leftImg} clickHandler={() => {setDate(-1);}}/>
|
||||
{getClientDateRep($currentDate)}
|
||||
<InvertableButton label="right" icon={rightImg} clickHandler={() => {setDate(1);}}/>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.DatePicker {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
/* for horizontal aligning of child divs */
|
||||
justify-content: center;
|
||||
/* for vertical aligning */
|
||||
align-items: center;
|
||||
/* width: 100%; */
|
||||
}
|
||||
</style>
|
||||
@ -1,23 +0,0 @@
|
||||
<script lang="ts">
|
||||
import TabBar from './tabs/TabBar.svelte';
|
||||
import Body from './Body.svelte';
|
||||
</script>
|
||||
|
||||
<div class="Main">
|
||||
<TabBar/>
|
||||
<Body/>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.Main {
|
||||
vertical-align: top;
|
||||
width: 100%;
|
||||
/* position: relative; */
|
||||
}
|
||||
|
||||
@media screen and (min-width: 550px) {
|
||||
.Main {
|
||||
width: 550px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -1,25 +0,0 @@
|
||||
<script lang="ts">
|
||||
import Text from './InputText.svelte';
|
||||
import Real from './InputReal.svelte';
|
||||
import Bool from './InputBool.svelte';
|
||||
import Select from './InputSelect.svelte';
|
||||
|
||||
export let type : string = '';
|
||||
export let selectables : string = '';
|
||||
export let val : any = null;
|
||||
export let onInput : () => void;
|
||||
export let onFocus : () => void;
|
||||
|
||||
</script>
|
||||
|
||||
<div>
|
||||
{#if type === "text"}
|
||||
<Text bind:val onInput={onInput} onFocus={onFocus} />
|
||||
{:else if type === "real"}
|
||||
<Real bind:val onInput={onInput}/>
|
||||
{:else if type === "bool"}
|
||||
<Bool bind:val onInput={onInput}/>
|
||||
{:else if type === "select"}
|
||||
<Select bind:val bind:selectables onInput={onInput}/>
|
||||
{/if}
|
||||
</div>
|
||||
@ -1,8 +0,0 @@
|
||||
<script lang="ts">
|
||||
|
||||
export let val : boolean = false;
|
||||
export let onInput : () => void;
|
||||
|
||||
</script>
|
||||
|
||||
<input type="checkbox" on:input={onInput} bind:checked={val}/>
|
||||
@ -1,21 +0,0 @@
|
||||
<script lang="ts">
|
||||
export let selectables : string = '';
|
||||
export let val : string = null;
|
||||
export let onInput : () => void;
|
||||
|
||||
$: {
|
||||
if (val === null) {
|
||||
val = "";
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-no-onchange -->
|
||||
<select bind:value={val} on:change={onInput}>
|
||||
<option value=""></option>
|
||||
{#each selectables.split(';') as opt}
|
||||
<option value={opt}>
|
||||
{opt}
|
||||
</option>
|
||||
{/each}
|
||||
</select>
|
||||
@ -1,32 +0,0 @@
|
||||
<script lang="ts">
|
||||
import InvertableIcon from "./InvertableIcon.svelte";
|
||||
|
||||
|
||||
export let label: string;
|
||||
export let icon: string;
|
||||
export let clickHandler: () => void;
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<button class="InvertableButton" on:click={clickHandler}>
|
||||
<InvertableIcon bind:label bind:icon/>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.InvertableButton {
|
||||
border: None;
|
||||
border-radius: 0;
|
||||
background-color: transparent;
|
||||
width: 2em;
|
||||
height: 2em;
|
||||
padding: 0 0;
|
||||
}
|
||||
|
||||
.InvertableButton:active {
|
||||
filter:invert(50%);
|
||||
/* background-color: #3e8e41;
|
||||
box-shadow: 0 5px #666;
|
||||
transform: translateY(4px); */
|
||||
}
|
||||
</style>
|
||||
@ -1,14 +0,0 @@
|
||||
<script lang="ts">
|
||||
export let label: string;
|
||||
export let icon: string;
|
||||
</script>
|
||||
|
||||
<img class="InvertableIcon" alt={label} src={icon} height=100% width=100%/>
|
||||
|
||||
<style>
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.InvertableIcon {
|
||||
filter:invert(100%);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -1,18 +0,0 @@
|
||||
<script lang="ts">
|
||||
export let value : any;
|
||||
export let onInput : () => void;
|
||||
</script>
|
||||
|
||||
<!-- <div class="shadowed"> -->
|
||||
<div>
|
||||
<textarea bind:value on:keyup={onInput} cols=15 rows=3/>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
textarea {
|
||||
margin: 10px;
|
||||
border: none;
|
||||
box-shadow: 1px 1px 4px grey;
|
||||
resize: none;
|
||||
}
|
||||
</style>
|
||||
@ -1,157 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import shortid from 'shortid';
|
||||
|
||||
import sunSolid from '../../assets/sun-solid.svg';
|
||||
import sunRegular from '../../assets/sun-regular.svg';
|
||||
import mountainSun from '../../assets/mountain-sun-solid.svg';
|
||||
import moonSolid from '../../assets/moon-solid.svg';
|
||||
import type { PlanDay } from '../../dashclient'
|
||||
import { dateUpdate, currentDate } from '../../stores/appStore'
|
||||
import { planApi } from '../../stores/apiStore'
|
||||
import type { PlanItem } from '../../stores/planStore'
|
||||
import PlanDndList from './PlanDndList.svelte'
|
||||
import InvertableIcon from '../inputs/InvertableIcon.svelte';
|
||||
|
||||
let updateTimeout: NodeJS.Timeout;
|
||||
|
||||
interface DayItems {
|
||||
morning: Array<PlanItem>,
|
||||
midday: Array<PlanItem>,
|
||||
afternoon: Array<PlanItem>,
|
||||
evening: Array<PlanItem>
|
||||
}
|
||||
|
||||
let planDay: PlanDay = {
|
||||
date: $currentDate,
|
||||
morning: [],
|
||||
midday: [],
|
||||
afternoon: [],
|
||||
evening: []
|
||||
};
|
||||
|
||||
let planDayItems: DayItems = {
|
||||
morning: [],
|
||||
midday: [],
|
||||
afternoon: [],
|
||||
evening: []
|
||||
};
|
||||
|
||||
function arrayToItems(arr: Array<string>): Array<PlanItem> {
|
||||
let items: Array<PlanItem> = [];
|
||||
|
||||
if (arr) {
|
||||
arr.forEach((it) => {
|
||||
if (it.trim().length != 0) {
|
||||
items.push({id: shortid.generate(), text:it});
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
function itemsToArray(items: Array<PlanItem>): Array<string> {
|
||||
let arr: Array<string> = [];
|
||||
|
||||
items.forEach((it) => {
|
||||
if (it.text.trim().length != 0) {
|
||||
arr.push(it.text);
|
||||
}
|
||||
})
|
||||
|
||||
return arr;
|
||||
}
|
||||
|
||||
function initPlanDay(plan: PlanDay) {
|
||||
console.log(plan);
|
||||
planDay = plan;
|
||||
planDayItems = {
|
||||
morning: arrayToItems(planDay.morning),
|
||||
midday: arrayToItems(planDay.midday),
|
||||
afternoon: arrayToItems(planDay.afternoon),
|
||||
evening: arrayToItems(planDay.evening)
|
||||
}
|
||||
}
|
||||
|
||||
function preparePlanDayForWrite(items: DayItems): PlanDay {
|
||||
planDay.morning = itemsToArray(items.morning);
|
||||
planDay.midday = itemsToArray(items.midday);
|
||||
planDay.afternoon = itemsToArray(items.afternoon);
|
||||
planDay.evening = itemsToArray(items.evening);
|
||||
|
||||
return planDay;
|
||||
}
|
||||
|
||||
async function fetchPlan() {
|
||||
planApi.getPlanDayForDate({'date': $currentDate}).then(resp => (initPlanDay(resp)));
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
$dateUpdate = fetchPlan;
|
||||
fetchPlan();
|
||||
});
|
||||
|
||||
function onInput() {
|
||||
console.log("input");
|
||||
clearTimeout(updateTimeout);
|
||||
|
||||
updateTimeout = setTimeout(function() {
|
||||
planApi.savePlanForDay({planDay: preparePlanDayForWrite(planDayItems)});
|
||||
}, 1000);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="plan">
|
||||
<div class="timeOfDay">
|
||||
<div class="timeIcon">
|
||||
<InvertableIcon label="morgens" icon={sunRegular}/>
|
||||
</div>
|
||||
<PlanDndList bind:items={planDayItems.morning} onInput={onInput}/>
|
||||
</div>
|
||||
|
||||
<div class="timeOfDay">
|
||||
<div class="timeIcon">
|
||||
<InvertableIcon label="mittags" icon={sunSolid}/>
|
||||
</div>
|
||||
<PlanDndList bind:items={planDayItems.midday} onInput={onInput}/>
|
||||
</div>
|
||||
|
||||
<div class="timeOfDay">
|
||||
<div class="timeIcon">
|
||||
<InvertableIcon label="nachmittags" icon={mountainSun}/>
|
||||
</div>
|
||||
<PlanDndList bind:items={planDayItems.afternoon} onInput={onInput}/>
|
||||
</div>
|
||||
|
||||
<div class="timeOfDay">
|
||||
<div class="timeIcon">
|
||||
<InvertableIcon label="abends" icon={moonSolid}/>
|
||||
</div>
|
||||
<PlanDndList bind:items={planDayItems.evening} onInput={onInput}/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.plan {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.timeOfDay {
|
||||
min-width: 130px;
|
||||
flex-grow: 1;
|
||||
flex-basis: 0;
|
||||
}
|
||||
|
||||
.timeIcon {
|
||||
/* position: absolute;
|
||||
float: left; */
|
||||
height: 2rem;
|
||||
width: 2rem;
|
||||
margin: 0 auto;
|
||||
margin-bottom: 10px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
</style>
|
||||
@ -1,43 +0,0 @@
|
||||
<script lang="ts">
|
||||
import penImg from '../../assets/pen-solid.svg'
|
||||
import calendarImg from '../../assets/calendar-check-regular.svg'
|
||||
import chartImg from '../../assets/chart-line-solid.svg'
|
||||
import fileImg from '../../assets/file-alt-solid.svg'
|
||||
import { curTab } from '../../stores/appStore';
|
||||
|
||||
import InvertableButton from '../inputs/InvertableButton.svelte';
|
||||
import DatePicker from '../DatePicker.svelte';
|
||||
|
||||
function clickHandler(tab: string) {
|
||||
curTab.set(tab)
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="TabBar">
|
||||
<div class="datePicker">
|
||||
<DatePicker/>
|
||||
</div>
|
||||
<InvertableButton label="Journal" icon={penImg} clickHandler={()=>{clickHandler("Journal")}}/>
|
||||
<InvertableButton label="Plan" icon={calendarImg} clickHandler={()=>{clickHandler("Plan")}}/>
|
||||
<InvertableButton label="Tracking" icon={chartImg} clickHandler={()=>{clickHandler("Tracking")}}/>
|
||||
<InvertableButton label="Inbox" icon={fileImg} clickHandler={()=>{clickHandler("Inbox")}}/>
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
<hr/>
|
||||
|
||||
<style>
|
||||
.TabBar {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
/* for horizontal aligning of child divs */
|
||||
/* justify-content: center; */
|
||||
/* for vertical aligning */
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.datePicker {
|
||||
margin-right: auto;
|
||||
}
|
||||
</style>
|
||||
@ -1,6 +0,0 @@
|
||||
apis/DefaultApi.ts
|
||||
apis/index.ts
|
||||
index.ts
|
||||
models/JournalEntry.ts
|
||||
models/index.ts
|
||||
runtime.ts
|
||||
@ -1 +0,0 @@
|
||||
6.2.1-SNAPSHOT
|
||||
19
frontend/src/lib/components/BooleanInput.svelte
Normal file
@ -0,0 +1,19 @@
|
||||
<script lang="ts">
|
||||
export let selected: string = "false";
|
||||
export let onInput: (newValue) => void;
|
||||
|
||||
let isTrue: boolean = false;
|
||||
|
||||
function handleSelection(choice) {
|
||||
isTrue = choice;
|
||||
onInput(isTrue ? "true" : "false");
|
||||
}
|
||||
|
||||
$: isTrue = selected === "true";
|
||||
</script>
|
||||
|
||||
<div class="w-full grow flex">
|
||||
<button class={isTrue ? "w-full bg-slate-600 dark:bg-slate-200 text-slate-100 dark:text-slate-800" : "w-full bg-slate-200 dark:bg-slate-600 text-slate-800 dark:text-slate-100"} style="max-w-[130px]" on:click={() => handleSelection(!isTrue)}>
|
||||
{isTrue ? '☑️' : '🔲'}
|
||||
</button>
|
||||
</div>
|
||||
17
frontend/src/lib/components/ChoiceInput.svelte
Normal file
@ -0,0 +1,17 @@
|
||||
<script lang="ts">
|
||||
export let choices: Array<string> = [];
|
||||
export let selected: string = "";
|
||||
export let onInput: (newValue) => void;
|
||||
|
||||
function handleSelection(choice) {
|
||||
selected = choice;
|
||||
onInput(selected);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="w-full grow flex">
|
||||
{#each choices as choice}
|
||||
<!-- <button class={choice == selected ? "w-full bg-slate-600 dark:bg-slate-200 text-slate-100 dark:text-slate-800" : "w-full bg-slate-200 dark:bg-slate-600 text-slate-800 dark:text-slate-100"} style="max-w-[130px]" on:click={() => {selected = choice; onInput();}}>{choice}</button> -->
|
||||
<button class={choice == selected ? "w-full bg-slate-600 dark:bg-slate-200 text-slate-100 dark:text-slate-800" : "w-full bg-slate-200 dark:bg-slate-600 text-slate-800 dark:text-slate-100"} style="max-w-[130px]" on:click={() => handleSelection(choice)}>{choice}</button>
|
||||
{/each}
|
||||
</div>
|
||||
24
frontend/src/lib/components/DatePicker.svelte
Normal file
@ -0,0 +1,24 @@
|
||||
<script lang="ts">
|
||||
import { currentDate, dateUpdate } from '../stores/appStore';
|
||||
import { getClientDateRep } from '../modules/dateHelpers';
|
||||
import InvertableIconButton from './InvertableIconButton.svelte';
|
||||
|
||||
async function setDate(day: number) {
|
||||
var newDate = new Date($currentDate);
|
||||
newDate.setDate(newDate.getDate() + day);
|
||||
currentDate.set(newDate);
|
||||
$dateUpdate();
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex content-center justify-center">
|
||||
<div class="h-3.5 w-3.5 -mt-0.5">
|
||||
<InvertableIconButton label="left" icon="/chevron-left-solid.svg" clickHandler={() => {setDate(-1);}}/>
|
||||
</div>
|
||||
<div class="ml-2 mr-2">
|
||||
{getClientDateRep($currentDate)}
|
||||
</div>
|
||||
<div class="h-3.5 w-3.5 -mt-0.5">
|
||||
<InvertableIconButton label="right" icon="/chevron-right-solid.svg" clickHandler={() => {setDate(1);}}/>
|
||||
</div>
|
||||
</div>
|
||||
@ -1,9 +1,9 @@
|
||||
<script lang="ts">
|
||||
export let val : any;
|
||||
export let onInput : () => void;
|
||||
export let val : number;
|
||||
export let onInput : () => void = () => {};
|
||||
</script>
|
||||
|
||||
<input type="number" bind:value={val} on:keyup={onInput}/>
|
||||
<input type="number" step="1" bind:value={val} on:input={onInput}/>
|
||||
|
||||
<style>
|
||||
input[type=number] {
|
||||
9
frontend/src/lib/components/InvertableIcon.svelte
Normal file
@ -0,0 +1,9 @@
|
||||
<script lang="ts">
|
||||
export let label: string;
|
||||
export let icon: string;
|
||||
</script>
|
||||
|
||||
<!-- <img class="InvertableIcon" alt={label} src={icon} height=100% width=100%/> -->
|
||||
<div class="h-full w-full">
|
||||
<img class="dark:invert" alt={label} src={icon}/>
|
||||
</div>
|
||||
50
frontend/src/lib/components/InvertableIconButton.svelte
Normal file
@ -0,0 +1,50 @@
|
||||
<script lang="ts">
|
||||
import InvertableIcon from "./InvertableIcon.svelte";
|
||||
|
||||
|
||||
export let label: string;
|
||||
export let icon: string;
|
||||
export let clickHandler: () => void;
|
||||
</script>
|
||||
|
||||
<div class="h-full w-full text-center">
|
||||
<button class="border-none rounded-none bg-transparent p-0 m-0 h-full w-full" on:click={clickHandler}>
|
||||
<InvertableIcon bind:label bind:icon/>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.InvertableButton {
|
||||
border: None;
|
||||
border-radius: 0;
|
||||
background-color: transparent;
|
||||
/* width: 2em;
|
||||
height: 2em; */
|
||||
padding: 0 0;
|
||||
margin: 0 0;
|
||||
line-height: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.InvertableButton:active {
|
||||
filter:invert(50%);
|
||||
/* background-color: #3e8e41;
|
||||
box-shadow: 0 5px #666;
|
||||
transform: translateY(4px); */
|
||||
}
|
||||
|
||||
.buttonDiv {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
/* position: relative; */
|
||||
/* top: 0%; */
|
||||
text-align: center;
|
||||
/* border: 1px solid red; */
|
||||
line-height: 0;
|
||||
}
|
||||
|
||||
/* div {
|
||||
border: 1px solid;
|
||||
} */
|
||||
</style>
|
||||
@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import Text from './InputText.svelte';
|
||||
import * as TxtArr from '../../modules/arrayHelpers';
|
||||
import Text from './TextInput.svelte';
|
||||
import * as TxtArr from '../modules/arrayHelpers';
|
||||
|
||||
export let textArray : Array<string> = [];
|
||||
export let onInput : () => void;
|
||||
@ -24,11 +24,8 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- <div on:blur={onBlur}> -->
|
||||
<div>
|
||||
<div class="space-y-1">
|
||||
{#each textArray as inTxt}
|
||||
<!-- <Text bind:val={inTxt} onInput={updateInput} onBlur={onBlur}/> -->
|
||||
<!-- <Text bind:val={inTxt} onInput={updateInput} placeholder={"New Item"}/> -->
|
||||
<Text bind:val={inTxt} onInput={updateInput} onBlur={onBlur} placeholder={"New Item"}/>
|
||||
{/each}
|
||||
</div>
|
||||
@ -13,15 +13,14 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<input tabindex="1" type="text" placeholder={placeholder} bind:value={val} on:input={onInput} on:blur={onBlur} on:keypress={onKeypress} use:init size=10/>
|
||||
<!-- <input tabindex="1" type="text" placeholder={placeholder} bind:value={val} on:input={onInput} on:blur={onBlur} on:keypress={()=>{}} use:init size=10/> -->
|
||||
<input class="w-full border-b border-slate-950/50 dark:border-slate-50/50 bg-slate-50 dark:bg-slate-900 rounded-md" tabindex="1" type="text" placeholder={placeholder} bind:value={val} on:input={onInput} on:blur={onBlur} on:keypress={onKeypress} use:init size=10/>
|
||||
|
||||
<style>
|
||||
input[type=text] {
|
||||
/* input[type=text] {
|
||||
width: 100%;
|
||||
border: none;
|
||||
border-bottom: solid 1px grey;
|
||||
font-size: 1em;
|
||||
font-weight: bold;
|
||||
}
|
||||
} */
|
||||
</style>
|
||||
@ -4,11 +4,10 @@
|
||||
|
||||
import type { PlanItem } from '../../stores/planStore'
|
||||
import PlanInput from './PlanInput.svelte'
|
||||
import MultiItemTextInput from '../inputs/MultiItemTextInput.svelte'
|
||||
import Text from '../inputs/InputText.svelte';
|
||||
import Text from '../TextInput.svelte';
|
||||
import shortid from 'shortid'
|
||||
|
||||
export let items: Array<{id: number, text: string}> = [];
|
||||
export let items: Array<PlanItem> = [];
|
||||
export let onInput: () => void;
|
||||
|
||||
const flipDurationMs = 200;
|
||||
@ -46,42 +45,15 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="newItemInput">
|
||||
<div class="w-11/12">
|
||||
<Text bind:val={newItem} onInput={onInput} onBlur={onInputNewItem} onKeypress={onKeypress} placeholder={"New Item"}/>
|
||||
<!-- <MultiItemTextInput bind:textArray={newArray} onInput={onInput} onBlur={onInputNewItem}/> -->
|
||||
</div>
|
||||
<section contenteditable="true" use:dndzone={{items, flipDurationMs}} on:consider={handleSort} on:finalize={handleSort}>
|
||||
<!-- <div class="planItems"> -->
|
||||
{#each items as item(item.id)}
|
||||
<div class="planRow" animate:flip={{duration:flipDurationMs}}>
|
||||
<div class="planInput">
|
||||
<div class="min-h-4" animate:flip={{duration:flipDurationMs}}>
|
||||
<div class="w-11/12">
|
||||
<PlanInput bind:text={item.text} onInput={onInput}/>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
<!-- </div> -->
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.planItems {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.planInput {
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
.planRow {
|
||||
/* display: flex; */
|
||||
width: 100%;
|
||||
min-height: 1rem;
|
||||
}
|
||||
|
||||
.newItemInput {
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
</style>
|
||||
</section>
|
||||
@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import InputText from '../inputs/InputText.svelte'
|
||||
import InputText from '../TextInput.svelte'
|
||||
|
||||
export let text: string;
|
||||
export let onInput: () => void;
|
||||
@ -20,13 +20,7 @@
|
||||
{#if doEdit}
|
||||
<InputText bind:val={text} onInput={onInput} onBlur={handleBlur} doAutofocus={true}/>
|
||||
{:else}
|
||||
<div class="editableEntry" on:click={onClick}>
|
||||
<div class="w-full" on:click={onClick}>
|
||||
<b>{text}</b>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.editableEntry {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
{/if}
|
||||
123
frontend/src/lib/components/plan/PlanTodoDndList.svelte
Normal file
@ -0,0 +1,123 @@
|
||||
<script lang="ts">
|
||||
import {dndzone, TRIGGERS, SHADOW_ITEM_MARKER_PROPERTY_NAME} from 'svelte-dnd-action';
|
||||
import {flip} from 'svelte/animate';
|
||||
|
||||
import type { PlanWeekItemItem } from '../../stores/planStore'
|
||||
import PlanTodoInput from './PlanTodoInput.svelte'
|
||||
import Text from '../TextInput.svelte';
|
||||
import shortid from 'shortid'
|
||||
|
||||
export let items: Array<PlanWeekItemItem> = [];
|
||||
export let onInput: () => void;
|
||||
|
||||
const flipDurationMs = 200;
|
||||
let shouldIgnoreDndEvents = false;
|
||||
|
||||
function handleConsider(e: any) {
|
||||
// let sortedItems: Array<PlanWeekItemItem> = [];
|
||||
|
||||
// e.detail.items.forEach((it: PlanWeekItemItem) => {
|
||||
// if (it.text.trim().length != 0) {
|
||||
// sortedItems.push(it);
|
||||
// }
|
||||
|
||||
// });
|
||||
|
||||
// items = sortedItems;
|
||||
|
||||
const {trigger, id} = e.detail.info;
|
||||
if (trigger === TRIGGERS.DRAG_STARTED) {
|
||||
console.warn(`copying ${id}`);
|
||||
const idx = items.findIndex(item => item.id === id);
|
||||
const newId = shortid.generate();
|
||||
// the line below was added in order to be compatible with version svelte-dnd-action 0.7.4 and above
|
||||
e.detail.items = e.detail.items.filter(item => !item[SHADOW_ITEM_MARKER_PROPERTY_NAME]);
|
||||
e.detail.items.splice(idx, 0, {...items[idx], id: newId});
|
||||
items = e.detail.items;
|
||||
shouldIgnoreDndEvents = true;
|
||||
}
|
||||
else if (!shouldIgnoreDndEvents) {
|
||||
let sortedItems: Array<PlanWeekItemItem> = [];
|
||||
|
||||
e.detail.items.forEach((it: PlanWeekItemItem) => {
|
||||
if (it.text.trim().length != 0) {
|
||||
sortedItems.push(it);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
items = sortedItems;
|
||||
// items = e.detail.items;
|
||||
}
|
||||
else {
|
||||
items = [...items];
|
||||
}
|
||||
|
||||
onInput();
|
||||
}
|
||||
|
||||
function handleSort(e: any) {
|
||||
let sortedItems: Array<PlanWeekItemItem> = [];
|
||||
|
||||
e.detail.items.forEach((it: PlanWeekItemItem) => {
|
||||
if (it.text.trim().length != 0) {
|
||||
sortedItems.push(it);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
items = sortedItems;
|
||||
|
||||
onInput();
|
||||
}
|
||||
|
||||
let newItem: string = "";
|
||||
|
||||
function onInputNewItem() {
|
||||
if (newItem.trim().length != 0) {
|
||||
items = [...items, {id: shortid.generate(), text: newItem}];
|
||||
}
|
||||
newItem = "";
|
||||
|
||||
onInput();
|
||||
}
|
||||
|
||||
function onKeypress(e: KeyboardEvent, ) {
|
||||
if(e.key == "Enter") {
|
||||
onInputNewItem();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="newItemInput">
|
||||
<Text bind:val={newItem} onInput={onInput} onBlur={onInputNewItem} onKeypress={onKeypress} placeholder={"New Item"}/>
|
||||
</div>
|
||||
<section contenteditable="true" use:dndzone={{items, flipDurationMs, dropFromOthersDisabled: true}} on:consider={handleConsider} on:finalize={handleSort}>
|
||||
{#each items as item(item.id)}
|
||||
<div class="planRow" animate:flip={{duration:flipDurationMs}}>
|
||||
<div class="planInput">
|
||||
<PlanTodoInput bind:text={item.text} bind:done={item.done} bind:todo={item.todo} onInput={onInput}/>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.planInput {
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
.planRow {
|
||||
/* display: flex; */
|
||||
width: 100%;
|
||||
min-height: 1rem;
|
||||
}
|
||||
|
||||
.newItemInput {
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
div {
|
||||
border: 1px solid;
|
||||
}
|
||||
</style>
|
||||
113
frontend/src/lib/components/plan/PlanTodoInput.svelte
Normal file
@ -0,0 +1,113 @@
|
||||
<script lang="ts">
|
||||
import InputText from '../TextInput.svelte'
|
||||
import InputInt from '../IntInput.svelte'
|
||||
import InvertableButton from '../InvertableIconButton.svelte';
|
||||
|
||||
export let text: string;
|
||||
export let todo: number = null;
|
||||
export let done: number = null;
|
||||
export let onInput: () => void;
|
||||
export let onBlur: () => void = () => {};
|
||||
|
||||
let doEdit: boolean = false;
|
||||
|
||||
function onClick() {
|
||||
doEdit = !doEdit;
|
||||
}
|
||||
|
||||
function handleBlur() {
|
||||
doEdit = !doEdit;
|
||||
onBlur();
|
||||
}
|
||||
|
||||
function saveEntry() {
|
||||
if (todo && !done) {
|
||||
done = 0;
|
||||
}
|
||||
onInput();
|
||||
doEdit = false;
|
||||
}
|
||||
|
||||
function deleteEntry() {
|
||||
text = "";
|
||||
todo = null;
|
||||
done = null;
|
||||
onInput();
|
||||
doEdit = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if doEdit}
|
||||
<!-- <InputText bind:val={text} onInput={onInput} onBlur={handleBlur} doAutofocus={true}/> <InputInt bind:val={done}/> <InputInt bind:val={todo}/> -->
|
||||
<div class="planTodoEntry">
|
||||
<div class="planTodoEntryText">
|
||||
<InputText bind:val={text} doAutofocus={true}/>
|
||||
</div>
|
||||
<div class="planTodoIntegerInput">
|
||||
<InputInt bind:val={done}/>
|
||||
</div>
|
||||
<div class="planTodoIntegerInput">
|
||||
<InputInt bind:val={todo}/>
|
||||
</div>
|
||||
<div class="planTodoButton">
|
||||
<InvertableButton label="Journal" icon="/check-solid.svg" clickHandler={saveEntry}/>
|
||||
</div>
|
||||
<div class="planTodoButton">
|
||||
<InvertableButton label="Journal" icon="/xmark-solid.svg" clickHandler={deleteEntry}/>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="editableEntry" on:click={onClick}>
|
||||
{#if todo}
|
||||
<div class="planTodoEntry">
|
||||
<div class="planTodoEntryText">
|
||||
<b>{text}</b>
|
||||
</div>
|
||||
<div class="planTodoEntryTodo">
|
||||
{done}/{todo}
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="planTodoEntry">
|
||||
<div class="planTodoEntryText">
|
||||
<b>{text}</b>
|
||||
</div>
|
||||
<div class="planTodoEntryTodo">
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.editableEntry {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.planTodoEntry {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.planTodoEntryText {
|
||||
margin: 0 auto;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.planTodoButton {
|
||||
height: 2rem;
|
||||
width: 2rem;
|
||||
}
|
||||
|
||||
.planTodoIntegerInput {
|
||||
flex-basis: 30px;
|
||||
}
|
||||
|
||||
.planTodoEntryTodo {
|
||||
min-width: 3rem;
|
||||
}
|
||||
|
||||
div {
|
||||
border: 1px solid;
|
||||
}
|
||||
|
||||
</style>
|
||||
119
frontend/src/lib/dashclient/apis/InboxApi.ts
Normal file
@ -0,0 +1,119 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* Dash API
|
||||
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
|
||||
*
|
||||
* The version of the OpenAPI document: 0.1
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
* https://openapi-generator.tech
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
|
||||
|
||||
import * as runtime from '../runtime';
|
||||
import type {
|
||||
InboxItem,
|
||||
} from '../models';
|
||||
import {
|
||||
InboxItemFromJSON,
|
||||
InboxItemToJSON,
|
||||
} from '../models';
|
||||
|
||||
export interface AddInboxItemRequest {
|
||||
inboxItem: InboxItem;
|
||||
}
|
||||
|
||||
export interface DeleteInboxItemRequest {
|
||||
item: number;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export class InboxApi extends runtime.BaseAPI {
|
||||
|
||||
/**
|
||||
*/
|
||||
async addInboxItemRaw(requestParameters: AddInboxItemRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<void>> {
|
||||
if (requestParameters.inboxItem === null || requestParameters.inboxItem === undefined) {
|
||||
throw new runtime.RequiredError('inboxItem','Required parameter requestParameters.inboxItem was null or undefined when calling addInboxItem.');
|
||||
}
|
||||
|
||||
const queryParameters: any = {};
|
||||
|
||||
const headerParameters: runtime.HTTPHeaders = {};
|
||||
|
||||
headerParameters['Content-Type'] = 'application/json';
|
||||
|
||||
const response = await this.request({
|
||||
path: `/inbox/`,
|
||||
method: 'POST',
|
||||
headers: headerParameters,
|
||||
query: queryParameters,
|
||||
body: InboxItemToJSON(requestParameters.inboxItem),
|
||||
}, initOverrides);
|
||||
|
||||
return new runtime.VoidApiResponse(response);
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
async addInboxItem(requestParameters: AddInboxItemRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<void> {
|
||||
await this.addInboxItemRaw(requestParameters, initOverrides);
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
async deleteInboxItemRaw(requestParameters: DeleteInboxItemRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<void>> {
|
||||
if (requestParameters.item === null || requestParameters.item === undefined) {
|
||||
throw new runtime.RequiredError('item','Required parameter requestParameters.item was null or undefined when calling deleteInboxItem.');
|
||||
}
|
||||
|
||||
const queryParameters: any = {};
|
||||
|
||||
const headerParameters: runtime.HTTPHeaders = {};
|
||||
|
||||
const response = await this.request({
|
||||
path: `/inbox/{item}`.replace(`{${"item"}}`, encodeURIComponent(String(requestParameters.item))),
|
||||
method: 'DELETE',
|
||||
headers: headerParameters,
|
||||
query: queryParameters,
|
||||
}, initOverrides);
|
||||
|
||||
return new runtime.VoidApiResponse(response);
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
async deleteInboxItem(requestParameters: DeleteInboxItemRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<void> {
|
||||
await this.deleteInboxItemRaw(requestParameters, initOverrides);
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
async getInboxItemsRaw(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<Array<InboxItem>>> {
|
||||
const queryParameters: any = {};
|
||||
|
||||
const headerParameters: runtime.HTTPHeaders = {};
|
||||
|
||||
const response = await this.request({
|
||||
path: `/inbox/`,
|
||||
method: 'GET',
|
||||
headers: headerParameters,
|
||||
query: queryParameters,
|
||||
}, initOverrides);
|
||||
|
||||
return new runtime.JSONApiResponse(response, (jsonValue) => jsonValue.map(InboxItemFromJSON));
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
async getInboxItems(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<Array<InboxItem>> {
|
||||
const response = await this.getInboxItemsRaw(initOverrides);
|
||||
return await response.value();
|
||||
}
|
||||
|
||||
}
|
||||
123
frontend/src/lib/dashclient/apis/TrackingApi.ts
Normal file
@ -0,0 +1,123 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* Dash API
|
||||
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
|
||||
*
|
||||
* The version of the OpenAPI document: 0.1
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
* https://openapi-generator.tech
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
|
||||
|
||||
import * as runtime from '../runtime';
|
||||
import type {
|
||||
TrackingCategories,
|
||||
TrackingEntry,
|
||||
} from '../models';
|
||||
import {
|
||||
TrackingCategoriesFromJSON,
|
||||
TrackingCategoriesToJSON,
|
||||
TrackingEntryFromJSON,
|
||||
TrackingEntryToJSON,
|
||||
} from '../models';
|
||||
|
||||
export interface GetTrackingEntryForDateRequest {
|
||||
date: Date;
|
||||
}
|
||||
|
||||
export interface WriteTrackingEntryRequest {
|
||||
trackingEntry: TrackingEntry;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export class TrackingApi extends runtime.BaseAPI {
|
||||
|
||||
/**
|
||||
*/
|
||||
async getTrackingCategoriesRaw(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<TrackingCategories>> {
|
||||
const queryParameters: any = {};
|
||||
|
||||
const headerParameters: runtime.HTTPHeaders = {};
|
||||
|
||||
const response = await this.request({
|
||||
path: `/tracking/categories`,
|
||||
method: 'GET',
|
||||
headers: headerParameters,
|
||||
query: queryParameters,
|
||||
}, initOverrides);
|
||||
|
||||
return new runtime.JSONApiResponse(response, (jsonValue) => TrackingCategoriesFromJSON(jsonValue));
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
async getTrackingCategories(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<TrackingCategories> {
|
||||
const response = await this.getTrackingCategoriesRaw(initOverrides);
|
||||
return await response.value();
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
async getTrackingEntryForDateRaw(requestParameters: GetTrackingEntryForDateRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<TrackingEntry>> {
|
||||
if (requestParameters.date === null || requestParameters.date === undefined) {
|
||||
throw new runtime.RequiredError('date','Required parameter requestParameters.date was null or undefined when calling getTrackingEntryForDate.');
|
||||
}
|
||||
|
||||
const queryParameters: any = {};
|
||||
|
||||
const headerParameters: runtime.HTTPHeaders = {};
|
||||
|
||||
const response = await this.request({
|
||||
path: `/tracking/entry/{date}`.replace(`{${"date"}}`, encodeURIComponent(String(requestParameters.date.toISOString().substring(0,10)))),
|
||||
method: 'GET',
|
||||
headers: headerParameters,
|
||||
query: queryParameters,
|
||||
}, initOverrides);
|
||||
|
||||
return new runtime.JSONApiResponse(response, (jsonValue) => TrackingEntryFromJSON(jsonValue));
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
async getTrackingEntryForDate(requestParameters: GetTrackingEntryForDateRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<TrackingEntry> {
|
||||
const response = await this.getTrackingEntryForDateRaw(requestParameters, initOverrides);
|
||||
return await response.value();
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
async writeTrackingEntryRaw(requestParameters: WriteTrackingEntryRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<void>> {
|
||||
if (requestParameters.trackingEntry === null || requestParameters.trackingEntry === undefined) {
|
||||
throw new runtime.RequiredError('trackingEntry','Required parameter requestParameters.trackingEntry was null or undefined when calling writeTrackingEntry.');
|
||||
}
|
||||
|
||||
const queryParameters: any = {};
|
||||
|
||||
const headerParameters: runtime.HTTPHeaders = {};
|
||||
|
||||
headerParameters['Content-Type'] = 'application/json';
|
||||
|
||||
const response = await this.request({
|
||||
path: `/tracking/entry`,
|
||||
method: 'POST',
|
||||
headers: headerParameters,
|
||||
query: queryParameters,
|
||||
body: TrackingEntryToJSON(requestParameters.trackingEntry),
|
||||
}, initOverrides);
|
||||
|
||||
return new runtime.VoidApiResponse(response);
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
async writeTrackingEntry(requestParameters: WriteTrackingEntryRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<void> {
|
||||
await this.writeTrackingEntryRaw(requestParameters, initOverrides);
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,4 +1,6 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
export * from './InboxApi';
|
||||
export * from './JournalApi';
|
||||
export * from './PlanApi';
|
||||
export * from './TrackingApi';
|
||||
74
frontend/src/lib/dashclient/models/InboxItem.ts
Normal file
@ -0,0 +1,74 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* Dash API
|
||||
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
|
||||
*
|
||||
* The version of the OpenAPI document: 0.1
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
* https://openapi-generator.tech
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
|
||||
import { exists, mapValues } from '../runtime';
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
* @interface InboxItem
|
||||
*/
|
||||
export interface InboxItem {
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof InboxItem
|
||||
*/
|
||||
id?: number;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof InboxItem
|
||||
*/
|
||||
item: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given object implements the InboxItem interface.
|
||||
*/
|
||||
export function instanceOfInboxItem(value: object): boolean {
|
||||
let isInstance = true;
|
||||
isInstance = isInstance && "item" in value;
|
||||
|
||||
return isInstance;
|
||||
}
|
||||
|
||||
export function InboxItemFromJSON(json: any): InboxItem {
|
||||
return InboxItemFromJSONTyped(json, false);
|
||||
}
|
||||
|
||||
export function InboxItemFromJSONTyped(json: any, ignoreDiscriminator: boolean): InboxItem {
|
||||
if ((json === undefined) || (json === null)) {
|
||||
return json;
|
||||
}
|
||||
return {
|
||||
|
||||
'id': !exists(json, 'id') ? undefined : json['id'],
|
||||
'item': json['item'],
|
||||
};
|
||||
}
|
||||
|
||||
export function InboxItemToJSON(value?: InboxItem | null): any {
|
||||
if (value === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
if (value === null) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
|
||||
'id': value.id,
|
||||
'item': value.item,
|
||||
};
|
||||
}
|
||||
|
||||
@ -30,7 +30,7 @@ export interface PlanWeekItem {
|
||||
* @type {number}
|
||||
* @memberof PlanWeekItem
|
||||
*/
|
||||
numTodos?: number;
|
||||
numTodo?: number;
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
@ -60,7 +60,7 @@ export function PlanWeekItemFromJSONTyped(json: any, ignoreDiscriminator: boolea
|
||||
return {
|
||||
|
||||
'item': json['item'],
|
||||
'numTodos': !exists(json, 'numTodos') ? undefined : json['numTodos'],
|
||||
'numTodo': !exists(json, 'numTodo') ? undefined : json['numTodo'],
|
||||
'numDone': !exists(json, 'numDone') ? undefined : json['numDone'],
|
||||
};
|
||||
}
|
||||
@ -75,7 +75,7 @@ export function PlanWeekItemToJSON(value?: PlanWeekItem | null): any {
|
||||
return {
|
||||
|
||||
'item': value.item,
|
||||
'numTodos': value.numTodos,
|
||||
'numTodo': value.numTodo,
|
||||
'numDone': value.numDone,
|
||||
};
|
||||
}
|
||||
72
frontend/src/lib/dashclient/models/TrackingCategories.ts
Normal file
@ -0,0 +1,72 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* Dash API
|
||||
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
|
||||
*
|
||||
* The version of the OpenAPI document: 0.1
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
* https://openapi-generator.tech
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
|
||||
import { exists, mapValues } from '../runtime';
|
||||
import type { TrackingCategory } from './TrackingCategory';
|
||||
import {
|
||||
TrackingCategoryFromJSON,
|
||||
TrackingCategoryFromJSONTyped,
|
||||
TrackingCategoryToJSON,
|
||||
} from './TrackingCategory';
|
||||
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
* @interface TrackingCategories
|
||||
*/
|
||||
export interface TrackingCategories {
|
||||
/**
|
||||
*
|
||||
* @type {Array<TrackingCategory>}
|
||||
* @memberof TrackingCategories
|
||||
*/
|
||||
categories?: Array<TrackingCategory>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given object implements the TrackingCategories interface.
|
||||
*/
|
||||
export function instanceOfTrackingCategories(value: object): boolean {
|
||||
let isInstance = true;
|
||||
|
||||
return isInstance;
|
||||
}
|
||||
|
||||
export function TrackingCategoriesFromJSON(json: any): TrackingCategories {
|
||||
return TrackingCategoriesFromJSONTyped(json, false);
|
||||
}
|
||||
|
||||
export function TrackingCategoriesFromJSONTyped(json: any, ignoreDiscriminator: boolean): TrackingCategories {
|
||||
if ((json === undefined) || (json === null)) {
|
||||
return json;
|
||||
}
|
||||
return {
|
||||
|
||||
'categories': !exists(json, 'categories') ? undefined : ((json['categories'] as Array<any>).map(TrackingCategoryFromJSON)),
|
||||
};
|
||||
}
|
||||
|
||||
export function TrackingCategoriesToJSON(value?: TrackingCategories | null): any {
|
||||
if (value === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
if (value === null) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
|
||||
'categories': value.categories === undefined ? undefined : ((value.categories as Array<any>).map(TrackingCategoryToJSON)),
|
||||
};
|
||||
}
|
||||
|
||||