Initial commit
This commit is contained in:
commit
981e1a663c
90
.github/workflows/build.yml
vendored
Normal file
90
.github/workflows/build.yml
vendored
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
name: Build
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: ["*"] # Any branch
|
||||||
|
tags: ["v*.*.*"] # Release tags
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v4
|
||||||
|
with:
|
||||||
|
go-version: "1.22"
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
go mod download
|
||||||
|
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.59.1
|
||||||
|
|
||||||
|
- name: Lint
|
||||||
|
run: golangci-lint run
|
||||||
|
|
||||||
|
- name: Validate SQL schema
|
||||||
|
run: |
|
||||||
|
sqlite3 whatever.db < pkg/db/schema.sql
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
run: |
|
||||||
|
mkdir -p sample_data
|
||||||
|
go test ./...
|
||||||
|
|
||||||
|
release:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: build # Only run if build is successful
|
||||||
|
if: startsWith(github.ref, 'refs/tags/v')
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v4
|
||||||
|
with:
|
||||||
|
go-version: "1.22"
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
go mod download
|
||||||
|
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.59.1
|
||||||
|
|
||||||
|
- name: Install musl
|
||||||
|
run: |
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install -y musl-tools musl-dev
|
||||||
|
|
||||||
|
- name: Compile with musl
|
||||||
|
env:
|
||||||
|
CC: musl-gcc # Use musl-gcc as the C compiler
|
||||||
|
GOOS: linux
|
||||||
|
CGO_ENABLED: 1
|
||||||
|
GOARCH: amd64
|
||||||
|
run: |
|
||||||
|
go build -v -ldflags '-s -w -linkmode external -extldflags "-static"' -o azm ./cmd
|
||||||
|
|
||||||
|
- name: Create release
|
||||||
|
id: create_release
|
||||||
|
uses: actions/create-release@v1
|
||||||
|
with:
|
||||||
|
tag_name: ${{ github.ref_name }} # The tag that triggered the workflow
|
||||||
|
release_name: Release ${{ github.ref_name }} # Name of the release
|
||||||
|
draft: true # Set to true if you want to create a draft release
|
||||||
|
prerelease: true # Set to true if it's a prerelease
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Upload Release Assets
|
||||||
|
uses: actions/upload-release-asset@v1
|
||||||
|
with:
|
||||||
|
upload_url: ${{ steps.create_release.outputs.upload_url }} # URL to upload assets
|
||||||
|
asset_path: azm # Path to your built artifact(s)
|
||||||
|
asset_name: azm # Name of the asset
|
||||||
|
asset_content_type: application/octet-stream
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
sample_data/data
|
659
.golangci.yaml
Normal file
659
.golangci.yaml
Normal file
@ -0,0 +1,659 @@
|
|||||||
|
# This file contains all available configuration options
|
||||||
|
# with their default values.
|
||||||
|
|
||||||
|
# output configuration options
|
||||||
|
output:
|
||||||
|
# colored-line-number|line-number|json|tab|checkstyle|code-climate|junit-xml|github-actions
|
||||||
|
formats: colored-line-number
|
||||||
|
|
||||||
|
# sorts results by: filepath, line and column
|
||||||
|
sort-results: true
|
||||||
|
|
||||||
|
linters:
|
||||||
|
disable-all: true
|
||||||
|
enable:
|
||||||
|
- depguard
|
||||||
|
- errcheck
|
||||||
|
- gosimple
|
||||||
|
- govet
|
||||||
|
- ineffassign
|
||||||
|
- staticcheck
|
||||||
|
- typecheck
|
||||||
|
- unused
|
||||||
|
- whitespace
|
||||||
|
- wrapcheck
|
||||||
|
- lll
|
||||||
|
- godox
|
||||||
|
- gofmt
|
||||||
|
- errorlint
|
||||||
|
- nolintlint
|
||||||
|
- sqlclosecheck
|
||||||
|
|
||||||
|
|
||||||
|
# Useless linters:
|
||||||
|
# - dogsled
|
||||||
|
# - wsl (don't like it for now)
|
||||||
|
# - golint (deprecated, replaced by 'revive')
|
||||||
|
|
||||||
|
# TODO: "go fix" -- what is it? What does it do?
|
||||||
|
|
||||||
|
|
||||||
|
linters-settings:
|
||||||
|
# cyclop:
|
||||||
|
# # the maximal code complexity to report
|
||||||
|
# max-complexity: 10
|
||||||
|
# # the maximal average package complexity. If it's higher than 0.0 (float) the check is enabled (default 0.0)
|
||||||
|
# package-average: 0.0
|
||||||
|
# # should ignore tests (default false)
|
||||||
|
# skip-tests: false
|
||||||
|
|
||||||
|
# dupl:
|
||||||
|
# # tokens count to trigger issue, 150 by default
|
||||||
|
# threshold: 100
|
||||||
|
|
||||||
|
errcheck:
|
||||||
|
# report about not checking of errors in type assertions: `a := b.(MyStruct)`;
|
||||||
|
# default is false: such cases aren't reported by default.
|
||||||
|
check-type-assertions: true
|
||||||
|
|
||||||
|
# report about assignment of errors to blank identifier: `num, _ := strconv.Atoi(numStr)`;
|
||||||
|
# default is false: such cases aren't reported by default.
|
||||||
|
check-blank: true
|
||||||
|
|
||||||
|
# # list of functions to exclude from checking, where each entry is a single function to exclude.
|
||||||
|
# # see https://github.com/kisielk/errcheck#excluding-functions for details
|
||||||
|
# exclude-functions:
|
||||||
|
# - io/ioutil.ReadFile
|
||||||
|
# - io.Copy(*bytes.Buffer)
|
||||||
|
# - io.Copy(os.Stdout)
|
||||||
|
|
||||||
|
errorlint:
|
||||||
|
errorf: true # Ensure Errorf only uses %w (not %v or %s etc) for errors
|
||||||
|
asserts: true # Require errors.As instead of type-asserting
|
||||||
|
comparison: true # Require errors.Is instead of equality-checking
|
||||||
|
|
||||||
|
# exhaustive:
|
||||||
|
# # check switch statements in generated files also
|
||||||
|
# check-generated: false
|
||||||
|
# # indicates that switch statements are to be considered exhaustive if a
|
||||||
|
# # 'default' case is present, even if all enum members aren't listed in the
|
||||||
|
# # switch
|
||||||
|
# default-signifies-exhaustive: false
|
||||||
|
|
||||||
|
# exhaustivestruct:
|
||||||
|
# # Struct Patterns is list of expressions to match struct packages and names
|
||||||
|
# # The struct packages have the form example.com/package.ExampleStruct
|
||||||
|
# # The matching patterns can use matching syntax from https://pkg.go.dev/path#Match
|
||||||
|
# # If this list is empty, all structs are tested.
|
||||||
|
# struct-patterns:
|
||||||
|
# - '*.Test'
|
||||||
|
# - 'example.com/package.ExampleStruct'
|
||||||
|
|
||||||
|
# forbidigo:
|
||||||
|
# # Forbid the following identifiers (identifiers are written using regexp):
|
||||||
|
# forbid:
|
||||||
|
# - ^print.*$
|
||||||
|
# - 'fmt\.Print.*'
|
||||||
|
# # Exclude godoc examples from forbidigo checks. Default is true.
|
||||||
|
# exclude_godoc_examples: false
|
||||||
|
|
||||||
|
# gci:
|
||||||
|
# # put imports beginning with prefix after 3rd-party packages;
|
||||||
|
# # only support one prefix
|
||||||
|
# # if not set, use goimports.local-prefixes
|
||||||
|
# local-prefixes: github.com/org/project
|
||||||
|
|
||||||
|
# gocognit:
|
||||||
|
# # minimal code complexity to report, 30 by default (but we recommend 10-20)
|
||||||
|
# min-complexity: 10
|
||||||
|
|
||||||
|
# goconst:
|
||||||
|
# # minimal length of string constant, 3 by default
|
||||||
|
# min-len: 3
|
||||||
|
# # minimum occurrences of constant string count to trigger issue, 3 by default
|
||||||
|
# min-occurrences: 3
|
||||||
|
# # ignore test files, false by default
|
||||||
|
# ignore-tests: false
|
||||||
|
# # look for existing constants matching the values, true by default
|
||||||
|
# match-constant: true
|
||||||
|
# # search also for duplicated numbers, false by default
|
||||||
|
# numbers: false
|
||||||
|
# # minimum value, only works with goconst.numbers, 3 by default
|
||||||
|
# min: 3
|
||||||
|
# # maximum value, only works with goconst.numbers, 3 by default
|
||||||
|
# max: 3
|
||||||
|
# # ignore when constant is not used as function argument, true by default
|
||||||
|
# ignore-calls: true
|
||||||
|
|
||||||
|
# gocritic:
|
||||||
|
# # Which checks should be enabled; can't be combined with 'disabled-checks';
|
||||||
|
# # See https://go-critic.github.io/overview#checks-overview
|
||||||
|
# # To check which checks are enabled run `GL_DEBUG=gocritic golangci-lint run`
|
||||||
|
# # By default list of stable checks is used.
|
||||||
|
# enabled-checks:
|
||||||
|
# - rangeValCopy
|
||||||
|
|
||||||
|
# # Which checks should be disabled; can't be combined with 'enabled-checks'; default is empty
|
||||||
|
# disabled-checks:
|
||||||
|
# - regexpMust
|
||||||
|
|
||||||
|
# # Enable multiple checks by tags, run `GL_DEBUG=gocritic golangci-lint run` to see all tags and checks.
|
||||||
|
# # Empty list by default. See https://github.com/go-critic/go-critic#usage -> section "Tags".
|
||||||
|
# enabled-tags:
|
||||||
|
# - performance
|
||||||
|
# disabled-tags:
|
||||||
|
# - experimental
|
||||||
|
|
||||||
|
# # Settings passed to gocritic.
|
||||||
|
# # The settings key is the name of a supported gocritic checker.
|
||||||
|
# # The list of supported checkers can be find in https://go-critic.github.io/overview.
|
||||||
|
# settings:
|
||||||
|
# captLocal: # must be valid enabled check name
|
||||||
|
# # whether to restrict checker to params only (default true)
|
||||||
|
# paramsOnly: true
|
||||||
|
# elseif:
|
||||||
|
# # whether to skip balanced if-else pairs (default true)
|
||||||
|
# skipBalanced: true
|
||||||
|
# hugeParam:
|
||||||
|
# # size in bytes that makes the warning trigger (default 80)
|
||||||
|
# sizeThreshold: 80
|
||||||
|
# nestingReduce:
|
||||||
|
# # min number of statements inside a branch to trigger a warning (default 5)
|
||||||
|
# bodyWidth: 5
|
||||||
|
# rangeExprCopy:
|
||||||
|
# # size in bytes that makes the warning trigger (default 512)
|
||||||
|
# sizeThreshold: 512
|
||||||
|
# # whether to check test functions (default true)
|
||||||
|
# skipTestFuncs: true
|
||||||
|
# rangeValCopy:
|
||||||
|
# # size in bytes that makes the warning trigger (default 128)
|
||||||
|
# sizeThreshold: 32
|
||||||
|
# # whether to check test functions (default true)
|
||||||
|
# skipTestFuncs: true
|
||||||
|
# ruleguard:
|
||||||
|
# # path to a gorules file for the ruleguard checker
|
||||||
|
# rules: ''
|
||||||
|
# truncateCmp:
|
||||||
|
# # whether to skip int/uint/uintptr types (default true)
|
||||||
|
# skipArchDependent: true
|
||||||
|
# underef:
|
||||||
|
# # whether to skip (*x).method() calls where x is a pointer receiver (default true)
|
||||||
|
# skipRecvDeref: true
|
||||||
|
# unnamedResult:
|
||||||
|
# # whether to check exported functions
|
||||||
|
# checkExported: true
|
||||||
|
|
||||||
|
# gocyclo:
|
||||||
|
# # minimal code complexity to report, 30 by default (but we recommend 10-20)
|
||||||
|
# min-complexity: 10
|
||||||
|
|
||||||
|
# godot:
|
||||||
|
# # comments to be checked: `declarations`, `toplevel`, or `all`
|
||||||
|
# scope: declarations
|
||||||
|
# # list of regexps for excluding particular comment lines from check
|
||||||
|
# exclude:
|
||||||
|
# # example: exclude comments which contain numbers
|
||||||
|
# # - '[0-9]+'
|
||||||
|
# # check that each sentence starts with a capital letter
|
||||||
|
# capital: false
|
||||||
|
|
||||||
|
godox:
|
||||||
|
# report any comments starting with keywords, this is useful for TODO or FIXME comments that
|
||||||
|
# might be left in the code accidentally and should be resolved before merging
|
||||||
|
keywords: # default keywords are TODO, BUG, and FIXME, these can be overwritten by this setting
|
||||||
|
- XXX
|
||||||
|
|
||||||
|
gofmt:
|
||||||
|
# simplify code: gofmt with `-s` option, true by default
|
||||||
|
simplify: true
|
||||||
|
|
||||||
|
# gofumpt:
|
||||||
|
# # Select the Go version to target. The default is `1.15`.
|
||||||
|
# lang-version: "1.15"
|
||||||
|
|
||||||
|
# # Choose whether or not to use the extra rules that are disabled
|
||||||
|
# # by default
|
||||||
|
# extra-rules: false
|
||||||
|
|
||||||
|
# goheader:
|
||||||
|
# values:
|
||||||
|
# const:
|
||||||
|
# # define here const type values in format k:v, for example:
|
||||||
|
# # COMPANY: MY COMPANY
|
||||||
|
# regexp:
|
||||||
|
# # define here regexp type values, for example
|
||||||
|
# # AUTHOR: .*@mycompany\.com
|
||||||
|
# template: # |-
|
||||||
|
# # put here copyright header template for source code files, for example:
|
||||||
|
# # Note: {{ YEAR }} is a builtin value that returns the year relative to the current machine time.
|
||||||
|
# #
|
||||||
|
# # {{ AUTHOR }} {{ COMPANY }} {{ YEAR }}
|
||||||
|
# # SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
# # Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# # you may not use this file except in compliance with the License.
|
||||||
|
# # You may obtain a copy of the License at:
|
||||||
|
|
||||||
|
# # http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
# # Unless required by applicable law or agreed to in writing, software
|
||||||
|
# # distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# # See the License for the specific language governing permissions and
|
||||||
|
# # limitations under the License.
|
||||||
|
# template-path:
|
||||||
|
# # also as alternative of directive 'template' you may put the path to file with the template source
|
||||||
|
|
||||||
|
# goimports:
|
||||||
|
# # put imports beginning with prefix after 3rd-party packages;
|
||||||
|
# # it's a comma-separated list of prefixes
|
||||||
|
|
||||||
|
# gomnd:
|
||||||
|
# settings:
|
||||||
|
# mnd:
|
||||||
|
# # the list of enabled checks, see https://github.com/tommy-muehle/go-mnd/#checks for description.
|
||||||
|
# checks: argument,case,condition,operation,return,assign
|
||||||
|
# # ignored-numbers: 1000
|
||||||
|
# # ignored-files: magic_.*.go
|
||||||
|
# # ignored-functions: math.*
|
||||||
|
|
||||||
|
# gomoddirectives:
|
||||||
|
# # Allow local `replace` directives. Default is false.
|
||||||
|
# replace-local: false
|
||||||
|
# # List of allowed `replace` directives. Default is empty.
|
||||||
|
# replace-allow-list:
|
||||||
|
# - launchpad.net/gocheck
|
||||||
|
# # Allow to not explain why the version has been retracted in the `retract` directives. Default is false.
|
||||||
|
# retract-allow-no-explanation: false
|
||||||
|
# # Forbid the use of the `exclude` directives. Default is false.
|
||||||
|
# exclude-forbidden: false
|
||||||
|
|
||||||
|
# gomodguard:
|
||||||
|
# allowed:
|
||||||
|
# modules: # List of allowed modules
|
||||||
|
# # - gopkg.in/yaml.v2
|
||||||
|
# domains: # List of allowed module domains
|
||||||
|
# # - golang.org
|
||||||
|
# blocked:
|
||||||
|
# modules: # List of blocked modules
|
||||||
|
# # - github.com/uudashr/go-module: # Blocked module
|
||||||
|
# # recommendations: # Recommended modules that should be used instead (Optional)
|
||||||
|
# # - golang.org/x/mod
|
||||||
|
# # reason: "`mod` is the official go.mod parser library." # Reason why the recommended module should be used (Optional)
|
||||||
|
# versions: # List of blocked module version constraints
|
||||||
|
# # - github.com/mitchellh/go-homedir: # Blocked module with version constraint
|
||||||
|
# # version: "< 1.1.0" # Version constraint, see https://github.com/Masterminds/semver#basic-comparisons
|
||||||
|
# # reason: "testing if blocked version constraint works." # Reason why the version constraint exists. (Optional)
|
||||||
|
# local_replace_directives: false # Set to true to raise lint issues for packages that are loaded from a local path via replace directive
|
||||||
|
|
||||||
|
# gosec:
|
||||||
|
# # To select a subset of rules to run.
|
||||||
|
# # Available rules: https://github.com/securego/gosec#available-rules
|
||||||
|
# includes:
|
||||||
|
# - G401
|
||||||
|
# - G306
|
||||||
|
# - G101
|
||||||
|
# # To specify a set of rules to explicitly exclude.
|
||||||
|
# # Available rules: https://github.com/securego/gosec#available-rules
|
||||||
|
# excludes:
|
||||||
|
# - G204
|
||||||
|
# # To specify the configuration of rules.
|
||||||
|
# # The configuration of rules is not fully documented by gosec:
|
||||||
|
# # https://github.com/securego/gosec#configuration
|
||||||
|
# # https://github.com/securego/gosec/blob/569328eade2ccbad4ce2d0f21ee158ab5356a5cf/rules/rulelist.go#L60-L102
|
||||||
|
# config:
|
||||||
|
# G306: "0600"
|
||||||
|
# G101:
|
||||||
|
# pattern: "(?i)example"
|
||||||
|
# ignore_entropy: false
|
||||||
|
# entropy_threshold: "80.0"
|
||||||
|
# per_char_threshold: "3.0"
|
||||||
|
# truncate: "32"
|
||||||
|
|
||||||
|
# gosimple:
|
||||||
|
# # Select the Go version to target. The default is '1.13'.
|
||||||
|
# go: "1.15"
|
||||||
|
# # https://staticcheck.io/docs/options#checks
|
||||||
|
# checks: [ "all" ]
|
||||||
|
|
||||||
|
govet:
|
||||||
|
# report about shadowed variables
|
||||||
|
# check-shadowing: true
|
||||||
|
|
||||||
|
# settings per analyzer
|
||||||
|
# settings:
|
||||||
|
# printf: # analyzer name, run `go tool vet help` to see all analyzers
|
||||||
|
# funcs: # run `go tool vet help printf` to see available settings for `printf` analyzer
|
||||||
|
|
||||||
|
# enable or disable analyzers by name
|
||||||
|
# run `go tool vet help` to see all analyzers
|
||||||
|
# enable:
|
||||||
|
# - atomicalign
|
||||||
|
enable-all: true
|
||||||
|
# disable-all: false
|
||||||
|
disable:
|
||||||
|
- fieldalignment
|
||||||
|
- composites
|
||||||
|
- shadow
|
||||||
|
|
||||||
|
|
||||||
|
depguard:
|
||||||
|
# Rules to apply.
|
||||||
|
#
|
||||||
|
# Variables:
|
||||||
|
# - File Variables
|
||||||
|
# you can still use and exclamation mark ! in front of a variable to say not to use it.
|
||||||
|
# Example !$test will match any file that is not a go test file.
|
||||||
|
#
|
||||||
|
# `$all` - matches all go files
|
||||||
|
# `$test` - matches all go test files
|
||||||
|
#
|
||||||
|
# - Package Variables
|
||||||
|
#
|
||||||
|
# `$gostd` - matches all of go's standard library (Pulled from `GOROOT`)
|
||||||
|
#
|
||||||
|
# Default: no rules.
|
||||||
|
rules:
|
||||||
|
# Name of a rule.
|
||||||
|
main:
|
||||||
|
# List of allowed packages.
|
||||||
|
# allow:
|
||||||
|
# - $gostd
|
||||||
|
# - $all
|
||||||
|
# Packages that are not allowed where the value is a suggestion.
|
||||||
|
deny:
|
||||||
|
- pkg: io/ioutil
|
||||||
|
desc: "replace with the matching functions from `io` or `os` packages"
|
||||||
|
- pkg: "github.com/pkg/errors"
|
||||||
|
desc: Should be replaced by standard lib errors package
|
||||||
|
- pkg: github.com/sirupsen/logrus
|
||||||
|
desc: "use stdlib logger, just create loggers for each log level that you want"
|
||||||
|
|
||||||
|
# ifshort:
|
||||||
|
# # Maximum length of variable declaration measured in number of lines, after which linter won't suggest using short syntax.
|
||||||
|
# # Has higher priority than max-decl-chars.
|
||||||
|
# max-decl-lines: 1
|
||||||
|
# # Maximum length of variable declaration measured in number of characters, after which linter won't suggest using short syntax.
|
||||||
|
# max-decl-chars: 30
|
||||||
|
|
||||||
|
# importas:
|
||||||
|
# # if set to `true`, force to use alias.
|
||||||
|
# no-unaliased: true
|
||||||
|
# # List of aliases
|
||||||
|
# alias:
|
||||||
|
# # using `servingv1` alias for `knative.dev/serving/pkg/apis/serving/v1` package
|
||||||
|
# - pkg: knative.dev/serving/pkg/apis/serving/v1
|
||||||
|
# alias: servingv1
|
||||||
|
# # using `autoscalingv1alpha1` alias for `knative.dev/serving/pkg/apis/autoscaling/v1alpha1` package
|
||||||
|
# - pkg: knative.dev/serving/pkg/apis/autoscaling/v1alpha1
|
||||||
|
# alias: autoscalingv1alpha1
|
||||||
|
# # You can specify the package path by regular expression,
|
||||||
|
# # and alias by regular expression expansion syntax like below.
|
||||||
|
# # see https://github.com/julz/importas#use-regular-expression for details
|
||||||
|
# - pkg: knative.dev/serving/pkg/apis/(\w+)/(v[\w\d]+)
|
||||||
|
# alias: $1$2
|
||||||
|
|
||||||
|
lll:
|
||||||
|
# max line length, lines longer will be reported. Default is 120.
|
||||||
|
# '\t' is counted as 1 character by default, and can be changed with the tab-width option
|
||||||
|
line-length: 140
|
||||||
|
# tab width in spaces. Default to 1.
|
||||||
|
tab-width: 4
|
||||||
|
|
||||||
|
# makezero:
|
||||||
|
# # Allow only slices initialized with a length of zero. Default is false.
|
||||||
|
# always: false
|
||||||
|
|
||||||
|
# maligned:
|
||||||
|
# # print struct with more effective memory layout or not, false by default
|
||||||
|
# suggest-new: true
|
||||||
|
|
||||||
|
# misspell:
|
||||||
|
# # Correct spellings using locale preferences for US or UK.
|
||||||
|
# # Default is to use a neutral variety of English.
|
||||||
|
# # Setting locale to US will correct the British spelling of 'colour' to 'color'.
|
||||||
|
# locale: US
|
||||||
|
# ignore-words:
|
||||||
|
# - someword
|
||||||
|
|
||||||
|
# nakedret:
|
||||||
|
# # make an issue if func has more lines of code than this setting and it has naked returns; default is 30
|
||||||
|
# max-func-lines: 30
|
||||||
|
|
||||||
|
# prealloc:
|
||||||
|
# # XXX: we don't recommend using this linter before doing performance profiling.
|
||||||
|
# # For most programs usage of prealloc will be a premature optimization.
|
||||||
|
|
||||||
|
# # Report preallocation suggestions only on simple loops that have no returns/breaks/continues/gotos in them.
|
||||||
|
# # True by default.
|
||||||
|
# simple: true
|
||||||
|
# range-loops: true # Report preallocation suggestions on range loops, true by default
|
||||||
|
# for-loops: false # Report preallocation suggestions on for loops, false by default
|
||||||
|
|
||||||
|
# promlinter:
|
||||||
|
# # Promlinter cannot infer all metrics name in static analysis.
|
||||||
|
# # Enable strict mode will also include the errors caused by failing to parse the args.
|
||||||
|
# strict: false
|
||||||
|
# # Please refer to https://github.com/yeya24/promlinter#usage for detailed usage.
|
||||||
|
# disabled-linters:
|
||||||
|
# # - "Help"
|
||||||
|
# # - "MetricUnits"
|
||||||
|
# # - "Counter"
|
||||||
|
# # - "HistogramSummaryReserved"
|
||||||
|
# # - "MetricTypeInName"
|
||||||
|
# # - "ReservedChars"
|
||||||
|
# # - "CamelCase"
|
||||||
|
# # - "lintUnitAbbreviations"
|
||||||
|
|
||||||
|
# predeclared:
|
||||||
|
# # comma-separated list of predeclared identifiers to not report on
|
||||||
|
# ignore: ""
|
||||||
|
# # include method names and field names (i.e., qualified names) in checks
|
||||||
|
# q: false
|
||||||
|
|
||||||
|
nolintlint:
|
||||||
|
# Enable to ensure that nolint directives are all used. Default is true.
|
||||||
|
allow-unused: false
|
||||||
|
# Disable to ensure that nolint directives don't have a leading space. Default is true.
|
||||||
|
allow-leading-space: true
|
||||||
|
# Exclude following linters from requiring an explanation. Default is [].
|
||||||
|
allow-no-explanation: []
|
||||||
|
# Enable to require an explanation of nonzero length after each nolint directive. Default is false.
|
||||||
|
require-explanation: true
|
||||||
|
# Enable to require nolint directives to mention the specific linter being suppressed. Default is false.
|
||||||
|
require-specific: true
|
||||||
|
|
||||||
|
# rowserrcheck:
|
||||||
|
# packages:
|
||||||
|
# - github.com/jmoiron/sqlx
|
||||||
|
|
||||||
|
# revive:
|
||||||
|
# # see https://github.com/mgechev/revive#available-rules for details.
|
||||||
|
# ignore-generated-header: true
|
||||||
|
# severity: warning
|
||||||
|
# rules:
|
||||||
|
# - name: indent-error-flow
|
||||||
|
# severity: warning
|
||||||
|
# - name: add-constant
|
||||||
|
# severity: warning
|
||||||
|
# arguments:
|
||||||
|
# - maxLitCount: "3"
|
||||||
|
# allowStrs: '""'
|
||||||
|
# allowInts: "0,1,2"
|
||||||
|
# allowFloats: "0.0,0.,1.0,1.,2.0,2."
|
||||||
|
|
||||||
|
# staticcheck:
|
||||||
|
# # Select the Go version to target. The default is '1.13'.
|
||||||
|
# go: "1.15"
|
||||||
|
# # https://staticcheck.io/docs/options#checks
|
||||||
|
# checks: [ "all" ]
|
||||||
|
|
||||||
|
# stylecheck:
|
||||||
|
# # Select the Go version to target. The default is '1.13'.
|
||||||
|
# go: "1.15"
|
||||||
|
# # https://staticcheck.io/docs/options#checks
|
||||||
|
# checks: [ "all", "-ST1000", "-ST1003", "-ST1016", "-ST1020", "-ST1021", "-ST1022" ]
|
||||||
|
# # https://staticcheck.io/docs/options#dot_import_whitelist
|
||||||
|
# dot-import-whitelist:
|
||||||
|
# - fmt
|
||||||
|
# # https://staticcheck.io/docs/options#initialisms
|
||||||
|
# initialisms: [ "ACL", "API", "ASCII", "CPU", "CSS", "DNS", "EOF", "GUID", "HTML", "HTTP", "HTTPS", "ID", "IP", "JSON", "QPS", "RAM", "RPC", "SLA", "SMTP", "SQL", "SSH", "TCP", "TLS", "TTL", "UDP", "UI", "GID", "UID", "UUID", "URI", "URL", "UTF8", "VM", "XML", "XMPP", "XSRF", "XSS" ]
|
||||||
|
# # https://staticcheck.io/docs/options#http_status_code_whitelist
|
||||||
|
# http-status-code-whitelist: [ "200", "400", "404", "500" ]
|
||||||
|
|
||||||
|
# tagliatelle:
|
||||||
|
# # check the struck tag name case
|
||||||
|
# case:
|
||||||
|
# # use the struct field name to check the name of the struct tag
|
||||||
|
# use-field-name: true
|
||||||
|
# rules:
|
||||||
|
# # any struct tag type can be used.
|
||||||
|
# # support string case: `camel`, `pascal`, `kebab`, `snake`, `goCamel`, `goPascal`, `goKebab`, `goSnake`, `upper`, `lower`
|
||||||
|
# json: camel
|
||||||
|
# yaml: camel
|
||||||
|
# xml: camel
|
||||||
|
# bson: camel
|
||||||
|
# avro: snake
|
||||||
|
# mapstructure: kebab
|
||||||
|
|
||||||
|
# testpackage:
|
||||||
|
# # regexp pattern to skip files
|
||||||
|
# skip-regexp: (export|internal)_test\.go
|
||||||
|
|
||||||
|
# thelper:
|
||||||
|
# # The following configurations enable all checks. It can be omitted because all checks are enabled by default.
|
||||||
|
# # You can enable only required checks deleting unnecessary checks.
|
||||||
|
# test:
|
||||||
|
# first: true
|
||||||
|
# name: true
|
||||||
|
# begin: true
|
||||||
|
# benchmark:
|
||||||
|
# first: true
|
||||||
|
# name: true
|
||||||
|
# begin: true
|
||||||
|
# tb:
|
||||||
|
# first: true
|
||||||
|
# name: true
|
||||||
|
# begin: true
|
||||||
|
|
||||||
|
# unparam:
|
||||||
|
# # Inspect exported functions, default is false. Set to true if no external program/library imports your code.
|
||||||
|
# # XXX: if you enable this setting, unparam will report a lot of false-positives in text editors:
|
||||||
|
# # if it's called for subdir of a project it can't find external interfaces. All text editor integrations
|
||||||
|
# # with golangci-lint call it on a directory with the changed file.
|
||||||
|
# check-exported: false
|
||||||
|
|
||||||
|
# unused:
|
||||||
|
# # Select the Go version to target. The default is '1.13'.
|
||||||
|
# go: "1.15"
|
||||||
|
|
||||||
|
# whitespace:
|
||||||
|
# multi-if: false # Enforces newlines (or comments) after every multi-line if statement
|
||||||
|
# multi-func: false # Enforces newlines (or comments) after every multi-line function signature
|
||||||
|
|
||||||
|
wrapcheck:
|
||||||
|
# An array of strings that specify substrings of signatures to ignore.
|
||||||
|
# If this set, it will override the default set of ignored signatures.
|
||||||
|
# See https://github.com/tomarrell/wrapcheck#configuration for more information.
|
||||||
|
ignoreSigs:
|
||||||
|
|
||||||
|
# # The custom section can be used to define linter plugins to be loaded at runtime.
|
||||||
|
# # See README doc for more info.
|
||||||
|
# custom:
|
||||||
|
# # Each custom linter should have a unique name.
|
||||||
|
# example:
|
||||||
|
# # The path to the plugin *.so. Can be absolute or local. Required for each custom linter
|
||||||
|
# path: /path/to/example.so
|
||||||
|
# # The description of the linter. Optional, just for documentation purposes.
|
||||||
|
# description: This is an example usage of a plugin linter.
|
||||||
|
# # Intended to point to the repo location of the linter. Optional, just for documentation purposes.
|
||||||
|
# original-url: github.com/golangci/example-linter
|
||||||
|
|
||||||
|
|
||||||
|
# issues:
|
||||||
|
# # List of regexps of issue texts to exclude, empty list by default.
|
||||||
|
# # But independently from this option we use default exclude patterns,
|
||||||
|
# # it can be disabled by `exclude-use-default: false`. To list all
|
||||||
|
# # excluded by default patterns execute `golangci-lint run --help`
|
||||||
|
# exclude:
|
||||||
|
# - abcdef
|
||||||
|
|
||||||
|
# # Excluding configuration per-path, per-linter, per-text and per-source
|
||||||
|
# exclude-rules:
|
||||||
|
# # Exclude some linters from running on tests files.
|
||||||
|
# - path: _test\.go
|
||||||
|
# linters:
|
||||||
|
# - gocyclo
|
||||||
|
# - errcheck
|
||||||
|
# - dupl
|
||||||
|
# - gosec
|
||||||
|
|
||||||
|
# # Exclude known linters from partially hard-vendored code,
|
||||||
|
# # which is impossible to exclude via "nolint" comments.
|
||||||
|
# - path: internal/hmac/
|
||||||
|
# text: "weak cryptographic primitive"
|
||||||
|
# linters:
|
||||||
|
# - gosec
|
||||||
|
|
||||||
|
# # Exclude some staticcheck messages
|
||||||
|
# - linters:
|
||||||
|
# - staticcheck
|
||||||
|
# text: "SA9003:"
|
||||||
|
|
||||||
|
# # Exclude lll issues for long lines with go:generate
|
||||||
|
# - linters:
|
||||||
|
# - lll
|
||||||
|
# source: "^//go:generate "
|
||||||
|
|
||||||
|
# # Independently from option `exclude` we use default exclude patterns,
|
||||||
|
# # it can be disabled by this option. To list all
|
||||||
|
# # excluded by default patterns execute `golangci-lint run --help`.
|
||||||
|
# # Default value for this option is true.
|
||||||
|
# exclude-use-default: false
|
||||||
|
|
||||||
|
# # The default value is false. If set to true exclude and exclude-rules
|
||||||
|
# # regular expressions become case sensitive.
|
||||||
|
# exclude-case-sensitive: false
|
||||||
|
|
||||||
|
# # The list of ids of default excludes to include or disable. By default it's empty.
|
||||||
|
# include:
|
||||||
|
# - EXC0002 # disable excluding of issues about comments from golint
|
||||||
|
|
||||||
|
# # Maximum issues count per one linter. Set to 0 to disable. Default is 50.
|
||||||
|
# max-issues-per-linter: 0
|
||||||
|
|
||||||
|
# # Maximum count of issues with the same text. Set to 0 to disable. Default is 3.
|
||||||
|
# max-same-issues: 0
|
||||||
|
|
||||||
|
# # Show only new issues: if there are unstaged changes or untracked files,
|
||||||
|
# # only those changes are analyzed, else only changes in HEAD~ are analyzed.
|
||||||
|
# # It's a super-useful option for integration of golangci-lint into existing
|
||||||
|
# # large codebase. It's not practical to fix all existing issues at the moment
|
||||||
|
# # of integration: much better don't allow issues in new code.
|
||||||
|
# # Default is false.
|
||||||
|
# new: false
|
||||||
|
|
||||||
|
# severity:
|
||||||
|
# # Default value is empty string.
|
||||||
|
# # Set the default severity for issues. If severity rules are defined and the issues
|
||||||
|
# # do not match or no severity is provided to the rule this will be the default
|
||||||
|
# # severity applied. Severities should match the supported severity names of the
|
||||||
|
# # selected out format.
|
||||||
|
# # - Code climate: https://docs.codeclimate.com/docs/issues#issue-severity
|
||||||
|
# # - Checkstyle: https://checkstyle.sourceforge.io/property_types.html#severity
|
||||||
|
# # - Github: https://help.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-an-error-message
|
||||||
|
# default-severity: error
|
||||||
|
|
||||||
|
# # The default value is false.
|
||||||
|
# # If set to true severity-rules regular expressions become case sensitive.
|
||||||
|
# case-sensitive: false
|
||||||
|
|
||||||
|
# # Default value is empty list.
|
||||||
|
# # When a list of severity rules are provided, severity information will be added to lint
|
||||||
|
# # issues. Severity rules have the same filtering capability as exclude rules except you
|
||||||
|
# # are allowed to specify one matcher per severity rule.
|
||||||
|
# # Only affects out formats that support setting severity information.
|
||||||
|
# rules:
|
||||||
|
# - linters:
|
||||||
|
# - dupl
|
||||||
|
# severity: info
|
57
cmd/main.go
Normal file
57
cmd/main.go
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
pkg_db "recipe_book/pkg/db"
|
||||||
|
)
|
||||||
|
|
||||||
|
const DB_FILENAME = "food.db"
|
||||||
|
|
||||||
|
var db_path string = ""
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
flag.StringVar(&db_path, "db", "sample_data/data", "database path")
|
||||||
|
|
||||||
|
flag.Parse()
|
||||||
|
args := flag.Args()
|
||||||
|
|
||||||
|
if len(args) == 0 {
|
||||||
|
fmt.Printf("subcommand needed\n")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch args[0] {
|
||||||
|
case "init":
|
||||||
|
init_db()
|
||||||
|
default:
|
||||||
|
fmt.Printf(COLOR_RED+"invalid subcommand: %q\n"+COLOR_RESET, args[0])
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func init_db() {
|
||||||
|
db_filename := filepath.Join(db_path, DB_FILENAME)
|
||||||
|
_, err := pkg_db.DBCreate(db_filename)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(COLOR_RED + err.Error() + COLOR_RESET)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
fmt.Println(COLOR_GREEN + "Successfully created the db" + COLOR_RESET)
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
COLOR_RESET = "\033[0m"
|
||||||
|
COLOR_BLACK = "\033[30m"
|
||||||
|
COLOR_RED = "\033[31m"
|
||||||
|
COLOR_GREEN = "\033[32m"
|
||||||
|
COLOR_YELLOW = "\033[33m"
|
||||||
|
COLOR_BLUE = "\033[34m"
|
||||||
|
COLOR_PURPLE = "\033[35m"
|
||||||
|
COLOR_CYAN = "\033[36m"
|
||||||
|
COLOR_GRAY = "\033[37m"
|
||||||
|
COLOR_WHITE = "\033[97m"
|
||||||
|
)
|
16
go.mod
Normal file
16
go.mod
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
module recipe_book
|
||||||
|
|
||||||
|
go 1.22.5
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/go-test/deep v1.1.1
|
||||||
|
github.com/jmoiron/sqlx v1.4.0
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.24
|
||||||
|
github.com/stretchr/testify v1.9.0
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
)
|
23
go.sum
Normal file
23
go.sum
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||||
|
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
||||||
|
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||||
|
github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U=
|
||||||
|
github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
|
||||||
|
github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o=
|
||||||
|
github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY=
|
||||||
|
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||||
|
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
122
pkg/db/db_connect.go
Normal file
122
pkg/db/db_connect.go
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
package db
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
_ "github.com/mattn/go-sqlite3"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed schema.sql
|
||||||
|
var sql_schema string
|
||||||
|
|
||||||
|
// Database starts at version 0. First migration brings us to version 1
|
||||||
|
var MIGRATIONS = []string{}
|
||||||
|
var ENGINE_DATABASE_VERSION = len(MIGRATIONS)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrTargetExists = errors.New("target already exists")
|
||||||
|
)
|
||||||
|
|
||||||
|
type DB struct {
|
||||||
|
DB *sqlx.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func DBCreate(path string) (DB, error) {
|
||||||
|
// First check if the path already exists
|
||||||
|
_, err := os.Stat(path)
|
||||||
|
if err == nil {
|
||||||
|
return DB{}, ErrTargetExists
|
||||||
|
} else if !errors.Is(err, os.ErrNotExist) {
|
||||||
|
return DB{}, fmt.Errorf("path error: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create DB file
|
||||||
|
fmt.Printf("Creating.............%s\n", path)
|
||||||
|
db := sqlx.MustOpen("sqlite3", path+"?_foreign_keys=on&_journal_mode=WAL")
|
||||||
|
db.MustExec(sql_schema)
|
||||||
|
|
||||||
|
return DB{db}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func DBConnect(path string) (DB, error) {
|
||||||
|
db := sqlx.MustOpen("sqlite3", fmt.Sprintf("%s?_foreign_keys=on&_journal_mode=WAL", path))
|
||||||
|
ret := DB{db}
|
||||||
|
err := ret.CheckAndUpdateVersion()
|
||||||
|
return ret, err
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Colors for terminal output
|
||||||
|
*/
|
||||||
|
const (
|
||||||
|
COLOR_RESET = "\033[0m"
|
||||||
|
COLOR_BLACK = "\033[30m"
|
||||||
|
COLOR_RED = "\033[31m"
|
||||||
|
COLOR_GREEN = "\033[32m"
|
||||||
|
COLOR_YELLOW = "\033[33m"
|
||||||
|
COLOR_BLUE = "\033[34m"
|
||||||
|
COLOR_PURPLE = "\033[35m"
|
||||||
|
COLOR_CYAN = "\033[36m"
|
||||||
|
COLOR_GRAY = "\033[37m"
|
||||||
|
COLOR_WHITE = "\033[97m"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (db DB) CheckAndUpdateVersion() error {
|
||||||
|
var version int
|
||||||
|
err := db.DB.Get(&version, "select version from db_version")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("couldn't check database version: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if version > ENGINE_DATABASE_VERSION {
|
||||||
|
return VersionMismatchError{ENGINE_DATABASE_VERSION, version}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ENGINE_DATABASE_VERSION > version {
|
||||||
|
fmt.Print(COLOR_YELLOW)
|
||||||
|
fmt.Printf("================================================\n")
|
||||||
|
fmt.Printf("Database version is out of date. Upgrading database from version %d to version %d!\n", version,
|
||||||
|
ENGINE_DATABASE_VERSION)
|
||||||
|
fmt.Print(COLOR_RESET)
|
||||||
|
db.UpgradeFromXToY(version, ENGINE_DATABASE_VERSION)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run all the migrations from version X to version Y, and update the `database_version` table's `version_number`
|
||||||
|
func (db DB) UpgradeFromXToY(x int, y int) {
|
||||||
|
for i := x; i < y; i++ {
|
||||||
|
fmt.Print(COLOR_CYAN)
|
||||||
|
fmt.Println(MIGRATIONS[i])
|
||||||
|
fmt.Print(COLOR_RESET)
|
||||||
|
|
||||||
|
db.DB.MustExec(MIGRATIONS[i])
|
||||||
|
db.DB.MustExec("update db_version set version = ?", i+1)
|
||||||
|
|
||||||
|
fmt.Print(COLOR_YELLOW)
|
||||||
|
fmt.Printf("Now at database schema version %d.\n", i+1)
|
||||||
|
fmt.Print(COLOR_RESET)
|
||||||
|
}
|
||||||
|
fmt.Print(COLOR_GREEN)
|
||||||
|
fmt.Printf("================================================\n")
|
||||||
|
fmt.Printf("Database version has been upgraded to version %d.\n", y)
|
||||||
|
fmt.Print(COLOR_RESET)
|
||||||
|
}
|
||||||
|
|
||||||
|
type VersionMismatchError struct {
|
||||||
|
EngineVersion int
|
||||||
|
DatabaseVersion int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e VersionMismatchError) Error() string {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
`This profile was created with database schema version %d, which is newer than this application's database schema version, %d.
|
||||||
|
Please upgrade this application to a newer version to use this profile. Or downgrade the profile's schema version, somehow.`,
|
||||||
|
e.DatabaseVersion, e.EngineVersion,
|
||||||
|
)
|
||||||
|
}
|
33
pkg/db/db_connect_test.go
Normal file
33
pkg/db/db_connect_test.go
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
package db_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
. "recipe_book/pkg/db"
|
||||||
|
)
|
||||||
|
|
||||||
|
func get_test_db() DB {
|
||||||
|
db_path := "../../sample_data/data/test.db"
|
||||||
|
db, err := DBCreate(db_path)
|
||||||
|
if errors.Is(err, ErrTargetExists) {
|
||||||
|
db, err = DBConnect(db_path)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return db
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateAndConnectToDB(t *testing.T) {
|
||||||
|
i := rand.Uint32()
|
||||||
|
_, err := DBCreate(fmt.Sprintf("../../sample_data/data/random-%d.db", i))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
_, err = DBConnect(fmt.Sprintf("../../sample_data/data/random-%d.db", i))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
105
pkg/db/food.go
Normal file
105
pkg/db/food.go
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
package db
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FoodID uint64
|
||||||
|
|
||||||
|
type Food struct {
|
||||||
|
ID FoodID `db:"rowid"`
|
||||||
|
Name string `db:"name"`
|
||||||
|
|
||||||
|
Cals float32 `db:"cals"`
|
||||||
|
Carbs float32 `db:"carbs"`
|
||||||
|
Protein float32 `db:"protein"`
|
||||||
|
Fat float32 `db:"fat"`
|
||||||
|
Sugar float32 `db:"sugar"`
|
||||||
|
Alcohol float32 `db:"alcohol"`
|
||||||
|
|
||||||
|
Water float32 `db:"water"`
|
||||||
|
|
||||||
|
Potassium float32 `db:"potassium"`
|
||||||
|
Calcium float32 `db:"calcium"`
|
||||||
|
Sodium float32 `db:"sodium"`
|
||||||
|
Magnesium float32 `db:"magnesium"`
|
||||||
|
Phosphorus float32 `db:"phosphorus"`
|
||||||
|
Iron float32 `db:"iron"`
|
||||||
|
Zinc float32 `db:"zinc"`
|
||||||
|
|
||||||
|
Mass float32 `db:"mass"`
|
||||||
|
Price float32 `db:"price"`
|
||||||
|
Density float32 `db:"density"`
|
||||||
|
CookRatio float32 `db:"cook_ratio"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format as string
|
||||||
|
func (f Food) String() string {
|
||||||
|
return fmt.Sprintf("%s(%d)", f.Name, f.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *DB) SaveFood(f *Food) {
|
||||||
|
if f.ID == FoodID(0) {
|
||||||
|
// Do create
|
||||||
|
result, err := db.DB.NamedExec(`
|
||||||
|
insert into foods (name, cals, carbs, protein, fat, sugar, alcohol, water, potassium, calcium, sodium,
|
||||||
|
magnesium, phosphorus, iron, zinc, mass, price, density, cook_ratio)
|
||||||
|
values (:name, :cals, :carbs, :protein, :fat, :sugar, :alcohol, :water, :potassium, :calcium,
|
||||||
|
:sodium, :magnesium, :phosphorus, :iron, :zinc, :mass, :price, :density, :cook_ratio)
|
||||||
|
`, f)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
// Update the ID if necessary
|
||||||
|
id, err := result.LastInsertId()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
f.ID = FoodID(id)
|
||||||
|
} else {
|
||||||
|
// Do update
|
||||||
|
result, err := db.DB.NamedExec(`
|
||||||
|
update foods
|
||||||
|
set name=:name,
|
||||||
|
cals=:cals,
|
||||||
|
carbs=:carbs,
|
||||||
|
protein=:protein,
|
||||||
|
fat=:fat,
|
||||||
|
sugar=:sugar,
|
||||||
|
alcohol=:alcohol,
|
||||||
|
water=:water,
|
||||||
|
potassium=:potassium,
|
||||||
|
calcium=:calcium,
|
||||||
|
sodium=:sodium,
|
||||||
|
magnesium=:magnesium,
|
||||||
|
phosphorus=:phosphorus,
|
||||||
|
iron=:iron,
|
||||||
|
zinc=:zinc,
|
||||||
|
mass=:mass,
|
||||||
|
price=:price,
|
||||||
|
density=:density,
|
||||||
|
cook_ratio=:cook_ratio
|
||||||
|
where rowid = :rowid
|
||||||
|
`, f)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
count, err := result.RowsAffected()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if count != 1 {
|
||||||
|
panic(fmt.Errorf("Got food with ID (%d), so attempted update, but it doesn't exist", f.ID))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *DB) GetFoodByID(id FoodID) (ret Food, err error) {
|
||||||
|
err = db.DB.Get(&ret, `
|
||||||
|
select rowid, name, cals, carbs, protein, fat, sugar, alcohol, water, potassium, calcium, sodium,
|
||||||
|
magnesium, phosphorus, iron, zinc, mass, price, density, cook_ratio
|
||||||
|
from foods
|
||||||
|
where rowid = ?
|
||||||
|
`, id)
|
||||||
|
return
|
||||||
|
}
|
72
pkg/db/food_test.go
Normal file
72
pkg/db/food_test.go
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
package db_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/go-test/deep"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
. "recipe_book/pkg/db"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFoodSaveAndLoad(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
db := get_test_db()
|
||||||
|
food := Food{
|
||||||
|
Name: "some food",
|
||||||
|
Cals: 1.0,
|
||||||
|
Carbs: 2.0,
|
||||||
|
Protein: 3.0,
|
||||||
|
Fat: 4.0,
|
||||||
|
Sugar: 5.0,
|
||||||
|
Alcohol: 6.0,
|
||||||
|
Water: 7.0,
|
||||||
|
Potassium: 8.0,
|
||||||
|
Calcium: 9.0,
|
||||||
|
Sodium: 10.0,
|
||||||
|
Magnesium: 11.0,
|
||||||
|
Phosphorus: 12.0,
|
||||||
|
Iron: 13.0,
|
||||||
|
Zinc: 14.0,
|
||||||
|
Mass: 15.0,
|
||||||
|
Price: 16.0,
|
||||||
|
Density: 17.0,
|
||||||
|
CookRatio: 18.0,
|
||||||
|
}
|
||||||
|
assert.Equal(food.ID, FoodID(0))
|
||||||
|
db.SaveFood(&food)
|
||||||
|
assert.NotEqual(food.ID, FoodID(0))
|
||||||
|
new_food, err := db.GetFoodByID(food.ID)
|
||||||
|
assert.NoError(err)
|
||||||
|
if diff := deep.Equal(food, new_food); diff != nil {
|
||||||
|
t.Error(diff)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Modify it
|
||||||
|
food.Name = "another food"
|
||||||
|
food.Cals = food.Cals + 9.2
|
||||||
|
food.Carbs = food.Carbs + 9.2
|
||||||
|
food.Protein = food.Protein + 9.2
|
||||||
|
food.Fat = food.Fat + 9.2
|
||||||
|
food.Sugar = food.Sugar + 9.2
|
||||||
|
food.Alcohol = food.Alcohol + 9.2
|
||||||
|
food.Water = food.Water + 9.2
|
||||||
|
food.Potassium = food.Potassium + 9.2
|
||||||
|
food.Calcium = food.Calcium + 9.2
|
||||||
|
food.Sodium = food.Sodium + 9.2
|
||||||
|
food.Phosphorus = food.Phosphorus + 9.2
|
||||||
|
food.Iron = food.Iron + 9.2
|
||||||
|
food.Zinc = food.Zinc + 9.2
|
||||||
|
food.Mass = food.Mass + 9.2
|
||||||
|
food.Price = food.Price + 9.2
|
||||||
|
food.Density = food.Density + 9.2
|
||||||
|
food.CookRatio = food.CookRatio + 9.2
|
||||||
|
|
||||||
|
// Save it and reload it
|
||||||
|
db.SaveFood(&food)
|
||||||
|
new_food, err = db.GetFoodByID(food.ID)
|
||||||
|
assert.NoError(err)
|
||||||
|
if diff := deep.Equal(food, new_food); diff != nil {
|
||||||
|
t.Error(diff)
|
||||||
|
}
|
||||||
|
}
|
26
pkg/db/ingredient.go
Normal file
26
pkg/db/ingredient.go
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
package db
|
||||||
|
|
||||||
|
import (
|
||||||
|
// "fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type IngredientID uint64
|
||||||
|
|
||||||
|
type Ingredient struct {
|
||||||
|
ID IngredientID `db:"rowid"`
|
||||||
|
FoodID FoodID `db:"food_id"`
|
||||||
|
RecipeID RecipeID `db:"recipe_id"`
|
||||||
|
|
||||||
|
QuantityNumerator int64 `db:"quantity_numerator"`
|
||||||
|
QuantityDenominator int64 `db:"quantity_denominator"`
|
||||||
|
Units Units `db:"units"`
|
||||||
|
|
||||||
|
InRecipeID RecipeID `db:"in_recipe_id"`
|
||||||
|
ListOrder int64 `db:"list_order"`
|
||||||
|
IsHidden bool `db:"is_hidden"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// // Format as string
|
||||||
|
// func (i Ingredient) String() string {
|
||||||
|
// return fmt.Sprintf("%s(%d)", f.Name, f.ID)
|
||||||
|
// }
|
103
pkg/db/recipe.go
Normal file
103
pkg/db/recipe.go
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
package db
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql/driver"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RecipeID uint64
|
||||||
|
|
||||||
|
type RecipeInstructions []string
|
||||||
|
|
||||||
|
// Join the instructions with 0x1F, the "Unit Separator" ASCII character
|
||||||
|
func (ri RecipeInstructions) Value() (driver.Value, error) {
|
||||||
|
return strings.Join(ri, "\x1F"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split the stored string by "Unit Separator" characters
|
||||||
|
func (ri *RecipeInstructions) Scan(src interface{}) error {
|
||||||
|
val, is_ok := src.(string)
|
||||||
|
if !is_ok {
|
||||||
|
return fmt.Errorf("incompatible type for RecipeInstructions list: %#v", src)
|
||||||
|
}
|
||||||
|
*ri = RecipeInstructions(strings.Split(val, "\x1F"))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Recipe struct {
|
||||||
|
ID RecipeID `db:"rowid"`
|
||||||
|
Name string `db:"name"`
|
||||||
|
Blurb string `db:"blurb"`
|
||||||
|
Instructions RecipeInstructions `db:"instructions"`
|
||||||
|
|
||||||
|
Ingredients []Ingredient
|
||||||
|
|
||||||
|
ComputedFoodID FoodID `db:"computed_food_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *DB) SaveRecipe(r *Recipe) {
|
||||||
|
if r.ID == RecipeID(0) {
|
||||||
|
// Do create
|
||||||
|
result, err := db.DB.NamedExec(`
|
||||||
|
insert into recipes (name, blurb, instructions)
|
||||||
|
values (:name, :blurb, :instructions)
|
||||||
|
on conflict do update
|
||||||
|
set name=:name,
|
||||||
|
blurb=:blurb,
|
||||||
|
instructions=:instructions
|
||||||
|
`, r)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the ID
|
||||||
|
id, err := result.LastInsertId()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
r.ID = RecipeID(id)
|
||||||
|
} else {
|
||||||
|
// Do update
|
||||||
|
result, err := db.DB.NamedExec(`
|
||||||
|
update recipes set name=:name, blurb=:blurb, instructions=:instructions where rowid = :rowid
|
||||||
|
`, r)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
count, err := result.RowsAffected()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if count != 1 {
|
||||||
|
panic(fmt.Errorf("Got recipe with ID (%d), so attempted update, but it doesn't exist", r.ID))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO: recompute the computed_food
|
||||||
|
}
|
||||||
|
|
||||||
|
// func (db *DB) AddIngredientToRecipe(r Recipe, i *Ingredient) {
|
||||||
|
// result, err := db.DB.NamedExec(`
|
||||||
|
// insert into ingredients
|
||||||
|
// `, ingr)
|
||||||
|
// }
|
||||||
|
|
||||||
|
func (db *DB) GetRecipeByID(id RecipeID) (ret Recipe, err error) {
|
||||||
|
err = db.DB.Get(&ret, `
|
||||||
|
select rowid, name, blurb, instructions
|
||||||
|
from recipes
|
||||||
|
where rowid = ?
|
||||||
|
`, id)
|
||||||
|
if err != nil {
|
||||||
|
return Recipe{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the ingredients
|
||||||
|
err = db.DB.Select(&ret.Ingredients, `
|
||||||
|
select food_id, recipe_id, quantity_numerator, quantity_denominator, units, list_order, is_hidden
|
||||||
|
from ingredients
|
||||||
|
where in_recipe_id = ?
|
||||||
|
order by list_order asc
|
||||||
|
`, id)
|
||||||
|
return
|
||||||
|
}
|
44
pkg/db/recipe_test.go
Normal file
44
pkg/db/recipe_test.go
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
package db_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/go-test/deep"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
. "recipe_book/pkg/db"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRecipeSaveAndLoad(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
db := get_test_db()
|
||||||
|
|
||||||
|
recipe := Recipe{
|
||||||
|
Name: "some Recipe",
|
||||||
|
Blurb: "Lorem Ispum dolor sit amet consquiter id blah blabh albha blahbla blahblahblh",
|
||||||
|
Instructions: RecipeInstructions{
|
||||||
|
"instr 1", "isntr 2", "instr3", "ins32gjkifw",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
assert.Equal(recipe.ID, RecipeID(0))
|
||||||
|
db.SaveRecipe(&recipe)
|
||||||
|
assert.NotEqual(recipe.ID, RecipeID(0))
|
||||||
|
new_recipe, err := db.GetRecipeByID(recipe.ID)
|
||||||
|
assert.NoError(err)
|
||||||
|
if diff := deep.Equal(recipe, new_recipe); diff != nil {
|
||||||
|
t.Error(diff)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Modify it
|
||||||
|
recipe.Name = "some recipe 2"
|
||||||
|
recipe.Blurb = "another blurb"
|
||||||
|
recipe.Instructions = RecipeInstructions{"i1", "i2", "i3"}
|
||||||
|
|
||||||
|
// Save it and reload
|
||||||
|
db.SaveRecipe(&recipe)
|
||||||
|
new_recipe, err = db.GetRecipeByID(recipe.ID)
|
||||||
|
assert.NoError(err)
|
||||||
|
if diff := deep.Equal(recipe, new_recipe); diff != nil {
|
||||||
|
t.Error(diff)
|
||||||
|
}
|
||||||
|
}
|
117
pkg/db/schema.sql
Normal file
117
pkg/db/schema.sql
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
PRAGMA foreign_keys = on;
|
||||||
|
|
||||||
|
-- =======
|
||||||
|
-- DB meta
|
||||||
|
-- =======
|
||||||
|
|
||||||
|
create table db_version (
|
||||||
|
version integer not null
|
||||||
|
) strict;
|
||||||
|
insert into db_version values(0);
|
||||||
|
|
||||||
|
create table food_types (rowid integer primary key,
|
||||||
|
name text not null unique check(length(name) != 0)
|
||||||
|
);
|
||||||
|
insert into food_types (name) values
|
||||||
|
('grocery'),
|
||||||
|
('recipe'),
|
||||||
|
('daily log');
|
||||||
|
|
||||||
|
create table foods (rowid integer primary key,
|
||||||
|
name text not null check(length(name) != 0),
|
||||||
|
|
||||||
|
-- created_at integer not null,
|
||||||
|
-- updated_at integer,
|
||||||
|
|
||||||
|
cals real not null,
|
||||||
|
carbs real not null,
|
||||||
|
protein real not null,
|
||||||
|
fat real not null,
|
||||||
|
|
||||||
|
sugar real not null,
|
||||||
|
alcohol real not null default 0,
|
||||||
|
water real not null default 0,
|
||||||
|
|
||||||
|
potassium real not null default 0,
|
||||||
|
sodium real not null default 0,
|
||||||
|
calcium real not null default 0,
|
||||||
|
magnesium real not null default 0,
|
||||||
|
phosphorus real not null default 0,
|
||||||
|
iron real not null default 0,
|
||||||
|
zinc real not null default 0,
|
||||||
|
|
||||||
|
mass real not null default 100,
|
||||||
|
price real not null default 0,
|
||||||
|
density real not null default -1,
|
||||||
|
cook_ratio real not null default 1
|
||||||
|
) strict;
|
||||||
|
|
||||||
|
|
||||||
|
create table units (rowid integer primary key,
|
||||||
|
name text not null unique check(length(name) != 0),
|
||||||
|
abbreviation text not null unique check(length(abbreviation) != 0)
|
||||||
|
-- is_metric integer not null check(is_metric in (0, 1))
|
||||||
|
);
|
||||||
|
insert into units(name, abbreviation) values
|
||||||
|
-- Count
|
||||||
|
('count', 'ct'),
|
||||||
|
-- Mass
|
||||||
|
('grams', 'g'),
|
||||||
|
('pounds', 'lbs'),
|
||||||
|
('ounces', 'oz'),
|
||||||
|
-- Volume
|
||||||
|
('milliliters', 'mL'),
|
||||||
|
('cups', 'cups'),
|
||||||
|
('teaspoons', 'tsp'),
|
||||||
|
('tablespoons', 'tbsp'),
|
||||||
|
('fluid ounces', 'fl-oz');
|
||||||
|
|
||||||
|
|
||||||
|
create table ingredients (rowid integer primary key,
|
||||||
|
-- created_at integer not null,
|
||||||
|
-- updated_at integer,
|
||||||
|
|
||||||
|
food_id integer references foods(rowid),
|
||||||
|
recipe_id integer references recipes(rowid),
|
||||||
|
|
||||||
|
-- Portion size (rational numbers)
|
||||||
|
quantity_numerator integer not null default 1,
|
||||||
|
quantity_denominator integer not null default 1,
|
||||||
|
units integer not null default 0, -- Display purposes only
|
||||||
|
|
||||||
|
in_recipe_id integer references recipes(rowid) on delete cascade not null,
|
||||||
|
list_order integer not null,
|
||||||
|
is_hidden integer not null default false,
|
||||||
|
unique (recipe_id, list_order)
|
||||||
|
check((food_id is null) + (recipe_id is null) = 1) -- Exactly one should be active
|
||||||
|
) strict;
|
||||||
|
|
||||||
|
create table recipes (rowid integer primary key,
|
||||||
|
-- created_at integer not null,
|
||||||
|
-- updated_at integer,
|
||||||
|
|
||||||
|
name text not null check(length(name) != 0),
|
||||||
|
blurb text not null,
|
||||||
|
instructions text not null
|
||||||
|
|
||||||
|
-- computed_food_id integer references foods(rowid) not null
|
||||||
|
) strict;
|
||||||
|
|
||||||
|
create table iterations (rowid integer primary key,
|
||||||
|
-- created_at integer not null,
|
||||||
|
-- updated_at integer,
|
||||||
|
|
||||||
|
original_recipe_id integer references recipes(rowid),
|
||||||
|
-- original_author integer not null, -- For azimuth integration
|
||||||
|
derived_recipe_id integer references recipes(rowid),
|
||||||
|
unique(derived_recipe_id)
|
||||||
|
) strict;
|
||||||
|
|
||||||
|
create table daily_logs (rowid integer primary key,
|
||||||
|
-- created_at integer not null,
|
||||||
|
-- updated_at integer,
|
||||||
|
|
||||||
|
date integer not null unique,
|
||||||
|
|
||||||
|
computed_food_id integer references foods(rowid) not null
|
||||||
|
);
|
31
pkg/db/timestamp.go
Normal file
31
pkg/db/timestamp.go
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
package db
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql/driver"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Timestamp struct {
|
||||||
|
time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Timestamp) Value() (driver.Value, error) {
|
||||||
|
return t.UnixMilli(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Timestamp) Scan(src interface{}) error {
|
||||||
|
val, is_ok := src.(int64)
|
||||||
|
if !is_ok {
|
||||||
|
return fmt.Errorf("Incompatible type for Timestamp: %#v", src)
|
||||||
|
}
|
||||||
|
*t = Timestamp{time.UnixMilli(val)}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TimestampFromUnix(num int64) Timestamp {
|
||||||
|
return Timestamp{time.Unix(num, 0)}
|
||||||
|
}
|
||||||
|
func TimestampFromUnixMilli(num int64) Timestamp {
|
||||||
|
return Timestamp{time.UnixMilli(num)}
|
||||||
|
}
|
9
pkg/db/units.go
Normal file
9
pkg/db/units.go
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
package db
|
||||||
|
|
||||||
|
type UnitsID uint64
|
||||||
|
|
||||||
|
type Units struct {
|
||||||
|
ID UnitsID `db:"rowid"`
|
||||||
|
Name string `db:"name"`
|
||||||
|
Abbreviation string `db:"abbreviation"`
|
||||||
|
}
|
3
sample_data/mount.sh
Executable file
3
sample_data/mount.sh
Executable file
@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
mount -t tmpfs -o size=100M tmpfs sample_data/data
|
Loading…
x
Reference in New Issue
Block a user