Skip to content

Commit 0a639f4

Browse files
committed
- create user endpoint;
- login endpoint; - use of jwt for authenticate users - create middleware for all other endpoint's - use init to start the database
1 parent 99c1c02 commit 0a639f4

File tree

11 files changed

+51096
-50773
lines changed

11 files changed

+51096
-50773
lines changed

controllers/controller.go

Lines changed: 125 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,120 @@ import (
66
"github.com/Darklabel91/API_Names/models"
77
Metaphone "github.com/Darklabel91/metaphone-br"
88
"github.com/gin-gonic/gin"
9+
"github.com/golang-jwt/jwt/v5"
10+
"golang.org/x/crypto/bcrypt"
911
"net/http"
12+
"os"
1013
"sort"
14+
"strconv"
1115
"strings"
16+
"time"
1217
)
1318

1419
const levenshtein = 0.8
1520

21+
//Signup a new user to the database
22+
func Signup(c *gin.Context) {
23+
//get email/pass off req body
24+
var body struct {
25+
Email string
26+
Password string
27+
}
28+
29+
if c.Bind(&body) != nil {
30+
c.JSON(http.StatusBadRequest, gin.H{"Message": "Failed to read body"})
31+
return
32+
}
33+
34+
//hash the password
35+
hash, err := bcrypt.GenerateFromPassword([]byte(body.Password), 10)
36+
if err != nil {
37+
c.JSON(http.StatusBadRequest, gin.H{"Message": "Failed to hash password"})
38+
return
39+
}
40+
41+
//create the user
42+
user := models.User{Email: body.Email, Password: string(hash)}
43+
result := database.Db.Create(&user)
44+
45+
if result.Error != nil {
46+
c.JSON(http.StatusBadRequest, gin.H{"Message": "Email already registered"})
47+
return
48+
}
49+
50+
//respond
51+
c.JSON(http.StatusOK, gin.H{"Message": "User created", "User": user})
52+
}
53+
54+
//Login verifies cookie session for login
55+
func Login(c *gin.Context) {
56+
// Get the email and password from request body
57+
var body struct {
58+
Email string
59+
Password string
60+
}
61+
62+
if c.Bind(&body) != nil {
63+
c.JSON(http.StatusBadRequest, gin.H{"Message": "Failed to read body"})
64+
return
65+
}
66+
67+
// Look up requested user
68+
var user models.User
69+
database.Db.First(&user, "email = ?", body.Email)
70+
71+
if user.ID == 0 {
72+
c.JSON(http.StatusBadRequest, gin.H{"Message": "Invalid email or password"})
73+
return
74+
}
75+
76+
// Compare sent-in password with saved user password hash
77+
err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(body.Password))
78+
79+
if err != nil {
80+
c.JSON(http.StatusBadRequest, gin.H{"Message": "Invalid email or password"})
81+
return
82+
}
83+
84+
// Generate JWT token
85+
token, err := generateJWTToken(user.ID, 1)
86+
if err != nil {
87+
c.JSON(http.StatusInternalServerError, gin.H{"Message": "Failed to generate token"})
88+
return
89+
}
90+
91+
// Set token as a cookie
92+
c.SetCookie("token", token, 60*60, "/", "", false, true)
93+
94+
// Return success response
95+
c.JSON(http.StatusOK, gin.H{"Token": token})
96+
}
97+
98+
//generateJWTToken generates a JWT token with a specified expiration time and user ID. It first sets the token expiration time based on the amountDays parameter passed into the function.
99+
func generateJWTToken(userID uint, amountDays time.Duration) (string, error) {
100+
// Set token expiration time
101+
expirationTime := time.Now().Add(amountDays * 24 * time.Hour)
102+
103+
// Create JWT claims
104+
claims := jwt.RegisteredClaims{
105+
ExpiresAt: jwt.NewNumericDate(expirationTime),
106+
IssuedAt: jwt.NewNumericDate(time.Now()),
107+
Subject: strconv.Itoa(int(userID)),
108+
}
109+
110+
// Create token using claims and signing method
111+
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
112+
113+
// Sign token using secret key
114+
secretKey := []byte(os.Getenv("SECRET"))
115+
signedToken, err := token.SignedString(secretKey)
116+
if err != nil {
117+
return "", errors.New("Failed to sign token")
118+
}
119+
120+
return signedToken, nil
121+
}
122+
16123
//CreateName create new name on database of type NameType
17124
func CreateName(c *gin.Context) {
18125
var name models.NameType
@@ -43,24 +150,31 @@ func GetID(c *gin.Context) {
43150
//DeleteName delete name off database by id
44151
func DeleteName(c *gin.Context) {
45152
var name models.NameType
153+
46154
id := c.Params.ByName("id")
155+
database.Db.First(&name, id)
47156

48157
if name.ID == 0 {
49158
c.JSON(http.StatusNotFound, gin.H{"Not found": "name id not found"})
50159
return
51160
}
52161

53162
database.Db.Delete(&name, id)
54-
c.JSON(http.StatusOK, gin.H{"data": "name data deleted"})
163+
c.JSON(http.StatusOK, gin.H{"Delete": "name id " + id + " was deleted"})
55164
}
56165

57166
//UpdateName update name by id
58167
func UpdateName(c *gin.Context) {
59168
var name models.NameType
60-
id := c.Param("id")
61169

170+
id := c.Param("id")
62171
database.Db.First(&name, id)
63172

173+
if name.ID == 0 {
174+
c.JSON(http.StatusNotFound, gin.H{"Not found": "name id not found"})
175+
return
176+
}
177+
64178
if err := c.ShouldBindJSON(&name); err != nil {
65179
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
66180
return
@@ -146,7 +260,7 @@ func SearchSimilarNames(c *gin.Context) {
146260
c.JSON(200, r)
147261
}
148262

149-
/*-------ALL BELLOW USED ONLY ON searchSimilarNames-------*/
263+
/*---------- used on SearchSimilarNames ----------*/
150264

151265
//searchForAllSimilarMetaphone used in case of not finding exact metaphone match
152266
func searchForAllSimilarMetaphone(mtf string) []models.NameType {
@@ -204,8 +318,8 @@ func findCanonical(name string, matchingMetaphoneNames []models.NameType, nameVa
204318
return models.NameType{}, errors.New("couldn't find canonical name")
205319
}
206320

207-
//findNames return []models.NameVar with all similar names of searched string. For recall purpose we reduce the threshold given in 0.1 in case of empty return
208-
func findNames(names []models.NameType, name string, threshold float32) []models.NameVar {
321+
//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
322+
func findNames(names []models.NameType, name string, threshold float32) []models.NameLevenshtein {
209323
similarNames := findSimilarNames(name, names, threshold)
210324
//reduce the threshold given in 0.1 and search again
211325
if len(similarNames) == 0 {
@@ -216,17 +330,17 @@ func findNames(names []models.NameType, name string, threshold float32) []models
216330
}
217331

218332
//findSimilarNames loop for all names given checking the similarity between words by a given threshold, called on findNames
219-
func findSimilarNames(name string, names []models.NameType, threshold float32) []models.NameVar {
220-
var similarNames []models.NameVar
333+
func findSimilarNames(name string, names []models.NameType, threshold float32) []models.NameLevenshtein {
334+
var similarNames []models.NameLevenshtein
221335

222336
for _, n := range names {
223337
similarity := Metaphone.SimilarityBetweenWords(strings.ToLower(name), strings.ToLower(n.Name))
224338
if similarity >= threshold {
225-
similarNames = append(similarNames, models.NameVar{Name: n.Name, Levenshtein: similarity})
339+
similarNames = append(similarNames, models.NameLevenshtein{Name: n.Name, Levenshtein: similarity})
226340
varWords := strings.Split(n.NameVariations, "|")
227341
for _, vw := range varWords {
228342
if vw != "" {
229-
similarNames = append(similarNames, models.NameVar{Name: vw, Levenshtein: similarity})
343+
similarNames = append(similarNames, models.NameLevenshtein{Name: vw, Levenshtein: similarity})
230344
}
231345
}
232346
}
@@ -236,9 +350,9 @@ func findSimilarNames(name string, names []models.NameType, threshold float32) [
236350
}
237351

238352
//orderByLevenshtein used to sort an array by Levenshtein and len of the name
239-
func orderByLevenshtein(arr []models.NameVar) []string {
353+
func orderByLevenshtein(arr []models.NameLevenshtein) []string {
240354
// creates copy of original array
241-
sortedArr := make([]models.NameVar, len(arr))
355+
sortedArr := make([]models.NameLevenshtein, len(arr))
242356
copy(sortedArr, arr)
243357

244358
// order by func

database/create_database.txt

Lines changed: 0 additions & 1 deletion
This file was deleted.

database/db.go

Lines changed: 142 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,35 @@
11
package database
22

33
import (
4+
"encoding/csv"
5+
"errors"
46
"fmt"
57
"github.com/Darklabel91/API_Names/models"
8+
"github.com/Darklabel91/metaphone-br"
69
"github.com/joho/godotenv"
10+
"golang.org/x/crypto/bcrypt"
711
"gorm.io/driver/mysql"
812
"gorm.io/gorm"
13+
"io"
914
"os"
15+
"time"
1016
)
1117

1218
var Db *gorm.DB
1319

1420
func InitDb() *gorm.DB {
1521
Db = connectDB()
22+
23+
err := createRoot()
24+
if err != nil {
25+
return nil
26+
}
27+
28+
err = uploadCSVNameTypes()
29+
if err != nil {
30+
return nil
31+
}
32+
1633
return Db
1734
}
1835

@@ -32,15 +49,30 @@ func connectDB() *gorm.DB {
3249
DbPort = os.Getenv("DB_PORT")
3350
)
3451

52+
//create database
53+
err = createDatabase(DbHost, DbUsername, DbPassword, DbName)
54+
if err != nil {
55+
fmt.Printf("Error on gorm creating database : error=%v\n", err)
56+
return nil
57+
}
58+
3559
dsn := DbUsername + ":" + DbPassword + "@tcp" + "(" + DbHost + ":" + DbPort + ")/" + DbName + "?" + "parseTime=true&loc=Local"
3660
fmt.Println("dsn : ", dsn)
37-
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
3861

62+
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
3963
if err != nil {
4064
fmt.Printf("Error connecting to database : error=%v\n", err)
4165
return nil
4266
}
4367

68+
//create table users
69+
err = db.AutoMigrate(&models.User{})
70+
if err != nil {
71+
fmt.Printf("Error on gorm auto migrate to database : error=%v\n", err)
72+
return nil
73+
}
74+
75+
//create table name_type
4476
err = db.AutoMigrate(&models.NameType{})
4577
if err != nil {
4678
fmt.Printf("Error on gorm auto migrate to database : error=%v\n", err)
@@ -49,3 +81,112 @@ func connectDB() *gorm.DB {
4981

5082
return db
5183
}
84+
85+
func createDatabase(host, username, password, dbName string) error {
86+
fmt.Println("cra")
87+
// Set up the MySQL DSN string
88+
dsn := fmt.Sprintf("%s:%s@tcp(%s)/?charset=utf8mb4&parseTime=True&loc=Local", username, password, host)
89+
90+
// Open a connection to the MySQL server
91+
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
92+
if err != nil {
93+
return fmt.Errorf("failed to connect to MySQL: %v", err)
94+
}
95+
96+
// Check if the database already exists
97+
var result int64
98+
db.Raw("SELECT COUNT(*) FROM information_schema.SCHEMATA WHERE SCHEMA_NAME = ?", dbName).Scan(&result)
99+
if result > 0 {
100+
fmt.Println("database already exists")
101+
return nil
102+
}
103+
104+
// Create the database
105+
fmt.Println("creating database")
106+
err = db.Exec(fmt.Sprintf("CREATE DATABASE %s", dbName)).Error
107+
if err != nil {
108+
return fmt.Errorf("failed to create database: %v", err)
109+
}
110+
111+
return nil
112+
}
113+
114+
func createRoot() error {
115+
var user models.User
116+
Db.First(&user, 1)
117+
118+
if user.ID == 0 {
119+
fmt.Println("creating user root")
120+
121+
//password
122+
hash, err := bcrypt.GenerateFromPassword([]byte(os.Getenv("SECRET")), 10)
123+
if err != nil {
124+
return err
125+
}
126+
127+
userRoot := models.User{
128+
129+
Password: string(hash),
130+
}
131+
132+
Db.Create(&userRoot)
133+
134+
fmt.Println("root user created")
135+
136+
return nil
137+
} else {
138+
fmt.Println("root user already on the database")
139+
return nil
140+
}
141+
}
142+
143+
func uploadCSVNameTypes() error {
144+
var name models.NameType
145+
Db.Raw("SELECT * FROM name_types WHERE id = 1").Find(&name)
146+
147+
if name.ID == 0 {
148+
start := time.Now()
149+
fmt.Println("initiating csv upload to the database")
150+
151+
filePath := "database/name_types .csv"
152+
file, err := os.Open(filePath)
153+
if err != nil {
154+
return errors.New("Error opening file:" + err.Error())
155+
156+
}
157+
defer file.Close()
158+
159+
reader := csv.NewReader(file)
160+
var rows [][]string
161+
for {
162+
row, err := reader.Read()
163+
if err == io.EOF {
164+
break
165+
}
166+
if err != nil {
167+
return errors.New("error reading CSV:" + err.Error())
168+
}
169+
rows = append(rows, row)
170+
}
171+
172+
for i, row := range rows {
173+
if i != 0 {
174+
nameType := models.NameType{
175+
Name: row[0],
176+
Classification: row[1],
177+
Metaphone: metaphone.Pack(row[0]),
178+
NameVariations: row[3],
179+
}
180+
if err = Db.Create(&nameType).Error; err != nil {
181+
return errors.New("error creating NameType:" + err.Error())
182+
}
183+
}
184+
}
185+
186+
fmt.Println("csv upload finished in:" + time.Since(start).String())
187+
return nil
188+
}
189+
fmt.Println("csv already imported")
190+
return nil
191+
192+
}

0 commit comments

Comments
 (0)