mirror of https://github.com/jackc/pgx.git
Update url_shortener example
parent
54c520695f
commit
6d23b58b01
|
@ -6,20 +6,28 @@ This is a sample REST URL shortener service implemented using pgx as the connect
|
||||||
|
|
||||||
Create a PostgreSQL database and run structure.sql into it to create the necessary data schema.
|
Create a PostgreSQL database and run structure.sql into it to create the necessary data schema.
|
||||||
|
|
||||||
Edit connectionOptions in main.go with the location and credentials for your database.
|
Configure the database connection with `DATABASE_URL` or standard PostgreSQL (`PG*`) environment variables or
|
||||||
|
|
||||||
Run main.go:
|
Run main.go:
|
||||||
|
|
||||||
go run main.go
|
```
|
||||||
|
go run main.go
|
||||||
|
```
|
||||||
|
|
||||||
## Create or Update a Shortened URL
|
## Create or Update a Shortened URL
|
||||||
|
|
||||||
curl -X PUT -d 'http://www.google.com' http://localhost:8080/google
|
```
|
||||||
|
curl -X PUT -d 'http://www.google.com' http://localhost:8080/google
|
||||||
|
```
|
||||||
|
|
||||||
## Get a Shortened URL
|
## Get a Shortened URL
|
||||||
|
|
||||||
curl http://localhost:8080/google
|
```
|
||||||
|
curl http://localhost:8080/google
|
||||||
|
```
|
||||||
|
|
||||||
## Delete a Shortened URL
|
## Delete a Shortened URL
|
||||||
|
|
||||||
curl -X DELETE http://localhost:8080/google
|
```
|
||||||
|
curl -X DELETE http://localhost:8080/google
|
||||||
|
```
|
||||||
|
|
|
@ -1,127 +1,121 @@
|
||||||
// TODO - Fix after v4 churn ends
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
func main() {
|
import (
|
||||||
|
"context"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v4"
|
||||||
|
"github.com/jackc/pgx/v4/log/log15adapter"
|
||||||
|
"github.com/jackc/pgx/v4/pool"
|
||||||
|
log "gopkg.in/inconshreveable/log15.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
var db *pool.Pool
|
||||||
|
|
||||||
|
// afterConnect creates the prepared statements that this application uses
|
||||||
|
func afterConnect(ctx context.Context, conn *pgx.Conn) (err error) {
|
||||||
|
_, err = conn.Prepare(ctx, "getUrl", `
|
||||||
|
select url from shortened_urls where id=$1
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = conn.Prepare(ctx, "deleteUrl", `
|
||||||
|
delete from shortened_urls where id=$1
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = conn.Prepare(ctx, "putUrl", `
|
||||||
|
insert into shortened_urls(id, url) values ($1, $2)
|
||||||
|
on conflict (id) do update set url=excluded.url
|
||||||
|
`)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// import (
|
func getUrlHandler(w http.ResponseWriter, req *http.Request) {
|
||||||
// "io/ioutil"
|
var url string
|
||||||
// "net/http"
|
err := db.QueryRow(context.Background(), "getUrl", req.URL.Path).Scan(&url)
|
||||||
// "os"
|
switch err {
|
||||||
|
case nil:
|
||||||
|
http.Redirect(w, req, url, http.StatusSeeOther)
|
||||||
|
case pgx.ErrNoRows:
|
||||||
|
http.NotFound(w, req)
|
||||||
|
default:
|
||||||
|
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// "github.com/jackc/pgx/v4"
|
func putUrlHandler(w http.ResponseWriter, req *http.Request) {
|
||||||
// "github.com/jackc/pgx/v4/log/log15adapter"
|
id := req.URL.Path
|
||||||
// log "gopkg.in/inconshreveable/log15.v2"
|
var url string
|
||||||
// )
|
if body, err := ioutil.ReadAll(req.Body); err == nil {
|
||||||
|
url = string(body)
|
||||||
|
} else {
|
||||||
|
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// var pool *pgx.ConnPool
|
if _, err := db.Exec(context.Background(), "putUrl", id, url); err == nil {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
} else {
|
||||||
|
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// // afterConnect creates the prepared statements that this application uses
|
func deleteUrlHandler(w http.ResponseWriter, req *http.Request) {
|
||||||
// func afterConnect(conn *pgx.Conn) (err error) {
|
if _, err := db.Exec(context.Background(), "deleteUrl", req.URL.Path); err == nil {
|
||||||
// _, err = conn.Prepare("getUrl", `
|
w.WriteHeader(http.StatusOK)
|
||||||
// select url from shortened_urls where id=$1
|
} else {
|
||||||
// `)
|
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
||||||
// if err != nil {
|
}
|
||||||
// return
|
}
|
||||||
// }
|
|
||||||
|
|
||||||
// _, err = conn.Prepare("deleteUrl", `
|
func urlHandler(w http.ResponseWriter, req *http.Request) {
|
||||||
// delete from shortened_urls where id=$1
|
switch req.Method {
|
||||||
// `)
|
case "GET":
|
||||||
// if err != nil {
|
getUrlHandler(w, req)
|
||||||
// return
|
|
||||||
// }
|
|
||||||
|
|
||||||
// _, err = conn.Prepare("putUrl", `
|
case "PUT":
|
||||||
// insert into shortened_urls(id, url) values ($1, $2)
|
putUrlHandler(w, req)
|
||||||
// on conflict (id) do update set url=excluded.url
|
|
||||||
// `)
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func getUrlHandler(w http.ResponseWriter, req *http.Request) {
|
case "DELETE":
|
||||||
// var url string
|
deleteUrlHandler(w, req)
|
||||||
// err := pool.QueryRow("getUrl", req.URL.Path).Scan(&url)
|
|
||||||
// switch err {
|
|
||||||
// case nil:
|
|
||||||
// http.Redirect(w, req, url, http.StatusSeeOther)
|
|
||||||
// case pgx.ErrNoRows:
|
|
||||||
// http.NotFound(w, req)
|
|
||||||
// default:
|
|
||||||
// http.Error(w, "Internal server error", http.StatusInternalServerError)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func putUrlHandler(w http.ResponseWriter, req *http.Request) {
|
default:
|
||||||
// id := req.URL.Path
|
w.Header().Add("Allow", "GET, PUT, DELETE")
|
||||||
// var url string
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||||
// if body, err := ioutil.ReadAll(req.Body); err == nil {
|
}
|
||||||
// url = string(body)
|
}
|
||||||
// } else {
|
|
||||||
// http.Error(w, "Internal server error", http.StatusInternalServerError)
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if _, err := pool.Exec("putUrl", id, url); err == nil {
|
func main() {
|
||||||
// w.WriteHeader(http.StatusOK)
|
logger := log15adapter.NewLogger(log.New("module", "pgx"))
|
||||||
// } else {
|
|
||||||
// http.Error(w, "Internal server error", http.StatusInternalServerError)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func deleteUrlHandler(w http.ResponseWriter, req *http.Request) {
|
poolConfig, err := pool.ParseConfig(os.Getenv("DATABASE_URL"))
|
||||||
// if _, err := pool.Exec("deleteUrl", req.URL.Path); err == nil {
|
if err != nil {
|
||||||
// w.WriteHeader(http.StatusOK)
|
log.Crit("Unable to parse DATABASE_URL", "error", err)
|
||||||
// } else {
|
os.Exit(1)
|
||||||
// http.Error(w, "Internal server error", http.StatusInternalServerError)
|
}
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func urlHandler(w http.ResponseWriter, req *http.Request) {
|
poolConfig.AfterConnect = afterConnect
|
||||||
// switch req.Method {
|
poolConfig.ConnConfig.Logger = logger
|
||||||
// case "GET":
|
|
||||||
// getUrlHandler(w, req)
|
|
||||||
|
|
||||||
// case "PUT":
|
db, err = pool.ConnectConfig(context.Background(), poolConfig)
|
||||||
// putUrlHandler(w, req)
|
if err != nil {
|
||||||
|
log.Crit("Unable to create connection pool", "error", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
// case "DELETE":
|
http.HandleFunc("/", urlHandler)
|
||||||
// deleteUrlHandler(w, req)
|
|
||||||
|
|
||||||
// default:
|
log.Info("Starting URL shortener on localhost:8080")
|
||||||
// w.Header().Add("Allow", "GET, PUT, DELETE")
|
err = http.ListenAndServe("localhost:8080", nil)
|
||||||
// w.WriteHeader(http.StatusMethodNotAllowed)
|
if err != nil {
|
||||||
// }
|
log.Crit("Unable to start web server", "error", err)
|
||||||
// }
|
os.Exit(1)
|
||||||
|
}
|
||||||
// func main() {
|
}
|
||||||
// logger := log15adapter.NewLogger(log.New("module", "pgx"))
|
|
||||||
|
|
||||||
// var err error
|
|
||||||
// connPoolConfig := pgx.ConnPoolConfig{
|
|
||||||
// ConnConfig: pgx.ConnConfig{
|
|
||||||
// Host: "127.0.0.1",
|
|
||||||
// User: "jack",
|
|
||||||
// Password: "jack",
|
|
||||||
// Database: "url_shortener",
|
|
||||||
// Logger: logger,
|
|
||||||
// },
|
|
||||||
// MaxConnections: 5,
|
|
||||||
// AfterConnect: afterConnect,
|
|
||||||
// }
|
|
||||||
// pool, err = pgx.NewConnPool(connPoolConfig)
|
|
||||||
// if err != nil {
|
|
||||||
// log.Crit("Unable to create connection pool", "error", err)
|
|
||||||
// os.Exit(1)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// http.HandleFunc("/", urlHandler)
|
|
||||||
|
|
||||||
// log.Info("Starting URL shortener on localhost:8080")
|
|
||||||
// err = http.ListenAndServe("localhost:8080", nil)
|
|
||||||
// if err != nil {
|
|
||||||
// log.Crit("Unable to start web server", "error", err)
|
|
||||||
// os.Exit(1)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
5
go.mod
5
go.mod
|
@ -4,12 +4,14 @@ go 1.12
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/cockroachdb/apd v1.1.0
|
github.com/cockroachdb/apd v1.1.0
|
||||||
|
github.com/go-stack/stack v1.8.0 // indirect
|
||||||
github.com/jackc/pgconn v0.0.0-20190424214952-1e3961bd0ea4
|
github.com/jackc/pgconn v0.0.0-20190424214952-1e3961bd0ea4
|
||||||
github.com/jackc/pgio v1.0.0
|
github.com/jackc/pgio v1.0.0
|
||||||
github.com/jackc/pgproto3 v1.1.0
|
|
||||||
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db
|
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db
|
||||||
github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0
|
github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0
|
||||||
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b
|
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b
|
||||||
|
github.com/mattn/go-colorable v0.1.1 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.7 // indirect
|
||||||
github.com/rs/zerolog v1.13.0
|
github.com/rs/zerolog v1.13.0
|
||||||
github.com/satori/go.uuid v1.2.0
|
github.com/satori/go.uuid v1.2.0
|
||||||
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24
|
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24
|
||||||
|
@ -18,4 +20,5 @@ require (
|
||||||
go.uber.org/atomic v1.4.0 // indirect
|
go.uber.org/atomic v1.4.0 // indirect
|
||||||
go.uber.org/zap v1.9.1
|
go.uber.org/zap v1.9.1
|
||||||
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373
|
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373
|
||||||
|
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec
|
||||||
)
|
)
|
||||||
|
|
10
go.sum
10
go.sum
|
@ -4,6 +4,8 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
|
||||||
|
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||||
github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0=
|
github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0=
|
||||||
github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
|
github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
|
||||||
github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3 h1:ZFYpB74Kq8xE9gmfxCmXD6QxZ27ja+j3HwGFc+YurhQ=
|
github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3 h1:ZFYpB74Kq8xE9gmfxCmXD6QxZ27ja+j3HwGFc+YurhQ=
|
||||||
|
@ -34,6 +36,11 @@ github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A=
|
||||||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
github.com/lib/pq v1.1.0 h1:/5u4a+KGJptBRqGzPvYQL9p0d/tPR4S31+Tnzj9lEO4=
|
github.com/lib/pq v1.1.0 h1:/5u4a+KGJptBRqGzPvYQL9p0d/tPR4S31+Tnzj9lEO4=
|
||||||
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
|
github.com/mattn/go-colorable v0.1.1 h1:G1f5SKeVxmagw/IyvzvtZE4Gybcc4Tr1tf7I8z0XgOg=
|
||||||
|
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
|
||||||
|
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||||
|
github.com/mattn/go-isatty v0.0.7 h1:UvyT9uN+3r7yLEYSlJsbQGdsaB/a0DlgWP3pql6iwOc=
|
||||||
|
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
@ -63,6 +70,7 @@ golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a h1:Igim7XhdOpBnWPuYJ70XcN
|
||||||
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
||||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33 h1:I6FyU15t786LL7oL/hn43zqTuEGr4PN7F4XJ1p4E3Y8=
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33 h1:I6FyU15t786LL7oL/hn43zqTuEGr4PN7F4XJ1p4E3Y8=
|
||||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e h1:nFYrTHrdrAOpShe27kaFHjsqYSEQ0KWqdWLu3xuZJts=
|
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e h1:nFYrTHrdrAOpShe27kaFHjsqYSEQ0KWqdWLu3xuZJts=
|
||||||
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||||
|
@ -71,3 +79,5 @@ golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373 h1:PPwnA7z1Pjf7XYaBP9GL1
|
||||||
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec h1:RlWgLqCMMIYYEVcAR5MDsuHlVkaIPDAF+5Dehzg8L5A=
|
||||||
|
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
|
||||||
|
|
Loading…
Reference in New Issue