Skip to content

Commit

Permalink
feat: implement storage (#4)
Browse files Browse the repository at this point in the history
  • Loading branch information
beihai0xff authored Jun 19, 2024
1 parent 172ca52 commit ff32b29
Show file tree
Hide file tree
Showing 5 changed files with 183 additions and 35 deletions.
30 changes: 12 additions & 18 deletions .golangci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,6 @@ run:
# Default: 1m
timeout: 5m
tests: false
# Which dirs to skip: issues from them won't be reported.
skip-dirs:
- test
- third_party
# Which files to skip: they will be analyzed, but issues from them won't be reported.
skip-files:
- _test.go
- _mock.go
- ".*\\.pb\\.go"
- ".*\\.gen\\.go"
linters:
disable-all: true
enable: # please keep this alphabetized
Expand All @@ -29,7 +19,7 @@ linters:
- goconst
- gocritic
- gofmt
- gomnd
- mnd
- gocyclo
- gosec
- gosimple
Expand Down Expand Up @@ -62,8 +52,6 @@ linters-settings: # please keep this alphabetized
- name: atomic
severity: warning
- name: package-comments
- name: unhandled-error
arguments : ["fmt.Printf"]
- name: blank-imports
- name: context-as-argument
- name: context-keys-type
Expand Down Expand Up @@ -110,10 +98,6 @@ linters-settings: # please keep this alphabetized
staticcheck:
checks:
- "all"
- "-SA1019" # TODO(fix) Using a deprecated function, variable, constant or field
stylecheck:
checks:
- "ST1019" # Importing the same package multiple times.

lll:
line-length: 120
Expand All @@ -123,6 +107,16 @@ linters-settings: # please keep this alphabetized
min-complexity: 8

issues:
# Which dirs to skip: issues from them won't be reported.
exclude-dirs:
- test
- third_party
# Which files to skip: they will be analyzed, but issues from them won't be reported.
exclude-files:
- _test.go
- _mock.go
- ".*\\.pb\\.go"
- ".*\\.gen\\.go"
# List of regexps of issue texts to exclude.
include:
- EXC0012 # EXC0012 revive: exported (.+) should have comment( \(or a comment on this block\))? or be unexported
Expand All @@ -146,7 +140,7 @@ output:
# Example: "checkstyle:report.json,colored-line-number"
#
# Default: colored-line-number
format: colored-line-number,github-actions
formats: colored-line-number
# Print lines of code with issue.
# Default: true
print-issued-lines: false
Expand Down
38 changes: 26 additions & 12 deletions app/turl/server.go
Original file line number Diff line number Diff line change
@@ -1,19 +1,33 @@
// Package turl implements the business logic of the tiny URL service.
package turl

// type TinyURL struct {
// db db.TinyURL
// cache cache.TinyURL
// }
//
// func New(db db.TinyURL, cache cache.Interface) *TinyURL {
// return &TinyURL{
// db: db,
// cache: cache,
// }
import (
"github.com/beiai0xff/turl/pkg/cache"
"github.com/beiai0xff/turl/pkg/storage"
)

// TinyURL represents the tiny URL service.
type TinyURL struct {
db storage.Storage
distributedCache cache.Interface
localCache cache.Interface
}

// New creates a new TinyURL service.
func newTinyURL(db storage.Storage, dcache, lcache cache.Interface) *TinyURL {
return &TinyURL{
db: db,
distributedCache: dcache,
localCache: lcache,
}
}

// // Create creates a new tiny URL.
// func (t *TinyURL) Create(longURL []byte) error {
// return nil
// }
//
// func (t *TinyURL) Create(longURL string) error {
// // Retrieve a tiny URL.
// func (t *TinyURL) Retrieve(short string) error {
// return nil
//
// }
77 changes: 77 additions & 0 deletions pkg/storage/storage.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Package pkg provides the implementation of the storage interface.
package storage

import (
"context"

"github.com/redis/go-redis/v9"
"gorm.io/gorm"
)

// Ensuring that *storage implements the Storage interface
var _ Storage = (*storage)(nil)

// Storage is an interface that defines the methods that a storage system must implement.
type Storage interface {
// Insert adds a new TinyURL record to the storage.
Insert(ctx context.Context, short uint64, longURL []byte) error
// GetTinyURLByID retrieves a TinyURL record by its ID.
GetTinyURLByID(ctx context.Context, short uint64) (*TinyURL, error)
// Close closes the storage.
Close() error
}

// TinyURL represents a shortened URL record.
type TinyURL struct {
gorm.Model
LongURL []byte `gorm:"type:VARCHAR(500);not null" json:"long_url"` // The original URL.
Short uint64 `gorm:"type:BIGINT;index;not null" json:"short"` // The shortened URL ID.
}

// storage is a concrete implementation of the Storage interface.
type storage struct {
db *gorm.DB // Database client.
rdb redis.UniversalClient // Redis client.
}

// New creates a new storage instance.
func New(db *gorm.DB, rdb redis.UniversalClient) Storage {
return newStorage(db, rdb)
}

// newStorage is a helper function that creates a new storage instance.
func newStorage(db *gorm.DB, rdb redis.UniversalClient) *storage {
return &storage{
db: db,
rdb: rdb,
}
}

// Insert adds a new TinyURL record to the storage.
func (s *storage) Insert(ctx context.Context, short uint64, long []byte) error {
t := TinyURL{
Short: short,
LongURL: long,
}

// Create a new record in the database.
return s.db.WithContext(ctx).Create(&t).Error
}

// GetTinyURLByID retrieves a TinyURL record by its ID.
func (s *storage) GetTinyURLByID(ctx context.Context, short uint64) (*TinyURL, error) {
t := TinyURL{}
// Query the database for the record.
res := s.db.WithContext(ctx).Where("short = ?", short).Take(&t)

if res.Error != nil {
return nil, res.Error
}

return &t, nil
}

// Close closes the storage.
func (s *storage) Close() error {
return s.rdb.Close()
}
63 changes: 63 additions & 0 deletions pkg/storage/storage_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package storage

import (
"context"
"os"
"testing"

"github.com/stretchr/testify/require"
"gorm.io/gorm"

"github.com/beiai0xff/turl/pkg/db/mysql"
"github.com/beiai0xff/turl/pkg/db/redis"
"github.com/beiai0xff/turl/test"
)

func TestMain(m *testing.M) {
db, _ := mysql.New(test.DSN)

db.AutoMigrate(&TinyURL{})

os.Exit(m.Run())
}

func TestNew(t *testing.T) {
db, _ := mysql.New(test.DSN)
rdb := redis.Client(test.RedisAddr)

s := New(db, rdb)
t.Cleanup(func() {
s.Close()
})

require.NotNil(t, s)
}

func Test_newStorage(t *testing.T) {
db, _ := mysql.New(test.DSN)
rdb := redis.Client(test.RedisAddr)

s := newStorage(db, rdb)
t.Cleanup(func() {
s.Close()
})

require.NotNil(t, s)
}

func Test_storage_GetTinyURLByID(t *testing.T) {
db, _ := mysql.New(test.DSN)
rdb := redis.Client(test.RedisAddr)

short, long := uint64(10000), []byte("www.google.com")
s, ctx := newStorage(db, rdb), context.Background()
t.Cleanup(func() { s.Close() })

require.NoError(t, s.Insert(ctx, short, long))
got, err := s.GetTinyURLByID(ctx, short)
require.NoError(t, err)
require.Equal(t, long, got.LongURL)

got, err = s.GetTinyURLByID(ctx, 100)
require.ErrorIs(t, err, gorm.ErrRecordNotFound)
}
10 changes: 5 additions & 5 deletions test/docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
version: '3.1'

services:

mysql:
Expand All @@ -18,14 +16,16 @@ services:
interval: 3s

redis:
image: redis:latest
image: redis/redis-stack-server:7.2.0-v11
container_name: redis
restart: always
ports:
- '6379:6379'
command: redis-server --save 60 1 --loglevel warning
command: ["redis-server" , "--protected-mode", "no"]
environment:
- REDIS_ARGS="--save 10 1 --appendonly yes"
healthcheck:
test: ["CMD", "redis-cli", "ping"]
timeout: 10s
retries: 5
interval: 3s
interval: 3s

0 comments on commit ff32b29

Please sign in to comment.