mv(all) moving repository

main
itsmrval 2024-07-10 16:10:20 +02:00
parent d31a940032
commit 77a6414fee
7 changed files with 650 additions and 0 deletions

4
.gitignore vendored
View File

@ -8,6 +8,10 @@
*.so
*.dylib
database.db
.env
# Test binary, built with `go test -c`
*.test

43
go.mod Normal file
View File

@ -0,0 +1,43 @@
module itsmrval/lksNinja
go 1.22.5
require (
github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff // indirect
github.com/bytedance/sonic v1.11.9 // indirect
github.com/bytedance/sonic/loader v0.1.1 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.4 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/gin-gonic/contrib v0.0.0-20240508051311-c1c6bf0061b0 // indirect
github.com/gin-gonic/gin v1.10.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.22.0 // indirect
github.com/goccy/go-json v0.10.3 // indirect
github.com/gomodule/redigo v2.0.0+incompatible // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/context v1.1.2 // indirect
github.com/gorilla/securecookie v1.1.2 // indirect
github.com/gorilla/sessions v1.3.0 // indirect
github.com/joho/godotenv v1.5.1 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.8 // 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.2 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
golang.org/x/arch v0.8.0 // indirect
golang.org/x/crypto v0.25.0 // indirect
golang.org/x/net v0.27.0 // indirect
golang.org/x/oauth2 v0.21.0 // indirect
golang.org/x/sys v0.22.0 // indirect
golang.org/x/text v0.16.0 // indirect
google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

102
go.sum Normal file
View File

@ -0,0 +1,102 @@
github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff h1:RmdPFa+slIr4SCBg4st/l/vZWVe9QJKMXGO60Bxbe04=
github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff/go.mod h1:+RTT1BOk5P97fT2CiHkbFQwkK3mjsFAP6zCYV2aXtjw=
github.com/bytedance/sonic v1.11.9 h1:LFHENlIY/SLzDWverzdOvgMztTxcfcF+cqNsz9pK5zg=
github.com/bytedance/sonic v1.11.9/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gabriel-vasile/mimetype v1.4.4 h1:QjV6pZ7/XZ7ryI2KuyeEDE8wnh7fHP9YnQy+R0LnH8I=
github.com/gabriel-vasile/mimetype v1.4.4/go.mod h1:JwLei5XPtWdGiMFB5Pjle1oEeoSeEuJfJE+TtfvdB/s=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/contrib v0.0.0-20240508051311-c1c6bf0061b0 h1:EUFmvQ8ffefnSAmaUZd9HZYZSw9w/bFjp3FiNaJ5WmE=
github.com/gin-gonic/contrib v0.0.0-20240508051311-c1c6bf0061b0/go.mod h1:iqneQ2Df3omzIVTkIfn7c1acsVnMGiSLn4XF5Blh3Yg=
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
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.22.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4Bx7ia+JlgcnOao=
github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0=
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/context v1.1.2 h1:WRkNAv2uoa03QNIc1A6u4O7DAGMUVoopZhkiXWA2V1o=
github.com/gorilla/context v1.1.2/go.mod h1:KDPwT9i/MeWHiLl90fuTgrt4/wPcv75vFAZLaOOcbxM=
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
github.com/gorilla/sessions v1.1.1/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w=
github.com/gorilla/sessions v1.3.0 h1:XYlkq7KcpOB2ZhHBPv5WpjMIxrQosiZanfoy1HLZFzg=
github.com/gorilla/sessions v1.3.0/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM=
github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
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=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.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.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs=
golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=

299
index.go Normal file
View File

@ -0,0 +1,299 @@
package main
import (
"database/sql"
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"github.com/joho/godotenv"
_ "github.com/mattn/go-sqlite3"
"golang.org/x/oauth2"
"golang.org/x/oauth2/github"
)
type Link struct {
ID string
OriginalURL string
ShortURL string
UserID string
}
type App struct {
DB *sql.DB
GithubOAuthConfig *oauth2.Config
Router *gin.Engine
}
func NewApp() (*App, error) {
if err := godotenv.Load(); err != nil {
log.Println("No .env file found")
}
db, err := initDB()
if err != nil {
return nil, fmt.Errorf("failed to initialize database: %w", err)
}
oauthConfig := initGithubOAuth()
router := gin.Default()
return &App{
DB: db,
GithubOAuthConfig: oauthConfig,
Router: router,
}, nil
}
func initDB() (*sql.DB, error) {
dbPath := os.Getenv("DB_PATH")
if dbPath == "" {
return nil, fmt.Errorf("DB_PATH not set in environment")
}
db, err := sql.Open("sqlite3", dbPath)
if err != nil {
return nil, fmt.Errorf("failed to open database: %w", err)
}
if err = db.Ping(); err != nil {
return nil, fmt.Errorf("failed to ping database: %w", err)
}
return db, nil
}
func initGithubOAuth() *oauth2.Config {
return &oauth2.Config{
ClientID: os.Getenv("GITHUB_CLIENT_ID"),
ClientSecret: os.Getenv("GITHUB_CLIENT_SECRET"),
RedirectURL: os.Getenv("GITHUB_REDIRECT_URL"),
Scopes: []string{"user:email"},
Endpoint: github.Endpoint,
}
}
func (app *App) SetupRoutes() {
app.Router.LoadHTMLGlob("templates/*")
app.Router.Static("/static", "./static")
app.Router.GET("/", app.homePage)
app.Router.POST("/", app.shortenURL)
app.Router.GET("/login", app.loginGithub)
app.Router.GET("/callback", app.githubCallback)
app.Router.GET("/logout", app.logout)
app.Router.GET("/dashboard", app.myLinks)
app.Router.DELETE("/dashboard", app.deleteLink)
app.Router.PUT("/dashboard", app.updateLink)
app.Router.GET("/:shortURL", app.redirectToOriginal)
}
func (app *App) Run() error {
port := os.Getenv("PORT")
return app.Router.Run(":" + port)
}
func (app *App) createTable() error {
_, err := app.DB.Exec(`CREATE TABLE IF NOT EXISTS links (
id TEXT PRIMARY KEY,
original_url TEXT NOT NULL,
short_url TEXT NOT NULL,
user_id TEXT
)`)
return err
}
func (app *App) logout(c *gin.Context) {
c.SetCookie("user_id", "", -1, "/", "", false, true)
c.Redirect(http.StatusFound, "/")
}
func (app *App) homePage(c *gin.Context) {
c.HTML(http.StatusOK, "index.html", gin.H{
"loggedIn": isLoggedIn(c),
})
}
func (app *App) shortenURL(c *gin.Context) {
originalURL := c.PostForm("url")
customName := c.PostForm("custom_name")
shortURL := customName
if shortURL == "" {
shortURL = uuid.New().String()[:8]
}
userID := getUserID(c)
id := uuid.New().String()
_, err := app.DB.Exec("INSERT INTO links (id, original_url, short_url, user_id) VALUES (?, ?, ?, ?)",
id, originalURL, shortURL, userID)
if err != nil {
log.Printf("Error inserting link: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"shortURL": shortURL})
}
func (app *App) loginGithub(c *gin.Context) {
url := app.GithubOAuthConfig.AuthCodeURL("state")
c.Redirect(http.StatusFound, url)
}
func (app *App) githubCallback(c *gin.Context) {
code := c.Query("code")
token, err := app.GithubOAuthConfig.Exchange(c, code)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to exchange token"})
return
}
client := app.GithubOAuthConfig.Client(c, token)
resp, err := client.Get("https://api.github.com/user")
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get user info"})
return
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to read response body"})
return
}
var githubUser struct {
ID int64 `json:"id"`
}
if err := json.Unmarshal(body, &githubUser); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to parse user info"})
return
}
userID := fmt.Sprintf("%d", githubUser.ID)
c.SetCookie("user_id", userID, 3600, "/", "", false, true)
c.Redirect(http.StatusFound, "/")
}
func (app *App) myLinks(c *gin.Context) {
userID := getUserID(c)
if userID == "" {
c.Redirect(http.StatusFound, "/")
return
}
rows, err := app.DB.Query("SELECT id, original_url, short_url FROM links WHERE user_id = ?", userID)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "error": err.Error()})
return
}
defer rows.Close()
var links []Link
for rows.Next() {
var link Link
err := rows.Scan(&link.ID, &link.OriginalURL, &link.ShortURL)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "error": err.Error()})
return
}
links = append(links, link)
}
c.HTML(http.StatusOK, "dashboard.html", gin.H{"links": links})
}
func (app *App) deleteLink(c *gin.Context) {
var payload struct {
ID string `json:"id"`
}
if err := c.BindJSON(&payload); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request payload"})
return
}
userID := getUserID(c)
_, err := app.DB.Exec("DELETE FROM links WHERE id = ? AND user_id = ?", payload.ID, userID)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"success": true, "id": payload.ID})
}
func (app *App) updateLink(c *gin.Context) {
var payload struct {
ID string `json:"id"`
NewName string `json:"new_name"`
}
if err := c.BindJSON(&payload); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request payload"})
return
}
userID := getUserID(c)
_, err := app.DB.Exec("UPDATE links SET short_url = ? WHERE id = ? AND user_id = ?", payload.NewName, payload.ID, userID)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"success": true})
}
func (app *App) redirectToOriginal(c *gin.Context) {
shortURL := c.Param("shortURL")
var originalURL string
err := app.DB.QueryRow("SELECT original_url FROM links WHERE short_url = ?", shortURL).Scan(&originalURL)
if err != nil {
if err == sql.ErrNoRows {
c.String(http.StatusNotFound, "Short URL not found")
} else {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
}
return
}
c.Redirect(http.StatusFound, originalURL)
}
func isLoggedIn(c *gin.Context) bool {
_, err := c.Cookie("user_id")
return err == nil
}
func getUserID(c *gin.Context) string {
userID, _ := c.Cookie("user_id")
return userID
}
func main() {
app, err := NewApp()
if err != nil {
log.Fatalf("Failed to initialize app: %v", err)
}
if err := app.createTable(); err != nil {
log.Fatalf("Failed to create table: %v", err)
}
app.SetupRoutes()
if err := app.Run(); err != nil {
log.Fatalf("Failed to run app: %v", err)
}
}

40
static/img/hero.svg Normal file
View File

@ -0,0 +1,40 @@
<svg width="1572" height="795" viewBox="0 0 1572 795" fill="none" xmlns="http://www.w3.org/2000/svg">
<mask id="mask0_340_660" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="1572" height="795">
<rect width="1572" height="795" fill="#D9D9D9"/>
</mask>
<g mask="url(#mask0_340_660)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M862.574 -54.9121V0.955067L808.953 0.955063L808.953 1.95506L862.574 1.95507V74.6094H808.953L808.953 75.6094H862.574V148.264H808.953L808.953 149.264H862.574V221.918H808.953L808.953 222.918H862.574V295.572H808.953L808.953 296.572H862.574V369.227H808.953L808.953 370.227H862.574V442.881H808.953L808.953 443.881H862.574V516.535H808.953L808.953 517.535H862.574V573.404H863.574V517.535H936.23V573.404H937.23V517.535H1009.88V573.404H1010.88V517.535H1083.53V573.404H1084.53V517.535L1157.19 517.535V573.404H1158.19V517.535H1230.85V573.404H1231.85V517.535H1304.5V573.404H1305.5V517.535H1378.15V573.404H1379.15V517.535H1437.27V516.535H1379.15V443.881H1437.27V442.881H1379.15V370.227H1437.27V369.227H1379.15V296.572H1437.27V295.572H1379.15V222.918H1437.27V221.918H1379.15V149.264H1437.27V148.264H1379.15V75.6094H1437.27V74.6094H1379.15V1.95509L1437.27 1.95509V0.95509L1379.15 0.955086V-54.9121H1378.15V0.955086H1305.5V-54.9121H1304.5V0.955086L1231.85 0.955082V-54.9121H1230.85V0.955082L1158.19 0.955078V-54.9121H1157.19V0.955078L1084.53 0.955074V-54.9121H1083.53V0.955074L1010.88 0.95507V-54.9121H1009.88V0.95507L937.23 0.955067V-54.9121H936.23V0.955067H863.574V-54.9121H862.574ZM1378.15 516.535V443.881H1305.5V516.535H1378.15ZM1304.5 516.535V443.881H1231.85V516.535H1304.5ZM1230.85 516.535V443.881H1158.19V516.535H1230.85ZM1157.19 516.535V443.881H1084.53V516.535L1157.19 516.535ZM1083.53 516.535V443.881H1010.88V516.535H1083.53ZM1009.88 516.535V443.881H937.23V516.535H1009.88ZM936.23 516.535V443.881H863.574V516.535H936.23ZM936.23 442.881H863.574V370.227H936.23V442.881ZM1009.88 442.881H937.23V370.227H1009.88V442.881ZM1083.53 442.881H1010.88V370.227H1083.53V442.881ZM1157.19 442.881H1084.53V370.227H1157.19V442.881ZM1230.85 442.881H1158.19V370.227H1230.85V442.881ZM1304.5 442.881H1231.85V370.227H1304.5V442.881ZM1378.15 442.881H1305.5V370.227H1378.15V442.881ZM1378.15 369.227V296.572H1305.5V369.227H1378.15ZM1304.5 369.227V296.572H1231.85V369.227H1304.5ZM1230.85 369.227V296.572H1158.19V369.227H1230.85ZM1157.19 369.227V296.572H1084.53V369.227H1157.19ZM1083.53 369.227V296.572H1010.88V369.227H1083.53ZM1009.88 369.227V296.572H937.23V369.227H1009.88ZM936.23 369.227V296.572H863.574V369.227H936.23ZM936.23 295.572H863.574V222.918H936.23V295.572ZM1009.88 295.572H937.23V222.918H1009.88V295.572ZM1083.53 295.572H1010.88V222.918H1083.53V295.572ZM1157.19 295.572H1084.53V222.918H1157.19V295.572ZM1230.85 295.572H1158.19V222.918H1230.85V295.572ZM1304.5 295.572H1231.85V222.918H1304.5V295.572ZM1378.15 295.572H1305.5V222.918H1378.15V295.572ZM1378.15 221.918V149.264H1305.5V221.918H1378.15ZM1304.5 221.918V149.264L1231.85 149.264V221.918H1304.5ZM1230.85 221.918V149.264H1158.19V221.918H1230.85ZM1157.19 221.918V149.264H1084.53V221.918H1157.19ZM1083.53 221.918V149.264H1010.88V221.918H1083.53ZM1009.88 221.918V149.264L937.23 149.264V221.918H1009.88ZM936.23 221.918V149.264H863.574V221.918H936.23ZM936.23 148.264H863.574V75.6094H936.23V148.264ZM1009.88 148.264L937.23 148.264V75.6094L1009.88 75.6094V148.264ZM1083.53 148.264H1010.88V75.6094H1083.53V148.264ZM1157.19 148.264H1084.53V75.6094H1157.19V148.264ZM1230.85 148.264H1158.19V75.6094H1230.85V148.264ZM1304.5 148.264L1231.85 148.264V75.6094L1304.5 75.6094V148.264ZM1378.15 148.264H1305.5V75.6094H1378.15V148.264ZM1378.15 74.6094V1.95509H1305.5V74.6094H1378.15ZM1304.5 74.6094V1.95509L1231.85 1.95508V74.6094L1304.5 74.6094ZM1230.85 74.6094V1.95508L1158.19 1.95508V74.6094H1230.85ZM1157.19 74.6094V1.95508L1084.53 1.95507V74.6094H1157.19ZM1083.53 74.6094V1.95507L1010.88 1.95507V74.6094H1083.53ZM1009.88 74.6094V1.95507L937.23 1.95507V74.6094L1009.88 74.6094ZM936.23 74.6094V1.95507H863.574V74.6094H936.23Z" fill="url(#paint0_radial_340_660)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M492.574 251.088V306.955L438.953 306.955L438.953 307.955L492.574 307.955V380.609H438.953L438.953 381.609H492.574V454.264H438.953L438.953 455.264H492.574V527.918H438.953L438.953 528.918H492.574V601.572H438.953L438.953 602.572H492.574V675.227H438.953L438.953 676.227H492.574V748.881H438.953L438.953 749.881H492.574V822.535H438.953L438.953 823.535H492.574V879.404H493.574V823.535H566.23V879.404H567.23V823.535H639.878V879.404H640.878V823.535H713.535V879.404H714.535V823.535L787.191 823.535V879.404H788.191V823.535H860.847V879.404H861.847V823.535H934.496V879.404H935.496V823.535H1008.15V879.404H1009.15V823.535H1067.27V822.535H1009.15V749.881H1067.27V748.881H1009.15V676.227H1067.27V675.227H1009.15V602.572H1067.27V601.572H1009.15V528.918H1067.27V527.918H1009.15V455.264H1067.27V454.264H1009.15V381.609H1067.27V380.609H1009.15V307.955L1067.27 307.955V306.955L1009.15 306.955V251.088H1008.15V306.955H935.496V251.088H934.496V306.955L861.847 306.955V251.088H860.847V306.955L788.191 306.955V251.088H787.191V306.955L714.535 306.955V251.088H713.535V306.955L640.878 306.955V251.088H639.878V306.955L567.23 306.955V251.088H566.23V306.955H493.574V251.088H492.574ZM1008.15 822.535V749.881H935.496V822.535H1008.15ZM934.496 822.535V749.881H861.847V822.535H934.496ZM860.847 822.535V749.881H788.191V822.535H860.847ZM787.191 822.535V749.881H714.535V822.535L787.191 822.535ZM713.535 822.535V749.881H640.878V822.535H713.535ZM639.878 822.535V749.881H567.23V822.535H639.878ZM566.23 822.535V749.881H493.574V822.535H566.23ZM566.23 748.881H493.574V676.227H566.23V748.881ZM639.878 748.881H567.23V676.227H639.878V748.881ZM713.535 748.881H640.878V676.227H713.535V748.881ZM787.191 748.881H714.535V676.227H787.191V748.881ZM860.847 748.881H788.191V676.227H860.847V748.881ZM934.496 748.881H861.847V676.227H934.496V748.881ZM1008.15 748.881H935.496V676.227H1008.15V748.881ZM1008.15 675.227V602.572H935.496V675.227H1008.15ZM934.496 675.227V602.572H861.847V675.227H934.496ZM860.847 675.227V602.572H788.191V675.227H860.847ZM787.191 675.227V602.572H714.535V675.227H787.191ZM713.535 675.227V602.572H640.878V675.227H713.535ZM639.878 675.227V602.572H567.23V675.227H639.878ZM566.23 675.227V602.572H493.574V675.227H566.23ZM566.23 601.572H493.574V528.918H566.23V601.572ZM639.878 601.572H567.23V528.918H639.878V601.572ZM713.535 601.572H640.878V528.918H713.535V601.572ZM787.191 601.572H714.535V528.918H787.191V601.572ZM860.847 601.572H788.191V528.918H860.847V601.572ZM934.496 601.572H861.847V528.918H934.496V601.572ZM1008.15 601.572H935.496V528.918H1008.15V601.572ZM1008.15 527.918V455.264H935.496V527.918H1008.15ZM934.496 527.918V455.264L861.847 455.264V527.918H934.496ZM860.847 527.918V455.264H788.191V527.918H860.847ZM787.191 527.918V455.264H714.535V527.918H787.191ZM713.535 527.918V455.264H640.878V527.918H713.535ZM639.878 527.918V455.264L567.23 455.264V527.918H639.878ZM566.23 527.918V455.264H493.574V527.918H566.23ZM566.23 454.264H493.574V381.609H566.23V454.264ZM639.878 454.264L567.23 454.264V381.609L639.878 381.609V454.264ZM713.535 454.264H640.878V381.609H713.535V454.264ZM787.191 454.264H714.535V381.609H787.191V454.264ZM860.847 454.264H788.191V381.609H860.847V454.264ZM934.496 454.264L861.847 454.264V381.609L934.496 381.609V454.264ZM1008.15 454.264H935.496V381.609H1008.15V454.264ZM1008.15 380.609V307.955H935.496V380.609H1008.15ZM934.496 380.609V307.955L861.847 307.955V380.609L934.496 380.609ZM860.847 380.609V307.955L788.191 307.955V380.609H860.847ZM787.191 380.609V307.955L714.535 307.955V380.609H787.191ZM713.535 380.609V307.955L640.878 307.955V380.609H713.535ZM639.878 380.609V307.955L567.23 307.955V380.609L639.878 380.609ZM566.23 380.609V307.955H493.574V380.609H566.23Z" fill="url(#paint1_radial_340_660)"/>
<g opacity="0.4" filter="url(#filter0_f_340_660)">
<ellipse cx="1097.67" cy="90.2204" rx="278.006" ry="262.262" transform="rotate(-157.304 1097.67 90.2204)" fill="url(#paint2_linear_340_660)" fill-opacity="0.23"/>
</g>
<g opacity="0.4" filter="url(#filter1_f_340_660)">
<ellipse cx="645.778" cy="-170.165" rx="362.917" ry="211.851" transform="rotate(46.3553 645.778 -170.165)" fill="#8244FF"/>
</g>
</g>
<defs>
<filter id="filter0_f_340_660" x="627.875" y="-368.511" width="939.586" height="917.463" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="97" result="effect1_foregroundBlur_340_660"/>
</filter>
<filter id="filter1_f_340_660" x="48.0703" y="-774.799" width="1195.42" height="1209.27" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="152" result="effect1_foregroundBlur_340_660"/>
</filter>
<radialGradient id="paint0_radial_340_660" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(1123.11 259.246) rotate(90) scale(238.243)">
<stop stop-color="#8244FF" stop-opacity="0.29"/>
<stop offset="1" stop-color="#8244FF" stop-opacity="0"/>
</radialGradient>
<radialGradient id="paint1_radial_340_660" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(753.111 565.246) rotate(90) scale(238.243)">
<stop stop-color="#8244FF" stop-opacity="0.29"/>
<stop offset="1" stop-color="#8244FF" stop-opacity="0"/>
</radialGradient>
<linearGradient id="paint2_linear_340_660" x1="1356.37" y1="-139.487" x2="1240.56" y2="384.088" gradientUnits="userSpaceOnUse">
<stop stop-color="#F926AE"/>
<stop offset="0.21875" stop-color="#F926AE"/>
<stop offset="1" stop-color="#F926AE" stop-opacity="0"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 9.6 KiB

64
templates/dashboard.html Normal file
View File

@ -0,0 +1,64 @@
<!DOCTYPE html>
<html>
<head>
<title>dashboard</title>
</head>
<body>
<h1>My Links</h1>
<table>
<tr>
<th>Original URL</th>
<th>Short URL</th>
<th>Actions</th>
</tr>
{{range .links}}
<tr>
<td>{{.OriginalURL}}</td>
<td>{{.ShortURL}}</td>
<td>
<button onclick="deleteLink('{{.ID}}')">Delete</button>
<input type="text" id="new-name-{{.ID}}" placeholder="New name">
<button onclick="updateLink('{{.ID}}')">Update</button>
</td>
</tr>
{{end}}
</table>
<a href="/">Back to Home</a>
</body>
<script>
function deleteLink(id) {
fetch('/dashboard', {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ id: id }),
})
.then(response => {
if (response.ok) {
location.reload();
} else {
alert('Failed to delete the link.');
}
});
}
function updateLink(id) {
const newName = document.getElementById(`new-name-${id}`).value;
fetch('/dashboard', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ id: id, new_name: newName }),
})
.then(response => {
if (response.ok) {
location.reload();
} else {
alert('Failed to update the link.');
}
});
}
</script>
</html>

98
templates/index.html Normal file
View File

@ -0,0 +1,98 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>lksNinja - Raccourcisseur d'URL</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css">
<script>
tailwind.config = {
theme: {
extend: {
boxShadow: {
'rainbow': '0 0 10px rgba(255, 0, 0, 0.2), 0 0 20px rgba(255, 165, 0, 0.2), 0 0 30px rgba(255, 255, 0, 0.2), 0 0 40px rgba(0, 255, 0, 0.2), 0 0 50px rgba(0, 0, 255, 0.2), 0 0 60px rgba(75, 0, 130, 0.2), 0 0 70px rgba(238, 130, 238, 0.2)',
'color': '0 0 15px rgba(99, 102, 241, 0.4)'
}
}
}
}
</script>
</head>
<body class="min-h-screen flex flex-col items-center justify-center p-4 bg-[url('/static/img/hero.svg')] bg-cover">
<h1 class="text-5xl p-4 text-center font-bold from-purple-600 via-pink-600 to-blue-600 bg-gradient-to-r bg-clip-text text-transparent">LKS <small>ninja</small></h1>
<div class="bg-white/0 ring-1 ring-black/5 rounded-3xl p-8 w-full max-w-2xl backdrop-blur-md">
<div class="bg-white rounded-2xl p-6 shadow mb-6">
<form id="shorten-form" class="space-y-4">
<input type="url" id="url" name="url" placeholder="📋 Paste here your long link" required
class="w-full px-4 py-2 rounded-full border border-gray-300 focus:outline-none focus:ring-2 focus:ring-blue-300">
<div class="flex space-x-4">
<input type="text" id="custom-name" name="custom_name" placeholder="{{if .loggedIn}} my-custom-name {{else}} Login to enable custom names ✨ {{end}}" {{if not .loggedIn}}disabled{{end}}
class="flex-grow px-4 py-2 rounded-full border border-gray-300 focus:outline-none focus:ring-2 focus:ring-blue-300" >
<button type="submit"
class="px-6 py-2 bg-gradient-to-r from-pink-400 to-red-400 text-white rounded-full shadow hover:from-pink-500 hover:to-red-500 focus:outline-none focus:ring-2 focus:ring-red-300 transition duration-300 ease-in-out transform hover:scale-105">
Generate 📬
</button>
</div>
</form>
</div>
<div id="result" class="hidden p-4 mb-4 text-sm text-green-800 rounded-lg bg-green-50 dark:bg-gray-800 dark:text-green-400" role="alert">
<span class="font-medium">📨 URL Generated!</span>
<span id="result-link"></span>
<button id="copy-btn" class="ml-2 px-2 py-1 bg-blue-500 text-white rounded hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-300">Copy</button>
</div>
<div class="mt-6 flex justify-between items-center">
{{if .loggedIn}}
<a href="/dashboard"
class="px-6 py-2 bg-gradient-to-r from-green-400 to-blue-400 text-white rounded-full hover:from-green-500 hover:to-blue-500 focus:outline-none focus:ring-2 focus:ring-green-300 transition duration-300 ease-in-out transform hover:scale-105">
Dashboard
</a>
<a href="/logout" class="px-6 py-2 bg-gradient-to-r from-purple-400 to-indigo-400 text-white rounded-full hover:from-purple-500 hover:to-indigo-500 focus:outline-none focus:ring-2 focus:ring-purple-300 transition duration-300 ease-in-out transform hover:scale-105">
Logout
</a>
{{else}}
<a href="/login"
class="px-6 py-2 bg-gradient-to-r from-blue-400 to-cyan-400 text-white rounded-full hover:from-blue-500 hover:to-cyan-500 focus:outline-none focus:ring-2 focus:ring-blue-300 transition duration-300 ease-in-out transform hover:scale-105">
<i class="fab fa-github"></i> Login using GitHub
</a>
{{end}}
</div>
</div>
<script>
document.getElementById('shorten-form').addEventListener('submit', function(e) {
e.preventDefault();
fetch('/', {
method: 'POST',
body: new FormData(this)
})
.then(response => response.json())
.then(data => {
this.reset();
const resultBox = document.getElementById('result');
const resultLink = document.getElementById('result-link');
const shortURL = window.location.origin + '/' + data.shortURL;
resultLink.textContent = shortURL;
resultLink.setAttribute('data-url', shortURL);
resultBox.classList.remove('hidden');
});
});
document.getElementById('copy-btn').addEventListener('click', function() {
const resultLink = document.getElementById('result-link');
const url = resultLink.getAttribute('data-url');
navigator.clipboard.writeText(url).then(() => {
this.textContent = 'Copied!';
setTimeout(() => {
this.textContent = 'Copy';
}, 1000);
})
});
</script>
</body>
</html>