diff --git a/README.md b/README.md new file mode 100644 index 0000000000000000000000000000000000000000..958fba8cbdb9c967c24232d70ac0049189f69647 --- /dev/null +++ b/README.md @@ -0,0 +1,24 @@ +# Boxes - a home inventory system + +I live out in the country and have a number of hobbies. Some projects +require certain tools or materials which can take time to acquire. +In an effort to make this easier to organise, I wanted a way to store +information on where I put things over the weeks, months and years it +takes to gain the skills or things I need. + +The model for this app is that contents go in boxes and boxes go in +locations. Contents can be tagged. So you can have a "bird house" +tag for a bird house project and look for where all the things you need +for a bird house. + +## References + + * Simple PWA [tutorial](https://www.geeksforgeeks.org/making-a-simple-pwa-under-5-minutes/) + and also [this tutorial](https://viblo.asia/p/creating-a-basic-progressive-web-app-using-vanillajs-m68Z0RVX5kG) + * App manifest [generator](https://app-manifest.firebaseapp.com/) + * Echo [embed resources](https://echo.labstack.com/docs/cookbook/embed-resources) + * [Mithril](https://mithril.js.org/) + * Pure Go [database/sql](https://pkg.go.dev/database/sql) implementation + of a [sqlite driver](https://pkg.go.dev/modernc.org/sqlite) + * [sqlc docs](https://docs.sqlc.dev/en/latest/index.html) + * [sqlite docs](https://www.sqlite.org/docs.html) diff --git a/api/boxes.yaml b/api/boxes.yaml index 1f95a39c44bb2acd6b40538c792fee37856cd5fc..3a3be92e7e279f737cc4dbb0b098a28c788c9c7f 100644 --- a/api/boxes.yaml +++ b/api/boxes.yaml @@ -15,7 +15,7 @@ servers: - url: https://localhost:8080/ paths: - /location/{lid}: + /api/location/{lid}: get: summary: Location description: Get a location. @@ -89,7 +89,7 @@ paths: '404': $ref: '#/components/responses/Error' - /locations: + /api/locations: get: summary: Locations description: Get a list of locations. @@ -104,7 +104,7 @@ paths: '500': $ref: '#/components/responses/Error' - /box/{bid}: + /api/box/{bid}: get: summary: Get a box description: | @@ -180,7 +180,7 @@ paths: '404': $ref: '#/components/responses/Error' - /boxes: + /api/boxes: get: summary: Boxes description: Get a list of boxes. @@ -196,7 +196,7 @@ paths: '500': $ref: '#/components/responses/Error' - /content/{cid}: + /api/content/{cid}: get: summary: Content description: Get a content. @@ -270,7 +270,7 @@ paths: '404': $ref: '#/components/responses/Error' - /contents: + /api/contents: get: summary: Contents description: Get a list of contents. diff --git a/database/query.sql b/database/query.sql index 4e8da7ca68022db13f15574dff5109ca55de15d3..ac8dc0b75a9424841f700b7699b793d3eaaef1d4 100644 --- a/database/query.sql +++ b/database/query.sql @@ -82,4 +82,16 @@ SELECT * FROM contents; SELECT * FROM contents WHERE description LIKE ?; +-- name: GetContentsWithBox :many +SELECT * FROM contents +WHERE box = ?; + +-- name: GetContentsWithLocation :many +SELECT c.* FROM contents AS c, boxes AS b +WHERE c.box = b.id AND b.location = ?; + +-- name: GetContentsWithTag :many +SELECT * FROM contents +WHERE tags LIKE ?; + -- vim:et diff --git a/server/contents.go b/server/contents.go index f7cb4a26593f195abf714feab47d106c4e7b0420..4e4024e0ce80791a77435cd455a093cc97613370 100644 --- a/server/contents.go +++ b/server/contents.go @@ -135,10 +135,24 @@ func (ep *Endpoints) DeleteContent(ctx context.Context, req api.DeleteContentReq func (ep *Endpoints) GetContents(ctx context.Context, req api.GetContentsRequestObject) (api.GetContentsResponseObject, error) { var contents []store.Content var err error - if req.Params.Filter == nil { - contents, err = ep.db.GetContents(ctx) - } else { + if req.Params.Filter != nil { contents, err = ep.db.GetContentsWithFilter(ctx, fmt.Sprintf("%%%s%%", *req.Params.Filter)) + } else if req.Params.Box == nil { + contents, err = ep.db.GetContentsWithBox(ctx, *req.Params.Box) + } else if req.Params.Location == nil { + contents, err = ep.db.GetContentsWithLocation(ctx, *req.Params.Location) + } else if req.Params.Tags == nil { + if len(*req.Params.Tags) == 1 { + contents, err = ep.db.GetContentsWithTag(ctx, sql.NullString{ + String: fmt.Sprintf("%%,%s,%%", (*req.Params.Tags)[0]), + Valid: true, + }) + } else { + // TODO: figure out more than one tag. + contents, err = ep.db.GetContents(ctx) + } + } else { + contents, err = ep.db.GetContents(ctx) } if err != nil { log.Printf("error: %+v", err) diff --git a/server/server.go b/server/server.go index 19952150e7e27e4f9cf6de33e686af1b85d568b3..1673dcb46aea3468ad480ceb59ac9f4b9a3fe01f 100644 --- a/server/server.go +++ b/server/server.go @@ -5,12 +5,16 @@ import ( "context" "database/sql" "fmt" + "io/fs" + "mime" + "net/http" // The sqlite db and migration driver. _ "github.com/golang-migrate/migrate/v4/database/sqlite" "git.lyda.ie/kevin/boxes/api" "git.lyda.ie/kevin/boxes/database" + "git.lyda.ie/kevin/boxes/web" "github.com/golang-migrate/migrate/v4" "github.com/golang-migrate/migrate/v4/source/iofs" "github.com/labstack/echo/v4" @@ -19,6 +23,10 @@ import ( "modernc.org/sqlite" ) +func init() { + mime.AddExtensionType(".ico", "image/vnd.microsoft.icon") +} + // Run starts the metrics and API servers. func Run(cmd *cobra.Command, _ []string) { // Get configuration and basic pieces. @@ -55,6 +63,14 @@ func Run(cmd *cobra.Command, _ []string) { db, err := sql.Open("sqlite", dbfile) cobra.CheckErr(err) + // Get the static files served. + filesTree, err := fs.Sub(web.Files, "files") + cobra.CheckErr(err) + filesHandler := http.FileServer(http.FS(filesTree)) + e.GET("/", echo.WrapHandler(filesHandler)) + e.GET("/*.*", echo.WrapHandler(filesHandler)) + //e.GET("/images/*", echo.WrapHandler(http.StripPrefix("/images/", filesHandler))) + // Get the endpoint handler set up. endpoints := NewEndpoints(db) server := api.NewStrictHandler(endpoints, nil) diff --git a/web/files/favicon.ico b/web/files/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..6d1f201f8a6cbfac967b665da57701b99dd3e183 Binary files /dev/null and b/web/files/favicon.ico differ diff --git a/web/files/images/icon-192x192.png b/web/files/images/icon-192x192.png new file mode 100644 index 0000000000000000000000000000000000000000..dd77ff709ad3aa6362a6071593671b00fee0f357 Binary files /dev/null and b/web/files/images/icon-192x192.png differ diff --git a/web/files/images/icon-512x512.png b/web/files/images/icon-512x512.png new file mode 100644 index 0000000000000000000000000000000000000000..5c2e10f35194116c39ea9f82f0a254ad987e7f5f Binary files /dev/null and b/web/files/images/icon-512x512.png differ diff --git a/web/files/index.html b/web/files/index.html new file mode 100644 index 0000000000000000000000000000000000000000..6acad3c9e39582cf1652423ab3e3eae4500c94a1 --- /dev/null +++ b/web/files/index.html @@ -0,0 +1,87 @@ +<!DOCTYPE html> +<html> +<head> + +<!-- Responsive --> +<meta charset="utf-8"> +<meta name="viewport" content="width=device-width, initial-scale=1"> +<meta http-equiv="X-UA-Compatible" content="ie=edge"> + +<!-- Title --> +<title>PWA Tutorial</title> + +<!-- Meta Tags required for + Progressive Web App --> +<meta name="apple-mobile-web-app-status-bar" content="#aa7700"> +<meta name="theme-color" content="black"> + +<!-- Manifest File link --> +<link rel="manifest" href="manifest.json"> +</head> + +<body> +<h1>Boxes</h1> + +<div id="app"></div> + +<footer> + <p>Credits:</p> + <ul> + <li><a href="https://www.flaticon.com/free-icons/open-box" title="open box icons">Open box icons created by yoyonpujiono - Flaticon</a></li> + </ul> +</footer> + +<!-- +<script src="https://cdnjs.cloudflare.com/ajax/libs/mithril/2.2.11/mithril.min.js" integrity="sha512-2/N5u5OSxz7VyKmGbko8Jwx6o8fudoJ70t/Nvu16UoKCyqeDNfvxkDTmj11ibe4fwXSDdojQAxuV+y1Ut1JSkQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script> +--> +<script src="https://cdnjs.cloudflare.com/ajax/libs/mithril/2.2.11/mithril.js" integrity="sha512-HQxrYG+jkimFdOc2fdtpe7urylm7yRsmYekrLMACDZk8GwF7UBnEfjG/e86r+lPUvfNsifVdQKK5iS6IBe70Og==" crossorigin="anonymous" referrerpolicy="no-referrer"></script> +<script> + var LocationsRoute = { + view: function() { + return m("p", "TODO: a list of locations"); + } + } + + var BoxesRoute = { + view: function() { + return m("p", "TODO: a list of boxes"); + } + } + + var ContentsRoute = { + view: function() { + return m("p", "TODO: a list of contents"); + } + } + + window.addEventListener('load', () => { + registerSW(); + var root = document.getElementById("app"); + m.route(root, + "/", { + "/": LocationsRoute, + "/locations": LocationsRoute, + "/Boxes": BoxesRoute, + "/Contents": ContentsRoute, + } + ); + }); + + // Register the Service Worker + async function registerSW() { + if ('serviceWorker' in navigator) { + try { + await navigator + .serviceWorker + .register('serviceworker.js'); + } + catch (e) { + console.log('SW registration failed'); + } + } + console.log('SW registration succeeded'); + } + +</script> +</body> +</html> diff --git a/web/files/manifest.json b/web/files/manifest.json new file mode 100644 index 0000000000000000000000000000000000000000..05e08e152562ddfa3b4cd9bf0472a248dda01793 --- /dev/null +++ b/web/files/manifest.json @@ -0,0 +1,22 @@ +{ + "name":"Boxes", + "short_name":"PWA", + "start_url":"index.html", + "display":"standalone", + "background_color":"#5900b3", + "theme_color":"black", + "scope": ".", + "description":"This is a home inventory tool.", + "icons":[ + { + "src":"images/icon-192x192.png", + "sizes":"192x192", + "type":"image/png" + }, + { + "src":"images/icon-512x512.png", + "sizes":"512x512", + "type":"image/png" + } +] +} diff --git a/web/files/serviceworker.js b/web/files/serviceworker.js new file mode 100644 index 0000000000000000000000000000000000000000..b00e37163e1136db2b76151cb5331682b1f1ed9c --- /dev/null +++ b/web/files/serviceworker.js @@ -0,0 +1,19 @@ +var staticCacheName = "pwa"; + +self.addEventListener("install", function (e) { + e.waitUntil( + caches.open(staticCacheName).then(function (cache) { + return cache.addAll(["/"]); + }) + ); +}); + +self.addEventListener("fetch", function (event) { + console.log(event.request.url); + + event.respondWith( + caches.match(event.request).then(function (response) { + return response || fetch(event.request); + }) + ); +}); diff --git a/web/web.go b/web/web.go new file mode 100644 index 0000000000000000000000000000000000000000..9bd1c4e4fc53b8ddd764c96b042d2295f4be06d2 --- /dev/null +++ b/web/web.go @@ -0,0 +1,9 @@ +// Package web contains the files for the static web elements. +package web + +import "embed" + +// Files contains all the static files that make up the website. +// +//go:embed files +var Files embed.FS