diff --git a/server/go.mod b/server/go.mod index 8fed5b2..a923917 100644 --- a/server/go.mod +++ b/server/go.mod @@ -9,6 +9,7 @@ require ( github.com/go-git/go-git/v5 v5.13.1 github.com/go-playground/validator/v10 v10.22.1 github.com/golang-jwt/jwt/v5 v5.2.1 + github.com/golang-migrate/migrate/v4 v4.18.2 github.com/google/uuid v1.6.0 github.com/mattn/go-sqlite3 v1.14.23 github.com/stretchr/testify v1.10.0 @@ -21,7 +22,7 @@ require ( require ( dario.cat/mergo v1.0.0 // indirect github.com/KyleBanks/depth v1.2.1 // indirect - github.com/Microsoft/go-winio v0.6.1 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect github.com/ProtonMail/go-crypto v1.1.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cloudflare/circl v1.3.7 // indirect @@ -38,10 +39,13 @@ require ( github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect + github.com/lib/pq v1.10.9 // indirect github.com/mailru/easyjson v0.7.6 // indirect github.com/pjbgf/sha1cd v0.3.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect @@ -49,12 +53,11 @@ require ( github.com/skeema/knownhosts v1.3.0 // indirect github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect - golang.org/x/mod v0.17.0 // indirect + go.uber.org/atomic v1.7.0 // indirect golang.org/x/net v0.33.0 // indirect - golang.org/x/sync v0.10.0 // indirect golang.org/x/sys v0.28.0 // indirect golang.org/x/text v0.21.0 // indirect - golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect + golang.org/x/tools v0.24.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/server/go.sum b/server/go.sum index 803c82b..912d559 100644 --- a/server/go.sum +++ b/server/go.sum @@ -1,10 +1,12 @@ dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= -github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/ProtonMail/go-crypto v1.1.3 h1:nRBOetoydLeUb4nHajyO2bKqMLfWQ/ZPwkXqXxPxCFk= github.com/ProtonMail/go-crypto v1.1.3/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= @@ -21,10 +23,22 @@ github.com/cyphar/filepath-securejoin v0.3.6/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGL github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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/dhui/dktest v0.4.4 h1:+I4s6JRE1yGuqflzwqG+aIaMdgXIorCf5P98JnaAWa8= +github.com/dhui/dktest v0.4.4/go.mod h1:4+22R4lgsdAXrDyaH4Nqx2JEz2hLp49MqQmm9HLCQhM= +github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= +github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/docker/docker v27.2.0+incompatible h1:Rk9nIVdfH3+Vz4cyI/uhbINhEZ/oLmc+CBXmH6fbNk4= +github.com/docker/docker v27.2.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= +github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/elazarl/goproxy v1.2.3 h1:xwIyKHbaP5yfT6O9KIeYJR5549MXRQkoQMRXGztz8YQ= github.com/elazarl/goproxy v1.2.3/go.mod h1:YfEbZtqP4AetfO6d40vWchF3znWX7C7Vd6ZMfdL8z64= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= @@ -43,6 +57,10 @@ github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMj github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= github.com/go-git/go-git/v5 v5.13.1 h1:DAQ9APonnlvSWpvolXWIuV6Q6zXy2wHbN4cVlNR5Q+M= github.com/go-git/go-git/v5 v5.13.1/go.mod h1:qryJB4cSBoq3FRoBRf5A77joojuBcmPJ0qu3XXXVixc= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= @@ -61,14 +79,23 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA= github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang-migrate/migrate/v4 v4.18.2 h1:2VSCMz7x7mjyTXx3m2zPokOY82LTRgxK1yQYKo6wWQ8= +github.com/golang-migrate/migrate/v4 v4.18.2/go.mod h1:2CM6tJvn2kqPXwnXO/d3rAQYiyoIm180VsO8PRX6Rpk= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= @@ -84,15 +111,27 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-sqlite3 v1.14.23 h1:gbShiuAP1W5j9UOksQ06aiiqPMxYecovVGwmTxWtuw0= github.com/mattn/go-sqlite3 v1.14.23/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= +github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= +github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= +github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= +github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -123,13 +162,23 @@ github.com/unrolled/secure v1.17.0 h1:Io7ifFgo99Bnh0J7+Q+qcMzWM6kaDPCA5FroFZEdbW github.com/unrolled/secure v1.17.0/go.mod h1:BmF5hyM6tXczk3MpQkFf1hpKSRqCyhqcbiQtiAF7+40= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8= +go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= +go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= +go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= +go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= +go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= +go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= +go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= -golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= -golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= +golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= @@ -151,8 +200,8 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= -golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= +golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/server/internal/app/config.go b/server/internal/app/config.go index 3e0f49c..218b923 100644 --- a/server/internal/app/config.go +++ b/server/internal/app/config.go @@ -2,9 +2,11 @@ package app import ( "fmt" + "lemma/internal/db" "lemma/internal/logging" "lemma/internal/secrets" "os" + "path/filepath" "strconv" "strings" "time" @@ -12,7 +14,8 @@ import ( // Config holds the configuration for the application type Config struct { - DBPath string + DBURL string + DBType db.DBType WorkDir string StaticPath string Port string @@ -31,7 +34,7 @@ type Config struct { // DefaultConfig returns a new Config instance with default values func DefaultConfig() *Config { return &Config{ - DBPath: "./lemma.db", + DBURL: "sqlite://lemma.db", WorkDir: "./data", StaticPath: "../app/dist", Port: "8080", @@ -65,6 +68,31 @@ func (c *Config) Redact() *Config { return &redacted } +// ParseDBURL parses a database URL and returns the driver name and data source +func ParseDBURL(dbURL string) (db.DBType, string, error) { + if strings.HasPrefix(dbURL, "sqlite://") || strings.HasPrefix(dbURL, "sqlite3://") { + + path := strings.TrimPrefix(dbURL, "sqlite://") + path = strings.TrimPrefix(path, "sqlite3://") + + if path == ":memory:" { + return db.DBTypeSQLite, path, nil + } + + if !filepath.IsAbs(path) { + path = filepath.Clean(path) + } + return db.DBTypeSQLite, path, nil + } + + // Try to parse as postgres URL + if strings.HasPrefix(dbURL, "postgres://") || strings.HasPrefix(dbURL, "postgresql://") { + return db.DBTypePostgres, dbURL, nil + } + + return "", "", fmt.Errorf("unsupported database URL format: %s", dbURL) +} + // LoadConfig creates a new Config instance with values from environment variables func LoadConfig() (*Config, error) { config := DefaultConfig() @@ -73,8 +101,13 @@ func LoadConfig() (*Config, error) { config.IsDevelopment = env == "development" } - if dbPath := os.Getenv("LEMMA_DB_PATH"); dbPath != "" { - config.DBPath = dbPath + if dbURL := os.Getenv("LEMMA_DB_URL"); dbURL != "" { + dbType, dataSource, err := ParseDBURL(dbURL) + if err != nil { + return nil, err + } + config.DBURL = dataSource + config.DBType = dbType } if workDir := os.Getenv("LEMMA_WORKDIR"); workDir != "" { diff --git a/server/internal/app/config_test.go b/server/internal/app/config_test.go index c15ca9b..ebf62ef 100644 --- a/server/internal/app/config_test.go +++ b/server/internal/app/config_test.go @@ -17,7 +17,7 @@ func TestDefaultConfig(t *testing.T) { got interface{} expected interface{} }{ - {"DBPath", cfg.DBPath, "./lemma.db"}, + {"DBPath", cfg.DBURL, "./lemma.db"}, {"WorkDir", cfg.WorkDir, "./data"}, {"StaticPath", cfg.StaticPath, "../app/dist"}, {"Port", cfg.Port, "8080"}, @@ -81,8 +81,8 @@ func TestLoad(t *testing.T) { t.Fatalf("Load() error = %v", err) } - if cfg.DBPath != "./lemma.db" { - t.Errorf("default DBPath = %v, want %v", cfg.DBPath, "./lemma.db") + if cfg.DBURL != "./lemma.db" { + t.Errorf("default DBPath = %v, want %v", cfg.DBURL, "./lemma.db") } }) @@ -122,7 +122,7 @@ func TestLoad(t *testing.T) { expected interface{} }{ {"IsDevelopment", cfg.IsDevelopment, true}, - {"DBPath", cfg.DBPath, "/custom/db/path.db"}, + {"DBPath", cfg.DBURL, "/custom/db/path.db"}, {"WorkDir", cfg.WorkDir, "/custom/work/dir"}, {"StaticPath", cfg.StaticPath, "/custom/static/path"}, {"Port", cfg.Port, "3000"}, diff --git a/server/internal/app/init.go b/server/internal/app/init.go index 5bb31b7..ebed735 100644 --- a/server/internal/app/init.go +++ b/server/internal/app/init.go @@ -28,9 +28,9 @@ func initSecretsService(cfg *Config) (secrets.Service, error) { // initDatabase initializes and migrates the database func initDatabase(cfg *Config, secretsService secrets.Service) (db.Database, error) { - logging.Debug("initializing database", "path", cfg.DBPath) + logging.Debug("initializing database", "path", cfg.DBURL) - database, err := db.Init(cfg.DBPath, secretsService) + database, err := db.Init(cfg.DBURL, secretsService) if err != nil { return nil, fmt.Errorf("failed to initialize database: %w", err) } diff --git a/server/internal/db/db.go b/server/internal/db/db.go index d08f8b8..fca938c 100644 --- a/server/internal/db/db.go +++ b/server/internal/db/db.go @@ -12,6 +12,13 @@ import ( _ "github.com/mattn/go-sqlite3" // SQLite driver ) +type DBType string + +const ( + DBTypeSQLite DBType = "sqlite3" + DBTypePostgres DBType = "postgres" +) + // UserStore defines the methods for interacting with user data in the database type UserStore interface { CreateUser(user *models.User) (*models.User, error) @@ -108,6 +115,7 @@ func getLogger() logging.Logger { type database struct { *sql.DB secretsService secrets.Service + dbType DBType } // Init initializes the database connection diff --git a/server/internal/db/migrations.go b/server/internal/db/migrations.go index 5182844..3803f7f 100644 --- a/server/internal/db/migrations.go +++ b/server/internal/db/migrations.go @@ -1,141 +1,60 @@ package db import ( + "embed" "fmt" + + "github.com/golang-migrate/migrate/v4" + "github.com/golang-migrate/migrate/v4/database/postgres" + "github.com/golang-migrate/migrate/v4/database/sqlite3" + "github.com/golang-migrate/migrate/v4/source/iofs" ) -// Migration represents a database migration -type Migration struct { - Version int - SQL string -} - -var migrations = []Migration{ - { - Version: 1, - SQL: ` - -- Create users table - CREATE TABLE IF NOT EXISTS users ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - email TEXT NOT NULL UNIQUE, - display_name TEXT, - password_hash TEXT NOT NULL, - role TEXT NOT NULL CHECK(role IN ('admin', 'editor', 'viewer')), - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - last_workspace_id INTEGER - ); - - -- Create workspaces table with integrated settings - CREATE TABLE IF NOT EXISTS workspaces ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - user_id INTEGER NOT NULL, - name TEXT NOT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - last_opened_file_path TEXT, - -- Settings fields - theme TEXT NOT NULL DEFAULT 'light' CHECK(theme IN ('light', 'dark')), - auto_save BOOLEAN NOT NULL DEFAULT 0, - git_enabled BOOLEAN NOT NULL DEFAULT 0, - git_url TEXT, - git_user TEXT, - git_token TEXT, - git_auto_commit BOOLEAN NOT NULL DEFAULT 0, - git_commit_msg_template TEXT DEFAULT '${action} ${filename}', - git_commit_name TEXT, - git_commit_email TEXT, - show_hidden_files BOOLEAN NOT NULL DEFAULT 0, - created_by INTEGER REFERENCES users(id), - updated_by INTEGER REFERENCES users(id), - updated_at TIMESTAMP, - FOREIGN KEY (user_id) REFERENCES users (id) - ); - - -- Create sessions table for authentication - CREATE TABLE IF NOT EXISTS sessions ( - id TEXT PRIMARY KEY, - user_id INTEGER NOT NULL, - refresh_token TEXT NOT NULL, - expires_at TIMESTAMP NOT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE - ); - - -- Create system_settings table for application settings - CREATE TABLE IF NOT EXISTS system_settings ( - key TEXT PRIMARY KEY, - value TEXT NOT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP - ); - - -- Create indexes for performance - CREATE INDEX idx_sessions_user_id ON sessions(user_id); - CREATE INDEX idx_sessions_expires_at ON sessions(expires_at); - CREATE INDEX idx_sessions_refresh_token ON sessions(refresh_token); - `, - }, -} +//go:embed migrations/*.sql +var migrationsFS embed.FS // Migrate applies all database migrations func (db *database) Migrate() error { log := getLogger().WithGroup("migrations") log.Info("starting database migration") - // Create migrations table if it doesn't exist - _, err := db.Exec(`CREATE TABLE IF NOT EXISTS migrations ( - version INTEGER PRIMARY KEY - )`) + sourceInstance, err := iofs.New(migrationsFS, "migrations") if err != nil { - return fmt.Errorf("failed to create migrations table: %w", err) + return fmt.Errorf("failed to create source instance: %w", err) } - // Get current version - var currentVersion int - err = db.QueryRow("SELECT COALESCE(MAX(version), 0) FROM migrations").Scan(¤tVersion) - if err != nil { - return fmt.Errorf("failed to get current migration version: %w", err) - } + var m *migrate.Migrate - // Apply new migrations - for _, migration := range migrations { - if migration.Version > currentVersion { - log := log.With("migration_version", migration.Version) - tx, err := db.Begin() - if err != nil { - return fmt.Errorf("failed to begin transaction for migration %d: %w", migration.Version, err) - } - - // Execute migration SQL - _, err = tx.Exec(migration.SQL) - if err != nil { - if rbErr := tx.Rollback(); rbErr != nil { - return fmt.Errorf("migration %d failed: %v, rollback failed: %v", - migration.Version, err, rbErr) - } - return fmt.Errorf("migration %d failed: %w", migration.Version, err) - } - - // Update migrations table - _, err = tx.Exec("INSERT INTO migrations (version) VALUES (?)", migration.Version) - if err != nil { - if rbErr := tx.Rollback(); rbErr != nil { - return fmt.Errorf("failed to update migration version: %v, rollback failed: %v", - err, rbErr) - } - return fmt.Errorf("failed to update migration version: %w", err) - } - - // Commit transaction - err = tx.Commit() - if err != nil { - return fmt.Errorf("failed to commit migration %d: %w", migration.Version, err) - } - - currentVersion = migration.Version - log.Debug("migration applied", "new_version", currentVersion) + driverName := db.dbType + switch driverName { + case "postgres": + driver, err := postgres.WithInstance(db.DB, &postgres.Config{}) + if err != nil { + return fmt.Errorf("failed to create postgres driver: %w", err) } + m, err = migrate.NewWithInstance("iofs", sourceInstance, "postgres", driver) + if err != nil { + return fmt.Errorf("failed to create migrate instance: %w", err) + } + + case "sqlite3": + driver, err := sqlite3.WithInstance(db.DB, &sqlite3.Config{}) + if err != nil { + return fmt.Errorf("failed to create sqlite driver: %w", err) + } + m, err = migrate.NewWithInstance("iofs", sourceInstance, "sqlite3", driver) + if err != nil { + return fmt.Errorf("failed to create migrate instance: %w", err) + } + + default: + return fmt.Errorf("unsupported database driver: %s", driverName) } - log.Info("database migration completed", "final_version", currentVersion) + if err := m.Up(); err != nil && err != migrate.ErrNoChange { + return fmt.Errorf("failed to run migrations: %w", err) + } + + log.Info("database migration completed") return nil } diff --git a/server/internal/db/migrations/001_initial_schema.down.sql b/server/internal/db/migrations/001_initial_schema.down.sql new file mode 100644 index 0000000..f32272a --- /dev/null +++ b/server/internal/db/migrations/001_initial_schema.down.sql @@ -0,0 +1,8 @@ +-- 001_initial_schema.down.sql +DROP INDEX IF EXISTS idx_sessions_refresh_token; +DROP INDEX IF EXISTS idx_sessions_expires_at; +DROP INDEX IF EXISTS idx_sessions_user_id; +DROP TABLE IF EXISTS sessions; +DROP TABLE IF EXISTS workspaces; +DROP TABLE IF EXISTS system_settings; +DROP TABLE IF EXISTS users; \ No newline at end of file diff --git a/server/internal/db/migrations/001_initial_schema.up.sql b/server/internal/db/migrations/001_initial_schema.up.sql new file mode 100644 index 0000000..03f1722 --- /dev/null +++ b/server/internal/db/migrations/001_initial_schema.up.sql @@ -0,0 +1,59 @@ +-- 001_initial_schema.up.sql +-- Create users table +CREATE TABLE IF NOT EXISTS users ( + id INTEGER PRIMARY KEY AUTO_INCREMENT, + email TEXT NOT NULL UNIQUE, + display_name TEXT, + password_hash TEXT NOT NULL, + role TEXT NOT NULL CHECK(role IN ('admin', 'editor', 'viewer')), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + last_workspace_id INTEGER +); + +-- Create workspaces table with integrated settings +CREATE TABLE IF NOT EXISTS workspaces ( + id INTEGER PRIMARY KEY AUTO_INCREMENT, + user_id INTEGER NOT NULL, + name TEXT NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + last_opened_file_path TEXT, + -- Settings fields + theme TEXT NOT NULL DEFAULT 'light' CHECK(theme IN ('light', 'dark')), + auto_save BOOLEAN NOT NULL DEFAULT 0, + git_enabled BOOLEAN NOT NULL DEFAULT 0, + git_url TEXT, + git_user TEXT, + git_token TEXT, + git_auto_commit BOOLEAN NOT NULL DEFAULT 0, + git_commit_msg_template TEXT DEFAULT '${action} ${filename}', + git_commit_name TEXT, + git_commit_email TEXT, + show_hidden_files BOOLEAN NOT NULL DEFAULT 0, + created_by INTEGER REFERENCES users(id), + updated_by INTEGER REFERENCES users(id), + updated_at TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users (id) +); + +-- Create sessions table for authentication +CREATE TABLE IF NOT EXISTS sessions ( + id TEXT PRIMARY KEY, + user_id INTEGER NOT NULL, + refresh_token TEXT NOT NULL, + expires_at TIMESTAMP NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE +); + +-- Create system_settings table for application settings +CREATE TABLE IF NOT EXISTS system_settings ( + key TEXT PRIMARY KEY, + value TEXT NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- Create indexes for performance +CREATE INDEX idx_sessions_user_id ON sessions(user_id); +CREATE INDEX idx_sessions_expires_at ON sessions(expires_at); +CREATE INDEX idx_sessions_refresh_token ON sessions(refresh_token); \ No newline at end of file diff --git a/server/internal/handlers/integration_test.go b/server/internal/handlers/integration_test.go index b726d95..30ae7c6 100644 --- a/server/internal/handlers/integration_test.go +++ b/server/internal/handlers/integration_test.go @@ -99,7 +99,7 @@ func setupTestHarness(t *testing.T) *testHarness { // Create test config testConfig := &app.Config{ - DBPath: ":memory:", + DBURL: ":memory:", WorkDir: tempDir, StaticPath: "../testdata", Port: "8081",