Skip to content

Commit

Permalink
Add database stores
Browse files Browse the repository at this point in the history
  • Loading branch information
razonyang committed Mar 9, 2020
1 parent f3b76d9 commit 0d24ebc
Show file tree
Hide file tree
Showing 13 changed files with 291 additions and 0 deletions.
12 changes: 12 additions & 0 deletions dbstore/dialect.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Copyright 2020 CleverGo. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be found
// in the LICENSE file.

package dbstore

type Dialect interface {
Insert(table string) string
Delete(table string) string
QueryRow(table string) string
DeleteExpired(table string) string
}
5 changes: 5 additions & 0 deletions dbstore/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module github.com/clevergo/captchas/dbstore

go 1.14

require github.com/clevergo/captchas v0.3.2
2 changes: 2 additions & 0 deletions dbstore/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
github.com/clevergo/captchas v0.3.2 h1:/P/Lc8umYp052aGeyiQf8a8mED+g+lmZLqmogMrt1e0=
github.com/clevergo/captchas v0.3.2/go.mod h1:MgQIvLx9mhOoScMJnSFvXuqbAQVIwWtLJ862K1jRvR8=
7 changes: 7 additions & 0 deletions dbstore/migrations/mysql.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
CREATE TABLE captchas (
id VARCHAR(64) NOT NULL PRIMARY KEY,
answer VARCHAR(32) NOT NULL,
created_at BIGINT NOT NULL,
expires_in BIGINT NOT NULL,
INDEX(expires_in)
);
8 changes: 8 additions & 0 deletions dbstore/migrations/postgres.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
CREATE TABLE captchas (
id VARCHAR(64) NOT NULL PRIMARY KEY,
answer VARCHAR(32) NOT NULL,
created_at BIGINT NOT NULL,
expires_in BIGINT NOT null
);

CREATE INDEX captchas_expires_on_idx ON captchas (expires_in);
9 changes: 9 additions & 0 deletions dbstore/migrations/sqlite3.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
BEGIN;
CREATE TABLE captchas (
id VARCHAR(64) NOT NULL PRIMARY KEY,
answer VARCHAR(32) NOT NULL,
created_at BIGINT NOT NULL,
expires_in BIGINT NOT NULL
);
CREATE INDEX captchas_expires_in_idx ON captchas (expires_in);
COMMIT;
122 changes: 122 additions & 0 deletions dbstore/store.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
// Copyright 2020 CleverGo. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be found
// in the LICENSE file.

package dbstore

import (
"database/sql"
"time"

"github.com/clevergo/captchas"
)

type item struct {
ID string `db:"id"`
Answer string `db:"answer"`
CreatedAt int64 `db:"created_at"`
ExpiresIn int64 `db:"expires_in"`
}

// Option is a function that receives a pointer of store.
type Option func(*Store)

// Expiration sets expiration.
func Expiration(expiration time.Duration) Option {
return func(s *Store) {
s.expiration = expiration
}
}

// GCInterval sets garbage collection .
func GCInterval(interval time.Duration) Option {
return func(s *Store) {
s.gcInterval = interval
}
}

type Store struct {
db *sql.DB
dialect Dialect
tableName string
prefix string
expiration time.Duration
gcInterval time.Duration
}

// New returns a db store.
func New(db *sql.DB, dialect Dialect, opts ...Option) *Store {
s := &Store{
db: db,
dialect: dialect,
tableName: "captchas",
prefix: "default",
expiration: 10 * time.Minute,
gcInterval: time.Hour,
}

for _, f := range opts {
f(s)
}

go s.gc()

return s
}

func (s *Store) getID(id string) string {
return s.prefix + ":" + id
}

// Get implements Store.Get.
func (s *Store) Get(id string, clear bool) (string, error) {
id = s.getID(id)
row := s.db.QueryRow(s.dialect.QueryRow(s.tableName), id)
if row == nil {
return "", captchas.ErrCaptchaIncorrect
}
item := item{}
if err := row.Scan(&item.ID, &item.Answer, &item.ExpiresIn); err != nil {
if err == sql.ErrNoRows {
return "", captchas.ErrCaptchaIncorrect
}
return "", err
}
if time.Now().Unix() > item.ExpiresIn {
return "", captchas.ErrCaptchaExpired
}

if clear {
_, err := s.db.Exec(s.dialect.Delete(s.tableName), id)
if err != nil {
return "", err
}
}

return item.Answer, nil
}

// Set implements Store.Set.
func (s *Store) Set(id, answer string) error {
id = s.getID(id)
now := time.Now()
_, err := s.db.Exec(
s.dialect.Insert(s.tableName),
id, answer, now.Unix(), now.Add(s.expiration).Unix(),
)
return err
}

func (s *Store) gc() {
ticker := time.NewTicker(s.gcInterval)
for {
select {
case <-ticker.C:
s.deleteExpired()
}
}
}

func (s *Store) deleteExpired() {
s.db.Exec(s.dialect.DeleteExpired(s.tableName), time.Now().Unix())
}
3 changes: 3 additions & 0 deletions mysqlstore/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module github.com/clevergo/captchas/mysqlstore

go 1.14
39 changes: 39 additions & 0 deletions mysqlstore/store.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Copyright 2020 CleverGo. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be found
// in the LICENSE file.

package mysqlstore

import (
"database/sql"
"fmt"

"github.com/clevergo/captchas/dbstore"
)

type Store struct {
*dbstore.Store
}

func New(db *sql.DB, opts ...dbstore.Option) *Store {
return &Store{dbstore.New(db, &dialect{}, opts...)}
}

type dialect struct {
}

func (d *dialect) Insert(table string) string {
return fmt.Sprintf(`INSERT INTO %s(id, answer, created_at, expires_in) VALUES(?, ?, ?, ?)`, table)
}

func (d *dialect) QueryRow(table string) string {
return fmt.Sprintf(`SELECT id, answer, expires_in FROM %s WHERE id = ?`, table)
}

func (d *dialect) Delete(table string) string {
return fmt.Sprintf(`DELETE FROM %s WHERE id=?`, table)
}

func (d *dialect) DeleteExpired(table string) string {
return fmt.Sprintf(`DELETE FROM %s WHERE expires_in<?`, table)
}
3 changes: 3 additions & 0 deletions postgresstore/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module github.com/clevergo/captchas/postgresstore

go 1.14
39 changes: 39 additions & 0 deletions postgresstore/store.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Copyright 2020 CleverGo. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be found
// in the LICENSE file.

package postgresstore

import (
"database/sql"
"fmt"

"github.com/clevergo/captchas/dbstore"
)

type Store struct {
*dbstore.Store
}

func New(db *sql.DB, opts ...dbstore.Option) *Store {
return &Store{dbstore.New(db, &dialect{}, opts...)}
}

type dialect struct {
}

func (d *dialect) Insert(table string) string {
return fmt.Sprintf(`INSERT INTO %s(id, answer, created_at, expires_in) VALUES($1, $2, $3, $4)`, table)
}

func (d *dialect) QueryRow(table string) string {
return fmt.Sprintf(`SELECT id, answer, expires_in FROM %s WHERE id = $1`, table)
}

func (d *dialect) Delete(table string) string {
return fmt.Sprintf(`DELETE FROM %s WHERE id=$1`, table)
}

func (d *dialect) DeleteExpired(table string) string {
return fmt.Sprintf(`DELETE FROM %s WHERE expires_in<$1`, table)
}
3 changes: 3 additions & 0 deletions sqlite3store/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module github.com/clevergo/captchas/sqlite3store

go 1.14
39 changes: 39 additions & 0 deletions sqlite3store/store.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Copyright 2020 CleverGo. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be found
// in the LICENSE file.

package sqlite3store

import (
"database/sql"
"fmt"

"github.com/clevergo/captchas/dbstore"
)

type Store struct {
*dbstore.Store
}

func New(db *sql.DB, opts ...dbstore.Option) *Store {
return &Store{dbstore.New(db, &dialect{}, opts...)}
}

type dialect struct {
}

func (d *dialect) Insert(table string) string {
return fmt.Sprintf(`INSERT INTO %s(id, answer, created_at, expires_in) VALUES(?, ?, ?, ?)`, table)
}

func (d *dialect) QueryRow(table string) string {
return fmt.Sprintf(`SELECT id, answer, expires_in FROM %s WHERE id = ?`, table)
}

func (d *dialect) Delete(table string) string {
return fmt.Sprintf(`DELETE FROM %s WHERE id=?`, table)
}

func (d *dialect) DeleteExpired(table string) string {
return fmt.Sprintf(`DELETE FROM %s WHERE expires_in<?`, table)
}

0 comments on commit 0d24ebc

Please sign in to comment.