🎉 Init project from scratch
This commit is contained in:
		
							
								
								
									
										8
									
								
								.idea/.gitignore
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								.idea/.gitignore
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | |||||||
|  | # Default ignored files | ||||||
|  | /shelf/ | ||||||
|  | /workspace.xml | ||||||
|  | # Editor-based HTTP Client requests | ||||||
|  | /httpRequests/ | ||||||
|  | # Datasource local storage ignored files | ||||||
|  | /dataSources/ | ||||||
|  | /dataSources.local.xml | ||||||
							
								
								
									
										9
									
								
								.idea/Interactive.iml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								.idea/Interactive.iml
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | |||||||
|  | <?xml version="1.0" encoding="UTF-8"?> | ||||||
|  | <module type="WEB_MODULE" version="4"> | ||||||
|  |   <component name="Go" enabled="true" /> | ||||||
|  |   <component name="NewModuleRootManager"> | ||||||
|  |     <content url="file://$MODULE_DIR$" /> | ||||||
|  |     <orderEntry type="inheritedJdk" /> | ||||||
|  |     <orderEntry type="sourceFolder" forTests="false" /> | ||||||
|  |   </component> | ||||||
|  | </module> | ||||||
							
								
								
									
										8
									
								
								.idea/modules.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								.idea/modules.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | |||||||
|  | <?xml version="1.0" encoding="UTF-8"?> | ||||||
|  | <project version="4"> | ||||||
|  |   <component name="ProjectModuleManager"> | ||||||
|  |     <modules> | ||||||
|  |       <module fileurl="file://$PROJECT_DIR$/.idea/Interactive.iml" filepath="$PROJECT_DIR$/.idea/Interactive.iml" /> | ||||||
|  |     </modules> | ||||||
|  |   </component> | ||||||
|  | </project> | ||||||
							
								
								
									
										6
									
								
								.idea/vcs.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								.idea/vcs.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | |||||||
|  | <?xml version="1.0" encoding="UTF-8"?> | ||||||
|  | <project version="4"> | ||||||
|  |   <component name="VcsDirectoryMappings"> | ||||||
|  |     <mapping directory="$PROJECT_DIR$" vcs="Git" /> | ||||||
|  |   </component> | ||||||
|  | </project> | ||||||
							
								
								
									
										72
									
								
								go.mod
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								go.mod
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,72 @@ | |||||||
|  | module code.smartsheep.studio/hydrogen/interactive | ||||||
|  |  | ||||||
|  | go 1.21.6 | ||||||
|  |  | ||||||
|  | require ( | ||||||
|  | 	code.smartsheep.studio/hydrogen/passport v0.0.0-20240201075828-dbc09bd7af8a | ||||||
|  | 	github.com/go-playground/validator/v10 v10.17.0 | ||||||
|  | 	github.com/gofiber/fiber/v2 v2.52.0 | ||||||
|  | 	github.com/json-iterator/go v1.1.12 | ||||||
|  | 	github.com/rs/zerolog v1.31.0 | ||||||
|  | 	github.com/samber/lo v1.39.0 | ||||||
|  | 	github.com/spf13/viper v1.18.2 | ||||||
|  | 	gorm.io/driver/postgres v1.5.4 | ||||||
|  | 	gorm.io/gorm v1.25.6 | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | require ( | ||||||
|  | 	github.com/andybalholm/brotli v1.0.5 // indirect | ||||||
|  | 	github.com/fsnotify/fsnotify v1.7.0 // indirect | ||||||
|  | 	github.com/gabriel-vasile/mimetype v1.4.2 // indirect | ||||||
|  | 	github.com/go-playground/locales v0.14.1 // indirect | ||||||
|  | 	github.com/go-playground/universal-translator v0.18.1 // indirect | ||||||
|  | 	github.com/go-sql-driver/mysql v1.7.1 // indirect | ||||||
|  | 	github.com/golang-jwt/jwt/v5 v5.2.0 // indirect | ||||||
|  | 	github.com/golang/protobuf v1.5.3 // indirect | ||||||
|  | 	github.com/google/uuid v1.5.0 // indirect | ||||||
|  | 	github.com/hashicorp/hcl v1.0.0 // indirect | ||||||
|  | 	github.com/jackc/pgpassfile v1.0.0 // indirect | ||||||
|  | 	github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 // indirect | ||||||
|  | 	github.com/jackc/pgx/v5 v5.5.1 // indirect | ||||||
|  | 	github.com/jackc/puddle/v2 v2.2.1 // indirect | ||||||
|  | 	github.com/jinzhu/inflection v1.0.0 // indirect | ||||||
|  | 	github.com/jinzhu/now v1.1.5 // indirect | ||||||
|  | 	github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible // indirect | ||||||
|  | 	github.com/klauspost/compress v1.17.0 // indirect | ||||||
|  | 	github.com/leodido/go-urn v1.2.4 // indirect | ||||||
|  | 	github.com/magiconair/properties v1.8.7 // indirect | ||||||
|  | 	github.com/mattn/go-colorable v0.1.13 // indirect | ||||||
|  | 	github.com/mattn/go-isatty v0.0.20 // indirect | ||||||
|  | 	github.com/mattn/go-runewidth v0.0.15 // indirect | ||||||
|  | 	github.com/mitchellh/mapstructure v1.5.0 // 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.1.1 // indirect | ||||||
|  | 	github.com/philhofer/fwd v1.1.2 // indirect | ||||||
|  | 	github.com/rivo/uniseg v0.2.0 // indirect | ||||||
|  | 	github.com/sagikazarmark/locafero v0.4.0 // indirect | ||||||
|  | 	github.com/sagikazarmark/slog-shim v0.1.0 // indirect | ||||||
|  | 	github.com/sourcegraph/conc v0.3.0 // indirect | ||||||
|  | 	github.com/spf13/afero v1.11.0 // indirect | ||||||
|  | 	github.com/spf13/cast v1.6.0 // indirect | ||||||
|  | 	github.com/spf13/pflag v1.0.5 // indirect | ||||||
|  | 	github.com/subosito/gotenv v1.6.0 // indirect | ||||||
|  | 	github.com/tinylib/msgp v1.1.8 // indirect | ||||||
|  | 	github.com/valyala/bytebufferpool v1.0.0 // indirect | ||||||
|  | 	github.com/valyala/fasthttp v1.51.0 // indirect | ||||||
|  | 	github.com/valyala/tcplisten v1.0.0 // indirect | ||||||
|  | 	go.uber.org/multierr v1.11.0 // indirect | ||||||
|  | 	golang.org/x/crypto v0.18.0 // indirect | ||||||
|  | 	golang.org/x/exp v0.0.0-20231219180239-dc181d75b848 // indirect | ||||||
|  | 	golang.org/x/net v0.20.0 // indirect | ||||||
|  | 	golang.org/x/oauth2 v0.16.0 // indirect | ||||||
|  | 	golang.org/x/sync v0.5.0 // indirect | ||||||
|  | 	golang.org/x/sys v0.16.0 // indirect | ||||||
|  | 	golang.org/x/text v0.14.0 // indirect | ||||||
|  | 	google.golang.org/appengine v1.6.8 // indirect | ||||||
|  | 	google.golang.org/protobuf v1.32.0 // indirect | ||||||
|  | 	gopkg.in/ini.v1 v1.67.0 // indirect | ||||||
|  | 	gopkg.in/yaml.v3 v3.0.1 // indirect | ||||||
|  | 	gorm.io/datatypes v1.2.0 // indirect | ||||||
|  | 	gorm.io/driver/mysql v1.5.2 // indirect | ||||||
|  | ) | ||||||
							
								
								
									
										225
									
								
								go.sum
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										225
									
								
								go.sum
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,225 @@ | |||||||
|  | code.smartsheep.studio/hydrogen/passport v0.0.0-20240201075828-dbc09bd7af8a h1:66YEiBiB+S7QaUnNqQ/Q8zzgGNkQwcHlJdE2s/RdV6k= | ||||||
|  | code.smartsheep.studio/hydrogen/passport v0.0.0-20240201075828-dbc09bd7af8a/go.mod h1:LM5I1tdQLXY6n+hou3TPNV/v9hA/cD59zlYpspdzGks= | ||||||
|  | github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= | ||||||
|  | github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= | ||||||
|  | github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= | ||||||
|  | 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/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= | ||||||
|  | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||||||
|  | github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= | ||||||
|  | github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= | ||||||
|  | github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= | ||||||
|  | github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= | ||||||
|  | github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= | ||||||
|  | github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= | ||||||
|  | github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= | ||||||
|  | github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= | ||||||
|  | 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.17.0 h1:SmVVlfAOtlZncTxRuinDPomC2DkXJ4E5T9gDA0AIH74= | ||||||
|  | github.com/go-playground/validator/v10 v10.17.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= | ||||||
|  | github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= | ||||||
|  | github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= | ||||||
|  | github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= | ||||||
|  | github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= | ||||||
|  | github.com/gofiber/fiber/v2 v2.52.0 h1:S+qXi7y+/Pgvqq4DrSmREGiFwtB7Bu6+QFLuIHYw/UE= | ||||||
|  | github.com/gofiber/fiber/v2 v2.52.0/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ= | ||||||
|  | github.com/golang-jwt/jwt/v5 v5.2.0 h1:d/ix8ftRUorsN+5eMIlF4T6J8CAt9rch3My2winC1Jw= | ||||||
|  | github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= | ||||||
|  | github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA= | ||||||
|  | github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= | ||||||
|  | github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A= | ||||||
|  | github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI= | ||||||
|  | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= | ||||||
|  | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= | ||||||
|  | github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= | ||||||
|  | github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= | ||||||
|  | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | ||||||
|  | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= | ||||||
|  | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= | ||||||
|  | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= | ||||||
|  | github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= | ||||||
|  | github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= | ||||||
|  | github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= | ||||||
|  | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= | ||||||
|  | github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= | ||||||
|  | github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= | ||||||
|  | github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 h1:L0QtFUgDarD7Fpv9jeVMgy/+Ec0mtnmYuImjTz6dtDA= | ||||||
|  | github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= | ||||||
|  | github.com/jackc/pgx/v5 v5.5.1 h1:5I9etrGkLrN+2XPCsi6XLlV5DITbSL/xBZdmAxFcXPI= | ||||||
|  | github.com/jackc/pgx/v5 v5.5.1/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA= | ||||||
|  | github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= | ||||||
|  | github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= | ||||||
|  | github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= | ||||||
|  | github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= | ||||||
|  | github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= | ||||||
|  | github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= | ||||||
|  | github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible h1:jdpOPRN1zP63Td1hDQbZW73xKmzDvZHzVdNYxhnTMDA= | ||||||
|  | github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible/go.mod h1:1c7szIrayyPPB/987hsnvNzLushdWf4o/79s3P08L8A= | ||||||
|  | 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.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM= | ||||||
|  | github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= | ||||||
|  | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= | ||||||
|  | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= | ||||||
|  | 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.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= | ||||||
|  | github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= | ||||||
|  | github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= | ||||||
|  | github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= | ||||||
|  | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= | ||||||
|  | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= | ||||||
|  | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= | ||||||
|  | github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= | ||||||
|  | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= | ||||||
|  | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= | ||||||
|  | github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= | ||||||
|  | github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= | ||||||
|  | github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI= | ||||||
|  | github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= | ||||||
|  | github.com/microsoft/go-mssqldb v0.17.0 h1:Fto83dMZPnYv1Zwx5vHHxpNraeEaUlQ/hhHLgZiaenE= | ||||||
|  | github.com/microsoft/go-mssqldb v0.17.0/go.mod h1:OkoNGhGEs8EZqchVTtochlXruEhEOaO4S0d2sB5aeGQ= | ||||||
|  | github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= | ||||||
|  | github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= | ||||||
|  | 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.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI= | ||||||
|  | github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= | ||||||
|  | github.com/philhofer/fwd v1.1.2 h1:bnDivRJ1EWPjUIRXV5KfORO897HTbpFAQddBdE8t7Gw= | ||||||
|  | github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0= | ||||||
|  | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= | ||||||
|  | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||||||
|  | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= | ||||||
|  | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||||||
|  | github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= | ||||||
|  | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= | ||||||
|  | github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= | ||||||
|  | github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= | ||||||
|  | github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= | ||||||
|  | github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A= | ||||||
|  | github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= | ||||||
|  | github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= | ||||||
|  | github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= | ||||||
|  | github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= | ||||||
|  | github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= | ||||||
|  | github.com/samber/lo v1.39.0 h1:4gTz1wUhNYLhFSKl6O+8peW0v2F4BCY034GRpU9WnuA= | ||||||
|  | github.com/samber/lo v1.39.0/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= | ||||||
|  | github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= | ||||||
|  | github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= | ||||||
|  | github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= | ||||||
|  | github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= | ||||||
|  | github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= | ||||||
|  | github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= | ||||||
|  | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= | ||||||
|  | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= | ||||||
|  | github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= | ||||||
|  | github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= | ||||||
|  | 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/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= | ||||||
|  | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | ||||||
|  | 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.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= | ||||||
|  | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= | ||||||
|  | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= | ||||||
|  | github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= | ||||||
|  | github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= | ||||||
|  | github.com/tinylib/msgp v1.1.8 h1:FCXC1xanKO4I8plpHGH2P7koL/RzZs12l/+r7vakfm0= | ||||||
|  | github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw= | ||||||
|  | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= | ||||||
|  | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= | ||||||
|  | github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA= | ||||||
|  | github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g= | ||||||
|  | github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= | ||||||
|  | github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= | ||||||
|  | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= | ||||||
|  | go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= | ||||||
|  | go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= | ||||||
|  | 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.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= | ||||||
|  | golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= | ||||||
|  | golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= | ||||||
|  | golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= | ||||||
|  | golang.org/x/exp v0.0.0-20231219180239-dc181d75b848 h1:+iq7lrkxmFNBM7xx+Rae2W6uyPfhPeDWD+n+JgppptE= | ||||||
|  | golang.org/x/exp v0.0.0-20231219180239-dc181d75b848/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= | ||||||
|  | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= | ||||||
|  | golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= | ||||||
|  | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= | ||||||
|  | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= | ||||||
|  | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= | ||||||
|  | golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= | ||||||
|  | golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= | ||||||
|  | golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= | ||||||
|  | golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= | ||||||
|  | golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= | ||||||
|  | golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ= | ||||||
|  | golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o= | ||||||
|  | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||||
|  | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||||
|  | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||||
|  | golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= | ||||||
|  | golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= | ||||||
|  | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||||||
|  | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||||
|  | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||||
|  | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||||
|  | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||||
|  | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||||
|  | golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||||
|  | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||||
|  | golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||||
|  | golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= | ||||||
|  | golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= | ||||||
|  | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= | ||||||
|  | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= | ||||||
|  | golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= | ||||||
|  | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | ||||||
|  | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= | ||||||
|  | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= | ||||||
|  | golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= | ||||||
|  | golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= | ||||||
|  | golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= | ||||||
|  | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= | ||||||
|  | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | ||||||
|  | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= | ||||||
|  | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= | ||||||
|  | golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= | ||||||
|  | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||||||
|  | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||||||
|  | google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= | ||||||
|  | google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= | ||||||
|  | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= | ||||||
|  | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= | ||||||
|  | google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= | ||||||
|  | google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= | ||||||
|  | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||||||
|  | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= | ||||||
|  | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= | ||||||
|  | gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= | ||||||
|  | gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= | ||||||
|  | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | ||||||
|  | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= | ||||||
|  | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | ||||||
|  | gorm.io/datatypes v1.2.0 h1:5YT+eokWdIxhJgWHdrb2zYUimyk0+TaFth+7a0ybzco= | ||||||
|  | gorm.io/datatypes v1.2.0/go.mod h1:o1dh0ZvjIjhH/bngTpypG6lVRJ5chTBxE09FH/71k04= | ||||||
|  | gorm.io/driver/mysql v1.5.2 h1:QC2HRskSE75wBuOxe0+iCkyJZ+RqpudsQtqkp+IMuXs= | ||||||
|  | gorm.io/driver/mysql v1.5.2/go.mod h1:pQLhh1Ut/WUAySdTHwBpBv6+JKcj+ua4ZFx1QQTBzb8= | ||||||
|  | gorm.io/driver/postgres v1.5.4 h1:Iyrp9Meh3GmbSuyIAGyjkN+n9K+GHX9b9MqsTL4EJCo= | ||||||
|  | gorm.io/driver/postgres v1.5.4/go.mod h1:Bgo89+h0CRcdA33Y6frlaHHVuTdOf87pmyzwW9C/BH0= | ||||||
|  | gorm.io/driver/sqlite v1.4.3 h1:HBBcZSDnWi5BW3B3rwvVTc510KGkBkexlOg0QrmLUuU= | ||||||
|  | gorm.io/driver/sqlite v1.4.3/go.mod h1:0Aq3iPO+v9ZKbcdiz8gLWRw5VOPcBOPUQJFLq5e2ecI= | ||||||
|  | gorm.io/driver/sqlserver v1.4.1 h1:t4r4r6Jam5E6ejqP7N82qAJIJAht27EGT41HyPfXRw0= | ||||||
|  | gorm.io/driver/sqlserver v1.4.1/go.mod h1:DJ4P+MeZbc5rvY58PnmN1Lnyvb5gw5NPzGshHDnJLig= | ||||||
|  | gorm.io/gorm v1.25.2-0.20230530020048-26663ab9bf55/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= | ||||||
|  | gorm.io/gorm v1.25.6 h1:V92+vVda1wEISSOMtodHVRcUIOPYa2tgQtyF+DfFx+A= | ||||||
|  | gorm.io/gorm v1.25.6/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= | ||||||
							
								
								
									
										52
									
								
								pkg/cmd/main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								pkg/cmd/main.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,52 @@ | |||||||
|  | package main | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"code.smartsheep.studio/hydrogen/interactive/pkg/server" | ||||||
|  | 	"os" | ||||||
|  | 	"os/signal" | ||||||
|  | 	"syscall" | ||||||
|  |  | ||||||
|  | 	interactive "code.smartsheep.studio/hydrogen/interactive/pkg" | ||||||
|  | 	"code.smartsheep.studio/hydrogen/interactive/pkg/database" | ||||||
|  | 	"github.com/rs/zerolog" | ||||||
|  | 	"github.com/rs/zerolog/log" | ||||||
|  | 	"github.com/spf13/viper" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func init() { | ||||||
|  | 	zerolog.TimeFieldFormat = zerolog.TimeFormatUnix | ||||||
|  | 	log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stdout}) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func main() { | ||||||
|  | 	// Configure settings | ||||||
|  | 	viper.AddConfigPath(".") | ||||||
|  | 	viper.AddConfigPath("..") | ||||||
|  | 	viper.SetConfigName("settings") | ||||||
|  | 	viper.SetConfigType("toml") | ||||||
|  |  | ||||||
|  | 	// Load settings | ||||||
|  | 	if err := viper.ReadInConfig(); err != nil { | ||||||
|  | 		log.Panic().Err(err).Msg("An error occurred when loading settings.") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Connect to database | ||||||
|  | 	if err := database.NewSource(); err != nil { | ||||||
|  | 		log.Fatal().Err(err).Msg("An error occurred when connect to database.") | ||||||
|  | 	} else if err := database.RunMigration(database.C); err != nil { | ||||||
|  | 		log.Fatal().Err(err).Msg("An error occurred when running database auto migration.") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Server | ||||||
|  | 	server.NewServer() | ||||||
|  | 	go server.Listen() | ||||||
|  |  | ||||||
|  | 	// Messages | ||||||
|  | 	log.Info().Msgf("Passport v%s is started...", interactive.AppVersion) | ||||||
|  |  | ||||||
|  | 	quit := make(chan os.Signal, 1) | ||||||
|  | 	signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) | ||||||
|  | 	<-quit | ||||||
|  |  | ||||||
|  | 	log.Info().Msgf("Passport v%s is quitting...", interactive.AppVersion) | ||||||
|  | } | ||||||
							
								
								
									
										16
									
								
								pkg/database/migrator.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								pkg/database/migrator.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | |||||||
|  | package database | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"code.smartsheep.studio/hydrogen/interactive/pkg/models" | ||||||
|  | 	"gorm.io/gorm" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func RunMigration(source *gorm.DB) error { | ||||||
|  | 	if err := source.AutoMigrate( | ||||||
|  | 		&models.Account{}, | ||||||
|  | 	); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
							
								
								
									
										28
									
								
								pkg/database/source.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								pkg/database/source.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | |||||||
|  | package database | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"github.com/rs/zerolog/log" | ||||||
|  | 	"github.com/samber/lo" | ||||||
|  | 	"github.com/spf13/viper" | ||||||
|  | 	"gorm.io/driver/postgres" | ||||||
|  | 	"gorm.io/gorm" | ||||||
|  | 	"gorm.io/gorm/logger" | ||||||
|  | 	"gorm.io/gorm/schema" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | var C *gorm.DB | ||||||
|  |  | ||||||
|  | func NewSource() error { | ||||||
|  | 	var err error | ||||||
|  |  | ||||||
|  | 	dialector := postgres.Open(viper.GetString("database.dsn")) | ||||||
|  | 	C, err = gorm.Open(dialector, &gorm.Config{NamingStrategy: schema.NamingStrategy{ | ||||||
|  | 		TablePrefix: viper.GetString("database.prefix"), | ||||||
|  | 	}, Logger: logger.New(&log.Logger, logger.Config{ | ||||||
|  | 		Colorful:                  true, | ||||||
|  | 		IgnoreRecordNotFoundError: true, | ||||||
|  | 		LogLevel:                  lo.Ternary(viper.GetBool("debug"), logger.Info, logger.Silent), | ||||||
|  | 	})}) | ||||||
|  |  | ||||||
|  | 	return err | ||||||
|  | } | ||||||
							
								
								
									
										5
									
								
								pkg/meta.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								pkg/meta.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | package pkg | ||||||
|  |  | ||||||
|  | const ( | ||||||
|  | 	AppVersion = "1.0.0" | ||||||
|  | ) | ||||||
							
								
								
									
										14
									
								
								pkg/models/accounts.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								pkg/models/accounts.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | |||||||
|  | package models | ||||||
|  |  | ||||||
|  | // Account profiles basically fetched from Hydrogen.Passport | ||||||
|  | // But cache at here for better usage | ||||||
|  | // At the same time this model can make relations between local models | ||||||
|  | type Account struct { | ||||||
|  | 	BaseModel | ||||||
|  |  | ||||||
|  | 	Name         string `json:"name"` | ||||||
|  | 	Avatar       string `json:"avatar"` | ||||||
|  | 	EmailAddress string `json:"email_address"` | ||||||
|  | 	PowerLevel   int    `json:"power_level"` | ||||||
|  | 	ExternalID   uint   `json:"external_id"` | ||||||
|  | } | ||||||
							
								
								
									
										17
									
								
								pkg/models/base.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								pkg/models/base.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | |||||||
|  | package models | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
|  | 	"gorm.io/datatypes" | ||||||
|  | 	"gorm.io/gorm" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type JSONMap = datatypes.JSONType[map[string]any] | ||||||
|  |  | ||||||
|  | type BaseModel struct { | ||||||
|  | 	ID        uint           `json:"id" gorm:"primaryKey"` | ||||||
|  | 	CreatedAt time.Time      `json:"created_at"` | ||||||
|  | 	UpdatedAt time.Time      `json:"updated_at"` | ||||||
|  | 	DeletedAt gorm.DeletedAt `json:"deleted_at" gorm:"index"` | ||||||
|  | } | ||||||
							
								
								
									
										12
									
								
								pkg/security/encryptor.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								pkg/security/encryptor.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | |||||||
|  | package security | ||||||
|  |  | ||||||
|  | import "golang.org/x/crypto/bcrypt" | ||||||
|  |  | ||||||
|  | func HashPassword(raw string) string { | ||||||
|  | 	data, _ := bcrypt.GenerateFromPassword([]byte(raw), 12) | ||||||
|  | 	return string(data) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func VerifyPassword(text string, password string) bool { | ||||||
|  | 	return bcrypt.CompareHashAndPassword([]byte(password), []byte(text)) == nil | ||||||
|  | } | ||||||
							
								
								
									
										56
									
								
								pkg/security/jwt.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								pkg/security/jwt.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,56 @@ | |||||||
|  | package security | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
|  | 	"github.com/golang-jwt/jwt/v5" | ||||||
|  | 	"github.com/spf13/viper" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type PayloadClaims struct { | ||||||
|  | 	jwt.RegisteredClaims | ||||||
|  |  | ||||||
|  | 	Type string `json:"typ"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | const ( | ||||||
|  | 	JwtAccessType  = "access" | ||||||
|  | 	JwtRefreshType = "refresh" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func EncodeJwt(id string, typ, sub string, aud []string, exp time.Time) (string, error) { | ||||||
|  | 	tk := jwt.NewWithClaims(jwt.SigningMethodHS512, PayloadClaims{ | ||||||
|  | 		jwt.RegisteredClaims{ | ||||||
|  | 			Subject:   sub, | ||||||
|  | 			Audience:  aud, | ||||||
|  | 			Issuer:    fmt.Sprintf("https://%s", viper.GetString("domain")), | ||||||
|  | 			ExpiresAt: jwt.NewNumericDate(exp), | ||||||
|  | 			NotBefore: jwt.NewNumericDate(time.Now()), | ||||||
|  | 			IssuedAt:  jwt.NewNumericDate(time.Now()), | ||||||
|  | 			ID:        id, | ||||||
|  | 		}, | ||||||
|  | 		typ, | ||||||
|  | 	}) | ||||||
|  |  | ||||||
|  | 	return tk.SignedString([]byte(viper.GetString("secret"))) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func DecodeJwt(str string) (PayloadClaims, error) { | ||||||
|  | 	var claims PayloadClaims | ||||||
|  | 	tk, err := jwt.ParseWithClaims(str, &claims, func(token *jwt.Token) (interface{}, error) { | ||||||
|  | 		if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { | ||||||
|  | 			return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) | ||||||
|  | 		} | ||||||
|  | 		return []byte(viper.GetString("secret")), nil | ||||||
|  | 	}) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return claims, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if data, ok := tk.Claims.(*PayloadClaims); ok { | ||||||
|  | 		return *data, nil | ||||||
|  | 	} else { | ||||||
|  | 		return claims, fmt.Errorf("unexpected token payload: not payload claims type") | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										35
									
								
								pkg/server/auth.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								pkg/server/auth.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | |||||||
|  | package server | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"code.smartsheep.studio/hydrogen/interactive/pkg/database" | ||||||
|  | 	"code.smartsheep.studio/hydrogen/interactive/pkg/models" | ||||||
|  | 	"code.smartsheep.studio/hydrogen/passport/pkg/security" | ||||||
|  | 	"github.com/gofiber/fiber/v2" | ||||||
|  | 	"github.com/gofiber/fiber/v2/middleware/keyauth" | ||||||
|  | 	"strconv" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | var auth = keyauth.New(keyauth.Config{ | ||||||
|  | 	KeyLookup:  "header:Authorization", | ||||||
|  | 	AuthScheme: "Bearer", | ||||||
|  | 	Validator: func(c *fiber.Ctx, token string) (bool, error) { | ||||||
|  | 		claims, err := security.DecodeJwt(token) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return false, err | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		id, _ := strconv.Atoi(claims.Subject) | ||||||
|  |  | ||||||
|  | 		var user models.Account | ||||||
|  | 		if err := database.C.Where(&models.Account{ | ||||||
|  | 			BaseModel: models.BaseModel{ID: uint(id)}, | ||||||
|  | 		}).First(&user).Error; err != nil { | ||||||
|  | 			return false, err | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		c.Locals("principal", user) | ||||||
|  |  | ||||||
|  | 		return true, nil | ||||||
|  | 	}, | ||||||
|  | 	ContextKey: "token", | ||||||
|  | }) | ||||||
							
								
								
									
										87
									
								
								pkg/server/auth_api.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								pkg/server/auth_api.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,87 @@ | |||||||
|  | package server | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"code.smartsheep.studio/hydrogen/interactive/pkg/services" | ||||||
|  | 	"context" | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"fmt" | ||||||
|  | 	"github.com/gofiber/fiber/v2" | ||||||
|  | 	"github.com/google/uuid" | ||||||
|  | 	"github.com/spf13/viper" | ||||||
|  | 	"golang.org/x/oauth2" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | var cfg = oauth2.Config{ | ||||||
|  | 	RedirectURL:  fmt.Sprintf("https://%s/api/auth/callback", viper.GetString("domain")), | ||||||
|  | 	ClientID:     viper.GetString("passport.client_id"), | ||||||
|  | 	ClientSecret: viper.GetString("passport.client_secret"), | ||||||
|  | 	Scopes:       []string{"openid"}, | ||||||
|  | 	Endpoint: oauth2.Endpoint{ | ||||||
|  | 		AuthURL:   fmt.Sprintf("%s/auth/o/connect", viper.GetString("passport.endpoint")), | ||||||
|  | 		TokenURL:  fmt.Sprintf("%s/api/auth/token", viper.GetString("passport.endpoint")), | ||||||
|  | 		AuthStyle: oauth2.AuthStyleInParams, | ||||||
|  | 	}, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func doLogin(c *fiber.Ctx) error { | ||||||
|  | 	url := cfg.AuthCodeURL(uuid.NewString()) | ||||||
|  |  | ||||||
|  | 	return c.JSON(fiber.Map{ | ||||||
|  | 		"target": url, | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func doPostLogin(c *fiber.Ctx) error { | ||||||
|  | 	code := c.Query("code") | ||||||
|  |  | ||||||
|  | 	token, err := cfg.Exchange(context.Background(), code) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("failed to exchange token: %q", err)) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	agent := fiber. | ||||||
|  | 		Get(fmt.Sprintf("%s/api/users/me", viper.GetString("passport.endpoint"))). | ||||||
|  | 		Set(fiber.HeaderAuthorization, fmt.Sprintf("Bearer %s", token.AccessToken)) | ||||||
|  |  | ||||||
|  | 	_, body, errs := agent.Bytes() | ||||||
|  | 	if len(errs) > 0 { | ||||||
|  | 		return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("failed to get userinfo: %q", errs)) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	var userinfo services.PassportUserinfo | ||||||
|  | 	err = json.Unmarshal(body, &userinfo) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("failed to parse userinfo: %q", err)) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	account, err := services.LinkAccount(userinfo) | ||||||
|  | 	access, refresh, err := services.GetToken(account) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("failed to get token: %q", err)) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return c.JSON(fiber.Map{ | ||||||
|  | 		"access_token":  access, | ||||||
|  | 		"refresh_token": refresh, | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func doRefreshToken(c *fiber.Ctx) error { | ||||||
|  | 	var data struct { | ||||||
|  | 		RefreshToken string `json:"refresh_token" validate:"required"` | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if err := BindAndValidate(c, &data); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	access, refresh, err := services.RefreshToken(data.RefreshToken) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("failed to get token: %q", err)) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return c.JSON(fiber.Map{ | ||||||
|  | 		"access_token":  access, | ||||||
|  | 		"refresh_token": refresh, | ||||||
|  | 	}) | ||||||
|  | } | ||||||
							
								
								
									
										79
									
								
								pkg/server/startup.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								pkg/server/startup.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,79 @@ | |||||||
|  | package server | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"code.smartsheep.studio/hydrogen/interactive/pkg/view" | ||||||
|  | 	"github.com/gofiber/fiber/v2" | ||||||
|  | 	"github.com/gofiber/fiber/v2/middleware/cache" | ||||||
|  | 	"github.com/gofiber/fiber/v2/middleware/cors" | ||||||
|  | 	"github.com/gofiber/fiber/v2/middleware/filesystem" | ||||||
|  | 	"github.com/gofiber/fiber/v2/middleware/idempotency" | ||||||
|  | 	"github.com/gofiber/fiber/v2/middleware/logger" | ||||||
|  | 	jsoniter "github.com/json-iterator/go" | ||||||
|  | 	"github.com/rs/zerolog/log" | ||||||
|  | 	"github.com/spf13/viper" | ||||||
|  | 	"net/http" | ||||||
|  | 	"strings" | ||||||
|  | 	"time" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | var A *fiber.App | ||||||
|  |  | ||||||
|  | func NewServer() { | ||||||
|  | 	A = fiber.New(fiber.Config{ | ||||||
|  | 		DisableStartupMessage: true, | ||||||
|  | 		EnableIPValidation:    true, | ||||||
|  | 		ServerHeader:          "Hydrogen.Interactive", | ||||||
|  | 		AppName:               "Hydrogen.Interactive", | ||||||
|  | 		ProxyHeader:           fiber.HeaderXForwardedFor, | ||||||
|  | 		JSONEncoder:           jsoniter.ConfigCompatibleWithStandardLibrary.Marshal, | ||||||
|  | 		JSONDecoder:           jsoniter.ConfigCompatibleWithStandardLibrary.Unmarshal, | ||||||
|  | 		EnablePrintRoutes:     viper.GetBool("debug"), | ||||||
|  | 	}) | ||||||
|  |  | ||||||
|  | 	A.Use(idempotency.New()) | ||||||
|  | 	A.Use(cors.New(cors.Config{ | ||||||
|  | 		AllowCredentials: true, | ||||||
|  | 		AllowMethods: strings.Join([]string{ | ||||||
|  | 			fiber.MethodGet, | ||||||
|  | 			fiber.MethodPost, | ||||||
|  | 			fiber.MethodHead, | ||||||
|  | 			fiber.MethodOptions, | ||||||
|  | 			fiber.MethodPut, | ||||||
|  | 			fiber.MethodDelete, | ||||||
|  | 			fiber.MethodPatch, | ||||||
|  | 		}, ","), | ||||||
|  | 		AllowOriginsFunc: func(origin string) bool { | ||||||
|  | 			return true | ||||||
|  | 		}, | ||||||
|  | 	})) | ||||||
|  |  | ||||||
|  | 	A.Use(logger.New(logger.Config{ | ||||||
|  | 		Format: "${status} | ${latency} | ${method} ${path}\n", | ||||||
|  | 		Output: log.Logger, | ||||||
|  | 	})) | ||||||
|  |  | ||||||
|  | 	A.Get("/.well-known", getMetadata) | ||||||
|  |  | ||||||
|  | 	api := A.Group("/api").Name("API") | ||||||
|  | 	{ | ||||||
|  | 		api.Get("/auth", doLogin) | ||||||
|  | 		api.Get("/auth/callback", doPostLogin) | ||||||
|  | 		api.Post("/auth/refresh", doRefreshToken) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	A.Use("/", cache.New(cache.Config{ | ||||||
|  | 		Expiration:   24 * time.Hour, | ||||||
|  | 		CacheControl: true, | ||||||
|  | 	}), filesystem.New(filesystem.Config{ | ||||||
|  | 		Root:         http.FS(view.FS), | ||||||
|  | 		PathPrefix:   "dist", | ||||||
|  | 		Index:        "index.html", | ||||||
|  | 		NotFoundFile: "dist/index.html", | ||||||
|  | 	})) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func Listen() { | ||||||
|  | 	if err := A.Listen(viper.GetString("bind")); err != nil { | ||||||
|  | 		log.Fatal().Err(err).Msg("An error occurred when starting server...") | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										18
									
								
								pkg/server/utils.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								pkg/server/utils.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | |||||||
|  | package server | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"github.com/go-playground/validator/v10" | ||||||
|  | 	"github.com/gofiber/fiber/v2" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | var validation = validator.New(validator.WithRequiredStructEnabled()) | ||||||
|  |  | ||||||
|  | func BindAndValidate(c *fiber.Ctx, out any) error { | ||||||
|  | 	if err := c.BodyParser(out); err != nil { | ||||||
|  | 		return fiber.NewError(fiber.StatusBadRequest, err.Error()) | ||||||
|  | 	} else if err := validation.Struct(out); err != nil { | ||||||
|  | 		return fiber.NewError(fiber.StatusBadRequest, err.Error()) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
							
								
								
									
										13
									
								
								pkg/server/well_known_api.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								pkg/server/well_known_api.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | |||||||
|  | package server | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"github.com/gofiber/fiber/v2" | ||||||
|  | 	"github.com/spf13/viper" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func getMetadata(c *fiber.Ctx) error { | ||||||
|  | 	return c.JSON(fiber.Map{ | ||||||
|  | 		"name":   viper.GetString("name"), | ||||||
|  | 		"domain": viper.GetString("domain"), | ||||||
|  | 	}) | ||||||
|  | } | ||||||
							
								
								
									
										90
									
								
								pkg/services/auth.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								pkg/services/auth.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,90 @@ | |||||||
|  | package services | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"code.smartsheep.studio/hydrogen/interactive/pkg/database" | ||||||
|  | 	"code.smartsheep.studio/hydrogen/interactive/pkg/models" | ||||||
|  | 	"code.smartsheep.studio/hydrogen/interactive/pkg/security" | ||||||
|  | 	"errors" | ||||||
|  | 	"fmt" | ||||||
|  | 	"github.com/google/uuid" | ||||||
|  | 	"gorm.io/gorm" | ||||||
|  | 	"strconv" | ||||||
|  | 	"time" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type PassportUserinfo struct { | ||||||
|  | 	Sub                uint   `json:"sub"` | ||||||
|  | 	Email              string `json:"email"` | ||||||
|  | 	Picture            string `json:"picture"` | ||||||
|  | 	PreferredUsernames string `json:"preferred_usernames"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func LinkAccount(userinfo PassportUserinfo) (models.Account, error) { | ||||||
|  | 	var account models.Account | ||||||
|  | 	if err := database.C.Where(&models.Account{ | ||||||
|  | 		ExternalID: userinfo.Sub, | ||||||
|  | 	}).First(&account).Error; err != nil { | ||||||
|  | 		if errors.Is(err, gorm.ErrRecordNotFound) { | ||||||
|  | 			account = models.Account{ | ||||||
|  | 				Name:         userinfo.PreferredUsernames, | ||||||
|  | 				Avatar:       userinfo.Picture, | ||||||
|  | 				EmailAddress: userinfo.Email, | ||||||
|  | 				PowerLevel:   0, | ||||||
|  | 				ExternalID:   userinfo.Sub, | ||||||
|  | 			} | ||||||
|  | 			return account, database.C.Save(&account).Error | ||||||
|  | 		} | ||||||
|  | 		return account, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return account, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func GetToken(account models.Account) (string, string, error) { | ||||||
|  | 	var err error | ||||||
|  | 	var refresh, access string | ||||||
|  |  | ||||||
|  | 	sub := strconv.Itoa(int(account.ID)) | ||||||
|  | 	access, err = security.EncodeJwt( | ||||||
|  | 		uuid.NewString(), | ||||||
|  | 		security.JwtAccessType, | ||||||
|  | 		sub, | ||||||
|  | 		[]string{"interactive"}, | ||||||
|  | 		time.Now().Add(30*time.Minute), | ||||||
|  | 	) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return refresh, access, err | ||||||
|  | 	} | ||||||
|  | 	refresh, err = security.EncodeJwt( | ||||||
|  | 		uuid.NewString(), | ||||||
|  | 		security.JwtRefreshType, | ||||||
|  | 		sub, | ||||||
|  | 		[]string{"interactive"}, | ||||||
|  | 		time.Now().Add(30*24*time.Hour), | ||||||
|  | 	) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return refresh, access, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return access, refresh, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func RefreshToken(token string) (string, string, error) { | ||||||
|  | 	parseInt := func(str string) int { | ||||||
|  | 		val, _ := strconv.Atoi(str) | ||||||
|  | 		return val | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	var account models.Account | ||||||
|  | 	if claims, err := security.DecodeJwt(token); err != nil { | ||||||
|  | 		return "404", "403", err | ||||||
|  | 	} else if claims.Type != security.JwtRefreshType { | ||||||
|  | 		return "404", "403", fmt.Errorf("invalid token type, expected refresh token") | ||||||
|  | 	} else if err := database.C.Where(models.Account{ | ||||||
|  | 		BaseModel: models.BaseModel{ID: uint(parseInt(claims.Subject))}, | ||||||
|  | 	}).First(&account).Error; err != nil { | ||||||
|  | 		return "404", "403", err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return GetToken(account) | ||||||
|  | } | ||||||
							
								
								
									
										51
									
								
								pkg/services/mailer.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								pkg/services/mailer.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,51 @@ | |||||||
|  | package services | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"crypto/tls" | ||||||
|  | 	"fmt" | ||||||
|  | 	"net/smtp" | ||||||
|  | 	"net/textproto" | ||||||
|  |  | ||||||
|  | 	"github.com/jordan-wright/email" | ||||||
|  | 	"github.com/spf13/viper" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func SendMail(target string, subject string, content string) error { | ||||||
|  | 	mail := &email.Email{ | ||||||
|  | 		To:      []string{target}, | ||||||
|  | 		From:    viper.GetString("mailer.name"), | ||||||
|  | 		Subject: subject, | ||||||
|  | 		Text:    []byte(content), | ||||||
|  | 		Headers: textproto.MIMEHeader{}, | ||||||
|  | 	} | ||||||
|  | 	return mail.SendWithTLS( | ||||||
|  | 		fmt.Sprintf("%s:%d", viper.GetString("mailer.smtp_host"), viper.GetInt("mailer.smtp_port")), | ||||||
|  | 		smtp.PlainAuth( | ||||||
|  | 			"", | ||||||
|  | 			viper.GetString("mailer.username"), | ||||||
|  | 			viper.GetString("mailer.password"), | ||||||
|  | 			viper.GetString("mailer.smtp_host"), | ||||||
|  | 		), | ||||||
|  | 		&tls.Config{ServerName: viper.GetString("mailer.smtp_host")}, | ||||||
|  | 	) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func SendMailHTML(target string, subject string, content string) error { | ||||||
|  | 	mail := &email.Email{ | ||||||
|  | 		To:      []string{target}, | ||||||
|  | 		From:    viper.GetString("mailer.name"), | ||||||
|  | 		Subject: subject, | ||||||
|  | 		HTML:    []byte(content), | ||||||
|  | 		Headers: textproto.MIMEHeader{}, | ||||||
|  | 	} | ||||||
|  | 	return mail.SendWithTLS( | ||||||
|  | 		fmt.Sprintf("%s:%d", viper.GetString("mailer.smtp_host"), viper.GetInt("mailer.smtp_port")), | ||||||
|  | 		smtp.PlainAuth( | ||||||
|  | 			"", | ||||||
|  | 			viper.GetString("mailer.username"), | ||||||
|  | 			viper.GetString("mailer.password"), | ||||||
|  | 			viper.GetString("mailer.smtp_host"), | ||||||
|  | 		), | ||||||
|  | 		&tls.Config{ServerName: viper.GetString("mailer.smtp_host")}, | ||||||
|  | 	) | ||||||
|  | } | ||||||
							
								
								
									
										5
									
								
								pkg/view/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								pkg/view/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | /dist | ||||||
|  | /node_modules | ||||||
|  |  | ||||||
|  | package-lock.json | ||||||
|  | yarn.lock | ||||||
							
								
								
									
										28
									
								
								pkg/view/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								pkg/view/README.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | |||||||
|  | ## Usage | ||||||
|  |  | ||||||
|  | ```bash | ||||||
|  | $ npm install # or pnpm install or yarn install | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ### Learn more on the [Solid Website](https://solidjs.com) and come chat with us on our [Discord](https://discord.com/invite/solidjs) | ||||||
|  |  | ||||||
|  | ## Available Scripts | ||||||
|  |  | ||||||
|  | In the project directory, you can run: | ||||||
|  |  | ||||||
|  | ### `npm run dev` | ||||||
|  |  | ||||||
|  | Runs the app in the development mode.<br> | ||||||
|  | Open [http://localhost:5173](http://localhost:5173) to view it in the browser. | ||||||
|  |  | ||||||
|  | ### `npm run build` | ||||||
|  |  | ||||||
|  | Builds the app for production to the `dist` folder.<br> | ||||||
|  | It correctly bundles Solid in production mode and optimizes the build for the best performance. | ||||||
|  |  | ||||||
|  | The build is minified and the filenames include the hashes.<br> | ||||||
|  | Your app is ready to be deployed! | ||||||
|  |  | ||||||
|  | ## Deployment | ||||||
|  |  | ||||||
|  | Learn more about deploying your application with the [documentations](https://vitejs.dev/guide/static-deploy.html) | ||||||
							
								
								
									
										6
									
								
								pkg/view/embed.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								pkg/view/embed.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | |||||||
|  | package view | ||||||
|  |  | ||||||
|  | import "embed" | ||||||
|  |  | ||||||
|  | //go:embed all:dist | ||||||
|  | var FS embed.FS | ||||||
							
								
								
									
										13
									
								
								pkg/view/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								pkg/view/index.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | |||||||
|  | <!doctype html> | ||||||
|  | <html lang="en"> | ||||||
|  |   <head> | ||||||
|  |     <meta charset="UTF-8" /> | ||||||
|  |     <link rel="icon" type="image/svg+xml" href="/favicon.svg" /> | ||||||
|  |     <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||||||
|  |     <title>Goatpass</title> | ||||||
|  |   </head> | ||||||
|  |   <body> | ||||||
|  |     <div id="root"></div> | ||||||
|  |     <script type="module" src="/src/index.tsx"></script> | ||||||
|  |   </body> | ||||||
|  | </html> | ||||||
							
								
								
									
										26
									
								
								pkg/view/package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								pkg/view/package.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | |||||||
|  | { | ||||||
|  |   "name": "@hydrogen/interactive-web", | ||||||
|  |   "private": true, | ||||||
|  |   "version": "0.0.0", | ||||||
|  |   "type": "module", | ||||||
|  |   "scripts": { | ||||||
|  |     "dev": "vite", | ||||||
|  |     "build": "tsc && vite build", | ||||||
|  |     "preview": "vite preview" | ||||||
|  |   }, | ||||||
|  |   "dependencies": { | ||||||
|  |     "@solidjs/router": "^0.10.10", | ||||||
|  |     "solid-js": "^1.8.7", | ||||||
|  |     "universal-cookie": "^7.0.2" | ||||||
|  |   }, | ||||||
|  |   "devDependencies": { | ||||||
|  |     "autoprefixer": "^10.4.17", | ||||||
|  |     "daisyui": "^4.6.0", | ||||||
|  |     "postcss": "^8.4.33", | ||||||
|  |     "solid-devtools": "^0.29.3", | ||||||
|  |     "tailwindcss": "^3.4.1", | ||||||
|  |     "typescript": "^5.2.2", | ||||||
|  |     "vite": "^5.0.8", | ||||||
|  |     "vite-plugin-solid": "^2.8.0" | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										6
									
								
								pkg/view/postcss.config.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								pkg/view/postcss.config.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | |||||||
|  | export default { | ||||||
|  |   plugins: { | ||||||
|  |     tailwindcss: {}, | ||||||
|  |     autoprefixer: {}, | ||||||
|  |   }, | ||||||
|  | } | ||||||
							
								
								
									
										21
									
								
								pkg/view/public/favicon.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								pkg/view/public/favicon.svg
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| After Width: | Height: | Size: 12 KiB | 
							
								
								
									
										5
									
								
								pkg/view/src/.prettierrc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								pkg/view/src/.prettierrc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | { | ||||||
|  |   "printWidth": 120, | ||||||
|  |   "tabWidth": 2, | ||||||
|  |   "singleQuote": false | ||||||
|  | } | ||||||
							
								
								
									
										197
									
								
								pkg/view/src/assets/fonts/fonts.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										197
									
								
								pkg/view/src/assets/fonts/fonts.css
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,197 @@ | |||||||
|  | :root { | ||||||
|  |   --bs-body-font-family: "IBM Plex Serif", "Noto Serif SC", sans-serif !important; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | html, | ||||||
|  | body { | ||||||
|  |   font-family: var(--bs-body-font-family); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* ibm-plex-serif-100 - latin */ | ||||||
|  | @font-face { | ||||||
|  |   font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */ | ||||||
|  |   font-family: "IBM Plex Serif"; | ||||||
|  |   font-style: normal; | ||||||
|  |   font-weight: 100; | ||||||
|  |   src: url("./ibm-plex-serif-v19-latin-100.woff2") format("woff2"); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */ | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* ibm-plex-serif-100italic - latin */ | ||||||
|  | @font-face { | ||||||
|  |   font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */ | ||||||
|  |   font-family: "IBM Plex Serif"; | ||||||
|  |   font-style: italic; | ||||||
|  |   font-weight: 100; | ||||||
|  |   src: url("./ibm-plex-serif-v19-latin-100italic.woff2") format("woff2"); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */ | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* ibm-plex-serif-200 - latin */ | ||||||
|  | @font-face { | ||||||
|  |   font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */ | ||||||
|  |   font-family: "IBM Plex Serif"; | ||||||
|  |   font-style: normal; | ||||||
|  |   font-weight: 200; | ||||||
|  |   src: url("./ibm-plex-serif-v19-latin-200.woff2") format("woff2"); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */ | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* ibm-plex-serif-200italic - latin */ | ||||||
|  | @font-face { | ||||||
|  |   font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */ | ||||||
|  |   font-family: "IBM Plex Serif"; | ||||||
|  |   font-style: italic; | ||||||
|  |   font-weight: 200; | ||||||
|  |   src: url("./ibm-plex-serif-v19-latin-200italic.woff2") format("woff2"); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */ | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* ibm-plex-serif-300 - latin */ | ||||||
|  | @font-face { | ||||||
|  |   font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */ | ||||||
|  |   font-family: "IBM Plex Serif"; | ||||||
|  |   font-style: normal; | ||||||
|  |   font-weight: 300; | ||||||
|  |   src: url("./ibm-plex-serif-v19-latin-300.woff2") format("woff2"); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */ | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* ibm-plex-serif-300italic - latin */ | ||||||
|  | @font-face { | ||||||
|  |   font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */ | ||||||
|  |   font-family: "IBM Plex Serif"; | ||||||
|  |   font-style: italic; | ||||||
|  |   font-weight: 300; | ||||||
|  |   src: url("./ibm-plex-serif-v19-latin-300italic.woff2") format("woff2"); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */ | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* ibm-plex-serif-regular - latin */ | ||||||
|  | @font-face { | ||||||
|  |   font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */ | ||||||
|  |   font-family: "IBM Plex Serif"; | ||||||
|  |   font-style: normal; | ||||||
|  |   font-weight: 400; | ||||||
|  |   src: url("./ibm-plex-serif-v19-latin-regular.woff2") format("woff2"); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */ | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* ibm-plex-serif-italic - latin */ | ||||||
|  | @font-face { | ||||||
|  |   font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */ | ||||||
|  |   font-family: "IBM Plex Serif"; | ||||||
|  |   font-style: italic; | ||||||
|  |   font-weight: 400; | ||||||
|  |   src: url("./ibm-plex-serif-v19-latin-italic.woff2") format("woff2"); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */ | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* ibm-plex-serif-500 - latin */ | ||||||
|  | @font-face { | ||||||
|  |   font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */ | ||||||
|  |   font-family: "IBM Plex Serif"; | ||||||
|  |   font-style: normal; | ||||||
|  |   font-weight: 500; | ||||||
|  |   src: url("./ibm-plex-serif-v19-latin-500.woff2") format("woff2"); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */ | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* ibm-plex-serif-500italic - latin */ | ||||||
|  | @font-face { | ||||||
|  |   font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */ | ||||||
|  |   font-family: "IBM Plex Serif"; | ||||||
|  |   font-style: italic; | ||||||
|  |   font-weight: 500; | ||||||
|  |   src: url("./ibm-plex-serif-v19-latin-500italic.woff2") format("woff2"); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */ | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* ibm-plex-serif-600 - latin */ | ||||||
|  | @font-face { | ||||||
|  |   font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */ | ||||||
|  |   font-family: "IBM Plex Serif"; | ||||||
|  |   font-style: normal; | ||||||
|  |   font-weight: 600; | ||||||
|  |   src: url("./ibm-plex-serif-v19-latin-600.woff2") format("woff2"); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */ | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* ibm-plex-serif-600italic - latin */ | ||||||
|  | @font-face { | ||||||
|  |   font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */ | ||||||
|  |   font-family: "IBM Plex Serif"; | ||||||
|  |   font-style: italic; | ||||||
|  |   font-weight: 600; | ||||||
|  |   src: url("./ibm-plex-serif-v19-latin-600italic.woff2") format("woff2"); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */ | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* ibm-plex-serif-700 - latin */ | ||||||
|  | @font-face { | ||||||
|  |   font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */ | ||||||
|  |   font-family: "IBM Plex Serif"; | ||||||
|  |   font-style: normal; | ||||||
|  |   font-weight: 700; | ||||||
|  |   src: url("./ibm-plex-serif-v19-latin-700.woff2") format("woff2"); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */ | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* ibm-plex-serif-700italic - latin */ | ||||||
|  | @font-face { | ||||||
|  |   font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */ | ||||||
|  |   font-family: "IBM Plex Serif"; | ||||||
|  |   font-style: italic; | ||||||
|  |   font-weight: 700; | ||||||
|  |   src: url("./ibm-plex-serif-v19-latin-700italic.woff2") format("woff2"); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */ | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* noto-serif-sc-200 - chinese-simplified */ | ||||||
|  | @font-face { | ||||||
|  |   font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */ | ||||||
|  |   font-family: "Noto Serif SC"; | ||||||
|  |   font-style: normal; | ||||||
|  |   font-weight: 200; | ||||||
|  |   src: url("./noto-serif-sc-v22-chinese-simplified-200.woff2") format("woff2"); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */ | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* noto-serif-sc-300 - chinese-simplified */ | ||||||
|  | @font-face { | ||||||
|  |   font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */ | ||||||
|  |   font-family: "Noto Serif SC"; | ||||||
|  |   font-style: normal; | ||||||
|  |   font-weight: 300; | ||||||
|  |   src: url("./noto-serif-sc-v22-chinese-simplified-300.woff2") format("woff2"); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */ | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* noto-serif-sc-regular - chinese-simplified */ | ||||||
|  | @font-face { | ||||||
|  |   font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */ | ||||||
|  |   font-family: "Noto Serif SC"; | ||||||
|  |   font-style: normal; | ||||||
|  |   font-weight: 400; | ||||||
|  |   src: url("./noto-serif-sc-v22-chinese-simplified-regular.woff2") format("woff2"); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */ | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* noto-serif-sc-500 - chinese-simplified */ | ||||||
|  | @font-face { | ||||||
|  |   font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */ | ||||||
|  |   font-family: "Noto Serif SC"; | ||||||
|  |   font-style: normal; | ||||||
|  |   font-weight: 500; | ||||||
|  |   src: url("./noto-serif-sc-v22-chinese-simplified-500.woff2") format("woff2"); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */ | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* noto-serif-sc-600 - chinese-simplified */ | ||||||
|  | @font-face { | ||||||
|  |   font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */ | ||||||
|  |   font-family: "Noto Serif SC"; | ||||||
|  |   font-style: normal; | ||||||
|  |   font-weight: 600; | ||||||
|  |   src: url("./noto-serif-sc-v22-chinese-simplified-600.woff2") format("woff2"); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */ | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* noto-serif-sc-700 - chinese-simplified */ | ||||||
|  | @font-face { | ||||||
|  |   font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */ | ||||||
|  |   font-family: "Noto Serif SC"; | ||||||
|  |   font-style: normal; | ||||||
|  |   font-weight: 700; | ||||||
|  |   src: url("./noto-serif-sc-v22-chinese-simplified-700.woff2") format("woff2"); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */ | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* noto-serif-sc-900 - chinese-simplified */ | ||||||
|  | @font-face { | ||||||
|  |   font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */ | ||||||
|  |   font-family: "Noto Serif SC"; | ||||||
|  |   font-style: normal; | ||||||
|  |   font-weight: 900; | ||||||
|  |   src: url("./noto-serif-sc-v22-chinese-simplified-900.woff2") format("woff2"); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */ | ||||||
|  | } | ||||||
							
								
								
									
										
											BIN
										
									
								
								pkg/view/src/assets/fonts/ibm-plex-serif-v19-latin-100.woff2
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								pkg/view/src/assets/fonts/ibm-plex-serif-v19-latin-100.woff2
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								pkg/view/src/assets/fonts/ibm-plex-serif-v19-latin-100italic.woff2
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								pkg/view/src/assets/fonts/ibm-plex-serif-v19-latin-100italic.woff2
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								pkg/view/src/assets/fonts/ibm-plex-serif-v19-latin-200.woff2
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								pkg/view/src/assets/fonts/ibm-plex-serif-v19-latin-200.woff2
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								pkg/view/src/assets/fonts/ibm-plex-serif-v19-latin-200italic.woff2
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								pkg/view/src/assets/fonts/ibm-plex-serif-v19-latin-200italic.woff2
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								pkg/view/src/assets/fonts/ibm-plex-serif-v19-latin-300.woff2
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								pkg/view/src/assets/fonts/ibm-plex-serif-v19-latin-300.woff2
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								pkg/view/src/assets/fonts/ibm-plex-serif-v19-latin-300italic.woff2
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								pkg/view/src/assets/fonts/ibm-plex-serif-v19-latin-300italic.woff2
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								pkg/view/src/assets/fonts/ibm-plex-serif-v19-latin-500.woff2
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								pkg/view/src/assets/fonts/ibm-plex-serif-v19-latin-500.woff2
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								pkg/view/src/assets/fonts/ibm-plex-serif-v19-latin-500italic.woff2
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								pkg/view/src/assets/fonts/ibm-plex-serif-v19-latin-500italic.woff2
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								pkg/view/src/assets/fonts/ibm-plex-serif-v19-latin-600.woff2
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								pkg/view/src/assets/fonts/ibm-plex-serif-v19-latin-600.woff2
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								pkg/view/src/assets/fonts/ibm-plex-serif-v19-latin-600italic.woff2
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								pkg/view/src/assets/fonts/ibm-plex-serif-v19-latin-600italic.woff2
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								pkg/view/src/assets/fonts/ibm-plex-serif-v19-latin-700.woff2
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								pkg/view/src/assets/fonts/ibm-plex-serif-v19-latin-700.woff2
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								pkg/view/src/assets/fonts/ibm-plex-serif-v19-latin-700italic.woff2
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								pkg/view/src/assets/fonts/ibm-plex-serif-v19-latin-700italic.woff2
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								pkg/view/src/assets/fonts/ibm-plex-serif-v19-latin-italic.woff2
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								pkg/view/src/assets/fonts/ibm-plex-serif-v19-latin-italic.woff2
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								pkg/view/src/assets/fonts/ibm-plex-serif-v19-latin-regular.woff2
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								pkg/view/src/assets/fonts/ibm-plex-serif-v19-latin-regular.woff2
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								pkg/view/src/assets/fonts/noto-serif-sc-v22-chinese-simplified-200.woff2
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								pkg/view/src/assets/fonts/noto-serif-sc-v22-chinese-simplified-200.woff2
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								pkg/view/src/assets/fonts/noto-serif-sc-v22-chinese-simplified-300.woff2
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								pkg/view/src/assets/fonts/noto-serif-sc-v22-chinese-simplified-300.woff2
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								pkg/view/src/assets/fonts/noto-serif-sc-v22-chinese-simplified-500.woff2
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								pkg/view/src/assets/fonts/noto-serif-sc-v22-chinese-simplified-500.woff2
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								pkg/view/src/assets/fonts/noto-serif-sc-v22-chinese-simplified-600.woff2
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								pkg/view/src/assets/fonts/noto-serif-sc-v22-chinese-simplified-600.woff2
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								pkg/view/src/assets/fonts/noto-serif-sc-v22-chinese-simplified-700.woff2
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								pkg/view/src/assets/fonts/noto-serif-sc-v22-chinese-simplified-700.woff2
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								pkg/view/src/assets/fonts/noto-serif-sc-v22-chinese-simplified-900.woff2
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								pkg/view/src/assets/fonts/noto-serif-sc-v22-chinese-simplified-900.woff2
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								pkg/view/src/assets/fonts/noto-serif-sc-v22-chinese-simplified-regular.woff2
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								pkg/view/src/assets/fonts/noto-serif-sc-v22-chinese-simplified-regular.woff2
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										8
									
								
								pkg/view/src/index.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								pkg/view/src/index.css
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | |||||||
|  | @tailwind base; | ||||||
|  | @tailwind components; | ||||||
|  | @tailwind utilities; | ||||||
|  |  | ||||||
|  | html, body { | ||||||
|  |     padding: 0; | ||||||
|  |     margin: 0; | ||||||
|  | } | ||||||
							
								
								
									
										32
									
								
								pkg/view/src/index.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								pkg/view/src/index.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | |||||||
|  | import "solid-devtools"; | ||||||
|  |  | ||||||
|  | /* @refresh reload */ | ||||||
|  | import { render } from "solid-js/web"; | ||||||
|  |  | ||||||
|  | import "./index.css"; | ||||||
|  | import "./assets/fonts/fonts.css"; | ||||||
|  | import { lazy } from "solid-js"; | ||||||
|  | import { Route, Router } from "@solidjs/router"; | ||||||
|  |  | ||||||
|  | import RootLayout from "./layouts/RootLayout.tsx"; | ||||||
|  | import { UserinfoProvider } from "./stores/userinfo.tsx"; | ||||||
|  | import { WellKnownProvider } from "./stores/wellKnown.tsx"; | ||||||
|  |  | ||||||
|  | const root = document.getElementById("root"); | ||||||
|  |  | ||||||
|  | render(() => ( | ||||||
|  |   <WellKnownProvider> | ||||||
|  |     <UserinfoProvider> | ||||||
|  |       <Router root={RootLayout}> | ||||||
|  |         <Route path="/" component={lazy(() => import("./pages/dashboard.tsx"))} /> | ||||||
|  |         <Route path="/security" component={lazy(() => import("./pages/security.tsx"))} /> | ||||||
|  |         <Route path="/personalise" component={lazy(() => import("./pages/personalise.tsx"))} /> | ||||||
|  |         <Route path="/auth/login" component={lazy(() => import("./pages/auth/login.tsx"))} /> | ||||||
|  |         <Route path="/auth/register" component={lazy(() => import("./pages/auth/register.tsx"))} /> | ||||||
|  |         <Route path="/auth/oauth/connect" component={lazy(() => import("./pages/auth/connect.tsx"))} /> | ||||||
|  |         <Route path="/auth/oauth/callback" component={lazy(() => import("./pages/auth/callback.tsx"))} /> | ||||||
|  |         <Route path="/users/me/confirm" component={lazy(() => import("./pages/users/confirm.tsx"))} /> | ||||||
|  |       </Router> | ||||||
|  |     </UserinfoProvider> | ||||||
|  |   </WellKnownProvider> | ||||||
|  | ), root!); | ||||||
							
								
								
									
										46
									
								
								pkg/view/src/layouts/RootLayout.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								pkg/view/src/layouts/RootLayout.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | |||||||
|  | import Navbar from "./shared/Navbar.tsx"; | ||||||
|  | import { readProfiles, useUserinfo } from "../stores/userinfo.tsx"; | ||||||
|  | import { createEffect, createSignal, Show } from "solid-js"; | ||||||
|  | import { readWellKnown } from "../stores/wellKnown.tsx"; | ||||||
|  | import { BeforeLeaveEventArgs, useBeforeLeave, useLocation, useNavigate } from "@solidjs/router"; | ||||||
|  |  | ||||||
|  | export default function RootLayout(props: any) { | ||||||
|  |   const [ready, setReady] = createSignal(false); | ||||||
|  |  | ||||||
|  |   Promise.all([readWellKnown(), readProfiles()]).then(() => setReady(true)); | ||||||
|  |  | ||||||
|  |   const navigate = useNavigate(); | ||||||
|  |   const userinfo = useUserinfo(); | ||||||
|  |  | ||||||
|  |   const location = useLocation(); | ||||||
|  |  | ||||||
|  |   createEffect(() => { | ||||||
|  |     if (ready()) { | ||||||
|  |       keepGate(location.pathname); | ||||||
|  |     } | ||||||
|  |   }, [ready, userinfo]); | ||||||
|  |  | ||||||
|  |   function keepGate(path: string, e?: BeforeLeaveEventArgs) { | ||||||
|  |     const whitelist = ["/auth/login", "/auth/register", "/users/me/confirm"]; | ||||||
|  |  | ||||||
|  |     if (!userinfo?.isLoggedIn && !whitelist.includes(path)) { | ||||||
|  |       if (!e?.defaultPrevented) e?.preventDefault(); | ||||||
|  |       navigate(`/auth/login?redirect_uri=${path}`); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   useBeforeLeave((e: BeforeLeaveEventArgs) => keepGate(e.to.toString().split("?")[0], e)); | ||||||
|  |  | ||||||
|  |   return ( | ||||||
|  |     <Show when={ready()} fallback={ | ||||||
|  |       <div class="h-screen w-screen flex justify-center items-center"> | ||||||
|  |         <div> | ||||||
|  |           <span class="loading loading-lg loading-infinity"></span> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |     }> | ||||||
|  |       <Navbar /> | ||||||
|  |       <main class="h-[calc(100vh-68px)] mt-[68px]">{props.children}</main> | ||||||
|  |     </Show> | ||||||
|  |   ); | ||||||
|  | } | ||||||
							
								
								
									
										118
									
								
								pkg/view/src/layouts/shared/Navbar.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								pkg/view/src/layouts/shared/Navbar.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,118 @@ | |||||||
|  | import { For, Match, Show, Switch } from "solid-js"; | ||||||
|  | import { clearUserinfo, useUserinfo } from "../../stores/userinfo.tsx"; | ||||||
|  | import { useNavigate } from "@solidjs/router"; | ||||||
|  | import { useWellKnown } from "../../stores/wellKnown.tsx"; | ||||||
|  |  | ||||||
|  | interface MenuItem { | ||||||
|  |   label: string; | ||||||
|  |   href?: string; | ||||||
|  |   children?: MenuItem[]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export default function Navbar() { | ||||||
|  |   const nav: MenuItem[] = [ | ||||||
|  |     { | ||||||
|  |       label: "You", children: [ | ||||||
|  |         { label: "Dashboard", href: "/" }, | ||||||
|  |         { label: "Security", href: "/security" }, | ||||||
|  |         { label: "Personalise", href: "/personalise" } | ||||||
|  |       ] | ||||||
|  |     } | ||||||
|  |   ]; | ||||||
|  |  | ||||||
|  |   const wellKnown = useWellKnown(); | ||||||
|  |   const userinfo = useUserinfo(); | ||||||
|  |   const navigate = useNavigate(); | ||||||
|  |  | ||||||
|  |   function logout() { | ||||||
|  |     clearUserinfo(); | ||||||
|  |     navigate("/auth/login"); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return ( | ||||||
|  |     <div class="navbar bg-base-100 shadow-md px-5 z-10 fixed top-0"> | ||||||
|  |       <div class="navbar-start"> | ||||||
|  |         <div class="dropdown"> | ||||||
|  |           <div tabIndex={0} role="button" class="btn btn-ghost lg:hidden"> | ||||||
|  |             <svg | ||||||
|  |               xmlns="http://www.w3.org/2000/svg" | ||||||
|  |               class="h-5 w-5" | ||||||
|  |               fill="none" | ||||||
|  |               viewBox="0 0 24 24" | ||||||
|  |               stroke="currentColor" | ||||||
|  |             > | ||||||
|  |               <path | ||||||
|  |                 stroke-linecap="round" | ||||||
|  |                 stroke-linejoin="round" | ||||||
|  |                 stroke-width="2" | ||||||
|  |                 d="M4 6h16M4 12h8m-8 6h16" | ||||||
|  |               /> | ||||||
|  |             </svg> | ||||||
|  |           </div> | ||||||
|  |           <ul | ||||||
|  |             tabIndex={0} | ||||||
|  |             class="menu menu-sm dropdown-content mt-3 z-[1] p-2 shadow bg-base-100 rounded-box w-52" | ||||||
|  |           > | ||||||
|  |             <For each={nav}> | ||||||
|  |               {(item) => ( | ||||||
|  |                 <li> | ||||||
|  |                   <a href={item.href}>{item.label}</a> | ||||||
|  |                   <Show when={item.children}> | ||||||
|  |                     <ul class="p-2"> | ||||||
|  |                       <For each={item.children}> | ||||||
|  |                         {(item) => | ||||||
|  |                           <li> | ||||||
|  |                             <a href={item.href}>{item.label}</a> | ||||||
|  |                           </li> | ||||||
|  |                         } | ||||||
|  |                       </For> | ||||||
|  |                     </ul> | ||||||
|  |                   </Show> | ||||||
|  |                 </li> | ||||||
|  |               )} | ||||||
|  |             </For> | ||||||
|  |           </ul> | ||||||
|  |         </div> | ||||||
|  |         <a href="/" class="btn btn-ghost text-xl"> | ||||||
|  |           {wellKnown?.name ?? "Goatpass"} | ||||||
|  |         </a> | ||||||
|  |       </div> | ||||||
|  |       <div class="navbar-center hidden lg:flex"> | ||||||
|  |         <ul class="menu menu-horizontal px-1"> | ||||||
|  |           <For each={nav}> | ||||||
|  |             {(item) => ( | ||||||
|  |               <li> | ||||||
|  |                 <Show when={item.children} fallback={<a href={item.href}>{item.label}</a>}> | ||||||
|  |                   <details> | ||||||
|  |                     <summary> | ||||||
|  |                       <a href={item.href}>{item.label}</a> | ||||||
|  |                     </summary> | ||||||
|  |                     <ul class="p-2"> | ||||||
|  |                       <For each={item.children}> | ||||||
|  |                         {(item) => | ||||||
|  |                           <li> | ||||||
|  |                             <a href={item.href}>{item.label}</a> | ||||||
|  |                           </li> | ||||||
|  |                         } | ||||||
|  |                       </For> | ||||||
|  |                     </ul> | ||||||
|  |                   </details> | ||||||
|  |                 </Show> | ||||||
|  |               </li> | ||||||
|  |             )} | ||||||
|  |           </For> | ||||||
|  |         </ul> | ||||||
|  |       </div> | ||||||
|  |       <div class="navbar-end pe-5"> | ||||||
|  |         <Switch> | ||||||
|  |           <Match when={userinfo?.isLoggedIn}> | ||||||
|  |             <button type="button" class="btn btn-sm btn-ghost" onClick={() => logout()}>Logout</button> | ||||||
|  |           </Match> | ||||||
|  |           <Match when={!userinfo?.isLoggedIn}> | ||||||
|  |             <a href="/auth/login" class="btn btn-sm btn-primary">Login</a> | ||||||
|  |           </Match> | ||||||
|  |         </Switch> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  |   ); | ||||||
|  | } | ||||||
							
								
								
									
										108
									
								
								pkg/view/src/pages/dashboard.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								pkg/view/src/pages/dashboard.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,108 @@ | |||||||
|  | import { getAtk, readProfiles, useUserinfo } from "../stores/userinfo.tsx"; | ||||||
|  | import { createSignal, For, Show } from "solid-js"; | ||||||
|  |  | ||||||
|  | export default function DashboardPage() { | ||||||
|  |   const userinfo = useUserinfo(); | ||||||
|  |  | ||||||
|  |   const [error, setError] = createSignal<string | null>(null); | ||||||
|  |  | ||||||
|  |   function getGreeting() { | ||||||
|  |     const currentHour = new Date().getHours(); | ||||||
|  |  | ||||||
|  |     if (currentHour >= 0 && currentHour < 12) { | ||||||
|  |       return "Good morning! Wishing you a day filled with joy and success. ☀️"; | ||||||
|  |     } else if (currentHour >= 12 && currentHour < 18) { | ||||||
|  |       return "Afternoon! Hope you have a productive and joyful afternoon! ☀️"; | ||||||
|  |     } else { | ||||||
|  |       return "Good evening! Wishing you a relaxing and pleasant evening. 🌙"; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   async function readNotification(item: any) { | ||||||
|  |     const res = await fetch(`/api/notifications/${item.id}/read`, { | ||||||
|  |       method: "PUT", | ||||||
|  |       headers: { Authorization: `Bearer ${getAtk()}` } | ||||||
|  |     }); | ||||||
|  |     if (res.status !== 200) { | ||||||
|  |       setError(await res.text()); | ||||||
|  |     } else { | ||||||
|  |       await readProfiles(); | ||||||
|  |       setError(null); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return ( | ||||||
|  |     <div class="max-w-[720px] mx-auto px-5 pt-12"> | ||||||
|  |       <div id="greeting" class="px-5"> | ||||||
|  |         <h1 class="text-2xl font-bold">{userinfo?.displayName}</h1> | ||||||
|  |         <p>{getGreeting()}</p> | ||||||
|  |       </div> | ||||||
|  |  | ||||||
|  |       <div id="alerts"> | ||||||
|  |         <Show when={!userinfo?.meta?.confirmed_at}> | ||||||
|  |           <div role="alert" class="alert alert-warning mt-5"> | ||||||
|  |             <svg xmlns="http://www.w3.org/2000/svg" class="stroke-current shrink-0 h-6 w-6" fill="none" | ||||||
|  |                  viewBox="0 0 24 24"> | ||||||
|  |               <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" | ||||||
|  |                     d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" /> | ||||||
|  |             </svg> | ||||||
|  |             <div> | ||||||
|  |               <span>Your account isn't confirmed yet. Please check your inbox and confirm your account.</span> <br /> | ||||||
|  |               <span>Otherwise your account will be deactivate after 48 hours.</span> | ||||||
|  |             </div> | ||||||
|  |           </div> | ||||||
|  |         </Show> | ||||||
|  |         <Show when={error()}> | ||||||
|  |           <div role="alert" class="alert alert-error mt-5"> | ||||||
|  |             <svg xmlns="http://www.w3.org/2000/svg" class="stroke-current shrink-0 h-6 w-6" fill="none" | ||||||
|  |                  viewBox="0 0 24 24"> | ||||||
|  |               <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" | ||||||
|  |                     d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" /> | ||||||
|  |             </svg> | ||||||
|  |             <span class="capitalize">{error()}</span> | ||||||
|  |           </div> | ||||||
|  |         </Show> | ||||||
|  |       </div> | ||||||
|  |  | ||||||
|  |       <div class="card shadow-xl mt-5"> | ||||||
|  |         <div class="card-body"> | ||||||
|  |           <h2 class="card-title">Notifications</h2> | ||||||
|  |           <div class="bg-base-200 mt-3 mx-[-32px]"> | ||||||
|  |             <Show when={userinfo?.meta?.notifications?.length <= 0}> | ||||||
|  |               <table class="table"> | ||||||
|  |                 <tbody> | ||||||
|  |                 <tr> | ||||||
|  |                   <td class="px-[32px]">You're done! There are no notifications unread for you.</td> | ||||||
|  |                 </tr> | ||||||
|  |                 </tbody> | ||||||
|  |               </table> | ||||||
|  |             </Show> | ||||||
|  |             <Show when={userinfo?.meta?.notifications?.length > 0}> | ||||||
|  |               <table class="table"> | ||||||
|  |                 <tbody> | ||||||
|  |                 <For each={userinfo?.meta?.notifications}> | ||||||
|  |                   {item => | ||||||
|  |                     <tr> | ||||||
|  |                       <td class="px-[32px]"> | ||||||
|  |                         <h2 class="font-bold">{item.subject}</h2> | ||||||
|  |                         <p>{item.content}</p> | ||||||
|  |                         <div class="flex gap-2"> | ||||||
|  |                           <Show when={item.is_important}> | ||||||
|  |                             <span class="font-bold">Important</span> | ||||||
|  |                           </Show> | ||||||
|  |                           <a class="link" onClick={() => readNotification(item)}>Mark as read</a> | ||||||
|  |                         </div> | ||||||
|  |                       </td> | ||||||
|  |                     </tr> | ||||||
|  |                   } | ||||||
|  |                 </For> | ||||||
|  |                 </tbody> | ||||||
|  |               </table> | ||||||
|  |             </Show> | ||||||
|  |           </div> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |  | ||||||
|  |     </div> | ||||||
|  |   ); | ||||||
|  | } | ||||||
							
								
								
									
										95
									
								
								pkg/view/src/stores/userinfo.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								pkg/view/src/stores/userinfo.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,95 @@ | |||||||
|  | import Cookie from "universal-cookie"; | ||||||
|  | import { createContext, useContext } from "solid-js"; | ||||||
|  | import { createStore } from "solid-js/store"; | ||||||
|  |  | ||||||
|  | export interface Userinfo { | ||||||
|  |   isLoggedIn: boolean, | ||||||
|  |   displayName: string, | ||||||
|  |   profiles: any, | ||||||
|  |   meta: any | ||||||
|  | } | ||||||
|  |  | ||||||
|  | const UserinfoContext = createContext<Userinfo>(); | ||||||
|  |  | ||||||
|  | const defaultUserinfo: Userinfo = { | ||||||
|  |   isLoggedIn: false, | ||||||
|  |   displayName: "Citizen", | ||||||
|  |   profiles: null, | ||||||
|  |   meta: null | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | const [userinfo, setUserinfo] = createStore<Userinfo>(structuredClone(defaultUserinfo)); | ||||||
|  |  | ||||||
|  | export function getAtk(): string { | ||||||
|  |   return new Cookie().get("access_token"); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export async function refreshAtk() { | ||||||
|  |   const rtk = new Cookie().get("refresh_token"); | ||||||
|  |  | ||||||
|  |   const res = await fetch("/api/auth/token", { | ||||||
|  |     method: "POST", | ||||||
|  |     headers: { "Content-Type": "application/json" }, | ||||||
|  |     body: JSON.stringify({ | ||||||
|  |       refresh_token: rtk, | ||||||
|  |       grant_type: "refresh_token" | ||||||
|  |     }) | ||||||
|  |   }); | ||||||
|  |   if (res.status !== 200) { | ||||||
|  |     console.error(await res.text()) | ||||||
|  |   } else { | ||||||
|  |     const data = await res.json(); | ||||||
|  |     new Cookie().set("access_token", data["access_token"], { path: "/", maxAge: undefined }); | ||||||
|  |     new Cookie().set("refresh_token", data["refresh_token"], { path: "/", maxAge: undefined }); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function checkLoggedIn(): boolean { | ||||||
|  |   return new Cookie().get("access_token"); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export async function readProfiles(recovering = true) { | ||||||
|  |   if (!checkLoggedIn()) return; | ||||||
|  |  | ||||||
|  |   const res = await fetch("/api/users/me", { | ||||||
|  |     headers: { "Authorization": `Bearer ${getAtk()}` } | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   if (res.status !== 200) { | ||||||
|  |     if (recovering) { | ||||||
|  |       // Auto retry after refresh access token | ||||||
|  |       await refreshAtk(); | ||||||
|  |       return await readProfiles(false); | ||||||
|  |     } else { | ||||||
|  |       clearUserinfo(); | ||||||
|  |       window.location.reload(); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   const data = await res.json(); | ||||||
|  |  | ||||||
|  |   setUserinfo({ | ||||||
|  |     isLoggedIn: true, | ||||||
|  |     displayName: data["nick"], | ||||||
|  |     profiles: null, | ||||||
|  |     meta: data | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export function clearUserinfo() { | ||||||
|  |   new Cookie().remove("access_token", { path: "/", maxAge: undefined }); | ||||||
|  |   new Cookie().remove("refresh_token", { path: "/", maxAge: undefined }); | ||||||
|  |   setUserinfo(defaultUserinfo); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export function UserinfoProvider(props: any) { | ||||||
|  |   return ( | ||||||
|  |     <UserinfoContext.Provider value={userinfo}> | ||||||
|  |       {props.children} | ||||||
|  |     </UserinfoContext.Provider> | ||||||
|  |   ); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export function useUserinfo() { | ||||||
|  |   return useContext(UserinfoContext); | ||||||
|  | } | ||||||
							
								
								
									
										23
									
								
								pkg/view/src/stores/wellKnown.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								pkg/view/src/stores/wellKnown.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | |||||||
|  | import { createContext, useContext } from "solid-js"; | ||||||
|  | import { createStore } from "solid-js/store"; | ||||||
|  |  | ||||||
|  | const WellKnownContext = createContext<any>(); | ||||||
|  |  | ||||||
|  | const [wellKnown, setWellKnown] = createStore<any>(null); | ||||||
|  |  | ||||||
|  | export async function readWellKnown() { | ||||||
|  |   const res = await fetch("/.well-known") | ||||||
|  |   setWellKnown(await res.json()) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export function WellKnownProvider(props: any) { | ||||||
|  |   return ( | ||||||
|  |     <WellKnownContext.Provider value={wellKnown}> | ||||||
|  |       {props.children} | ||||||
|  |     </WellKnownContext.Provider> | ||||||
|  |   ); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export function useWellKnown() { | ||||||
|  |   return useContext(WellKnownContext); | ||||||
|  | } | ||||||
							
								
								
									
										1
									
								
								pkg/view/src/vite-env.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								pkg/view/src/vite-env.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | /// <reference types="vite/client" /> | ||||||
							
								
								
									
										44
									
								
								pkg/view/tailwind.config.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								pkg/view/tailwind.config.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,44 @@ | |||||||
|  | /** @type {import("tailwindcss").Config} */ | ||||||
|  | export default { | ||||||
|  |   content: [ | ||||||
|  |     "./src/**/*.{js,jsx,ts,tsx}" | ||||||
|  |   ], | ||||||
|  |   daisyui: { | ||||||
|  |     themes: [ | ||||||
|  |       { | ||||||
|  |         light: { | ||||||
|  |           ...require("daisyui/src/theming/themes")["light"], | ||||||
|  |           primary: "#4750a3", | ||||||
|  |           secondary: "#93c5fd", | ||||||
|  |           accent: "#0f766e", | ||||||
|  |           info: "#67e8f9", | ||||||
|  |           success: "#15803d", | ||||||
|  |           warning: "#f97316", | ||||||
|  |           error: "#dc2626", | ||||||
|  |           "--rounded-box": "0", | ||||||
|  |           "--rounded-btn": "0", | ||||||
|  |           "--rounded-badge": "0", | ||||||
|  |           "--tab-radius": "0" | ||||||
|  |         } | ||||||
|  |       }, | ||||||
|  |       { | ||||||
|  |         dark: { | ||||||
|  |           ...require("daisyui/src/theming/themes")["dark"], | ||||||
|  |           primary: "#4750a3", | ||||||
|  |           secondary: "#93c5fd", | ||||||
|  |           accent: "#0f766e", | ||||||
|  |           info: "#67e8f9", | ||||||
|  |           success: "#15803d", | ||||||
|  |           warning: "#f97316", | ||||||
|  |           error: "#dc2626", | ||||||
|  |           "--rounded-box": "0", | ||||||
|  |           "--rounded-btn": "0", | ||||||
|  |           "--rounded-badge": "0", | ||||||
|  |           "--tab-radius": "0" | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     ] | ||||||
|  |   }, | ||||||
|  |   plugins: [require("daisyui")] | ||||||
|  | }; | ||||||
|  |  | ||||||
							
								
								
									
										26
									
								
								pkg/view/tsconfig.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								pkg/view/tsconfig.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | |||||||
|  | { | ||||||
|  |   "compilerOptions": { | ||||||
|  |     "target": "ES2020", | ||||||
|  |     "useDefineForClassFields": true, | ||||||
|  |     "module": "ESNext", | ||||||
|  |     "lib": ["ES2020", "ES2015", "DOM", "DOM.Iterable"], | ||||||
|  |     "skipLibCheck": true, | ||||||
|  |  | ||||||
|  |     /* Bundler mode */ | ||||||
|  |     "moduleResolution": "bundler", | ||||||
|  |     "allowImportingTsExtensions": true, | ||||||
|  |     "resolveJsonModule": true, | ||||||
|  |     "isolatedModules": true, | ||||||
|  |     "noEmit": true, | ||||||
|  |     "jsx": "preserve", | ||||||
|  |     "jsxImportSource": "solid-js", | ||||||
|  |  | ||||||
|  |     /* Linting */ | ||||||
|  |     "strict": true, | ||||||
|  |     "noUnusedLocals": true, | ||||||
|  |     "noUnusedParameters": true, | ||||||
|  |     "noFallthroughCasesInSwitch": true | ||||||
|  |   }, | ||||||
|  |   "include": ["src"], | ||||||
|  |   "references": [{ "path": "./tsconfig.node.json" }] | ||||||
|  | } | ||||||
							
								
								
									
										10
									
								
								pkg/view/tsconfig.node.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								pkg/view/tsconfig.node.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | |||||||
|  | { | ||||||
|  |   "compilerOptions": { | ||||||
|  |     "composite": true, | ||||||
|  |     "skipLibCheck": true, | ||||||
|  |     "module": "ESNext", | ||||||
|  |     "moduleResolution": "bundler", | ||||||
|  |     "allowSyntheticDefaultImports": true | ||||||
|  |   }, | ||||||
|  |   "include": ["vite.config.ts"] | ||||||
|  | } | ||||||
							
								
								
									
										13
									
								
								pkg/view/vite.config.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								pkg/view/vite.config.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | |||||||
|  | import { defineConfig } from "vite"; | ||||||
|  | import solid from "vite-plugin-solid"; | ||||||
|  | import devtools from "solid-devtools/vite"; | ||||||
|  |  | ||||||
|  | export default defineConfig({ | ||||||
|  |   plugins: [devtools({ autoname: true }), solid()], | ||||||
|  |   server: { | ||||||
|  |     proxy: { | ||||||
|  |       "/api": "http://localhost:8444", | ||||||
|  |       "/.well-known": "http://localhost:8444" | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | }); | ||||||
							
								
								
									
										28
									
								
								settings.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								settings.toml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | |||||||
|  | debug = true | ||||||
|  |  | ||||||
|  | name = "Goatplaza" | ||||||
|  | maintainer = "SmartSheep Studio" | ||||||
|  |  | ||||||
|  | bind = "0.0.0.0:8445" | ||||||
|  | domain = "feed.smartsheep.studio" | ||||||
|  | secret = "LtTjzAGFLshwXhN4ZD4nG5KlMv1MWcsvfv03TSZYnT1VhiAnLIZFTnHUwR0XhGgi" | ||||||
|  |  | ||||||
|  | content = "uploads" | ||||||
|  |  | ||||||
|  | everyone_postable = false | ||||||
|  |  | ||||||
|  | [passport] | ||||||
|  | client_id = "goatplaza" | ||||||
|  | client_secret = "Z9k9AFTj^p" | ||||||
|  | endpoint = "https://id.smartsheep.studio" | ||||||
|  |  | ||||||
|  | [mailer] | ||||||
|  | name = "Alphabot <alphabot@smartsheep.studio>" | ||||||
|  | smtp_host = "smtp.exmail.qq.com" | ||||||
|  | smtp_port = 465 | ||||||
|  | username = "alphabot@smartsheep.studio" | ||||||
|  | password = "gz937Zxxzfcd9SeH" | ||||||
|  |  | ||||||
|  | [database] | ||||||
|  | dsn = "host=localhost dbname=hy_interactive port=5432 sslmode=disable" | ||||||
|  | prefix = "interactive_" | ||||||
		Reference in New Issue
	
	Block a user