diff --git a/src/api/api.go b/src/api/api.go index 3f2eba3..8a6b22e 100644 --- a/src/api/api.go +++ b/src/api/api.go @@ -25,7 +25,7 @@ func Init(router *httprouter.Router) { router.GET("/", apiHandler(apiRoot)) router.GET(apiRootPath+"/auth", apiHandler(apiAuth)) - router.GET(apiRootPath+"/auth/login", apiHandler(apiAuthLogin)) + router.POST(apiRootPath+"/auth/login", apiHandler(apiAuthLogin)) router.GET(apiRootPath+"/auth/logout", apiHandler(apiAuthLogout)) router.GET(apiRootPath+"/page", apiHandler(apiPage)) @@ -34,6 +34,7 @@ func Init(router *httprouter.Router) { router.GET(apiRootPath+"/page/:folder/:page/content", apiHandler(apiPageFolderPageContent)) router.GET(apiRootPath+"/user", apiHandler(apiUser)) + router.POST(apiRootPath+"/user/:name", apiHandler(apiUser_Create)) router.GET(apiRootPath+"/user/:name", apiHandler(apiUserName)) router.GET(apiRootPath+"/tag", apiHandler(apiTag)) @@ -72,6 +73,18 @@ func apiAuth(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { func apiAuthLogin(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { fmt.Fprintf(w, "API Auth Login") + r.ParseForm() + var username string = r.FormValue("username") + var password string = r.FormValue("password") + fmt.Fprintf(w, "Login : User : %s, Password : %s\n\n", username, password) + + var result database.DatabaseResult = database.ExecuteStoredRoutine("CheckUser", username, password) + database.DecodeDatabaseResult(&result) + fmt.Println(result) + + if result.Error.HasError() { + fmt.Fprintf(w, "Error : %s\n\n", &result.Error) + } } func apiAuthLogout(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { @@ -100,10 +113,29 @@ func apiPageFolderPageContent(w http.ResponseWriter, r *http.Request, params htt func apiUser(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { fmt.Fprintf(w, "API User") + var result database.DatabaseResult = database.ExecuteStoredRoutine("ListUsers") + database.DecodeDatabaseResult(&result) + fmt.Println(result) +} + +func apiUser_Create(w http.ResponseWriter, r *http.Request, params httprouter.Params) { + r.ParseForm() + var username string = params.ByName("name") + var password string = r.FormValue("password") + fmt.Fprintf(w, "API User Create\n") + fmt.Fprintf(w, "User : %s, Password : %s\n\n", username, password) + + var result database.DatabaseResult = database.ExecuteStoredRoutine("CreateUser", username, password) + database.DecodeDatabaseResult(&result) + fmt.Println(result) + + if result.Error.HasError() { + fmt.Fprintf(w, "Error : %s\n\n", &result.Error) + } } func apiUserName(w http.ResponseWriter, r *http.Request, params httprouter.Params) { - fmt.Fprintf(w, "API User Name(%s)", params.ByName("page")) + fmt.Fprintf(w, "API User Name(%s)", params.ByName("name")) } // API Tag @@ -113,7 +145,7 @@ func apiTag(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { } func apiTagName(w http.ResponseWriter, r *http.Request, params httprouter.Params) { - fmt.Fprintf(w, "API Tag Name(%s)", params.ByName("page")) + fmt.Fprintf(w, "API Tag Name(%s)", params.ByName("name")) } // API Debug Profiler @@ -129,7 +161,7 @@ func apiDebugProfiler(w http.ResponseWriter, r *http.Request, _ httprouter.Param } func apiDebugProfilerName(w http.ResponseWriter, r *http.Request, params httprouter.Params) { - fmt.Fprintf(w, "API Debug Profiler(%s)\n%s", params.ByName("page"), string(profiler.Get(params.ByName("page")).ToString())) + fmt.Fprintf(w, "API Debug Profiler(%s)\n%s", params.ByName("name"), string(profiler.Get(params.ByName("name")).ToString())) } // API Debug Database diff --git a/src/database/database.go b/src/database/database.go index b4ac5ea..8786a0e 100644 --- a/src/database/database.go +++ b/src/database/database.go @@ -12,6 +12,32 @@ import ( _ "github.com/go-sql-driver/mysql" ) +type DatabaseError struct { + message string +} + +func createDatabaseError(message string) DatabaseError { + log.Printf("Database error : %s\n", message) + return DatabaseError{ + message: message, + } +} + +func (e *DatabaseError) Error() string { + return e.message +} + +func (e *DatabaseError) HasError() bool { + return e.message != "" +} + +type DatabaseResult struct { + Error DatabaseError + SqlRows *sql.Rows + SqlResult sql.Result + ParsedResult map[string][]sql.NullString +} + const ( profiler_DATABASE_CONNECT string = "Database Connect" profiler_DATABASE_QUERY string = "Database Query" @@ -372,9 +398,8 @@ var databaseStructure databaseDefinition = databaseDefinition{ }, Command: ` DECLARE f_user_id INT(11); -DECLARE f_salt VARCHAR(32); -DECLARE f_password VARCHAR(256); -DECLARE f_user VARCHAR(256); +DECLARE f_salt VARCHAR(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin; +DECLARE f_password VARCHAR(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin; SELECT TO_BASE64(RANDOM_BYTES(16)) INTO f_salt; SELECT SHA2(CONCAT(LOWER(f_user_name), f_user_password, f_salt), 512) INTO f_password; @@ -383,6 +408,40 @@ INSERT INTO user(name, password, password_salt) VALUES (f_user_name, f_password, SELECT LAST_INSERT_ID() INTO f_user_id; +RETURN f_user_id;`, + }, + { + Name: "CheckUser", + Parameters: []storedRoutineParameterDefinition{ + { + Name: "f_user_name", + Type: "VARCHAR(256)", + Charset: "utf8mb4", + Collation: "utf8mb4_unicode_ci", + }, + { + Name: "f_user_password", + Type: "VARCHAR(256)", + Charset: "utf8mb4", + Collation: "utf8mb4_bin", + }, + }, + Return: storedRoutineParameterDefinition{ + Type: "INT(11)", + }, + Command: ` +DECLARE f_user_id INT(11) DEFAULT NULL; +DECLARE f_salt VARCHAR(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin; +DECLARE f_password VARCHAR(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin; +DECLARE f_user VARCHAR(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +SET f_user = LOWER(f_user_name); + +SELECT password_salt INTO f_salt FROM user WHERE name = f_user LIMIT 1; +SELECT SHA2(CONCAT(f_user, f_user_password, f_salt), 512) INTO f_password; + +SELECT id INTO f_user_id FROM user WHERE name = f_user AND password = f_password LIMIT 1; + RETURN f_user_id;`, }, }, @@ -439,9 +498,11 @@ func connect() { profiler.Add(profiler_DATABASE_CONNECT, time.Duration(time.Since(tStart).Microseconds())) } -func ExecuteStoredRoutine(name string, args ...any) { +func ExecuteStoredRoutine(routineName string, args ...any) DatabaseResult { if !isInit { - return + return DatabaseResult{ + Error: createDatabaseError("Database not Init"), + } } if storedRoutines == nil { fetchStoredRoutines() @@ -449,16 +510,76 @@ func ExecuteStoredRoutine(name string, args ...any) { var exist bool var routineType uint8 - routineType, exist = storedRoutines[name] + routineType, exist = storedRoutines[routineName] if !exist { - return + return DatabaseResult{ + Error: createDatabaseError(fmt.Sprintf("Routine \"%s\" does not exist", routineName)), + } } + var databaseResult DatabaseResult + switch routineType { case database_ROUTINE_TYPE_PROCEDURE: - callStoredProcedure(name, args...) + databaseResult = callStoredProcedure(routineName, args...) case database_ROUTINE_TYPE_FUNCTION: - callStoredFunction(name, args...) + databaseResult = callStoredFunction(routineName, args...) + default: + return DatabaseResult{ + Error: createDatabaseError(fmt.Sprintf("Routine \"%s\" isn't a FUNCTION nor a PROCEDURE", routineName)), + } + + } + + return databaseResult +} + +func DecodeDatabaseResult(databaseResult *DatabaseResult) { + var err error + var rows *sql.Rows = databaseResult.SqlRows + + if rows != nil { + defer func() { + databaseResult.SqlRows.Close() + databaseResult.SqlRows = nil + }() + var columns []string + if columns, err = rows.Columns(); err != nil { + databaseResult.Error = createDatabaseError(fmt.Sprintf("Error reading columns name : %s", err)) + rows.Close() + databaseResult.SqlRows = nil + return + } + + if rows.Err() != nil { + fmt.Printf("Error : %s\n", rows.Err()) + } + + databaseResult.ParsedResult = map[string][]sql.NullString{} + var numCol int = len(columns) + for _, c := range columns { + databaseResult.ParsedResult[c] = make([]sql.NullString, 0) + } + + for rows.Next() { + var args []any = make([]any, numCol) + var strArr []sql.NullString = make([]sql.NullString, numCol) + for i := range numCol { + args[i] = &strArr[i] + } + if err = rows.Scan(args...); err != nil { + databaseResult.Error = createDatabaseError(fmt.Sprintf("Error Scanning database response : %s", err)) + break + } + for i, c := range columns { + databaseResult.ParsedResult[c] = append(databaseResult.ParsedResult[c], strArr[i]) + } + } + + if err = rows.Err(); err != nil { + databaseResult.Error = createDatabaseError(fmt.Sprintf("Error Scanning database response : %s", err)) + } + } } @@ -481,9 +602,11 @@ func ping() { } } -func executeQuery(query string, args ...any) *sql.Rows { +func executeQuery(query string, args ...any) DatabaseResult { if !isInit { - return nil + return DatabaseResult{ + Error: createDatabaseError("Database not Init"), + } } if db == nil { connect() @@ -497,6 +620,7 @@ func executeQuery(query string, args ...any) *sql.Rows { if err == nil { break } + log.Printf("Error while querying the database [%d/%d] : %s\n", i+1, dbConfig.RetriesOnError, err) if i+1 == dbConfig.RetriesOnError { break @@ -508,15 +632,22 @@ func executeQuery(query string, args ...any) *sql.Rows { if err != nil { log.Printf("Cannot query the database ! Query : %s", query) - log.Fatalln("With args :", args) + log.Printf("With args : %s", args...) + return DatabaseResult{ + Error: createDatabaseError("Database Query Failed"), + } } - return rows + return DatabaseResult{ + SqlRows: rows, + } } -func executeExec(query string, args ...any) sql.Result { +func executeExec(query string, args ...any) DatabaseResult { if !isInit { - return nil + return DatabaseResult{ + Error: createDatabaseError("Database not Init"), + } } if db == nil { connect() @@ -541,15 +672,21 @@ func executeExec(query string, args ...any) sql.Result { if err != nil { log.Printf("Cannot execute on the database ! Command : %s", query) - log.Fatalln("With args : ", args) + log.Printf("With args : %s\n", args) + return DatabaseResult{ + Error: createDatabaseError("Database Exec Failed"), + } } - return result + return DatabaseResult{ + SqlResult: result, + } } func fetchStoredRoutines() { storedRoutines = nil - var rows *sql.Rows = executeQuery("SELECT ROUTINE_NAME, ROUTINE_TYPE FROM information_schema.ROUTINES WHERE ROUTINE_SCHEMA=?;", dbConfig.Database) + var dbResult DatabaseResult = executeQuery("SELECT ROUTINE_NAME, ROUTINE_TYPE FROM information_schema.ROUTINES WHERE ROUTINE_SCHEMA=?;", dbConfig.Database) + var rows *sql.Rows = dbResult.SqlRows if rows != nil { storedRoutines = make(map[string]uint8, 0) defer rows.Close() @@ -568,7 +705,7 @@ func fetchStoredRoutines() { fmt.Println(storedRoutines) } -func callStoredProcedure(name string, args ...any) { +func callStoredProcedure(name string, args ...any) DatabaseResult { var query string if len(args) == 0 { query = fmt.Sprintf("CALL %s()", name) @@ -579,39 +716,28 @@ func callStoredProcedure(name string, args ...any) { query = fmt.Sprintf("CALL %s(?%s)", name, query) } - var rows *sql.Rows = executeQuery(query, args...) - defer rows.Close() - - for rows.Next() { - var res string - rows.Scan(&res) - log.Println(res) - } + var dbResult DatabaseResult = executeQuery(query, args...) + return dbResult } -func callStoredFunction(name string, args ...any) { +func callStoredFunction(name string, args ...any) DatabaseResult { var query string if len(args) == 0 { - query = fmt.Sprintf("SELECT %s()", name) + query = fmt.Sprintf("SELECT %s() AS result", name) } else if len(args) == 1 { - query = fmt.Sprintf("SELECT %s(?)", name) + query = fmt.Sprintf("SELECT %s(?) AS result", name) } else { query = strings.Repeat(", ?", len(args)-1) - query = fmt.Sprintf("SELECT %s(?%s)", name, query) + query = fmt.Sprintf("SELECT %s(?%s) AS result", name, query) } - var rows *sql.Rows = executeQuery(query, args...) - defer rows.Close() - - for rows.Next() { - var res string - rows.Scan(&res) - log.Println(res) - } + var dbResult DatabaseResult = executeQuery(query, args...) + return dbResult } func fetchTables() []string { - var rows *sql.Rows = executeQuery("SHOW TABLES;") + var queryResult DatabaseResult = executeQuery("SHOW TABLES;") + var rows *sql.Rows = queryResult.SqlRows //var rows *sql.Rows = executeQuery("SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA=?;", dbConfig.Database) var tables []string = make([]string, 0) if rows != nil { @@ -626,7 +752,8 @@ func fetchTables() []string { } func fetchColumns(table string) []columsDefinition { - var rows *sql.Rows = executeQuery("SELECT COLUMN_NAME, COLUMN_TYPE, COLUMN_DEFAULT, IS_NULLABLE, CHARACTER_SET_NAME, COLLATION_NAME FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=? AND TABLE_NAME=?;", dbConfig.Database, table) + var queryResult DatabaseResult = executeQuery("SELECT COLUMN_NAME, COLUMN_TYPE, COLUMN_DEFAULT, IS_NULLABLE, CHARACTER_SET_NAME, COLLATION_NAME FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=? AND TABLE_NAME=?;", dbConfig.Database, table) + var rows *sql.Rows = queryResult.SqlRows var columns []columsDefinition = make([]columsDefinition, 0) if rows != nil { defer rows.Close() @@ -654,7 +781,8 @@ func fetchIndexes() []indexDefinition { // Add PRIMARY : ALTER TABLE