github.com/readium/readium-lcp-server@v0.0.0-20240101192032-6e95190e99f1/transactions/transactions.go (about) 1 // Copyright 2020 Readium Foundation. All rights reserved. 2 // Use of this source code is governed by a BSD-style license 3 // that can be found in the LICENSE file exposed on Github (readium) in the project repository. 4 5 package transactions 6 7 import ( 8 "database/sql" 9 "errors" 10 "log" 11 "time" 12 13 "github.com/readium/readium-lcp-server/config" 14 "github.com/readium/readium-lcp-server/status" 15 ) 16 17 var ErrNotFound = errors.New("Event not found") 18 19 type Transactions interface { 20 Get(id int) (Event, error) 21 Add(e Event, eventType int) error 22 GetByLicenseStatusId(licenseStatusFk int) func() (Event, error) 23 CheckDeviceStatus(licenseStatusFk int, deviceId string) (string, error) 24 ListRegisteredDevices(licenseStatusFk int) func() (Device, error) 25 } 26 27 type RegisteredDevicesList struct { 28 ID string `json:"id"` 29 Devices []Device `json:"devices"` 30 } 31 32 type Device struct { 33 DeviceId string `json:"id"` 34 DeviceName string `json:"name"` 35 Timestamp time.Time `json:"timestamp"` 36 } 37 38 type Event struct { 39 ID int `json:"-"` 40 DeviceName string `json:"name"` 41 Timestamp time.Time `json:"timestamp"` 42 Type string `json:"type"` 43 DeviceId string `json:"id"` 44 LicenseStatusFk int `json:"-"` 45 } 46 47 type dbTransactions struct { 48 db *sql.DB 49 dbGet *sql.Stmt 50 dbGetByStatusID *sql.Stmt 51 dbCheckDeviceStatus *sql.Stmt 52 dbListRegisteredDevices *sql.Stmt 53 } 54 55 // Get returns an event by its id 56 func (i dbTransactions) Get(id int) (Event, error) { 57 58 row := i.dbGet.QueryRow(id) 59 var e Event 60 var typeInt int 61 err := row.Scan(&e.ID, &e.DeviceName, &e.Timestamp, &typeInt, &e.DeviceId, &e.LicenseStatusFk) 62 if err != nil { 63 return Event{}, err 64 } 65 e.Type = status.EventTypes[typeInt] 66 return e, err 67 } 68 69 // Add adds an event in the database, 70 // The parameter eventType corresponds to the field 'type' in table 'event' 71 func (i dbTransactions) Add(e Event, eventType int) error { 72 73 _, err := i.db.Exec("INSERT INTO event (device_name, timestamp, type, device_id, license_status_fk) VALUES (?, ?, ?, ?, ?)", 74 e.DeviceName, e.Timestamp, eventType, e.DeviceId, e.LicenseStatusFk) 75 return err 76 } 77 78 // GetByLicenseStatusId returns all events by license status id 79 func (i dbTransactions) GetByLicenseStatusId(licenseStatusFk int) func() (Event, error) { 80 rows, err := i.dbGetByStatusID.Query(licenseStatusFk) 81 if err != nil { 82 return func() (Event, error) { return Event{}, err } 83 } 84 return func() (Event, error) { 85 var e Event 86 var err error 87 var typeInt int 88 89 if rows.Next() { 90 err = rows.Scan(&e.ID, &e.DeviceName, &e.Timestamp, &typeInt, &e.DeviceId, &e.LicenseStatusFk) 91 if err == nil { 92 e.Type = status.EventTypes[typeInt] 93 } 94 } else { 95 rows.Close() 96 err = ErrNotFound 97 } 98 return e, err 99 } 100 } 101 102 // ListRegisteredDevices returns all devices which have an 'active' status by licensestatus id 103 func (i dbTransactions) ListRegisteredDevices(licenseStatusFk int) func() (Device, error) { 104 105 rows, err := i.dbListRegisteredDevices.Query(licenseStatusFk) 106 if err != nil { 107 return func() (Device, error) { return Device{}, err } 108 } 109 return func() (Device, error) { 110 var d Device 111 var err error 112 if rows.Next() { 113 err = rows.Scan(&d.DeviceId, &d.DeviceName, &d.Timestamp) 114 } else { 115 rows.Close() 116 err = ErrNotFound 117 } 118 return d, err 119 } 120 } 121 122 // CheckDeviceStatus gets the current status of a device as a string 123 // if the device has not been recorded in the 'event' table, returns an empty string. 124 func (i dbTransactions) CheckDeviceStatus(licenseStatusFk int, deviceId string) (string, error) { 125 var typeString string 126 var typeInt int 127 128 row := i.dbCheckDeviceStatus.QueryRow(licenseStatusFk, deviceId) 129 err := row.Scan(&typeInt) 130 if err != nil { 131 if err == sql.ErrNoRows { 132 return typeString, nil 133 } 134 } 135 typeString = status.EventTypes[typeInt] 136 return typeString, err 137 } 138 139 // Open defines scripts for queries & create the 'event' table if it does not exist 140 func Open(db *sql.DB) (t Transactions, err error) { 141 142 driver, _ := config.GetDatabase(config.Config.LsdServer.Database) 143 144 // if sqlite, create the event table in the lsd db if it does not exist 145 if driver == "sqlite3" { 146 _, err = db.Exec(tableDef) 147 if err != nil { 148 log.Println("Error creating sqlite event table") 149 return 150 } 151 } 152 153 // select an event by its id 154 dbGet, err := db.Prepare("SELECT * FROM event WHERE id = ?") 155 if err != nil { 156 return 157 } 158 159 dbGetByStatusID, err := db.Prepare("SELECT * FROM event WHERE license_status_fk = ?") 160 if err != nil { 161 return 162 } 163 164 // the status of a device corresponds to the latest event stored in the db. 165 var dbCheckDeviceStatus *sql.Stmt 166 if driver == "mssql" { 167 dbCheckDeviceStatus, err = db.Prepare(`SELECT TOP 1 type FROM event WHERE license_status_fk = ? 168 AND device_id = ? ORDER BY timestamp DESC`) 169 } else { 170 dbCheckDeviceStatus, err = db.Prepare(`SELECT type FROM event WHERE license_status_fk = ? 171 AND device_id = ? ORDER BY timestamp DESC LIMIT 1`) 172 } 173 if err != nil { 174 return 175 } 176 177 dbListRegisteredDevices, err := db.Prepare(`SELECT device_id, 178 device_name, timestamp FROM event WHERE license_status_fk = ? AND type = 1`) 179 if err != nil { 180 return 181 } 182 183 t = dbTransactions{db, dbGet, dbGetByStatusID, dbCheckDeviceStatus, dbListRegisteredDevices} 184 return 185 } 186 187 const tableDef = "CREATE TABLE IF NOT EXISTS event (" + 188 "id integer PRIMARY KEY," + 189 "device_name varchar(255) DEFAULT NULL," + 190 "timestamp datetime NOT NULL," + 191 "type int NOT NULL," + 192 "device_id varchar(255) DEFAULT NULL," + 193 "license_status_fk int NOT NULL," + 194 "FOREIGN KEY(license_status_fk) REFERENCES license_status(id)" + 195 ");" + 196 "CREATE INDEX IF NOT EXISTS license_status_fk_index on event (license_status_fk);"