1
0
mirror of https://github.com/ManyakRus/crud_generator.git synced 2025-01-01 13:21:20 +02:00

сделал готовый микросервис

This commit is contained in:
Nikitin Aleksandr 2023-11-20 18:02:26 +03:00
parent b92cff0e55
commit 34528a1aaf
74 changed files with 243 additions and 811 deletions

View File

@ -1,317 +0,0 @@
package db_lawsuit_status_types
import (
"context"
"errors"
"fmt"
"github.com/ManyakRus/starter/micro"
"github.com/ManyakRus/starter/postgres_gorm"
"gitlab.aescorp.ru/dsp_dev/claim/sync_service/pkg/db/constants"
"gitlab.aescorp.ru/dsp_dev/claim/sync_service/pkg/object_model/entities/lawsuit_status_types"
"gorm.io/gorm"
"time"
"github.com/ManyakRus/starter/contextmain"
)
// TableName - имя таблицы в БД Postgres
const TableName string = "lawsuit_status_types"
// Crud_DB - объект для CRUD операций через БД
type Crud_DB struct {
}
// Read - находит запись в БД по ID
func (crud Crud_DB) Read(m *lawsuit_status_types.LawsuitStatusType) error {
var err error
ctxMain := contextmain.GetContext()
ctx, ctxCancelFunc := context.WithTimeout(ctxMain, time.Second*time.Duration(constants.TIMEOUT_DB_SECONDS))
defer ctxCancelFunc()
err = crud.Read_ctx(ctx, m)
return err
}
// Read_ctx - находит запись в БД по ID
func (crud Crud_DB) Read_ctx(ctx context.Context, m *lawsuit_status_types.LawsuitStatusType) error {
var err error
if micro.ContextDone(ctx) == true {
err = context.Canceled
return err
}
id := int64(m.ID)
db := postgres_gorm.GetConnection()
db.WithContext(ctx)
tx := db.First(m, id)
err = tx.Error
return err
}
// Save - записывает новый или существующий объект в базу данных
func (crud Crud_DB) Save(m *lawsuit_status_types.LawsuitStatusType) error {
ctxMain := contextmain.GetContext()
ctx, ctxCancelFunc := context.WithTimeout(ctxMain, time.Second*time.Duration(constants.TIMEOUT_DB_SECONDS))
defer ctxCancelFunc()
err := crud.Save_ctx(ctx, m)
return err
}
// Save_ctx - записывает новый или существующий объект в базу данных
func (crud Crud_DB) Save_ctx(ctx context.Context, m *lawsuit_status_types.LawsuitStatusType) error {
var err error
if micro.ContextDone(ctx) == true {
err = context.Canceled
return err
}
is_create := !micro.BoolFromInt64(int64(m.ID))
err = crud.create_update_ctx(ctx, m, is_create)
return err
}
// Update - записывает существующий объект в базу данных
func (crud Crud_DB) Update(m *lawsuit_status_types.LawsuitStatusType) error {
ctxMain := contextmain.GetContext()
ctx, ctxCancelFunc := context.WithTimeout(ctxMain, time.Second*time.Duration(constants.TIMEOUT_DB_SECONDS))
defer ctxCancelFunc()
err := crud.Update_ctx(ctx, m)
return err
}
// Update_ctx - записывает существующий объект в базу данных
func (crud Crud_DB) Update_ctx(ctx context.Context, m *lawsuit_status_types.LawsuitStatusType) error {
var err error
if micro.ContextDone(ctx) == true {
err = context.Canceled
return err
}
err = crud.create_update_ctx(ctx, m, false)
return err
}
// Create - записывает новый объект в базу данных
func (crud Crud_DB) Create(m *lawsuit_status_types.LawsuitStatusType) error {
ctxMain := contextmain.GetContext()
ctx, ctxCancelFunc := context.WithTimeout(ctxMain, time.Second*time.Duration(constants.TIMEOUT_DB_SECONDS))
defer ctxCancelFunc()
err := crud.Create_ctx(ctx, m)
return err
}
// Create_ctx - записывает новый объект в базу данных
func (crud Crud_DB) Create_ctx(ctx context.Context, m *lawsuit_status_types.LawsuitStatusType) error {
var err error
if micro.ContextDone(ctx) == true {
err = context.Canceled
return err
}
err = crud.create_update_ctx(ctx, m, true)
return err
}
// create_update - записывает объект в базу данных
func (crud Crud_DB) create_update(m *lawsuit_status_types.LawsuitStatusType, is_create bool) error {
var err error
ctxMain := contextmain.GetContext()
ctx, ctxCancelFunc := context.WithTimeout(ctxMain, time.Second*time.Duration(constants.TIMEOUT_DB_SECONDS))
defer ctxCancelFunc()
err = crud.create_update_ctx(ctx, m, is_create)
return err
}
// create_update_ctx - записывает объект в базу данных
func (crud Crud_DB) create_update_ctx(ctx context.Context, m *lawsuit_status_types.LawsuitStatusType, is_create bool) error {
var err error
// log.Trace("start Save() ", TableName, " id: ", int64(m.ID))
if micro.ContextDone(ctx) == true {
err = context.Canceled
return err
}
// проверка ID
if is_create == true {
if int64(m.ID) != 0 {
TextError := fmt.Sprint("db.Save() ", TableName, " error: id !=0")
// log.Panic(sError)
err = errors.New(TextError)
return err
}
} else if int64(m.ID) == 0 {
TextError := fmt.Sprint("db.Save() ", TableName, " error: id =0")
err = errors.New(TextError)
// log.Panic(sError)
return err
}
//
db := postgres_gorm.GetConnection()
db.WithContext(ctx)
// заполним даты
//Text_modified_at
//Text_is_deleted_deleted_at
//колонки с null
tx := db
MassOmit := make([]string, 0)
var ColumnName string
//игнор пустых колонок
tx = tx.Omit(MassOmit...)
// запись
if is_create == true {
tx = tx.Create(&m)
} else {
tx = tx.Save(&m)
}
err = tx.Error
if err != nil {
return err
}
// запишем NULL в пустые колонки
for f := 0; f < len(MassOmit); f++ {
ColumnName := MassOmit[f]
tx = db.Model(&m).Update(ColumnName, gorm.Expr("NULL"))
err = tx.Error
if err != nil {
TextError := fmt.Sprint("db.Update() ", TableName, " id: ", m.ID, " error: ", err)
err = errors.New(TextError)
return err
// log.Panic(sError)
}
}
return err
}
// Delete - записывает is_deleted = true
func (crud Crud_DB) Delete(m *lawsuit_status_types.LawsuitStatusType) error {
var err error
ctxMain := contextmain.GetContext()
ctx, ctxCancelFunc := context.WithTimeout(ctxMain, time.Second*time.Duration(constants.TIMEOUT_DB_SECONDS))
defer ctxCancelFunc()
err = crud.Delete_ctx(ctx, m)
return err
}
// Delete_ctx - записывает is_deleted = true
func (crud Crud_DB) Delete_ctx(ctx context.Context, m *lawsuit_status_types.LawsuitStatusType) error {
var err error
if micro.ContextDone(ctx) == true {
err = context.Canceled
return err
}
m2 := lawsuit_status_types.LawsuitStatusType{}
m2.ID = m.ID
err = crud.Read_ctx(ctx, &m2)
if err != nil {
return err
}
m2.IsDeleted = true
m.IsDeleted = true
err = crud.Save_ctx(ctx, &m2)
return err
}
// Restore - записывает is_deleted = true
func (crud Crud_DB) Restore(m *lawsuit_status_types.LawsuitStatusType) error {
var err error
ctxMain := contextmain.GetContext()
ctx, ctxCancelFunc := context.WithTimeout(ctxMain, time.Second*time.Duration(constants.TIMEOUT_DB_SECONDS))
defer ctxCancelFunc()
err = crud.Restore_ctx(ctx, m)
return err
}
// Restore_ctx - записывает is_deleted = true
func (crud Crud_DB) Restore_ctx(ctx context.Context, m *lawsuit_status_types.LawsuitStatusType) error {
var err error
if micro.ContextDone(ctx) == true {
err = context.Canceled
return err
}
m2 := lawsuit_status_types.LawsuitStatusType{}
m2.ID = m.ID
err = crud.Read_ctx(ctx, &m2)
if err != nil {
return err
}
m2.IsDeleted = false
m.IsDeleted = false
err = crud.Save_ctx(ctx, &m2)
return err
}
// Find_ByExtID - находит запись в БД по ext_id и connection_id
func (crud Crud_DB) Find_ByExtID(m *lawsuit_status_types.LawsuitStatusType) error {
var err error
if m.ExtID == 0 {
err = errors.New("Error: ext_id =0")
return err
}
//
ctxMain := contextmain.GetContext()
ctx, ctxCancelFunc := context.WithTimeout(ctxMain, time.Second*time.Duration(constants.TIMEOUT_DB_SECONDS))
defer ctxCancelFunc()
err = crud.Find_ByExtID_ctx(ctx, m)
return err
}
// Find_ByExtID_ctx - находит запись в БД по ext_id и connection_id
func (crud Crud_DB) Find_ByExtID_ctx(ctx context.Context, m *lawsuit_status_types.LawsuitStatusType) error {
var err error
if micro.ContextDone(ctx) == true {
err = context.Canceled
return err
}
if m.ExtID == 0 {
err = errors.New("Error: ExtID=0")
return err
}
db := postgres_gorm.GetConnection()
db.WithContext(ctx)
tx := db.Where("ext_id = ?", m.ExtID).Where("connection_id = ?", m.ConnectionID).First(m)
err = tx.Error
return err
}

View File

@ -1,124 +0,0 @@
package db_lawsuit_status_types
import (
"github.com/ManyakRus/starter/config"
"github.com/ManyakRus/starter/postgres_gorm"
"gitlab.aescorp.ru/dsp_dev/claim/sync_service/pkg/object_model/entities/lawsuit_status_types"
"testing"
)
const Postgres_ID_Test = 1
func TestRead(t *testing.T) {
config.LoadEnv()
postgres_gorm.Connect()
defer postgres_gorm.CloseConnection()
crud := Crud_DB{}
Otvet := lawsuit_status_types.LawsuitStatusType{}
Otvet.ID = Postgres_ID_Test
err := crud.Read(&Otvet)
if err != nil {
t.Error("TestRead() error: ", err)
}
if Otvet.ID == 0 {
t.Error(TableName + "_test.TestRead() error ID=0 ")
} else {
t.Log(TableName+"_test.TestRead() Otvet: ", Otvet.ID)
}
}
func TestSave(t *testing.T) {
config.LoadEnv()
postgres_gorm.Connect()
defer postgres_gorm.CloseConnection()
crud := Crud_DB{}
Otvet := lawsuit_status_types.LawsuitStatusType{}
Otvet.ID = Postgres_ID_Test
err := crud.Read(&Otvet)
if err != nil {
t.Error("TestSave() error: ", err)
}
if Otvet.ID == 0 {
t.Error(TableName + "_test.TestSave() error ID=0 ")
}
err = crud.Save(&Otvet)
if err != nil {
t.Error("TestSave() error: ", err)
}
t.Log(TableName+"_test.TestSave() Otvet: ", Otvet.ID)
}
func TestDelete(t *testing.T) {
config.LoadEnv()
postgres_gorm.Connect()
defer postgres_gorm.CloseConnection()
crud := Crud_DB{}
Otvet := lawsuit_status_types.LawsuitStatusType{}
Otvet.ID = Postgres_ID_Test
err := crud.Read(&Otvet)
if err != nil {
t.Error("TestDelete() error: ", err)
}
if Otvet.IsDeleted == false {
err = crud.Delete(&Otvet)
if err != nil {
t.Error("TestDelete() error: ", err)
}
err = crud.Restore(&Otvet)
if err != nil {
t.Error("TestDelete() error: ", err)
}
} else {
err = crud.Restore(&Otvet)
if err != nil {
t.Error("TestDelete() error: ", err)
}
err = crud.Delete(&Otvet)
if err != nil {
t.Error("TestDelete() error: ", err)
}
}
}
func TestFind_ByExtID(t *testing.T) {
config.LoadEnv()
postgres_gorm.Connect()
defer postgres_gorm.CloseConnection()
crud := Crud_DB{}
Otvet := lawsuit_status_types.LawsuitStatusType{}
Otvet.ID = Postgres_ID_Test
err := crud.Read(&Otvet)
if err != nil {
t.Error("TestFind_ByExtID() error: ", err)
}
if Otvet.ExtID ==0 {
return
}
err = crud.Find_ByExtID(&Otvet)
if err != nil {
t.Error("TestFind_ByExtID() error: ", err)
}
if Otvet.ID == 0 {
t.Error("TestFind_ByExtID() error: ID =0")
}
}

View File

@ -1,5 +0,0 @@
if m.IsDeleted == true && m.DeletedAt.IsZero() == true {
m.DeletedAt = time.Now()
} else if m.IsDeleted == false && m.DeletedAt.IsZero() == false {
m.DeletedAt = time.Time{}
}

View File

@ -1 +0,0 @@
m.ModifiedAt = time.Now()

View File

@ -1,24 +0,0 @@
package calc_struct_version
import (
"github.com/ManyakRus/starter/micro"
"reflect"
)
// CalcStructVersion - вычисляет версию модели
func CalcStructVersion(t reflect.Type) uint32 {
var ReturnVar uint32
names := make([]string, t.NumField())
// имя + тип поля
s := ""
for i := range names {
s = s + t.Field(i).Name
s = s + t.Field(i).Type.Name()
}
ReturnVar = micro.Hash(s)
return ReturnVar
}

View File

@ -1,106 +0,0 @@
package grpc_client
import (
"github.com/ManyakRus/starter/contextmain"
"github.com/ManyakRus/starter/log"
"github.com/ManyakRus/starter/stopapp"
"gitlab.aescorp.ru/dsp_dev/claim/sync_service/pkg/db/constants"
"gitlab.aescorp.ru/dsp_dev/claim/sync_service/pkg/network/grpc/grpc_proto"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"os"
"strings"
)
type SettingsINI struct {
SYNC_SERVICE_HOST string
SYNC_SERVICE_PORT string
}
var Settings SettingsINI
var Conn *grpc.ClientConn
var Client grpc_proto.SyncServiceClient
func Connect() {
var err error
if Settings.SYNC_SERVICE_HOST == "" {
FillSettings()
}
addr := Settings.SYNC_SERVICE_HOST + ":" + Settings.SYNC_SERVICE_PORT
Conn, err = grpc.Dial(addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatalf("did not connect: %v", err)
}
log.Info("GRPC client connected. Address: ", addr)
Client = grpc_proto.NewSyncServiceClient(Conn)
}
func FillSettings() {
Settings = SettingsINI{}
Settings.SYNC_SERVICE_HOST = os.Getenv("SYNC_SERVICE_HOST")
Settings.SYNC_SERVICE_PORT = os.Getenv("SYNC_SERVICE_PORT")
if Settings.SYNC_SERVICE_HOST == "" {
log.Panic("Need fill SYNC_SERVICE_HOST ! in OS Environment ")
}
if Settings.SYNC_SERVICE_PORT == "" {
log.Panic("Need fill SYNC_SERVICE_PORT ! in OS Environment ")
}
}
// WaitStop - ожидает отмену глобального контекста
func WaitStop() {
select {
case <-contextmain.GetContext().Done():
log.Warn("Context app is canceled. grpc_connect")
}
// ждём пока отправляемых сейчас сообщений будет =0
stopapp.WaitTotalMessagesSendingNow("sync_service_client")
// закрываем соединение
CloseConnection()
stopapp.GetWaitGroup_Main().Done()
}
// Start - необходимые процедуры для запуска сервера GRPC
func Start() {
Connect()
stopapp.GetWaitGroup_Main().Add(1)
go WaitStop()
}
func CloseConnection() {
err := Conn.Close()
if err != nil {
log.Panic("GRPC client CloseConnection() error: ", err)
} else {
log.Info("GRPC client connection closed")
}
}
// IsRecordNotFound - возвращает true если ошибка = "record not found"
func IsRecordNotFound(err error) bool {
Otvet := false
if err == nil {
return Otvet
}
TextErr := err.Error()
pos1 := strings.Index(TextErr, constants.TEXT_RECORD_NOT_FOUND)
if pos1 >= 0 {
Otvet = true
}
return Otvet
}

View File

@ -1,23 +0,0 @@
package grpc_client
import (
"errors"
"gitlab.aescorp.ru/dsp_dev/claim/sync_service/pkg/db/constants"
"testing"
)
func TestIsRecordNotFound(t *testing.T) {
err := errors.New(constants.TEXT_RECORD_NOT_FOUND + " !")
Otvet := IsRecordNotFound(err)
if Otvet != true {
t.Error("TestIsRecordNotFound() error: false")
}
err = errors.New("rpc error: code = Unknown desc = record not found")
Otvet = IsRecordNotFound(err)
if Otvet != true {
t.Error("TestIsRecordNotFound() error: false")
}
}

View File

@ -1,123 +0,0 @@
#---------------------Database settings---------------------
#DB_HOST - Database server name or ip-address. Only Postgres SQL
DB_HOST=""
#DB_NAME - Database table name
DB_NAME=""
#DB_SCHEME - Database schema name
DB_SCHEME="public"
#DB_PORT - Database port number
DB_PORT="5432"
#DB_USER - Database login (user)
DB_USER=""
#DB_PASSWORD - Database login password
DB_PASSWORD=""
#INCLUDE_TABLES - table name filter. Regular expression
INCLUDE_TABLES=""
#INCLUDE_TABLES - table name ignore filter. Regular expression
EXCLUDE_TABLES="ext_"
#---------------------Template settings---------------------
#SERVICE_REPOSITORY_URL - URL of your new service repository (Github, gitlab, etc.)
SERVICE_REPOSITORY_URL="gitlab.aescorp.ru/dsp_dev/claim/sync_service"
#SERVICE_NAME - service name for your new service
SERVICE_NAME=sync_service
#TEMPLATE_SERVICE_NAME - service name in your template files
TEMPLATE_SERVICE_NAME=Sync_service
#same folder names uses for read template files and create new files
#TEMPLATE_FOLDERNAME - filder name in your computer with templates
TEMPLATE_FOLDERNAME="templates"
#TEMPLATE_FOLDERNAME_MODEL - folder name for create models (golang struct with all table fields)
TEMPLATE_FOLDERNAME_MODEL="pkg/object_model/entities"
#TEMPLATE_FOLDERNAME_DB - folder name for create database crud operations
TEMPLATE_FOLDERNAME_DB="pkg/db"
#TEMPLATE_FOLDERNAME_GRPC - folder name for create .proto file
TEMPLATE_FOLDERNAME_GRPC="pkg/network/grpc"
#TEMPLATE_FOLDERNAME_GRPC_SERVER - filder name for create grpc server files
TEMPLATE_FOLDERNAME_GRPC_SERVER="internal/server_grpc"
#TEMPLATE_FOLDERNAME_GRPC_CLIENT - folder name for create grpc client files
TEMPLATE_FOLDERNAME_GRPC_CLIENT="grpc_client"
#TEMPLATE_FOLDERNAME_NRPC_SERVER - folder name for create nrpc server files
TEMPLATE_FOLDERNAME_NRPC_SERVER="internal/server_nrpc"
#TEMPLATE_FOLDERNAME_NRPC - folder name for create common nrpc files
TEMPLATE_FOLDERNAME_NRPC="pkg/network/nrpc"
#TEMPLATE_FOLDERNAME_NRPC_CLIENT - foldr name for create nrpc client files
TEMPLATE_FOLDERNAME_NRPC_CLIENT="pkg/network/nrpc/nrpc_client"
#TEMPLATE_FOLDERNAME_CRUD_STARTER - folder name for create crud_starter.go file
TEMPLATE_FOLDERNAME_CRUD_STARTER="pkg/crud_starter"
#TEMPLATE_FOLDERNAME_ALIAS - folder name for create alias.go file, with go types with aliases
TEMPLATE_FOLDERNAME_ALIAS="pkg/object_model/types/alias"
#TEXT_TEMPLATE_MODEL - model name text in templates, for replace to new model names
TEXT_TEMPLATE_MODEL="LawsuitStatusType"
#TEXT_TEMPLATE_TABLENAME - table name text in tamplates, for replace to new table names
TEXT_TEMPLATE_TABLENAME="lawsuit_status_types"
#USE_DEFAULT_TEMPLATE - "true" will do default text replaces. "false" - for use own templates
USE_DEFAULT_TEMPLATE=true
#HAS_IS_DELETED - fill "true" if you have "is_deleted" column, and want Delete() Restore() functions.
HAS_IS_DELETED=true
#---------------------Create files settings---------------------
#NEED_CREATE_CRUD - fill "true" if you want create crud operations files
NEED_CREATE_CRUD=true
#NEED_CREATE_GRPC - fill "true" if you want create grpc operations files
NEED_CREATE_GRPC=true
#NEED_CREATE_NRPC - fill "true" if you want create nrpc operations files (need NATS message query server)
NEED_CREATE_NRPC=true
#NEED_CREATE_MODEL_STRUCT - fill "true" if you want create model files with golang struct with all database fields
NEED_CREATE_MODEL_STRUCT=true
#NEED_CREATE_MODEL_CRUD - fill "true" if you want create crud operations in model files
NEED_CREATE_MODEL_CRUD=true
#NEED_CREATE_DB_TEST - fill "true" if you want create DB _test.go files
NEED_CREATE_DB_TEST=true
#NEED_CREATE_GRPC_SERVER_TEST - fill "true" if you want create GRPC server _test.go files
NEED_CREATE_GRPC_SERVER_TEST=true
#NEED_CREATE_GRPC_CLIENT_TEST - fill "true" if you want create GRPC client _test.go files
NEED_CREATE_GRPC_CLIENT_TEST=true
#NEED_CREATE_NRPC_SERVER_TEST - fill "true" if you want create NRPC server _test.go files
NEED_CREATE_NRPC_SERVER_TEST=true
#NEED_CREATE_NRPC_CLIENT_TEST - fill "true" if you want create NRPC client _test.go files
NEED_CREATE_NRPC_CLIENT_TEST=true
#PREFIX_SERVER_GRPC - filename prefix for grpc server files
PREFIX_SERVER_GRPC="server_grpc_"
#COMMENT_MODEL_STRUCT - fill comments to every model structure
COMMENT_MODEL_STRUCT="модель для таблицы "
#TEXT_MODULE_GENERATED - comment text for every module file
TEXT_MODULE_GENERATED="//File generated automatic with crud_generator app\n//Do not change anything here.\n"

View File

@ -0,0 +1,21 @@
STAGE="local"
LOG_LEVEL="debug"
DB_HOST=""
DB_NAME=""
DB_SCHEME=""
DB_PORT=""
DB_USER=""
DB_PASSWORD=""
NATS_HOST=""
NATS_PORT=""
NATS_LOGIN=""
NATS_PASSWORD=""
SYNC_SERVICE_HOST=localhost
SYNC_SERVICE_PORT=30031
GRPC_PORT=30031

View File

@ -5,6 +5,8 @@ FILEMAIN=./cmd/$(SERVICENAME)/main.go
FILEAPP=./bin/$(SERVICENAME)
NEW_REPO=$(SERVICENAME)
CD_GENERATION_PROTO=cd ./pkg/network/grpc
GENERATION_PROTO=generation_code.sh
run:
@ -52,4 +54,6 @@ conn:
init:
clear
go mod init gitlab.aescorp.ru/dsp_dev/claim/sync_service
$(CD_GENERATION_PROTO)
$(GENERATION_PROTO)
go mod tidy

View File

@ -4,6 +4,7 @@ import (
configmain "github.com/ManyakRus/starter/config"
"github.com/ManyakRus/starter/stopapp"
"gitlab.aescorp.ru/dsp_dev/claim/sync_service/pkg/crud_starter"
"github.com/ManyakRus/starter/postgres_gorm"
)
// main - старт приложения

View File

@ -6,10 +6,10 @@ DB_HOST=""
DB_NAME=""
#DB_SCHEME - Database schema name
DB_SCHEME="public"
DB_SCHEME=""
#DB_PORT - Database port number
DB_PORT="5432"
DB_PORT=""
#DB_USER - Database login (user)
DB_USER=""
@ -64,13 +64,13 @@ TEMPLATE_FOLDERNAME_GRPC_PROTO="pkg/network/grpc"
TEMPLATE_FOLDERNAME_GRPC="pkg/network/grpc"
#TEMPLATE_FOLDERNAME_GRPC_SERVER - filder name for create grpc server files
TEMPLATE_FOLDERNAME_GRPC_SERVER="internal/server_grpc"
TEMPLATE_FOLDERNAME_GRPC_SERVER="internal/app/grpc/server_grpc"
#TEMPLATE_FOLDERNAME_GRPC_CLIENT - folder name for create grpc client files
TEMPLATE_FOLDERNAME_GRPC_CLIENT="pkg/network/grpc/grpc_client"
#TEMPLATE_FOLDERNAME_NRPC_SERVER - folder name for create nrpc server files
TEMPLATE_FOLDERNAME_NRPC_SERVER="internal/server_nrpc"
TEMPLATE_FOLDERNAME_NRPC_SERVER="internal/app/nrpc/server_nrpc"
#TEMPLATE_FOLDERNAME_NRPC - folder name for create common nrpc files
TEMPLATE_FOLDERNAME_NRPC="pkg/network/nrpc"

View File

@ -2,6 +2,7 @@ package server_grpc
import (
"context"
"github.com/ManyakRus/starter/micro"
"gitlab.aescorp.ru/dsp_dev/claim/sync_service/pkg/network/grpc/grpc_proto"
"gitlab.aescorp.ru/dsp_dev/claim/sync_service/pkg/object_model/entities/lawsuit_status_types"
)

View File

@ -1,4 +1,4 @@
package server_grpc
package server_nrpc
import (
"github.com/ManyakRus/starter/contextmain"
@ -65,9 +65,19 @@ func Connect() {
// FillSettings - заполняет настройки из переменных окружения
func FillSettings() {
Settings = SettingsINI{}
Settings.NATS_HOST = os.Getenv("BUS_LOCAL_HOST")
Settings.NATS_PORT = os.Getenv("BUS_LOCAL_PORT")
Settings.NATS_HOST = os.Getenv("NATS_HOST")
Settings.NATS_PORT = os.Getenv("NATS_PORT")
//синонимы
if Settings.NATS_HOST == "" {
Settings.NATS_HOST = os.Getenv("BUS_LOCAL_HOST")
}
if Settings.NATS_PORT == "" {
Settings.NATS_PORT = os.Getenv("BUS_LOCAL_PORT")
}
//проверка
if Settings.NATS_HOST == "" {
log.Panic("Need fill BUS_LOCAL_HOST ! in OS Environment ")
}

View File

@ -1,4 +1,4 @@
package server_grpc
package server_nrpc
import (
config_main "github.com/ManyakRus/starter/config"

View File

@ -0,0 +1,124 @@
package nrpc_client
import (
"github.com/ManyakRus/starter/contextmain"
"github.com/ManyakRus/starter/log"
"github.com/ManyakRus/starter/stopapp"
"github.com/nats-io/nats.go"
"gitlab.aescorp.ru/dsp_dev/claim/sync_service/pkg/db/constants"
"gitlab.aescorp.ru/dsp_dev/claim/sync_service/pkg/network/grpc/grpc_proto"
"os"
"strings"
"time"
)
type SettingsINI struct {
NATS_HOST string
NATS_PORT string
}
var Settings SettingsINI
// Conn - подключение к NATS
var Conn *nats.Conn
// Client - подключение к клиенту NRPC
var Client *grpc_proto.Sync_serviceClient
// Connect - подключается к NATS
func Connect() {
var err error
if Settings.NATS_HOST == "" {
FillSettings()
}
NatsURL := "nats://" + Settings.NATS_HOST + ":" + Settings.NATS_PORT
// Connect to the NATS server.
Conn, err = nats.Connect(NatsURL, nats.Timeout(5*time.Second))
if err != nil {
log.Panic(err)
}
// defer Conn.Close()
// This is our generated client.
Client = grpc_proto.NewSync_serviceClient(Conn)
log.Info("Client NRPC connected: ", NatsURL)
}
// FillSettings - заполняет настройки из переменных окружения
func FillSettings() {
Settings = SettingsINI{}
Settings.NATS_HOST = os.Getenv("NATS_HOST")
Settings.NATS_PORT = os.Getenv("NATS_PORT")
//синонимы
if Settings.NATS_HOST == "" {
Settings.NATS_HOST = os.Getenv("BUS_LOCAL_HOST")
}
if Settings.NATS_PORT == "" {
Settings.NATS_PORT = os.Getenv("BUS_LOCAL_PORT")
}
if Settings.NATS_HOST == "" {
log.Panic("Need fill BUS_LOCAL_HOST ! in OS Environment ")
}
if Settings.NATS_PORT == "" {
log.Panic("Need fill BUS_LOCAL_PORT ! in OS Environment ")
}
}
// WaitStop - ожидает отмену глобального контекста
func WaitStop() {
select {
case <-contextmain.GetContext().Done():
log.Warn("Context app is canceled. nrpc client connect")
}
// ждём пока отправляемых сейчас сообщений будет =0
stopapp.WaitTotalMessagesSendingNow("sync_service_client")
// закрываем соединение
CloseConnection()
stopapp.GetWaitGroup_Main().Done()
}
// Start - необходимые процедуры для запуска сервера NRPC
func Start() {
Connect()
stopapp.GetWaitGroup_Main().Add(1)
go WaitStop()
}
// CloseConnection - закрывает подключение к NATS
func CloseConnection() {
Conn.Close()
log.Info("NRPC client connection closed")
}
// IsRecordNotFound - возвращает true если ошибка = "record not found"
func IsRecordNotFound(err error) bool {
Otvet := false
if err == nil {
return Otvet
}
// len1 := len(constants.TEXT_RECORD_NOT_FOUND)
TextErr := err.Error()
pos1 := strings.Index(TextErr, constants.TEXT_RECORD_NOT_FOUND)
// if TextErr[0:len1] == constants.TEXT_RECORD_NOT_FOUND {
if pos1 >= 0 {
Otvet = true
}
return Otvet
}

View File

@ -0,0 +1,13 @@
package nrpc_client
import (
"github.com/ManyakRus/starter/config"
"testing"
)
func TestConnect(t *testing.T) {
config.LoadEnv()
FillSettings()
Connect()
CloseConnection()
}

View File

@ -43,7 +43,7 @@ func CreateFileCrudStarter(MapAll map[string]*types.Table) error {
DirReadyCrudStarter := DirReady + config.Settings.TEMPLATE_FOLDERNAME_CRUD_STARTER + micro.SeparatorFile()
FilenameReadyCrudStarter := DirReadyCrudStarter + "crud_starter.go"
//создадим папку ready
//создадим папку готовых файлов
folders.CreateFolder(DirReadyCrudStarter)
//

View File

@ -136,15 +136,15 @@ func CreateTestFiles(Table1 *types.Table) error {
DirReadyTable := DirReadyDB + config.Settings.PREFIX_CRUD + TableName
FilenameReadyDB := DirReadyTable + micro.SeparatorFile() + config.Settings.PREFIX_CRUD + TableName + "_test.go"
//создадим папку готовых файлов
folders.CreateFolder(DirReadyTable)
bytes, err := os.ReadFile(FilenameTemplateDB)
if err != nil {
log.Panic("ReadFile() ", FilenameTemplateDB, " error: ", err)
}
TextDB := string(bytes)
//создадим папку ready
folders.CreateFolder(DirReadyTable)
//заменим имя пакета на новое
create_files.ReplacePackageName(TextDB, DirReadyTable)

View File

@ -42,7 +42,7 @@ func CreateFiles(Table1 *types.Table) error {
DirTemplatesTable := DirTemplates + config.Settings.TEMPLATE_FOLDERNAME_TABLES + micro.SeparatorFile()
DirReadyTable := DirReady + config.Settings.TEMPLATE_FOLDERNAME_TABLES + micro.SeparatorFile() + config.Settings.PREFIX_TABLE + TableName + micro.SeparatorFile()
//создадим папку ready
//создадим папку готовых файлов
folders.CreateFolder(DirReadyTable)
// создание файла struct

View File

@ -41,7 +41,7 @@ func CreateENV() error {
FilenameReadyENV := DirReadyENV + constants.ENV_FILENAME
FilenameTemplateENV := DirTemplatesMakefile + constants.ENV_FILENAME + "_"
//создадим папку ready
//создадим папку готовых файлов
folders.CreateFolder(DirReadyENV)
//не стираем файл .env

View File

@ -47,15 +47,15 @@ func CreateFileGenerationCodeSh() error {
FilenameReadyProto := DirReadyProto + "generation_code.sh"
FilenameTemplateProto := DirTemplatesProto + "generation_code.sh_"
//создадим папку готовых файлов proto
folders.CreateFolder(DirReadyProto)
bytes, err := os.ReadFile(FilenameTemplateProto)
if err != nil {
log.Panic("ReadFile() ", FilenameTemplateProto, " error: ", err)
}
TextGenerationCode := string(bytes)
//создадим папку ready proto
folders.CreateFolder(DirReadyProto)
//replace
TextGenerationCode = strings.ReplaceAll(TextGenerationCode, config.Settings.TEMPLATE_SERVICE_NAME, config.Settings.SERVICE_NAME)

View File

@ -47,6 +47,9 @@ func CreateGRPCClient() error {
FilenameReadyMain := DirReadyClientGRPC + constants.GRPC_CLIENT_FILENAME
FilenameTemplateMain := DirTemplatesClientGRPC + constants.GRPC_CLIENT_FILENAME + "_"
//создадим папку готовых файлов
folders.CreateFolder(DirReadyClientGRPC)
bytes, err := os.ReadFile(FilenameTemplateMain)
if err != nil {
log.Panic("ReadFile() ", FilenameTemplateMain, " error: ", err)
@ -73,9 +76,6 @@ func CreateGRPCClient() error {
TextGRPCClient = strings.ReplaceAll(TextGRPCClient, strings.ToUpper(ServiceNameTemplate), strings.ToUpper(ServiceName))
}
//создадим папку ready
folders.CreateFolder(DirReadyClientGRPC)
//заменим имя сервиса на новое
ServiceNameTemplate := config.Settings.TEMPLATE_SERVICE_NAME
ServiceName := config.Settings.SERVICE_NAME
@ -107,15 +107,15 @@ func CreateGRPCClientTest() error {
FilenameReadyMain := DirReadyClientGRPC + constants.GRPC_CLIENT_TEST_FILENAME
FilenameTemplateMain := DirTemplatesClientGRPC + constants.GRPC_CLIENT_TEST_FILENAME + "_"
//создадим папку готовых файлов
folders.CreateFolder(DirReadyClientGRPC)
bytes, err := os.ReadFile(FilenameTemplateMain)
if err != nil {
log.Panic("ReadFile() ", FilenameTemplateMain, " error: ", err)
}
TextGRPCClient := string(bytes)
//создадим папку ready
folders.CreateFolder(DirReadyClientGRPC)
//заменим имя пакета на новое
create_files.ReplacePackageName(TextGRPCClient, DirReadyClientGRPC)
@ -134,9 +134,6 @@ func CreateGRPCClientTest() error {
}
//создадим папку ready
folders.CreateFolder(DirReadyClientGRPC)
//заменим имя сервиса на новое
ServiceNameTemplate := config.Settings.TEMPLATE_SERVICE_NAME
ServiceName := config.Settings.SERVICE_NAME

View File

@ -60,15 +60,15 @@ func CreateFiles(Table1 *types.Table) error {
DirReadyTable := DirReadyGRPCClient + "grpc_" + TableName + micro.SeparatorFile()
FilenameReadyGRPCClient := DirReadyTable + "grpc_" + TableName + ".go"
//создадим папку готовых файлов
folders.CreateFolder(DirReadyTable)
bytes, err := os.ReadFile(FilenameTemplateGRPCClient)
if err != nil {
log.Panic("ReadFile() ", FilenameTemplateGRPCClient, " error: ", err)
}
TextGRPCClient := string(bytes)
//создадим папку ready
folders.CreateFolder(DirReadyTable)
//заменим имя пакета на новое
create_files.ReplacePackageName(TextGRPCClient, DirReadyTable)
@ -143,15 +143,15 @@ func CreateTestFiles(Table1 *types.Table) error {
DirReadyTable := DirReadyGRPCClient + "grpc_" + TableName + micro.SeparatorFile()
FilenameReadyGRPCClient := DirReadyTable + "grpc_" + TableName + "_test.go"
//создадим папку готовых файлов
folders.CreateFolder(DirReadyTable)
bytes, err := os.ReadFile(FilenameTemplateGRPCClient)
if err != nil {
log.Panic("ReadFile() ", FilenameTemplateGRPCClient, " error: ", err)
}
TextGRPCClient := string(bytes)
//создадим папку ready
folders.CreateFolder(DirReadyTable)
//заменим имя пакета на новое
create_files.ReplacePackageName(TextGRPCClient, DirReadyTable)

View File

@ -59,15 +59,15 @@ func CreateFiles(Table1 *types.Table) error {
DirReadyTable := DirReadyGRPCServer
FilenameReadyGRPCServer := DirReadyTable + config.Settings.PREFIX_SERVER_GRPC + TableName + ".go"
//создадим папку готовых файлов
folders.CreateFolder(DirReadyTable)
bytes, err := os.ReadFile(FilenameTemplateGRPCServer)
if err != nil {
log.Panic("ReadFile() ", FilenameTemplateGRPCServer, " error: ", err)
}
TextGRPCServer := string(bytes)
//создадим папку ready
folders.CreateFolder(DirReadyTable)
//заменим имя пакета на новое
create_files.ReplacePackageName(TextGRPCServer, DirReadyTable)
@ -133,15 +133,15 @@ func CreateTestFiles(Table1 *types.Table) error {
DirReadyTable := DirReadyGRPCServer
FilenameReadyGRPCServer := DirReadyTable + config.Settings.PREFIX_SERVER_GRPC + TableName + "_test.go"
//создадим папку готовых файлов
folders.CreateFolder(DirReadyTable)
bytes, err := os.ReadFile(FilenameTemplateGRPCServer)
if err != nil {
log.Panic("ReadFile() ", FilenameTemplateGRPCServer, " error: ", err)
}
TextGRPCServer := string(bytes)
//создадим папку ready
folders.CreateFolder(DirReadyTable)
//заменим имя пакета на новое
create_files.ReplacePackageName(TextGRPCServer, DirReadyTable)

View File

@ -37,15 +37,15 @@ func CreateFileMain() error {
FilenameReadyMain := DirReadyMain + "main.go"
FilenameTemplateMain := DirTemplatesMain + "main.go_"
//создадим папку готовых файлов
folders.CreateFolder(DirReadyMain)
bytes, err := os.ReadFile(FilenameTemplateMain)
if err != nil {
log.Panic("ReadFile() ", FilenameTemplateMain, " error: ", err)
}
TextMain := string(bytes)
//создадим папку ready
folders.CreateFolder(DirReadyMain)
//
if config.Settings.USE_DEFAULT_TEMPLATE == true {
TextMain = create_files.DeleteTemplateRepositoryImports(TextMain)

View File

@ -40,15 +40,15 @@ func CreateMakefile() error {
FilenameReadyMakefile := DirReadyMakefile + constants.MAKEFILE_FILENAME
FilenameTemplateMakefile := DirTemplatesMakefile + constants.MAKEFILE_FILENAME + "_"
//создадим папку готовых файлов
folders.CreateFolder(DirReadyMakefile)
bytes, err := os.ReadFile(FilenameTemplateMakefile)
if err != nil {
log.Panic("ReadFile() ", FilenameTemplateMakefile, " error: ", err)
}
TextMakefile := string(bytes)
//создадим папку ready
folders.CreateFolder(DirReadyMakefile)
//ReplaceAll
TextMakefile = strings.ReplaceAll(TextMakefile, config.Settings.TEMPLATE_SERVICE_NAME, strings.ToLower(config.Settings.SERVICE_NAME))

View File

@ -42,7 +42,7 @@ func CreateFiles(Table1 *types.Table) error {
DirTemplatesModel := DirTemplates + config.Settings.TEMPLATE_FOLDERNAME_MODEL + micro.SeparatorFile()
DirReadyModel := DirReady + config.Settings.TEMPLATE_FOLDERNAME_MODEL + micro.SeparatorFile() + TableName + micro.SeparatorFile()
//создадим папку ready
//создадим папку готовых файлов
folders.CreateFolder(DirReadyModel)
// создание файла struct
@ -75,6 +75,9 @@ func CreateFilesModel_struct(Table1 *types.Table, DirTemplatesModel, DirReadyMod
FilenameTemplateModel := DirTemplatesModel + "model.go_"
FilenameReadyModel := DirReadyModel + config.Settings.PREFIX_MODEL + TableName + ".go"
//создадим папку готовых файлов
folders.CreateFolder(DirReadyModel)
//чтение файла шаблона
bytes, err := os.ReadFile(FilenameTemplateModel)
if err != nil {
@ -82,9 +85,6 @@ func CreateFilesModel_struct(Table1 *types.Table, DirTemplatesModel, DirReadyMod
}
TextModel := string(bytes)
//создадим папку ready
folders.CreateFolder(DirReadyModel)
//заменим имя пакета на новое
create_files.ReplacePackageName(TextModel, DirReadyModel)

View File

@ -47,15 +47,15 @@ func CreateNRPCClient() error {
FilenameReadyNRPC := DirReadyClientNRPC + constants.NRPC_CLIENT_FILENAME
FilenameTemplateNRPC := DirTemplatesClientNRPC + constants.NRPC_CLIENT_FILENAME + "_"
//создадим папку готовых файлов
folders.CreateFolder(DirReadyClientNRPC)
bytes, err := os.ReadFile(FilenameTemplateNRPC)
if err != nil {
log.Panic("ReadFile() ", FilenameTemplateNRPC, " error: ", err)
}
TextNRPCClient := string(bytes)
//создадим папку ready
folders.CreateFolder(FilenameReadyNRPC)
//заменим имя пакета на новое
create_files.ReplacePackageName(TextNRPCClient, DirReadyClientNRPC)
@ -73,9 +73,6 @@ func CreateNRPCClient() error {
TextNRPCClient = create_files.AddImport(TextNRPCClient, DBConstantsURL)
}
//создадим папку ready
folders.CreateFolder(DirReadyClientNRPC)
//заменим имя сервиса на новое
ServiceNameTemplate := config.Settings.TEMPLATE_SERVICE_NAME
ServiceName := config.Settings.SERVICE_NAME
@ -107,15 +104,15 @@ func CreateNRPCClientTest() error {
FilenameReadyNRPC := DirReadyClientNRPC + constants.NRPC_CLIENT_TEST_FILENAME
FilenameTemplateNRPC := DirTemplatesClientNRPC + constants.NRPC_CLIENT_TEST_FILENAME + "_"
//создадим папку готовых файлов
folders.CreateFolder(DirReadyClientNRPC)
bytes, err := os.ReadFile(FilenameTemplateNRPC)
if err != nil {
log.Panic("ReadFile() ", FilenameTemplateNRPC, " error: ", err)
}
TextNRPCClient := string(bytes)
//создадим папку ready
folders.CreateFolder(FilenameReadyNRPC)
//заменим имя пакета на новое
create_files.ReplacePackageName(TextNRPCClient, DirReadyClientNRPC)
@ -130,9 +127,6 @@ func CreateNRPCClientTest() error {
// TextNRPCClient = create_files.AddImport(TextNRPCClient, DBConstantsURL)
//}
//создадим папку ready
folders.CreateFolder(DirReadyClientNRPC)
//заменим имя сервиса на новое
ServiceNameTemplate := config.Settings.TEMPLATE_SERVICE_NAME
ServiceName := config.Settings.SERVICE_NAME

View File

@ -59,15 +59,15 @@ func CreateFiles(Table1 *types.Table) error {
DirReadyTable := DirReadyNRPCClient + "nrpc_" + TableName + micro.SeparatorFile()
FilenameReadyNRPCClient := DirReadyTable + "nrpc_" + TableName + ".go"
//создадим папку готовых файлов
folders.CreateFolder(DirReadyTable)
bytes, err := os.ReadFile(FilenameTemplateNRPCClient)
if err != nil {
log.Panic("ReadFile() ", FilenameTemplateNRPCClient, " error: ", err)
}
TextNRPCClient := string(bytes)
//создадим папку ready
folders.CreateFolder(DirReadyTable)
//заменим имя пакета на новое
create_files.ReplacePackageName(TextNRPCClient, DirReadyTable)
@ -127,15 +127,15 @@ func CreateTestFiles(Table1 *types.Table) error {
DirReadyTable := DirReadyNRPCClient + "nrpc_" + TableName + micro.SeparatorFile()
FilenameReadyNRPCClient := DirReadyTable + "nrpc_" + TableName + "_test.go"
//создадим папку готовых файлов
folders.CreateFolder(DirReadyTable)
bytes, err := os.ReadFile(FilenameTemplateNRPCClient)
if err != nil {
log.Panic("ReadFile() ", FilenameTemplateNRPCClient, " error: ", err)
}
TextNRPCClient := string(bytes)
//создадим папку ready
folders.CreateFolder(DirReadyTable)
//заменим имя пакета на новое
create_files.ReplacePackageName(TextNRPCClient, DirReadyTable)

View File

@ -38,6 +38,9 @@ func CreateFileProto(MapAll map[string]*types.Table) error {
DirReadyProto := DirReady + config.Settings.TEMPLATE_FOLDERNAME_GRPC_PROTO + micro.SeparatorFile()
FilenameReadyProto := DirReadyProto + config.Settings.SERVICE_NAME + ".proto"
//создадим папку готовых файлов
folders.CreateFolder(DirReadyProto)
FilenameTemplateProto := DirTemplatesProto + "service.proto_"
bytes, err := os.ReadFile(FilenameTemplateProto)
if err != nil {
@ -45,9 +48,6 @@ func CreateFileProto(MapAll map[string]*types.Table) error {
}
TextProto := string(bytes)
//создадим папку ready
folders.CreateFolder(DirReadyProto)
//заменим название сервиса
ServiceName := config.Settings.SERVICE_NAME
ServiceNameProto := micro.StringFromUpperCase(ServiceName)

View File

@ -40,21 +40,18 @@ func CreateServerGRPCFunc() error {
FilenameReadyServerGRPCFunc := DirReadyServerGRPC + constants.SERVER_GRPC_FUNC_FILENAME
FilenameTemplateServerGRPCFunc := DirTemplatesServerGRPC + constants.SERVER_GRPC_FUNC_FILENAME + "_"
//создадим папку готовых файлов
folders.CreateFolder(DirReadyServerGRPC)
bytes, err := os.ReadFile(FilenameTemplateServerGRPCFunc)
if err != nil {
log.Panic("ReadFile() ", FilenameTemplateServerGRPCFunc, " error: ", err)
}
TextGRPCFunc := string(bytes)
//создадим папку ready
folders.CreateFolder(DirReadyServerGRPC)
//заменим имя пакета на новое
create_files.ReplacePackageName(TextGRPCFunc, DirReadyServerGRPC)
//создадим папку ready
folders.CreateFolder(DirReadyServerGRPC)
////заменим имя сервиса на новое
//ServiceNameTemplate := config.Settings.TEMPLATE_SERVICE_NAME
//ServiceName := config.Settings.SERVICE_NAME

View File

@ -41,21 +41,18 @@ func CreateServerGRPCStarter() error {
FilenameReadyMain := DirReadyServerGRPC + constants.SERVER_GRPC_STARTER_FILENAME
FilenameTemplateMain := DirTemplatesServerGRPC + constants.SERVER_GRPC_STARTER_FILENAME + "_"
//создадим папку готовых файлов
folders.CreateFolder(DirReadyServerGRPC)
bytes, err := os.ReadFile(FilenameTemplateMain)
if err != nil {
log.Panic("ReadFile() ", FilenameTemplateMain, " error: ", err)
}
TextGRPCStarter := string(bytes)
//создадим папку ready
folders.CreateFolder(DirReadyServerGRPC)
//заменим имя пакета на новое
create_files.ReplacePackageName(TextGRPCStarter, DirReadyServerGRPC)
//создадим папку ready
folders.CreateFolder(DirReadyServerGRPC)
//заменим имя сервиса на новое
ServiceNameTemplate := config.Settings.TEMPLATE_SERVICE_NAME
ServiceName := config.Settings.SERVICE_NAME

View File

@ -41,21 +41,18 @@ func CreateServerGRPCStarter() error {
FilenameReadyMain := DirReadyServerNRPC + "server_nrpc_starter.go"
FilenameTemplateMain := DirTemplatesServerNRPC + "server_nrpc_starter.go_"
//создадим папку готовых файлов
folders.CreateFolder(DirReadyServerNRPC)
bytes, err := os.ReadFile(FilenameTemplateMain)
if err != nil {
log.Panic("ReadFile() ", FilenameTemplateMain, " error: ", err)
}
TextNRPCStarter := string(bytes)
//создадим папку ready
folders.CreateFolder(DirReadyServerNRPC)
//заменим имя пакета на новое
create_files.ReplacePackageName(TextNRPCStarter, DirReadyServerNRPC)
//создадим папку ready
folders.CreateFolder(DirReadyServerNRPC)
if config.Settings.USE_DEFAULT_TEMPLATE == true {
TextNRPCStarter = create_files.DeleteTemplateRepositoryImports(TextNRPCStarter)