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 }