Skip to content

Commit ad7d081

Browse files
committed
refactor: upgraded dependencies, added more documentation, increased type safety, removed go-logger dependency
1 parent c31e2f6 commit ad7d081

28 files changed

+757
-107
lines changed

errors.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,8 @@ var (
1515
ErrEmptyDataSourceName = errors.New("data source name cannot be empty")
1616
ErrNilQuery = errors.New("sql query cannot be nil")
1717
ErrNilRow = errors.New("sql row cannot be nil")
18+
ErrNilConnHandler = errors.New("connection handler cannot be nil")
19+
ErrNilPoolHandler = errors.New("pool handler cannot be nil")
20+
ErrNilPoolConfig = errors.New("pool config cannot be nil")
21+
ErrNilService = errors.New("database service cannot be nil")
1822
)

go.mod

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,31 @@
11
module github.com/ralvarezdev/go-databases
22

3-
go 1.23.4
3+
go 1.24.0
44

55
require (
66
github.com/go-redis/redis/v8 v8.11.5
7-
github.com/jackc/pgx/v5 v5.7.5
8-
github.com/ralvarezdev/go-logger v0.4.7
7+
github.com/jackc/pgx/v5 v5.7.6
98
go.mongodb.org/mongo-driver v1.17.4
10-
golang.org/x/net v0.42.0
11-
gorm.io/gorm v1.30.1
9+
golang.org/x/net v0.44.0
10+
gorm.io/gorm v1.31.0
1211
)
1312

1413
require (
1514
github.com/cespare/xxhash/v2 v2.3.0 // indirect
1615
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
17-
github.com/golang/snappy v0.0.4 // indirect
16+
github.com/golang/snappy v1.0.0 // indirect
1817
github.com/jackc/pgpassfile v1.0.0 // indirect
1918
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
2019
github.com/jackc/puddle/v2 v2.2.2 // indirect
2120
github.com/jinzhu/inflection v1.0.0 // indirect
2221
github.com/jinzhu/now v1.1.5 // indirect
23-
github.com/klauspost/compress v1.16.7 // indirect
22+
github.com/klauspost/compress v1.18.0 // indirect
2423
github.com/montanaflynn/stats v0.7.1 // indirect
25-
github.com/ralvarezdev/go-flags v0.3.3 // indirect
26-
github.com/ralvarezdev/go-strings v0.1.11 // indirect
2724
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
2825
github.com/xdg-go/scram v1.1.2 // indirect
2926
github.com/xdg-go/stringprep v1.0.4 // indirect
3027
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
31-
golang.org/x/crypto v0.40.0 // indirect
32-
golang.org/x/sync v0.16.0 // indirect
33-
golang.org/x/text v0.27.0 // indirect
28+
golang.org/x/crypto v0.42.0 // indirect
29+
golang.org/x/sync v0.17.0 // indirect
30+
golang.org/x/text v0.29.0 // indirect
3431
)

go.sum

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC
1111
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
1212
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
1313
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
14+
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
15+
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
1416
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
1517
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
1618
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
@@ -19,6 +21,8 @@ github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7Ulw
1921
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
2022
github.com/jackc/pgx/v5 v5.7.5 h1:JHGfMnQY+IEtGM63d+NGMjoRpysB2JBwDr5fsngwmJs=
2123
github.com/jackc/pgx/v5 v5.7.5/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M=
24+
github.com/jackc/pgx/v5 v5.7.6 h1:rWQc5FwZSPX58r1OQmkuaNicxdmExaEz5A2DO2hUuTk=
25+
github.com/jackc/pgx/v5 v5.7.6/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M=
2226
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
2327
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
2428
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
@@ -27,6 +31,8 @@ github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
2731
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
2832
github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I=
2933
github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
34+
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
35+
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
3036
github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE=
3137
github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
3238
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
@@ -39,10 +45,14 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
3945
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
4046
github.com/ralvarezdev/go-flags v0.3.3 h1:1NpwdSD+uCMF5NP+szJmq/mXqNFZwrzKAfDNZOznDEQ=
4147
github.com/ralvarezdev/go-flags v0.3.3/go.mod h1:R3yVBYvzwqfOp26LidaiJ/zftVAnPC3pKunVpV/vosE=
48+
github.com/ralvarezdev/go-flags v0.3.5 h1:oUq2IIf3Bg63iOKoG/8HbS6yxHq5AWnMAykZRsMKcCw=
49+
github.com/ralvarezdev/go-flags v0.3.5/go.mod h1:R3yVBYvzwqfOp26LidaiJ/zftVAnPC3pKunVpV/vosE=
4250
github.com/ralvarezdev/go-logger v0.4.7 h1:kqg3lLZml+oBFavDCPA4M4olhwWUAUnfDmU1qA0z6Qs=
4351
github.com/ralvarezdev/go-logger v0.4.7/go.mod h1:gOFbSSJ0rWqLtrNpwoOT3Gnim5aXo93lZC0cY0vU/yg=
4452
github.com/ralvarezdev/go-strings v0.1.11 h1:3Vla7fGOs+4nyQ3NVaFz+XRP3CTv5JheGt0PXryBcMo=
4553
github.com/ralvarezdev/go-strings v0.1.11/go.mod h1:8sFOqmPJpqzS7bTjf91EzUCITnwpmkfifwY80GxV5r8=
54+
github.com/ralvarezdev/go-strings v0.1.14 h1:NivBj8XzQ8mG/wS0mFKj2xlAIRpf+9QP56SJvPGJzGI=
55+
github.com/ralvarezdev/go-strings v0.1.14/go.mod h1:8sFOqmPJpqzS7bTjf91EzUCITnwpmkfifwY80GxV5r8=
4656
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
4757
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
4858
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
@@ -63,16 +73,22 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
6373
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
6474
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
6575
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
76+
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
77+
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
6678
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
6779
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
6880
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
6981
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
7082
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
7183
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
84+
golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I=
85+
golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
7286
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
7387
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
7488
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
7589
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
90+
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
91+
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
7692
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
7793
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
7894
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -88,6 +104,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
88104
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
89105
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
90106
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
107+
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
108+
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
91109
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
92110
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
93111
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
@@ -102,3 +120,5 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
102120
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
103121
gorm.io/gorm v1.30.1 h1:lSHg33jJTBxs2mgJRfRZeLDG+WZaHYCk3Wtfl6Ngzo4=
104122
gorm.io/gorm v1.30.1/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
123+
gorm.io/gorm v1.31.0 h1:0VlycGreVhK7RF/Bwt51Fk8v0xLiiiFdbGDPIZQ7mJY=
124+
gorm.io/gorm v1.31.0/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=

logger.go

Lines changed: 0 additions & 36 deletions
This file was deleted.

mongodb/collection.go

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,24 @@ import (
77
"golang.org/x/net/context"
88
)
99

10-
// Collection represents a MongoDB collection
11-
type Collection struct {
12-
name string
13-
Indexes *[]*mongo.IndexModel
14-
}
10+
type (
11+
// Collection represents a MongoDB collection
12+
Collection struct {
13+
name string
14+
Indexes *[]*mongo.IndexModel
15+
}
16+
)
1517

1618
// NewCollection creates a new MongoDB collection
19+
//
20+
// Parameters:
21+
//
22+
// - name: the name of the collection
23+
// - indexes: the indexes to create for the collection
24+
//
25+
// Returns:
26+
//
27+
// - *Collection: the new collection
1728
func NewCollection(
1829
name string,
1930
indexes *[]*mongo.IndexModel,
@@ -25,7 +36,11 @@ func NewCollection(
2536
}
2637

2738
// CreateCollection creates the collection
28-
func (c *Collection) CreateCollection(database *mongo.Database) (
39+
//
40+
// Parameters:
41+
//
42+
// - database: the MongoDB database
43+
func (c Collection) CreateCollection(database *mongo.Database) (
2944
collection *mongo.Collection, err error,
3045
) {
3146
// Get the collection
@@ -40,7 +55,15 @@ func (c *Collection) CreateCollection(database *mongo.Database) (
4055
}
4156

4257
// createIndexes creates the indexes for the collection
43-
func (c *Collection) createIndexes(collection *mongo.Collection) (err error) {
58+
//
59+
// Parameters:
60+
//
61+
// - collection: the MongoDB collection
62+
//
63+
// Returns:
64+
//
65+
// - error: if there was an error creating the indexes
66+
func (c Collection) createIndexes(collection *mongo.Collection) (err error) {
4467
if c.Indexes != nil {
4568
for _, index := range *c.Indexes {
4669
// Check if the index is nil

mongodb/config.go

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,15 @@ type (
1919
)
2020

2121
// NewConnConfig creates a new MongoDB connection configuration
22+
//
23+
// Parameters:
24+
//
25+
// - uri: MongoDB URI
26+
// - timeout: MongoDB connection timeout
27+
//
28+
// Returns:
29+
//
30+
// - *ConnConfig: MongoDB connection configuration
2231
func NewConnConfig(uri string, timeout time.Duration) *ConnConfig {
2332
return &ConnConfig{
2433
uri,
@@ -27,11 +36,19 @@ func NewConnConfig(uri string, timeout time.Duration) *ConnConfig {
2736
}
2837

2938
// URI returns the MongoDB URI
30-
func (c *ConnConfig) URI() string {
39+
//
40+
// Returns:
41+
//
42+
// - string: MongoDB URI
43+
func (c ConnConfig) URI() string {
3144
return c.uri
3245
}
3346

3447
// Timeout returns the MongoDB connection timeout
35-
func (c *ConnConfig) Timeout() time.Duration {
48+
//
49+
// Returns:
50+
//
51+
// - time.Duration: MongoDB connection timeout
52+
func (c ConnConfig) Timeout() time.Duration {
3653
return c.timeout
3754
}

mongodb/connection.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,15 @@ type (
2525
)
2626

2727
// NewDefaultConnHandler creates a new connection
28+
//
29+
// Parameters:
30+
//
31+
// - config: Config interface
32+
//
33+
// Returns:
34+
//
35+
// - *DefaultConnHandler: DefaultConnHandler struct
36+
// - error: error if any
2837
func NewDefaultConnHandler(config Config) (
2938
*DefaultConnHandler,
3039
error,
@@ -46,7 +55,16 @@ func NewDefaultConnHandler(config Config) (
4655
}
4756

4857
// Connect returns a new MongoDB client
58+
//
59+
// Returns:
60+
//
61+
// - *mongo.Client: MongoDB client
62+
// - error: error if any
4963
func (d *DefaultConnHandler) Connect() (*mongo.Client, error) {
64+
if d == nil {
65+
return nil, godatabases.ErrNilConnHandler
66+
}
67+
5068
// Check if the connection is already established
5169
if d.client != nil {
5270
return d.client, godatabases.ErrAlreadyConnected
@@ -73,7 +91,16 @@ func (d *DefaultConnHandler) Connect() (*mongo.Client, error) {
7391
}
7492

7593
// Client returns the MongoDB client
94+
//
95+
// Returns:
96+
//
97+
// - *mongo.Client: MongoDB client
98+
// - error: error if any
7699
func (d *DefaultConnHandler) Client() (*mongo.Client, error) {
100+
if d == nil {
101+
return nil, godatabases.ErrNilConnHandler
102+
}
103+
77104
// Check if the connection is established
78105
if d.client == nil {
79106
return nil, godatabases.ErrNotConnected
@@ -84,6 +111,10 @@ func (d *DefaultConnHandler) Client() (*mongo.Client, error) {
84111

85112
// Disconnect closes the MongoDB client connection
86113
func (d *DefaultConnHandler) Disconnect() {
114+
if d == nil {
115+
return
116+
}
117+
87118
// Check if the connection is established
88119
if d.client == nil {
89120
return

mongodb/errors.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
package mongodb
22

3-
import "errors"
3+
import (
4+
"errors"
5+
)
6+
7+
const (
8+
ErrFailedToCreateIndex = "failed to create index '%v': %v"
9+
)
410

511
var (
612
ErrFailedToCreateDocument = errors.New("failed to create document")
713
ErrFailedToStartSession = errors.New("failed to start session")
8-
ErrFailedToCreateIndex = "failed to create index '%v': %v"
914
ErrNilClient = errors.New("mongodb client cannot be nil")
1015
)

mongodb/index.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,13 @@ import (
66
"go.mongodb.org/mongo-driver/mongo/options"
77
)
88

9-
type FieldIndex struct {
10-
name string
11-
order Order
12-
}
9+
type (
10+
// FieldIndex represents a field index
11+
FieldIndex struct {
12+
name string
13+
order Order
14+
}
15+
)
1316

1417
// NewFieldIndex creates a new field index
1518
func NewFieldIndex(name string, order Order) *FieldIndex {

mongodb/object_id.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,15 @@ import (
66
)
77

88
// GetObjectIdFromString gets the object ID from the string
9+
//
10+
// Parameters:
11+
//
12+
// - id: the string ID to convert
13+
//
14+
// Returns:
15+
//
16+
// - *primitive.ObjectID: the object ID
17+
// - error: if any error occurred
918
func GetObjectIdFromString(id string) (*primitive.ObjectID, error) {
1019
// Check if the ID is empty
1120
if id == "" {

0 commit comments

Comments
 (0)