Skip to content

Commit 08814be

Browse files
committed
- Now the log is imported to the database every minute
- waitGroups discarded after a few tests - separate controller file into smaller files - separate models' fie into smaller files - separate middlewares file into smaller files - refactored auto-migrate tables
1 parent 6e43031 commit 08814be

24 files changed

+829
-588
lines changed

controllers/controller.go

Lines changed: 0 additions & 437 deletions
This file was deleted.

controllers/createName.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package controllers
2+
3+
import (
4+
"github.com/Darklabel91/API_Names/database"
5+
"github.com/Darklabel91/API_Names/models"
6+
"github.com/gin-gonic/gin"
7+
"net/http"
8+
)
9+
10+
//CreateName create new name on database of type NameType
11+
func CreateName(c *gin.Context) {
12+
var name models.NameType
13+
if err := c.ShouldBindJSON(&name); err != nil {
14+
c.JSON(http.StatusBadRequest, gin.H{"err": err.Error()})
15+
return
16+
}
17+
18+
database.DB.Create(&name)
19+
c.JSON(http.StatusOK, name)
20+
}

controllers/deleteName.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package controllers
2+
3+
import (
4+
"github.com/Darklabel91/API_Names/database"
5+
"github.com/Darklabel91/API_Names/models"
6+
"github.com/gin-gonic/gin"
7+
"net/http"
8+
)
9+
10+
//DeleteName delete name off database by id
11+
func DeleteName(c *gin.Context) {
12+
var name models.NameType
13+
14+
id := c.Params.ByName("id")
15+
database.DB.First(&name, id)
16+
17+
if name.ID == 0 {
18+
c.JSON(http.StatusNotFound, gin.H{"Not found": "name id not found"})
19+
return
20+
}
21+
22+
database.DB.Delete(&name, id)
23+
c.JSON(http.StatusOK, gin.H{"Delete": "name id " + id + " was deleted"})
24+
}

controllers/getID.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package controllers
2+
3+
import (
4+
"github.com/Darklabel91/API_Names/database"
5+
"github.com/Darklabel91/API_Names/models"
6+
"github.com/gin-gonic/gin"
7+
"net/http"
8+
)
9+
10+
//GetID read name by id
11+
func GetID(c *gin.Context) {
12+
var name models.NameType
13+
14+
id := c.Params.ByName("id")
15+
database.DB.First(&name, id)
16+
17+
if name.ID == 0 {
18+
c.JSON(http.StatusNotFound, gin.H{"Not found": "name id not found"})
19+
return
20+
}
21+
22+
c.JSON(http.StatusOK, name)
23+
}

controllers/getIPs.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package controllers
2+
3+
import (
4+
"github.com/Darklabel91/API_Names/database"
5+
"github.com/Darklabel91/API_Names/models"
6+
)
7+
8+
//GetTrustedIPs return all IPS from user's on the database
9+
func GetTrustedIPs() []string {
10+
var users []models.User
11+
if err := database.DB.Find(&users).Error; err != nil {
12+
return nil
13+
}
14+
15+
var ips []string
16+
for _, user := range users {
17+
ips = append(ips, user.IP)
18+
}
19+
20+
return ips
21+
}

controllers/getName.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package controllers
2+
3+
import (
4+
"github.com/Darklabel91/API_Names/database"
5+
"github.com/Darklabel91/API_Names/models"
6+
"github.com/gin-gonic/gin"
7+
"net/http"
8+
"strings"
9+
)
10+
11+
//GetName read name by name
12+
func GetName(c *gin.Context) {
13+
var name models.NameType
14+
15+
n := c.Params.ByName("name")
16+
database.DB.Raw("select * from name_types where name = ?", strings.ToUpper(n)).Find(&name)
17+
18+
if name.ID == 0 {
19+
c.JSON(http.StatusNotFound, gin.H{"Not found": "name not found"})
20+
return
21+
}
22+
23+
c.JSON(http.StatusOK, name)
24+
return
25+
}

controllers/getSimilarNames.go

Lines changed: 281 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,281 @@
1+
package controllers
2+
3+
import (
4+
"errors"
5+
"github.com/Darklabel91/API_Names/database"
6+
"github.com/Darklabel91/API_Names/models"
7+
"github.com/Darklabel91/metaphone-br"
8+
"github.com/gin-gonic/gin"
9+
"net/http"
10+
"sort"
11+
"strings"
12+
)
13+
14+
const levenshtein = 0.8
15+
16+
//GetSimilarNames search for all similar names by metaphone and Levenshtein method
17+
func GetSimilarNames(c *gin.Context) {
18+
var metaphoneNames []models.NameType
19+
20+
//name to be searched
21+
name := c.Params.ByName("name")
22+
nameMetaphone := metaphone.Pack(name)
23+
24+
//Check the cache
25+
var preloadTable []models.NameType
26+
cache, existKey := c.Get("nameTypes")
27+
if existKey {
28+
preloadTable = cache.([]models.NameType)
29+
} else {
30+
if err := database.DB.Find(&preloadTable).Error; err != nil {
31+
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to preload nameTypes"})
32+
return
33+
}
34+
}
35+
36+
//search perfect match
37+
nameCache, existName := searchCacheName(name, preloadTable)
38+
if existName {
39+
r := models.MetaphoneR{
40+
ID: nameCache.ID,
41+
Name: nameCache.Name,
42+
Classification: nameCache.Classification,
43+
Metaphone: nameCache.Metaphone,
44+
NameVariations: []string{nameCache.NameVariations},
45+
}
46+
c.JSON(200, r)
47+
return
48+
} else {
49+
//search perfect match on database
50+
database.DB.Raw("select * from name_types where name = ?", strings.ToUpper(name)).Find(&nameCache)
51+
if len(metaphoneNames) == 1 {
52+
r := models.MetaphoneR{
53+
ID: metaphoneNames[0].ID,
54+
Name: metaphoneNames[0].Name,
55+
Classification: metaphoneNames[0].Classification,
56+
Metaphone: metaphoneNames[0].Metaphone,
57+
NameVariations: []string{metaphoneNames[0].NameVariations},
58+
}
59+
c.JSON(200, r)
60+
return
61+
}
62+
}
63+
64+
//search metaphone
65+
metaphoneNames, existMetaphone := searchCacheMetaphone(nameMetaphone, preloadTable)
66+
if !existMetaphone {
67+
database.DB.Raw("select * from name_types where metaphone = ?", nameMetaphone).Find(&metaphoneNames)
68+
}
69+
70+
//find all metaphoneNames matching metaphone
71+
similarNames := findNames(metaphoneNames, name, levenshtein)
72+
73+
//for recall purposes we can't only search for metaphone exact match's if no similar word is found
74+
if len(metaphoneNames) == 0 || len(similarNames) == 0 {
75+
metaphoneNames = searchForAllSimilarMetaphone(nameMetaphone, preloadTable)
76+
similarNames = findNames(metaphoneNames, name, levenshtein)
77+
78+
if len(metaphoneNames) == 0 {
79+
c.JSON(http.StatusNotFound, gin.H{"Not found": "metaphone not found", "metaphone": nameMetaphone})
80+
return
81+
}
82+
83+
if len(similarNames) == 0 {
84+
c.JSON(http.StatusNotFound, gin.H{"Not found": "similar names not found", "metaphone": nameMetaphone})
85+
return
86+
}
87+
}
88+
89+
//when the similar metaphoneNames result's in less than 5 we search for every similar name of all similar metaphoneNames founded previously
90+
//this step can be ignored if you want to
91+
if len(similarNames) < 5 {
92+
for _, n := range similarNames {
93+
similar := findNames(metaphoneNames, n.Name, levenshtein)
94+
similarNames = append(similarNames, similar...)
95+
}
96+
}
97+
98+
//order all similar metaphoneNames from high to low Levenshtein
99+
nameV := orderByLevenshtein(similarNames)
100+
101+
//finds a name to consider Canonical on the database
102+
canonicalEntity, err := findCanonical(name, metaphoneNames, nameV)
103+
if err != nil {
104+
c.JSON(http.StatusNotFound, gin.H{"Not found": err.Error(), "metaphone": nameMetaphone})
105+
return
106+
}
107+
108+
//return
109+
r := models.MetaphoneR{
110+
ID: canonicalEntity.ID,
111+
Name: canonicalEntity.Name,
112+
Classification: canonicalEntity.Classification,
113+
Metaphone: canonicalEntity.Metaphone,
114+
NameVariations: nameV,
115+
}
116+
c.JSON(200, r)
117+
return
118+
}
119+
120+
//searchCacheMetaphone seeks for a given name on cache struct, return the name and a bool true if it is found
121+
func searchCacheName(name string, cache []models.NameType) (models.NameType, bool) {
122+
for _, c := range cache {
123+
if c.Name == name {
124+
return c, true
125+
}
126+
}
127+
128+
return models.NameType{}, false
129+
130+
}
131+
132+
//searchCacheMetaphone seeks for a given name on cache struct, return the name and a bool true if it is found
133+
func searchCacheMetaphone(metaphone string, cache []models.NameType) ([]models.NameType, bool) {
134+
var nameTypes []models.NameType
135+
for _, c := range cache {
136+
if c.Metaphone == metaphone {
137+
nameTypes = append(nameTypes, c)
138+
}
139+
}
140+
141+
if len(nameTypes) != 0 {
142+
return nameTypes, true
143+
}
144+
145+
return nil, false
146+
}
147+
148+
//----- All needed functions -----//
149+
150+
//searchForAllSimilarMetaphone used in case of not finding exact metaphone match
151+
func searchForAllSimilarMetaphone(mtf string, names []models.NameType) []models.NameType {
152+
var rNames []models.NameType
153+
for _, n := range names {
154+
if metaphone.IsMetaphoneSimilar(mtf, n.Metaphone) {
155+
rNames = append(rNames, n)
156+
}
157+
}
158+
159+
return rNames
160+
}
161+
162+
//findCanonical search for every similar name on the database returning the first matched name
163+
func findCanonical(name string, matchingMetaphoneNames []models.NameType, nameVariations []string) (models.NameType, error) {
164+
var canonicalEntity models.NameType
165+
n := strings.ToUpper(name)
166+
167+
//search exact match on matchingMetaphoneNames
168+
for _, similarName := range matchingMetaphoneNames {
169+
if similarName.Name == n {
170+
return similarName, nil
171+
}
172+
}
173+
174+
//search for similar names on matchingMetaphoneNames
175+
for _, similarName := range matchingMetaphoneNames {
176+
if metaphone.SimilarityBetweenWords(name, similarName.Name) >= levenshtein {
177+
return similarName, nil
178+
}
179+
}
180+
181+
//search exact match on nameVariations
182+
for _, similarName := range nameVariations {
183+
sn := strings.ToUpper(similarName)
184+
if sn == n {
185+
database.DB.Raw("select * from name_types where name = ?", sn).Find(&canonicalEntity)
186+
if canonicalEntity.ID != 0 {
187+
return canonicalEntity, nil
188+
}
189+
}
190+
}
191+
192+
//in case of failure on other attempts, we search every nameVariations directly on database
193+
for _, similarName := range nameVariations {
194+
database.DB.Raw("select * from name_types where name = ?", strings.ToUpper(similarName)).Find(&canonicalEntity)
195+
if canonicalEntity.ID != 0 {
196+
return canonicalEntity, nil
197+
}
198+
}
199+
200+
return models.NameType{}, errors.New("couldn't find canonical name")
201+
}
202+
203+
//findNames return []models.NameLevenshtein with all similar names of searched string. For recall purpose we reduce the threshold given in 0.1 in case of empty return
204+
func findNames(names []models.NameType, name string, threshold float32) []models.NameLevenshtein {
205+
similarNames := findSimilarNames(name, names, threshold)
206+
//reduce the threshold given in 0.1 and search again
207+
if len(similarNames) == 0 {
208+
similarNames = findSimilarNames(name, names, threshold-0.1)
209+
}
210+
211+
return similarNames
212+
}
213+
214+
//findSimilarNames loop for all names given checking the similarity between words by a given threshold, called on findNames
215+
func findSimilarNames(name string, names []models.NameType, threshold float32) []models.NameLevenshtein {
216+
var similarNames []models.NameLevenshtein
217+
218+
for _, n := range names {
219+
similarity := metaphone.SimilarityBetweenWords(strings.ToLower(name), strings.ToLower(n.Name))
220+
if similarity >= threshold {
221+
similarNames = append(similarNames, models.NameLevenshtein{Name: n.Name, Levenshtein: similarity})
222+
varWords := strings.Split(n.NameVariations, "|")
223+
for _, vw := range varWords {
224+
if vw != "" {
225+
similarNames = append(similarNames, models.NameLevenshtein{Name: vw, Levenshtein: similarity})
226+
}
227+
}
228+
}
229+
}
230+
231+
return similarNames
232+
}
233+
234+
//orderByLevenshtein used to sort an array by Levenshtein and len of the name
235+
func orderByLevenshtein(arr []models.NameLevenshtein) []string {
236+
// creates copy of original array
237+
sortedArr := make([]models.NameLevenshtein, len(arr))
238+
copy(sortedArr, arr)
239+
240+
// order by func
241+
sort.Slice(sortedArr, func(i, j int) bool {
242+
if sortedArr[i].Levenshtein != sortedArr[j].Levenshtein {
243+
return sortedArr[i].Levenshtein > sortedArr[j].Levenshtein
244+
} else {
245+
return len(sortedArr[i].Name) < len(sortedArr[j].Name)
246+
}
247+
})
248+
249+
//return array
250+
var retArr []string
251+
for _, lv := range sortedArr {
252+
retArr = append(retArr, lv.Name)
253+
}
254+
255+
//return without duplicates
256+
return removeDuplicates(retArr)
257+
}
258+
259+
//removeDuplicates remove duplicates of []string, called on orderByLevenshtein
260+
func removeDuplicates(arr []string) []string {
261+
var cleanArr []string
262+
263+
for _, a := range arr {
264+
if !contains(cleanArr, a) {
265+
cleanArr = append(cleanArr, a)
266+
}
267+
}
268+
269+
return cleanArr
270+
}
271+
272+
//contains verifies if []string already has a specific string, called on removeDuplicates
273+
func contains(s []string, str string) bool {
274+
for _, v := range s {
275+
if v == str {
276+
return true
277+
}
278+
}
279+
280+
return false
281+
}

0 commit comments

Comments
 (0)