github.com/sharovik/devbot@v1.0.1-0.20240308094637-4a0387c40516/internal/database/base.go (about)

     1  package database
     2  
     3  import (
     4  	"database/sql"
     5  	"strings"
     6  
     7  	"github.com/sharovik/orm/clients"
     8  	cdto "github.com/sharovik/orm/dto"
     9  	cquery "github.com/sharovik/orm/query"
    10  
    11  	"github.com/sharovik/devbot/internal/dto"
    12  )
    13  
    14  // BaseDatabaseInterface interface for base database client
    15  type BaseDatabaseInterface interface {
    16  	InitDatabaseConnection(cfg clients.DatabaseConfig) error
    17  	GetDBClient() clients.BaseClientInterface
    18  	CloseDatabaseConnection() error
    19  	FindAnswer(message string) (dto.DictionaryMessage, error)
    20  	InsertQuestion(question string, answer string, scenarioID int64, questionRegex string, questionRegexGroup string, isVariable bool) (int64, error)
    21  	InsertScenario(name string, eventID int64) (int64, error)
    22  	FindScenarioByID(scenarioID int64) (int64, error)
    23  	GetLastScenarioID() (int64, error)
    24  	FindEventByAlias(eventAlias string) (int64, error)
    25  	FindEventBy(eventAlias string, version string) (int64, error)
    26  	InsertEvent(alias string, version string) (int64, error)
    27  	FindRegex(regex string) (int64, error)
    28  	InsertQuestionRegex(questionRegex string, questionRegexGroup string) (int64, error)
    29  	GetAllRegex() (map[int64]string, error)
    30  	GetQuestionsByScenarioID(scenarioID int64, isVariable bool) (result []QuestionObject, err error)
    31  
    32  	//RunMigrations Should be used for custom event migrations loading
    33  	RunMigrations(path string) error
    34  	IsMigrationAlreadyExecuted(name string) (bool, error)
    35  	MarkMigrationExecuted(name string) error
    36  
    37  	//InstallEvent Should be used for your custom event installation. This will create a new event row in the database if previously this row wasn't
    38  	//exists and insert new scenario for specified question and answer
    39  	InstallEvent(eventName string, eventVersion string, question string, answer string, questionRegex string, questionRegexGroup string) error
    40  
    41  	//InstallNewEventScenario the method will be used for the new better way of scenario installation
    42  	InstallNewEventScenario(scenario EventScenario) error
    43  }
    44  
    45  // QuestionObject used for proper data mapping from questions table
    46  type QuestionObject struct {
    47  	ID           int64
    48  	Question     string
    49  	Answer       string
    50  	ReactionType string
    51  	IsVariable   bool
    52  }
    53  
    54  // ScenarioVariable similar to the Question, but here is a different concept. Question object is used as the main entrypoint for the scenario execution,
    55  // whereas ScenarioVariable is a part of already executed/triggered scenario. For example, you have a scenario, where before your logic execution you need to get the variables data from the answers.
    56  // In this case you will use scenario variables, just to make sure you ask required information.
    57  type ScenarioVariable struct {
    58  	Name     string
    59  	Value    string
    60  	Question string
    61  }
    62  
    63  // EventScenario the main object of event scenario. It will be used during scenarios installation process, via `Install` method call.
    64  type EventScenario struct {
    65  	//EventName name of the event, to which we need connect the scenario
    66  	EventName string
    67  
    68  	//ScenarioName name of the scenario
    69  	ScenarioName string
    70  
    71  	//EventID the id of existing event in the database
    72  	EventID int64
    73  
    74  	//EventVersion version of the event
    75  	EventVersion string
    76  
    77  	//ID id of scenario
    78  	ID int64
    79  
    80  	//Questions scenario questions list
    81  	Questions []Question
    82  
    83  	//RequiredVariables required variables list we've expecting for this scenario
    84  	RequiredVariables []ScenarioVariable
    85  }
    86  
    87  // VariablesToString converts the variables to the string.
    88  func (e *EventScenario) VariablesToString() string {
    89  	var result []string
    90  	for _, variable := range e.RequiredVariables {
    91  		result = append(result, variable.Value)
    92  	}
    93  
    94  	return strings.Join(result, ";")
    95  }
    96  
    97  // Question the question, which will be asked during scenario execution. It's a main entry point for your scenario.
    98  // the combination of Question, QuestionRegex attributes will be used during the answer search.
    99  type Question struct {
   100  	Question      string
   101  	Answer        string
   102  	QuestionRegex string
   103  	QuestionGroup string
   104  }
   105  
   106  // GetUnAnsweredQuestion retrieves unanswered question from the list of questions of the scenario
   107  func (e *EventScenario) GetUnAnsweredQuestion() string {
   108  	for _, variable := range e.RequiredVariables {
   109  		if variable.Value != "" {
   110  			continue
   111  		}
   112  
   113  		return variable.Question
   114  	}
   115  
   116  	return ""
   117  }
   118  
   119  func installNewEventScenario(d BaseDatabaseInterface, scenario EventScenario) error {
   120  	eventID, err := d.FindEventByAlias(scenario.EventName)
   121  	if err != nil {
   122  		return err
   123  	}
   124  
   125  	if eventID == 0 {
   126  		eventID, err = d.InsertEvent(scenario.EventName, scenario.EventVersion)
   127  		if err != nil {
   128  			return err
   129  		}
   130  	}
   131  
   132  	name := scenario.ScenarioName
   133  	if name == "" {
   134  		name = scenario.EventName
   135  	}
   136  
   137  	scenarioID, err := d.InsertScenario(name, eventID)
   138  	if err != nil {
   139  		return err
   140  	}
   141  
   142  	for _, q := range scenario.Questions {
   143  		_, err = d.InsertQuestion(q.Question, q.Answer, scenarioID, q.QuestionRegex, q.QuestionRegex, false)
   144  		if err != nil {
   145  			return err
   146  		}
   147  	}
   148  
   149  	for _, v := range scenario.RequiredVariables {
   150  		_, err = d.InsertQuestion("", v.Question, scenarioID, "", "", true)
   151  		if err != nil {
   152  			return err
   153  		}
   154  	}
   155  
   156  	return nil
   157  }
   158  
   159  func getQuestionsByScenarioID(d BaseDatabaseInterface, scenarioID int64, isVariable bool) (result []QuestionObject, err error) {
   160  	query := new(clients.Query).
   161  		Select([]interface{}{
   162  			"questions.id",
   163  			"questions.question",
   164  			"questions.answer",
   165  			"questions.is_variable",
   166  			"events.alias",
   167  		}).
   168  		From(&cdto.BaseModel{TableName: "questions"}).
   169  		Join(cquery.Join{
   170  			Target:    cquery.Reference{Table: "scenarios", Key: "id"},
   171  			With:      cquery.Reference{Table: "questions", Key: "scenario_id"},
   172  			Condition: "=",
   173  			Type:      cquery.InnerJoinType,
   174  		}).
   175  		Join(cquery.Join{
   176  			Target:    cquery.Reference{Table: "events", Key: "id"},
   177  			With:      cquery.Reference{Table: "scenarios", Key: "event_id"},
   178  			Condition: "=",
   179  			Type:      cquery.InnerJoinType,
   180  		}).
   181  		Where(cquery.Where{
   182  			First:    "scenarios.id",
   183  			Operator: "=",
   184  			Second:   cquery.Bind{Field: "scenarios.id", Value: scenarioID},
   185  		}).
   186  		OrderBy("questions.id", cquery.OrderDirectionAsc)
   187  
   188  	if isVariable {
   189  		query.Where(cquery.Where{
   190  			First:    "questions.is_variable",
   191  			Operator: "=",
   192  			Second:   cquery.Bind{Field: "is_variable", Value: true},
   193  		})
   194  	}
   195  
   196  	res, err := d.GetDBClient().Execute(query)
   197  	if err == sql.ErrNoRows {
   198  		return result, nil
   199  	} else if err != nil {
   200  		return result, err
   201  	}
   202  
   203  	for _, item := range res.Items() {
   204  		isVar := false
   205  		if item.GetField("is_variable").Value.(int) == 1 {
   206  			isVar = true
   207  		}
   208  
   209  		result = append(result, QuestionObject{
   210  			ID:           int64(item.GetField("id").Value.(int)),
   211  			Question:     item.GetField("question").Value.(string),
   212  			Answer:       item.GetField("answer").Value.(string),
   213  			ReactionType: item.GetField("alias").Value.(string),
   214  			IsVariable:   isVar,
   215  		})
   216  	}
   217  
   218  	return result, nil
   219  }