diff --git a/go.mod b/go.mod index 841f11a..df19640 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module mal -go 1.25.0 +go 1.25.7 require ( github.com/PuerkitoBio/goquery v1.11.0 @@ -15,9 +15,45 @@ require ( require github.com/hashicorp/golang-lru/v2 v2.0.7 require ( - github.com/andybalholm/brotli v1.1.0 // indirect + github.com/bytedance/gopkg v0.1.3 // indirect + github.com/bytedance/sonic v1.15.0 // indirect + github.com/bytedance/sonic/loader v0.5.0 // indirect + github.com/cloudwego/base64x v0.1.6 // indirect + github.com/gabriel-vasile/mimetype v1.4.12 // indirect + github.com/gin-contrib/sse v1.1.0 // indirect + github.com/gin-gonic/gin v1.12.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.30.1 // indirect + github.com/goccy/go-json v0.10.5 // indirect + github.com/goccy/go-yaml v1.19.2 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.3.0 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/mattn/go-isatty v0.0.21 // indirect + github.com/mfridman/interpolate v0.0.2 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pelletier/go-toml/v2 v2.2.4 // indirect + github.com/pressly/goose/v3 v3.27.1 // indirect + github.com/quic-go/qpack v0.6.0 // indirect + github.com/quic-go/quic-go v0.59.0 // indirect + github.com/sethvargo/go-retry v0.3.0 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.3.1 // indirect + go.mongodb.org/mongo-driver/v2 v2.5.0 // indirect + go.uber.org/dig v1.19.0 // indirect + go.uber.org/fx v1.24.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.26.0 // indirect + golang.org/x/arch v0.22.0 // indirect + google.golang.org/protobuf v1.36.11 // indirect +) + +require ( + github.com/andybalholm/brotli v1.2.1 // indirect github.com/andybalholm/cascadia v1.3.3 // indirect - github.com/klauspost/compress v1.17.4 // indirect + github.com/klauspost/compress v1.18.5 // indirect golang.org/x/sync v0.20.0 // direct golang.org/x/sys v0.43.0 // indirect golang.org/x/text v0.36.0 // indirect diff --git a/go.sum b/go.sum index 01abc20..843a244 100644 --- a/go.sum +++ b/go.sum @@ -2,22 +2,104 @@ github.com/PuerkitoBio/goquery v1.11.0 h1:jZ7pwMQXIITcUXNH83LLk+txlaEy6NVOfTuP43 github.com/PuerkitoBio/goquery v1.11.0/go.mod h1:wQHgxUOU3JGuj3oD/QFfxUdlzW6xPHfqyHre6VMY4DQ= github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= +github.com/andybalholm/brotli v1.2.1 h1:R+f5xP285VArJDRgowrfb9DqL18yVK0gKAW/F+eTWro= +github.com/andybalholm/brotli v1.2.1/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM= github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA= +github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M= +github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM= +github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE= +github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k= +github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE= +github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo= +github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= +github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw= +github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= +github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= +github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= +github.com/gin-gonic/gin v1.12.0 h1:b3YAbrZtnf8N//yjKeU2+MQsh2mY5htkZidOM7O0wG8= +github.com/gin-gonic/gin v1.12.0/go.mod h1:VxccKfsSllpKshkBWgVgRniFFAzFb9csfngsqANjnLc= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w= +github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM= +github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= +github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM= +github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 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/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE= +github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= +github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= +github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +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/mattn/go-isatty v0.0.21 h1:xYae+lCNBP7QuW4PUnNG61ffM4hVIfm+zUzDuSzYLGs= +github.com/mattn/go-isatty v0.0.21/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4= github.com/mattn/go-sqlite3 v1.14.40 h1:f7+saIsbq4EF86mUqe0uiecQOJYMOdfi5uATADmUG94= github.com/mattn/go-sqlite3 v1.14.40/go.mod h1:pjEuOr8IwzLJP2MfGeTb0A35jauH+C2kbHKBr7yXKVQ= +github.com/mfridman/interpolate v0.0.2 h1:pnuTK7MQIxxFz1Gr+rjSIx9u7qVjf5VOoM/u6BbAxPY= +github.com/mfridman/interpolate v0.0.2/go.mod h1:p+7uk6oE07mpE/Ik1b8EckO0O4ZXiGAfshKBWLUM9Xg= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pressly/goose/v3 v3.27.1 h1:6uEvcprBybDmW4hcz3gYujhARhye+GoWKhEWyzD5sh4= +github.com/pressly/goose/v3 v3.27.1/go.mod h1:maruOxsPnIG2yHHyo8UqKWXYKFcH7Q76csUV7+7KYoM= +github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8= +github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII= +github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw= +github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU= github.com/refraction-networking/utls v1.8.2 h1:j4Q1gJj0xngdeH+Ox/qND11aEfhpgoEvV+S9iJ2IdQo= github.com/refraction-networking/utls v1.8.2/go.mod h1:jkSOEkLqn+S/jtpEHPOsVv/4V4EVnelwbMQl4vCWXAM= +github.com/sethvargo/go-retry v0.3.0 h1:EEt31A35QhrcRZtrYFDTBg91cqZVnFL2navjDrah2SE= +github.com/sethvargo/go-retry v0.3.0/go.mod h1:mNX17F0C/HguQMyMyJxcnU471gOZGxCLyYaFyAZraas= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY= +github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF3xaE= +go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0= +go.uber.org/dig v1.19.0 h1:BACLhebsYdpQ7IROQ1AGPjrXcP5dF80U3gKoFzbaq/4= +go.uber.org/dig v1.19.0/go.mod h1:Us0rSJiThwCv2GteUN0Q7OKvU7n5J4dxZ9JKUXozFdE= +go.uber.org/fx v1.24.0 h1:wE8mruvpg2kiiL1Vqd0CC+tr0/24XIB10Iwp2lLWzkg= +go.uber.org/fx v1.24.0/go.mod h1:AmDeGyS+ZARGKM4tlH4FY2Jr63VjbEDJHtqXTGP5hbo= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= +go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= +golang.org/x/arch v0.22.0 h1:c/Zle32i5ttqRXjdLyyHZESLD/bB90DCU1g9l/0YBDI= +golang.org/x/arch v0.22.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= @@ -91,3 +173,8 @@ golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/database/database.go b/internal/database/database.go new file mode 100644 index 0000000..353087d --- /dev/null +++ b/internal/database/database.go @@ -0,0 +1,55 @@ +package database + +import ( + "database/sql" + "embed" + "fmt" + "log" + "mal/internal/db" + "os" + + "github.com/pressly/goose/v3" + "go.uber.org/fx" +) + +//go:embed migrations/*.sql +var migrationsFS embed.FS + +var Module = fx.Options( + fx.Provide( + ProvideSQLDB, + ProvideQueries, + ), + fx.Invoke(RunMigrations), +) + +func ProvideSQLDB() (*sql.DB, error) { + dbPath := os.Getenv("DB_PATH") + if dbPath == "" { + dbPath = "mal.db" + } + dbConn, err := db.Open(dbPath) + if err != nil { + return nil, fmt.Errorf("failed to open database: %w", err) + } + return dbConn, nil +} + +func ProvideQueries(sqlDB *sql.DB) *db.Queries { + return db.New(sqlDB) +} + +func RunMigrations(sqlDB *sql.DB) error { + goose.SetBaseFS(migrationsFS) + + if err := goose.SetDialect("sqlite3"); err != nil { + return fmt.Errorf("failed to set goose dialect: %w", err) + } + + log.Println("Running database migrations...") + if err := goose.Up(sqlDB, "migrations"); err != nil { + return fmt.Errorf("failed to run migrations: %w", err) + } + + return nil +} diff --git a/internal/database/migrations/001_init.sql b/internal/database/migrations/001_init.sql new file mode 100644 index 0000000..f3a2932 --- /dev/null +++ b/internal/database/migrations/001_init.sql @@ -0,0 +1,42 @@ +CREATE TABLE IF NOT EXISTS user ( + id TEXT PRIMARY KEY, + username TEXT NOT NULL UNIQUE, + password_hash TEXT NOT NULL, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +CREATE TABLE IF NOT EXISTS session ( + id TEXT PRIMARY KEY, + user_id TEXT NOT NULL REFERENCES user(id) ON DELETE CASCADE, + expires_at DATETIME NOT NULL, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +CREATE TABLE IF NOT EXISTS account ( + id TEXT PRIMARY KEY, + user_id TEXT NOT NULL REFERENCES user(id) ON DELETE CASCADE, + provider TEXT NOT NULL, + provider_account_id TEXT NOT NULL, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + UNIQUE(provider, provider_account_id) +); + +CREATE TABLE IF NOT EXISTS anime ( + id INTEGER PRIMARY KEY, -- Jikan ID + title TEXT NOT NULL, + image_url TEXT NOT NULL, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +CREATE TABLE IF NOT EXISTS watch_list_entry ( + id TEXT PRIMARY KEY, + user_id TEXT NOT NULL REFERENCES user(id) ON DELETE CASCADE, + anime_id INTEGER NOT NULL REFERENCES anime(id) ON DELETE CASCADE, + status TEXT NOT NULL CHECK(status IN ('completed', 'dropped', 'plan_to_watch')), + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + current_episode INTEGER DEFAULT 0, + last_episode_at DATETIME, + current_time_seconds REAL NOT NULL DEFAULT 0, + UNIQUE(user_id, anime_id) +); diff --git a/internal/database/migrations/002_add_anime_titles.sql b/internal/database/migrations/002_add_anime_titles.sql new file mode 100644 index 0000000..a1f2564 --- /dev/null +++ b/internal/database/migrations/002_add_anime_titles.sql @@ -0,0 +1,6 @@ +-- Add English and Japanese title columns to anime table +ALTER TABLE anime ADD COLUMN title_english TEXT; +ALTER TABLE anime ADD COLUMN title_japanese TEXT; + +-- Rename existing title to title_original for clarity +ALTER TABLE anime RENAME COLUMN title TO title_original; diff --git a/internal/database/migrations/003_add_anime_airing.sql b/internal/database/migrations/003_add_anime_airing.sql new file mode 100644 index 0000000..8f74ee6 --- /dev/null +++ b/internal/database/migrations/003_add_anime_airing.sql @@ -0,0 +1,2 @@ +-- Add airing status column to anime table +ALTER TABLE anime ADD COLUMN airing BOOLEAN DEFAULT 0; diff --git a/internal/database/migrations/004_add_notifications.sql b/internal/database/migrations/004_add_notifications.sql new file mode 100644 index 0000000..a51e1ce --- /dev/null +++ b/internal/database/migrations/004_add_notifications.sql @@ -0,0 +1,10 @@ +-- Note: watch_list_entry columns now in 001_init.sql + +-- Add notification preferences +CREATE TABLE IF NOT EXISTS notification_preference ( + id TEXT PRIMARY KEY, + user_id TEXT NOT NULL REFERENCES user(id) ON DELETE CASCADE, + notify_new_episodes BOOLEAN NOT NULL DEFAULT TRUE, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + UNIQUE(user_id) +); diff --git a/internal/database/migrations/005_add_anime_relations.sql b/internal/database/migrations/005_add_anime_relations.sql new file mode 100644 index 0000000..11a82ee --- /dev/null +++ b/internal/database/migrations/005_add_anime_relations.sql @@ -0,0 +1,9 @@ +ALTER TABLE anime ADD COLUMN status TEXT DEFAULT ''; +ALTER TABLE anime ADD COLUMN relations_synced_at DATETIME; + +CREATE TABLE IF NOT EXISTS anime_relation ( + anime_id INTEGER NOT NULL REFERENCES anime(id) ON DELETE CASCADE, + related_anime_id INTEGER NOT NULL, + relation_type TEXT NOT NULL, + PRIMARY KEY (anime_id, related_anime_id) +); diff --git a/internal/database/migrations/006_add_jikan_cache.sql b/internal/database/migrations/006_add_jikan_cache.sql new file mode 100644 index 0000000..bc4852a --- /dev/null +++ b/internal/database/migrations/006_add_jikan_cache.sql @@ -0,0 +1,6 @@ +CREATE TABLE IF NOT EXISTS jikan_cache ( + key TEXT PRIMARY KEY, + data TEXT NOT NULL, + expires_at DATETIME NOT NULL, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP +); diff --git a/internal/database/migrations/007_add_query_indexes.sql b/internal/database/migrations/007_add_query_indexes.sql new file mode 100644 index 0000000..206396f --- /dev/null +++ b/internal/database/migrations/007_add_query_indexes.sql @@ -0,0 +1,11 @@ +CREATE INDEX IF NOT EXISTS idx_watch_list_entry_user_status_updated_at +ON watch_list_entry(user_id, status, updated_at); + +CREATE INDEX IF NOT EXISTS idx_anime_relation_anime_id_relation_type +ON anime_relation(anime_id, relation_type); + +CREATE INDEX IF NOT EXISTS idx_anime_relations_synced_at_status +ON anime(relations_synced_at, status); + +CREATE INDEX IF NOT EXISTS idx_jikan_cache_expires_at +ON jikan_cache(expires_at); diff --git a/internal/database/migrations/009_add_anime_fetch_retry.sql b/internal/database/migrations/009_add_anime_fetch_retry.sql new file mode 100644 index 0000000..ffbbe40 --- /dev/null +++ b/internal/database/migrations/009_add_anime_fetch_retry.sql @@ -0,0 +1,11 @@ +CREATE TABLE IF NOT EXISTS anime_fetch_retry ( + anime_id INTEGER PRIMARY KEY, + attempts INTEGER NOT NULL DEFAULT 0, + next_retry_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + last_error TEXT NOT NULL DEFAULT '', + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX IF NOT EXISTS idx_anime_fetch_retry_next_retry_at +ON anime_fetch_retry(next_retry_at); diff --git a/internal/database/migrations/010_add_watch_progress_seconds.sql b/internal/database/migrations/010_add_watch_progress_seconds.sql new file mode 100644 index 0000000..c29e82b --- /dev/null +++ b/internal/database/migrations/010_add_watch_progress_seconds.sql @@ -0,0 +1 @@ +-- Note: watch_list_entry columns now in 001_init.sql \ No newline at end of file diff --git a/internal/database/migrations/011_add_continue_watching.sql b/internal/database/migrations/011_add_continue_watching.sql new file mode 100644 index 0000000..d12b2af --- /dev/null +++ b/internal/database/migrations/011_add_continue_watching.sql @@ -0,0 +1,13 @@ +CREATE TABLE IF NOT EXISTS continue_watching_entry ( + id TEXT PRIMARY KEY, + user_id TEXT NOT NULL REFERENCES user(id) ON DELETE CASCADE, + anime_id INTEGER NOT NULL REFERENCES anime(id) ON DELETE CASCADE, + current_episode INTEGER, + current_time_seconds REAL NOT NULL DEFAULT 0, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + UNIQUE(user_id, anime_id) +); + +CREATE INDEX IF NOT EXISTS idx_continue_watching_user_updated +ON continue_watching_entry(user_id, updated_at DESC); diff --git a/internal/database/migrations/012_remove_recovery_key.sql b/internal/database/migrations/012_remove_recovery_key.sql new file mode 100644 index 0000000..e60c060 --- /dev/null +++ b/internal/database/migrations/012_remove_recovery_key.sql @@ -0,0 +1,22 @@ +PRAGMA foreign_keys = OFF; + +BEGIN TRANSACTION; + +CREATE TABLE user_new ( + id TEXT PRIMARY KEY, + username TEXT NOT NULL UNIQUE, + password_hash TEXT NOT NULL, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +INSERT INTO user_new (id, username, password_hash, created_at) +SELECT id, username, password_hash, created_at +FROM user; + +DROP TABLE user; + +ALTER TABLE user_new RENAME TO user; + +COMMIT; + +PRAGMA foreign_keys = ON; diff --git a/internal/database/migrations/013_drop_account.sql b/internal/database/migrations/013_drop_account.sql new file mode 100644 index 0000000..6a6cca9 --- /dev/null +++ b/internal/database/migrations/013_drop_account.sql @@ -0,0 +1,2 @@ +DROP TABLE IF EXISTS account; +DROP TABLE IF EXISTS notification_preference; \ No newline at end of file diff --git a/internal/database/migrations/014_add_watchlist_statuses.sql b/internal/database/migrations/014_add_watchlist_statuses.sql new file mode 100644 index 0000000..b0d8bd1 --- /dev/null +++ b/internal/database/migrations/014_add_watchlist_statuses.sql @@ -0,0 +1,26 @@ +-- Add "watching" and "on_hold" to the valid statuses for watch_list_entry + +PRAGMA foreign_keys=OFF; + +ALTER TABLE watch_list_entry RENAME TO watch_list_entry_old; + +CREATE TABLE IF NOT EXISTS watch_list_entry ( + id TEXT PRIMARY KEY, + user_id TEXT NOT NULL REFERENCES user(id) ON DELETE CASCADE, + anime_id INTEGER NOT NULL REFERENCES anime(id) ON DELETE CASCADE, + status TEXT NOT NULL CHECK(status IN ('watching', 'completed', 'dropped', 'plan_to_watch', 'on_hold')), + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + current_episode INTEGER DEFAULT 0, + last_episode_at DATETIME, + current_time_seconds REAL NOT NULL DEFAULT 0, + UNIQUE(user_id, anime_id) +); + +INSERT OR IGNORE INTO watch_list_entry (id, user_id, anime_id, status, created_at, updated_at, current_episode, last_episode_at, current_time_seconds) +SELECT id, user_id, anime_id, status, created_at, updated_at, current_episode, last_episode_at, current_time_seconds +FROM watch_list_entry_old; + +DROP TABLE watch_list_entry_old; + +PRAGMA foreign_keys=ON; diff --git a/internal/database/migrations/015_add_duration.sql b/internal/database/migrations/015_add_duration.sql new file mode 100644 index 0000000..80057a5 --- /dev/null +++ b/internal/database/migrations/015_add_duration.sql @@ -0,0 +1,5 @@ +-- Add duration column to anime table to store episode duration in seconds +ALTER TABLE anime ADD COLUMN duration_seconds REAL; + +-- Add duration_seconds column to continue_watching_entry to track episode duration +ALTER TABLE continue_watching_entry ADD COLUMN duration_seconds REAL; \ No newline at end of file diff --git a/internal/database/migrations/016_add_avatar_url.sql b/internal/database/migrations/016_add_avatar_url.sql new file mode 100644 index 0000000..bb153ac --- /dev/null +++ b/internal/database/migrations/016_add_avatar_url.sql @@ -0,0 +1,3 @@ +ALTER TABLE user ADD COLUMN avatar_url TEXT NOT NULL DEFAULT ''; + +UPDATE user SET avatar_url = 'https://api.dicebear.com/9.x/dylan/svg?seed=' || username WHERE avatar_url = ''; \ No newline at end of file