Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

2 create migration file #4

Merged
merged 2 commits into from
Aug 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions cmd/cli.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package main

import (
"flag"
"fmt"
"log"
"os"

_ "github.com/lib/pq"

"ledger-service/internal/config"
"ledger-service/internal/pkg/db/postgres"
"ledger-service/internal/scheme"
)

func main() {
if _, ok := os.LookupEnv("PORT"); !ok {
config.Setup(".env")
}

// =========================================================================
// Logging
log := log.New(os.Stdout, "LMS : ", log.LstdFlags|log.Lmicroseconds|log.Lshortfile)
if err := run(log); err != nil {
log.Fatalf("error: shutting down: %s", err)
}
}

func run(log *log.Logger) error {
// =========================================================================
// App Starting

log.Printf("main : Started")
defer log.Println("main : Completed")

// =========================================================================

// Start Database

db, err := postgres.Open()
if err != nil {
return fmt.Errorf("connecting to db: %v", err)
}
defer db.Close()

// Handle cli command
flag.Parse()

switch flag.Arg(0) {
case "migrate":
if err := scheme.Migrate(db); err != nil {
return fmt.Errorf("applying migrations: %v", err)
}
log.Println("Migrations complete")
return nil

case "seed":
if err := scheme.Seed(db); err != nil {
return fmt.Errorf("seeding database: %v", err)
}
log.Println("Seed data complete")
return nil
}

return nil
}
11 changes: 11 additions & 0 deletions env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
PORT=6000

REDIS_ADDRESS=
REDIS_PASSWORD=

POSTGRES_PORT=
POSTGRES_HOST=
POSTGRES_USER=
POSTGRES_PASSWORD=
POSTGRES_DB=

24 changes: 24 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
module ledger-service

go 1.20

require (
github.com/GuiaBolso/darwin v0.0.0-20191218124601-fd6d2aa3d244
github.com/go-redis/redis/v8 v8.11.5
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0
github.com/lib/pq v1.10.9
google.golang.org/grpc v1.56.2
google.golang.org/protobuf v1.30.0
)

require (
github.com/DATA-DOG/go-sqlmock v1.5.0 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/cznic/ql v1.2.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/golang/protobuf v1.5.3 // indirect
golang.org/x/net v0.9.0 // indirect
golang.org/x/sys v0.7.0 // indirect
golang.org/x/text v0.9.0 // indirect
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
)
174 changes: 174 additions & 0 deletions go.sum

Large diffs are not rendered by default.

25 changes: 25 additions & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package config

import (
"io/ioutil"
"os"
"strings"
)

// Setup environment from file .env
func Setup(file string) error {
data, err := ioutil.ReadFile(file)
if err != nil {
return err
}

datas := strings.Split(string(data), "\n")
for _, env := range datas {
e := strings.Split(env, "=")
if len(e) >= 2 {
os.Setenv(strings.TrimSpace(e[0]), strings.TrimSpace(strings.Join(e[1:], "=")))
}
}

return nil
}
87 changes: 87 additions & 0 deletions internal/middleware/context.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package middleware

import (
"context"

"ledger-service/internal/pkg/app"

"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
)

type Context struct{}

// Unary interceptor
func (a *Context) Unary() grpc.UnaryServerInterceptor {
return func(
ctx context.Context,
req interface{},
info *grpc.UnaryServerInfo,
handler grpc.UnaryHandler,
) (interface{}, error) {
var err error

ctx, err = a.context(ctx)
if err != nil {
return nil, err
}

return handler(ctx, req)
}
}

// Stream interceptor
func (a *Context) Stream() grpc.StreamServerInterceptor {
return func(
srv interface{},
stream grpc.ServerStream,
info *grpc.StreamServerInfo,
handler grpc.StreamHandler,
) error {
_, err := a.context(stream.Context())
if err != nil {
return err
}

return handler(srv, stream)
}
}

func (a Context) context(ctx context.Context) (context.Context, error) {
// Get token from incoming metadata
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return ctx, status.Errorf(codes.Unauthenticated, "metadata is not provided")
}

userId := md["user_id"]
if len(userId) == 0 {
return ctx, status.Errorf(codes.Unauthenticated, "user_id metadata is not provided")
}

universityId := md["university_id"]
if len(universityId) == 0 {
return ctx, status.Errorf(codes.Unauthenticated, "university_id metadata is not provided")
}

programStudiId := md["program_studi_id"]
if len(programStudiId) == 0 {
return ctx, status.Errorf(codes.Unauthenticated, "program_studi_id metadata is not provided")
}

ctx = context.WithValue(ctx, app.Ctx("user_id"), userId[0])
ctx = context.WithValue(ctx, app.Ctx("university_id"), universityId[0])
ctx = context.WithValue(ctx, app.Ctx("program_studi_id"), programStudiId[0])

// Set token to outgoing metadata
mdOutgoing := metadata.New(map[string]string{
"user_id": userId[0],
"university_id": universityId[0],
"program_studi_id": programStudiId[0],
})

ctx = metadata.NewOutgoingContext(ctx, mdOutgoing)
return ctx, nil
}
4 changes: 4 additions & 0 deletions internal/pkg/app/app.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package app

// Ctx type
type Ctx string
29 changes: 29 additions & 0 deletions internal/pkg/array/string.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package array

import "strings"

func RemoveByValue(arr []string, val string) []string {
for i, v := range arr {
if v == val {
return append(arr[:i], arr[(i+1):]...)
}
}

return arr
}

func ConvertToWhereIn(arr []string) string {
var rep []string
for _, _ = range arr {
rep = append(rep, "?")
}
return strings.Join(rep, ",")
}

func ConvertToAny(arr []string) []any {
var rep []any
for _, v := range arr {
rep = append(rep, v)
}
return rep
}
39 changes: 39 additions & 0 deletions internal/pkg/db/postgres/postgres.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package postgres

import (
"context"
"database/sql"
"fmt"
"os"
"strconv"
)

// Open database commection
func Open() (*sql.DB, error) {
var db *sql.DB
port, err := strconv.Atoi(os.Getenv("POSTGRES_PORT"))
if err != nil {
return db, err
}

return sql.Open("postgres",
fmt.Sprintf(
"host=%s port=%d user=%s password=%s dbname=%s sslmode=disable",
os.Getenv("POSTGRES_HOST"), port, os.Getenv("POSTGRES_USER"),
os.Getenv("POSTGRES_PASSWORD"), os.Getenv("POSTGRES_DB"),
),
)
}

// StatusCheck returns nil if it can successfully talk to the database. It
// returns a non-nil error otherwise.
func StatusCheck(ctx context.Context, db *sql.DB) error {

// Run a simple query to determine connectivity. The db has a "Ping" method
// but it can false-positive when it was previously able to talk to the
// database but the database has since gone away. Running this query forces a
// round trip to the database.
const q = `SELECT true`
var tmp bool
return db.QueryRowContext(ctx, q).Scan(&tmp)
}
76 changes: 76 additions & 0 deletions internal/pkg/db/redis/cache.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package redis

import (
"context"
"fmt"
"time"

"github.com/go-redis/redis/v8"
)

// Cache struct
type Cache struct {
client redis.UniversalClient
ttl time.Duration
}

const apqPrefix = ""

// NewCache to create new object Cache
func NewCache(ctx context.Context, redisAddress string, password string, ttl time.Duration) (*Cache, error) {
client := redis.NewClient(&redis.Options{
Addr: redisAddress,
Password: password, // no password set
DB: 0, // use default DB
})

err := client.Ping(ctx).Err()
if err != nil {
return nil, fmt.Errorf("could not create cache: %w", err)
}

return &Cache{client: client, ttl: ttl}, nil
}

// SetTTL cache
func (c *Cache) SetTTL(ttl time.Duration) {
c.ttl = ttl
}

// ResetTTL cache
func (c *Cache) ResetTTL() {
c.ttl = 24 * time.Hour
}

// Add cache
func (c *Cache) Add(ctx context.Context, key string, value interface{}) {
c.client.Set(ctx, apqPrefix+key, value, c.ttl)
}

// Get Cache
func (c *Cache) Get(ctx context.Context, key string) (interface{}, bool) {
s, err := c.client.Get(ctx, apqPrefix+key).Result()
if err != nil {
return struct{}{}, false
}
return s, true
}

// DeleteByPrefix cache
func (c *Cache) DeleteByPrefix(ctx context.Context, prefix string) error {
var err error
iter := c.client.Scan(ctx, 0, prefix+"*", 0).Iterator()
for iter.Next(ctx) {
err = c.Del(ctx, iter.Val())
if err != nil {
return err
}
}

return nil
}

// Del cache
func (c *Cache) Del(ctx context.Context, keys ...string) error {
return c.client.Del(ctx, keys...).Err()
}
16 changes: 16 additions & 0 deletions internal/route/route.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package route

import (
"database/sql"
"log"

"google.golang.org/grpc"

"ledger-service/internal/pkg/db/redis"
)

// GrpcRoute func
func GrpcRoute(grpcServer *grpc.Server, db *sql.DB, log *log.Logger, cache *redis.Cache) {
//quizServer := quizDomain.QuizService{Db: db, Cache: cache}
//quizPb.RegisterQuizzesServer(grpcServer, &quizServer)
}
Loading