diff --git a/log/log.go b/log/log.go index b90a39b..3344bbe 100644 --- a/log/log.go +++ b/log/log.go @@ -15,6 +15,7 @@ type ( Log struct { ID string `json:"id" db:"id"` + Level LogLevel `json:"level" db:"level"` Type string `json:"type" db:"type"` Content string `json:"content" db:"content"` CreatedAt time.Time `json:"created_at" db:"created_at"` @@ -35,6 +36,8 @@ const ( LEVEL_WARN LogLevel = 1 ) +const DEFAULT_LOG_PAGE_LENGTH = 25 + func (self *Logger) Info(logType string, format string, args ...any) { logString := fmt.Sprintf(format, args...) fmt.Printf("[%s] INFO: %s", logType, logString) @@ -58,21 +61,61 @@ func (self *Logger) Fatal(logType string, format string, args ...any) { // we won't need to push fatal logs to DB, as these usually precede a panic or crash } -func (self *Logger) Fetch(id string) *Log { - // TODO: log fetch - return nil +func (self *Logger) Fetch(id string) (*Log, error) { + log := Log{} + err := self.DB.Get(&log, "SELECT * FROM auditlog WHERE id=$1", id) + return &log, err } -func (self *Logger) Search(typeFilters []string, content string, offset int, limit int) []Log { - // TODO: log search - return []Log{} -} +func (self *Logger) Search(levelFilters []LogLevel, typeFilters []string, content string, offset int, limit int) ([]*Log, error) { + logs := []*Log{} -func (self *Logger) Delete(id string) error { - // TODO: log deletion - // consider: logging the deletion of logs? - // or just not deleting logs at all - return nil + params := []any{ limit, offset } + conditions := "" + + if len(content) > 0 { + content = "%" + content + "%" + conditions += " WHERE content LIKE $3" + params = append(params, content) + } + + if len(levelFilters) > 0 { + conditions += " AND level IN (" + for i := range levelFilters { + conditions += fmt.Sprintf("$%d", len(params) + 1) + if i < len(levelFilters) - 1 { + conditions += "," + } + params = append(params, levelFilters[i]) + } + conditions += ")" + } + + if len(typeFilters) > 0 { + conditions += " AND type IN (" + for i := range typeFilters { + conditions += fmt.Sprintf("$%d", len(params) + 1) + if i < len(typeFilters) - 1 { + conditions += "," + } + params = append(params, typeFilters[i]) + } + conditions += ")" + } + + query := fmt.Sprintf( + "SELECT * FROM auditlog%s ORDER BY created_at DESC LIMIT $1 OFFSET $2", + conditions, + ) + + // TODO: remove after testing + fmt.Println(query) + + err := self.DB.Select(&logs, query, params...) + if err != nil { + return nil, err + } + return logs, nil } func createLog(db *sqlx.DB, logLevel LogLevel, logType string, content string) error { diff --git a/main.go b/main.go index 7e8e06f..b18681b 100644 --- a/main.go +++ b/main.go @@ -3,7 +3,7 @@ package main import ( "errors" "fmt" - "log" + stdLog "log" "math" "math/rand" "net/http" @@ -20,6 +20,7 @@ import ( "arimelody-web/model" "arimelody-web/templates" "arimelody-web/view" + "arimelody-web/log" "github.com/jmoiron/sqlx" _ "github.com/lib/pq" @@ -341,7 +342,19 @@ func main() { fmt.Printf("Account \"%s\" deleted successfully.\n", account.Username) return - + + case "testLogSearch": + // TODO: rename to "logs"; add parameters + logger := log.Logger { DB: app.DB } + logs, err := logger.Search([]log.LogLevel{ log.LEVEL_INFO, log.LEVEL_WARN }, []string{ log.TYPE_ACCOUNT, log.TYPE_MUSIC }, "ari", 0, 100) + if err != nil { + fmt.Fprintf(os.Stderr, "FATAL: Failed to fetch logs: %v\n", err) + os.Exit(1) + } + for _, log := range(logs) { + fmt.Printf("[%s] [%s] [%d] [%s] %s\n", log.CreatedAt.Format(time.UnixDate), log.ID, log.Level, log.Type, log.Content) + } + return } // command help @@ -401,7 +414,7 @@ func main() { // start the web server! mux := createServeMux(&app) fmt.Printf("Now serving at http://%s:%d\n", app.Config.Host, app.Config.Port) - log.Fatal( + stdLog.Fatal( http.ListenAndServe(fmt.Sprintf("%s:%d", app.Config.Host, app.Config.Port), HTTPLog(DefaultHeaders(mux)), ))