We can interface with a db now.
This commit is contained in:
parent
719832b790
commit
ccb06f2184
16 changed files with 126 additions and 57 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1,6 +1,9 @@
|
|||
# Ignore config, don't commit credentials.
|
||||
**/config.toml
|
||||
|
||||
# Ignore db/, don't commit the database.
|
||||
*.db
|
||||
|
||||
# Ignore node_modules.
|
||||
**/node_modules/*
|
||||
|
||||
|
|
0
db/.gitkeep
Normal file
0
db/.gitkeep
Normal file
|
@ -8,6 +8,7 @@ import (
|
|||
"os"
|
||||
"strconv"
|
||||
|
||||
// "example.com/lib/dbcommands"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/pelletier/go-toml/v2"
|
||||
"github.com/ravener/discord-oauth2"
|
||||
|
@ -24,7 +25,6 @@ type appConfig struct {
|
|||
}
|
||||
|
||||
type discordUser struct {
|
||||
LoggedIn bool
|
||||
Id string
|
||||
Username string
|
||||
Avatar string
|
||||
|
@ -51,7 +51,6 @@ type discordUser struct {
|
|||
var config appConfig
|
||||
var oauthConfig *oauth2.Config
|
||||
var oauthToken *oauth2.Token
|
||||
var userInfo discordUser
|
||||
|
||||
func parseConfig(configPath string) {
|
||||
configFile, err := os.Open(configPath)
|
||||
|
@ -80,7 +79,7 @@ func createDiscordOAuthConfig(clientId string, clientSecret string, redirectUrl
|
|||
}
|
||||
|
||||
func loginDisplay(context *gin.Context) {
|
||||
if userInfo.LoggedIn {
|
||||
if oauthToken.Valid() {
|
||||
context.Redirect(http.StatusTemporaryRedirect, "/dashboard")
|
||||
} else {
|
||||
context.HTML(http.StatusOK, "root.html", gin.H{
|
||||
|
@ -104,6 +103,10 @@ func authCallback(context *gin.Context) {
|
|||
"message": "internalservererror",
|
||||
})
|
||||
}
|
||||
context.Redirect(http.StatusTemporaryRedirect, "/dashboard")
|
||||
}
|
||||
|
||||
func getDiscordUserInfo(context *gin.Context) discordUser {
|
||||
response, err := oauthConfig.Client(context.Request.Context(), oauthToken).Get("https://discord.com/api/users/@me")
|
||||
if err != nil || response.StatusCode != 200 {
|
||||
responseMessage := ""
|
||||
|
@ -125,13 +128,12 @@ func authCallback(context *gin.Context) {
|
|||
}
|
||||
var discordUserInfo discordUser
|
||||
json.Unmarshal(body, &discordUserInfo)
|
||||
discordUserInfo.LoggedIn = true
|
||||
userInfo = discordUserInfo
|
||||
context.Redirect(http.StatusTemporaryRedirect, "/dashboard")
|
||||
return discordUserInfo
|
||||
}
|
||||
|
||||
func dashboardDisplay(context *gin.Context) {
|
||||
if userInfo.LoggedIn {
|
||||
if oauthToken.Valid() {
|
||||
userInfo := getDiscordUserInfo(context)
|
||||
context.HTML(http.StatusOK, "dashboard.html", gin.H{
|
||||
"discordUser": gin.H{
|
||||
"id": userInfo.Id,
|
||||
|
@ -163,6 +165,7 @@ func dashboardDisplay(context *gin.Context) {
|
|||
}
|
||||
|
||||
func main() {
|
||||
// db := dbcommands.InitializeDatabase()
|
||||
parseConfig("config.toml")
|
||||
oauthConfig = createDiscordOAuthConfig(config.OAuth.ClientID, config.OAuth.ClientSecret, config.OAuth.RedirectUrl)
|
||||
app := gin.Default()
|
||||
|
@ -172,5 +175,5 @@ func main() {
|
|||
app.GET("/login", loginRedirect)
|
||||
app.GET("/auth/callback", authCallback)
|
||||
app.GET("/dashboard", dashboardDisplay)
|
||||
app.Run()
|
||||
app.Run(":8080")
|
||||
}
|
9
go.mod
9
go.mod
|
@ -2,7 +2,10 @@ module gin-cpularp
|
|||
|
||||
go 1.24.2
|
||||
|
||||
replace example.com/lib/dbcommands => ./lib/dbcommands
|
||||
|
||||
require (
|
||||
example.com/lib/dbcommands v0.0.0
|
||||
github.com/bytedance/sonic v1.13.2 // indirect
|
||||
github.com/bytedance/sonic/loader v0.2.4 // indirect
|
||||
github.com/cloudwego/base64x v0.1.5 // indirect
|
||||
|
@ -14,10 +17,13 @@ require (
|
|||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.26.0 // indirect
|
||||
github.com/goccy/go-json v0.10.5 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.22 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
|
@ -33,4 +39,7 @@ require (
|
|||
golang.org/x/text v0.24.0 // indirect
|
||||
google.golang.org/protobuf v1.36.6 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
gorm.io/driver/sqlite v1.5.7 // indirect
|
||||
gorm.io/gorm v1.25.12 // indirect
|
||||
)
|
||||
|
||||
|
|
10
go.sum
10
go.sum
|
@ -24,6 +24,10 @@ github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAu
|
|||
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
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/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/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
|
@ -34,6 +38,8 @@ github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
|||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
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=
|
||||
|
@ -77,5 +83,9 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
|
|||
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/driver/sqlite v1.5.7 h1:8NvsrhP0ifM7LX9G4zPB97NwovUakUxc+2V2uuf3Z1I=
|
||||
gorm.io/driver/sqlite v1.5.7/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4=
|
||||
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
|
||||
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
|
||||
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||
|
|
24
lib/dbcommands/dbcommands.go
Normal file
24
lib/dbcommands/dbcommands.go
Normal file
|
@ -0,0 +1,24 @@
|
|||
package dbcommands
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
gorm.Model
|
||||
Name string
|
||||
Id string
|
||||
LoginToken string
|
||||
}
|
||||
|
||||
func InitializeDatabase() *gorm.DB {
|
||||
db, err := gorm.Open(sqlite.Open("db/main.db"), &gorm.Config{})
|
||||
if err != nil {
|
||||
log.Fatal("Failed to connect to database.")
|
||||
}
|
||||
db.AutoMigrate(&User{})
|
||||
return db
|
||||
}
|
12
lib/dbcommands/go.mod
Normal file
12
lib/dbcommands/go.mod
Normal file
|
@ -0,0 +1,12 @@
|
|||
module example.com/lib/dbc
|
||||
|
||||
go 1.24.2
|
||||
|
||||
require (
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.22 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
gorm.io/driver/sqlite v1.5.7 // indirect
|
||||
gorm.io/gorm v1.25.12 // indirect
|
||||
)
|
14
lib/dbcommands/go.sum
Normal file
14
lib/dbcommands/go.sum
Normal file
|
@ -0,0 +1,14 @@
|
|||
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/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
gorm.io/driver/sqlite v1.5.7 h1:8NvsrhP0ifM7LX9G4zPB97NwovUakUxc+2V2uuf3Z1I=
|
||||
gorm.io/driver/sqlite v1.5.7/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4=
|
||||
gorm.io/gorm v1.25.7-0.20240204074919-46816ad31dde h1:9DShaph9qhkIYw7QF91I/ynrr4cOO2PZra2PFD7Mfeg=
|
||||
gorm.io/gorm v1.25.7-0.20240204074919-46816ad31dde/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
|
||||
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
|
|
@ -1 +1,6 @@
|
|||
@import "tailwindcss" source("../templates");
|
||||
@theme {
|
||||
--color-primary: oklch(0.1 0 0);
|
||||
--color-secondary: oklch(0.85 0.1733 93.56);
|
||||
--color-accent: oklch(0.6 0.1556 246.71);
|
||||
}
|
|
@ -13,7 +13,7 @@
|
|||
<body>
|
||||
<div class="bg-gray-200">
|
||||
<div class="min-h-screen">
|
||||
<div class="container space-y-4 pt-4 pb-4 max-w-3xl">
|
||||
<div class="container max-w-3xl pt-4 pb-4 space-y-4">
|
||||
<h1 class="text-left">Oops!</h1>
|
||||
<p class="text-lg">There was some problem with the authentication process...</p>
|
||||
<p>{{ .message }}</p>
|
||||
|
|
|
@ -4,23 +4,29 @@
|
|||
<head>
|
||||
<title>Dashboard</title>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="description" content="User dashboard.">
|
||||
<meta name="keywords" content="HTML, CSS, JavaScript">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="/public/css/style.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="bg-gray-950">
|
||||
<div class="min-h-screen p-4">
|
||||
<div class="container space-y-4 p-4 max-w-3xl ring-4 ring-cyan-200 bg-gray-950 mx-auto">
|
||||
<div class="text-left text-lg pb-1 font-mono ring-4 ring-cyan-400 bg-gray-800 text-white px-4">
|
||||
<h1>> USER AUTHENTICATED</h1>
|
||||
<div class="bg-black">
|
||||
<div class="min-h-screen p-2 md:p-4">
|
||||
<div
|
||||
class="container max-w-3xl p-4 mx-auto space-y-2 font-mono text-white bg-primary min-w-xs md:space-y-4">
|
||||
<div class="p-2 bg-secondary md:p-4 ring-2 ring-secondary/80">
|
||||
<div class="bg-primary ring-2 ring-primary/80">
|
||||
<div class="flex flex-col w-full h-full p-2 space-x-4 rounded-sm space-2 drop-shadow-md drop-shadow-accent">
|
||||
<h1 class="text-md md:text-xl">> USER AUTHENTICATED</h1>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-2 bg-secondary md:p-4 ring-2 ring-secondary/80">
|
||||
{{ template "userinfo.html" .discordUser }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
0
src/templates/infoblock.html
Normal file
0
src/templates/infoblock.html
Normal file
|
@ -13,7 +13,7 @@
|
|||
<body>
|
||||
<div class="bg-gray-200">
|
||||
<div class="min-h-screen">
|
||||
<div class="container space-y-4 pt-4 pb-4 max-w-3xl">
|
||||
<div class="container max-w-3xl pt-4 pb-4 space-y-4">
|
||||
<h1 class="text-left">Oops!</h1>
|
||||
<p class="text-lg">We experienced some kind of server error...</p>
|
||||
<p>{{ .message }}</p>
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
<head>
|
||||
<title>Hello!</title>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="description" content="Root page.">
|
||||
<meta name="keywords" content="HTML, CSS, JavaScript">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="/public/css/style.css" />
|
||||
|
@ -13,14 +12,14 @@
|
|||
<body>
|
||||
<div class="bg-gray-200">
|
||||
<div class="min-h-screen px-4">
|
||||
<div class="container space-y-4 pt-4 pb-4 max-w-3xl m-auto w-full">
|
||||
<h1 class="text-center text-3xl">Welcome!</h1>
|
||||
<div class="container w-full max-w-3xl pt-4 pb-4 m-auto space-y-4">
|
||||
<h1 class="text-3xl text-center">Welcome!</h1>
|
||||
<div class="border-2 border-gray-900 border-solid bg-[#eeea] p-4">
|
||||
<div class="flex flex-col text-lg text-left align-middle m-auto">
|
||||
<div class="flex flex-col m-auto text-lg text-left align-middle">
|
||||
<div class="flex flex-col space-y-2">
|
||||
<p>There really isn't much here yet, but you can log in with Discord.</p>
|
||||
<a class="ring-2 ring-black bg-blue-600 text-lg" href="/login">
|
||||
<div class="m-auto align-middle text-center text-lg text-white">
|
||||
<a class="text-lg bg-blue-600 ring-2 ring-black" href="/login">
|
||||
<div class="m-auto text-lg text-center text-white align-middle">
|
||||
Login to Discord
|
||||
</div>
|
||||
</a>
|
||||
|
|
|
@ -1,35 +1,19 @@
|
|||
<div class="flex flex-col ring-4 ring-cyan-400 p-4 space-y-2 text-white font-mono"
|
||||
style="background-color: {{ .banner_color }};">
|
||||
<p>> ID: {{ .id }}</p>
|
||||
<p>> USERNAME: {{ .username }}</p>
|
||||
<p>> DISPLAY NAME: {{ .global_name }}</p>
|
||||
<p>> AVATAR</p>
|
||||
<div class="ring-2 ring-cyan-600 p-2 bg-gray-700 max-w-fit">
|
||||
<img src="https://cdn.discordapp.com/avatars/{{ .id }}/{{ .avatar }}.png" />
|
||||
<div class="bg-primary ring-2 ring-primary/80">
|
||||
<div class="flex flex-row w-full h-full p-4 space-x-4 rounded-sm">
|
||||
<div class="md:min-w-fit md:min-h-fit md:max-w-fit md:max-h-fit max-w-32 max-h-32">
|
||||
<img class="fixed z-1 max-w-[96px] max-h-[96px] md:max-w-[128px] md:max-h-[128px]"
|
||||
src="https://cdn.discordapp.com/avatar-decoration-presets/{{ .avatar_decoration_data.asset }}.png" />
|
||||
<img class="max-w-[96px] max-h-[96px] mask-clip-border rounded-full md:max-w-[128px] md:max-h-[128px] drop-shadow-accent drop-shadow-md border-8 border-accent"
|
||||
src="https://cdn.discordapp.com/avatars/{{ .id }}/{{ .avatar }}.png" />
|
||||
</div>
|
||||
<p>> BANNER</p>
|
||||
<div class="ring-2 ring-cyan-600 p-2 bg-gray-700 max-w-fit">
|
||||
<img src="https://cdn.discordapp.com/banners/{{ .id }}/{{ .banner }}.png?size=512" />
|
||||
</div>
|
||||
<p>discriminator: {{ .discriminator }}</p>
|
||||
<p>public_flags: {{ .public_flags }}</p>
|
||||
<p>flags: {{ .flags }}</p>
|
||||
<p>accent_color: {{ .accent_color }}</p>
|
||||
<details class="p-2 ring-2 ring-cyan-400 bg-gray-700 space-y-2">
|
||||
<summary>avatar_decoration_data</summary>
|
||||
<div class="text-lg ring-2 ring-cyan-400 p-2 space-y-2">
|
||||
<div class="flex flex-col space-y-2">
|
||||
<p>asset: {{ .avatar_decoration_data.asset }}</p>
|
||||
<p>sku_id: {{ .avatar_decoration_data.sku_id }}</p>
|
||||
<p>expires_at: {{ .avatar_decoration_data.expires_at }}</p>
|
||||
<div class="w-full m-auto drop-shadow-md drop-shadow-accent text-accent">
|
||||
<p class="text-xl md:text-3xl">
|
||||
{{ .global_name }}
|
||||
</p>
|
||||
<a class="ml-2 text-white text-md md:text-xl hover:text-gray-300" href="https://discord.com/users/{{ .id }}"
|
||||
target="_blank">
|
||||
@{{ .username }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
<p>collectibles: {{ .collectibles }}</p>
|
||||
<p>banner_color: {{ .banner_color }}</p>
|
||||
<p>clan: {{ .clan }}</p>
|
||||
<p>primary_guild: {{ .primary_guild }}</p>
|
||||
<p>mfa_enabled: {{ .mfa_enabled }}</p>
|
||||
<p>locale: {{ .locale }}</p>
|
||||
<p>premium_type: {{ .premium_type }}</p>
|
||||
</div>
|
0
tailwind.config.js
Normal file
0
tailwind.config.js
Normal file
Loading…
Reference in a new issue