user_store.go 47 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919
  1. package state
  2. import (
  3. "bytes"
  4. "context"
  5. "database/sql"
  6. "embed"
  7. "errors"
  8. "fmt"
  9. "io/fs"
  10. "math"
  11. "net/http"
  12. "net/mail"
  13. "strconv"
  14. "strings"
  15. "time"
  16. "github.com/golang-migrate/migrate/v4"
  17. migratesqlite "github.com/golang-migrate/migrate/v4/database/sqlite"
  18. "github.com/golang-migrate/migrate/v4/source/httpfs"
  19. "modernc.org/sqlite"
  20. lib "modernc.org/sqlite/lib"
  21. "github.com/mk6i/retro-aim-server/wire"
  22. )
  23. var (
  24. ErrKeywordCategoryExists = errors.New("keyword category already exists")
  25. ErrKeywordCategoryNotFound = errors.New("keyword category not found")
  26. ErrKeywordExists = errors.New("keyword already exists")
  27. ErrKeywordInUse = errors.New("can't delete keyword that is associated with a user")
  28. ErrKeywordNotFound = errors.New("keyword not found")
  29. errTooManyCategories = errors.New("there are too many keyword categories")
  30. errTooManyKeywords = errors.New("there are too many keywords")
  31. )
  32. //go:embed migrations/*
  33. var migrations embed.FS
  34. // SQLiteUserStore stores user feedbag (buddy list), profile, and
  35. // authentication credentials information in a SQLite database.
  36. type SQLiteUserStore struct {
  37. db *sql.DB
  38. }
  39. // NewSQLiteUserStore creates a new instance of SQLiteUserStore. If the
  40. // database does not already exist, a new one is created with the required
  41. // schema.
  42. func NewSQLiteUserStore(dbFilePath string) (*SQLiteUserStore, error) {
  43. db, err := sql.Open("sqlite", fmt.Sprintf("file:%s?_pragma=foreign_keys=on", dbFilePath))
  44. if err != nil {
  45. return nil, err
  46. }
  47. // Set the maximum number of open connections to 1.
  48. // This is crucial to prevent SQLITE_BUSY errors, which occur when the database
  49. // is locked due to concurrent access. By limiting the number of open connections
  50. // to 1, we ensure that all database operations are serialized, thus avoiding
  51. // any potential locking issues.
  52. db.SetMaxOpenConns(1)
  53. store := &SQLiteUserStore{db: db}
  54. if err := store.runMigrations(); err != nil {
  55. return nil, fmt.Errorf("failed to run migrations: %w", err)
  56. }
  57. return store, nil
  58. }
  59. func (f SQLiteUserStore) runMigrations() error {
  60. migrationFS, err := fs.Sub(migrations, "migrations")
  61. if err != nil {
  62. return fmt.Errorf("failed to prepare migration subdirectory: %v", err)
  63. }
  64. sourceInstance, err := httpfs.New(http.FS(migrationFS), ".")
  65. if err != nil {
  66. return fmt.Errorf("failed to create source instance from embedded filesystem: %v", err)
  67. }
  68. driver, err := migratesqlite.WithInstance(f.db, &migratesqlite.Config{})
  69. if err != nil {
  70. return fmt.Errorf("cannot create database driver: %v", err)
  71. }
  72. m, err := migrate.NewWithInstance("httpfs", sourceInstance, "sqlite", driver)
  73. if err != nil {
  74. return fmt.Errorf("failed to create migrate instance: %v", err)
  75. }
  76. if err := m.Up(); err != nil && !errors.Is(err, migrate.ErrNoChange) {
  77. return fmt.Errorf("failed to run migrations: %v", err)
  78. }
  79. return nil
  80. }
  81. func (f SQLiteUserStore) AllUsers(ctx context.Context) ([]User, error) {
  82. q := `SELECT identScreenName, displayScreenName, isICQ FROM users`
  83. rows, err := f.db.QueryContext(ctx, q)
  84. if err != nil {
  85. return nil, err
  86. }
  87. defer rows.Close()
  88. var users []User
  89. for rows.Next() {
  90. var identSN, displaySN string
  91. var isICQ bool
  92. if err := rows.Scan(&identSN, &displaySN, &isICQ); err != nil {
  93. return nil, err
  94. }
  95. users = append(users, User{
  96. IdentScreenName: NewIdentScreenName(identSN),
  97. DisplayScreenName: DisplayScreenName(displaySN),
  98. IsICQ: isICQ,
  99. })
  100. }
  101. if err := rows.Err(); err != nil {
  102. return nil, err
  103. }
  104. return users, nil
  105. }
  106. func (f SQLiteUserStore) FindByUIN(ctx context.Context, UIN uint32) (User, error) {
  107. users, err := f.queryUsers(ctx, `identScreenName = ?`, []any{strconv.Itoa(int(UIN))})
  108. if err != nil {
  109. return User{}, fmt.Errorf("FindByUIN: %w", err)
  110. }
  111. if len(users) == 0 {
  112. return User{}, ErrNoUser
  113. }
  114. return users[0], nil
  115. }
  116. func (f SQLiteUserStore) FindByICQEmail(ctx context.Context, email string) (User, error) {
  117. users, err := f.queryUsers(ctx, `icq_basicInfo_emailAddress = ?`, []any{email})
  118. if err != nil {
  119. return User{}, fmt.Errorf("FindByICQEmail: %w", err)
  120. }
  121. if len(users) == 0 {
  122. return User{}, ErrNoUser
  123. }
  124. return users[0], nil
  125. }
  126. func (f SQLiteUserStore) FindByAIMEmail(ctx context.Context, email string) (User, error) {
  127. users, err := f.queryUsers(ctx, `emailAddress = ?`, []any{email})
  128. if err != nil {
  129. return User{}, fmt.Errorf("FindByAIMEmail: %w", err)
  130. }
  131. if len(users) == 0 {
  132. return User{}, ErrNoUser
  133. }
  134. return users[0], nil
  135. }
  136. func (f SQLiteUserStore) FindByAIMKeyword(ctx context.Context, keyword string) ([]User, error) {
  137. where := `
  138. (SELECT id FROM aimKeyword WHERE name = ?) IN
  139. (aim_keyword1, aim_keyword2, aim_keyword3, aim_keyword4, aim_keyword5)
  140. `
  141. users, err := f.queryUsers(ctx, where, []any{keyword})
  142. if err != nil {
  143. return nil, err
  144. }
  145. return users, nil
  146. }
  147. func (f SQLiteUserStore) FindByICQName(ctx context.Context, firstName, lastName, nickName string) ([]User, error) {
  148. var args []any
  149. var clauses []string
  150. if firstName != "" {
  151. args = append(args, firstName)
  152. clauses = append(clauses, `LOWER(icq_basicInfo_firstName) = LOWER(?)`)
  153. }
  154. if lastName != "" {
  155. args = append(args, lastName)
  156. clauses = append(clauses, `LOWER(icq_basicInfo_lastName) = LOWER(?)`)
  157. }
  158. if nickName != "" {
  159. args = append(args, nickName)
  160. clauses = append(clauses, `LOWER(icq_basicInfo_nickName) = LOWER(?)`)
  161. }
  162. whereClause := strings.Join(clauses, " AND ")
  163. users, err := f.queryUsers(ctx, whereClause, args)
  164. if err != nil {
  165. err = fmt.Errorf("FindByICQName: %w", err)
  166. }
  167. return users, nil
  168. }
  169. func (f SQLiteUserStore) FindByAIMNameAndAddr(ctx context.Context, info AIMNameAndAddr) ([]User, error) {
  170. var args []any
  171. var clauses []string
  172. if info.FirstName != "" {
  173. args = append(args, info.FirstName)
  174. clauses = append(clauses, `LOWER(aim_firstName) = LOWER(?)`)
  175. }
  176. if info.LastName != "" {
  177. args = append(args, info.LastName)
  178. clauses = append(clauses, `LOWER(aim_lastName) = LOWER(?)`)
  179. }
  180. if info.MiddleName != "" {
  181. args = append(args, info.MiddleName)
  182. clauses = append(clauses, `LOWER(aim_middleName) = LOWER(?)`)
  183. }
  184. if info.MaidenName != "" {
  185. args = append(args, info.MaidenName)
  186. clauses = append(clauses, `LOWER(aim_maidenName) = LOWER(?)`)
  187. }
  188. if info.Country != "" {
  189. args = append(args, info.Country)
  190. clauses = append(clauses, `LOWER(aim_country) = LOWER(?)`)
  191. }
  192. if info.State != "" {
  193. args = append(args, info.State)
  194. clauses = append(clauses, `LOWER(aim_state) = LOWER(?)`)
  195. }
  196. if info.City != "" {
  197. args = append(args, info.City)
  198. clauses = append(clauses, `LOWER(aim_city) = LOWER(?)`)
  199. }
  200. if info.NickName != "" {
  201. args = append(args, info.NickName)
  202. clauses = append(clauses, `LOWER(aim_nickName) = LOWER(?)`)
  203. }
  204. if info.ZIPCode != "" {
  205. args = append(args, info.ZIPCode)
  206. clauses = append(clauses, `LOWER(aim_zipCode) = LOWER(?)`)
  207. }
  208. if info.Address != "" {
  209. args = append(args, info.Address)
  210. clauses = append(clauses, `LOWER(aim_address) = LOWER(?)`)
  211. }
  212. whereClause := strings.Join(clauses, " AND ")
  213. users, err := f.queryUsers(ctx, whereClause, args)
  214. if err != nil {
  215. err = fmt.Errorf("FindByAIMNameAndAddr: %w", err)
  216. }
  217. return users, nil
  218. }
  219. func (f SQLiteUserStore) FindByICQInterests(ctx context.Context, code uint16, keywords []string) ([]User, error) {
  220. var args []any
  221. var clauses []string
  222. for i := 1; i <= 4; i++ {
  223. var subClauses []string
  224. args = append(args, code)
  225. for _, key := range keywords {
  226. subClauses = append(subClauses, fmt.Sprintf("icq_interests_keyword%d LIKE ?", i))
  227. args = append(args, "%"+key+"%")
  228. }
  229. clauses = append(clauses, fmt.Sprintf("(icq_interests_code%d = ? AND (%s))", i, strings.Join(subClauses, " OR ")))
  230. }
  231. cond := strings.Join(clauses, " OR ")
  232. users, err := f.queryUsers(ctx, cond, args)
  233. if err != nil {
  234. err = fmt.Errorf("FindByICQInterests: %w", err)
  235. }
  236. return users, nil
  237. }
  238. func (f SQLiteUserStore) FindByICQKeyword(ctx context.Context, keyword string) ([]User, error) {
  239. var args []any
  240. var clauses []string
  241. for i := 1; i <= 4; i++ {
  242. args = append(args, "%"+keyword+"%")
  243. clauses = append(clauses, fmt.Sprintf("icq_interests_keyword%d LIKE ?", i))
  244. }
  245. whereClause := strings.Join(clauses, " OR ")
  246. users, err := f.queryUsers(ctx, whereClause, args)
  247. if err != nil {
  248. err = fmt.Errorf("FindByICQKeyword: %w", err)
  249. }
  250. return users, nil
  251. }
  252. func (f SQLiteUserStore) User(ctx context.Context, screenName IdentScreenName) (*User, error) {
  253. users, err := f.queryUsers(ctx, `identScreenName = ?`, []any{screenName.String()})
  254. if err != nil {
  255. return nil, fmt.Errorf("User: %w", err)
  256. }
  257. if len(users) == 0 {
  258. return nil, nil
  259. }
  260. return &users[0], nil
  261. }
  262. // queryUsers retrieves a list of users from the database based on the
  263. // specified WHERE clause and query parameters. Returns a slice of User objects
  264. // or an error if the query fails.
  265. func (f SQLiteUserStore) queryUsers(ctx context.Context, whereClause string, queryParams []any) ([]User, error) {
  266. q := `
  267. SELECT
  268. identScreenName,
  269. displayScreenName,
  270. emailAddress,
  271. authKey,
  272. strongMD5Pass,
  273. weakMD5Pass,
  274. confirmStatus,
  275. regStatus,
  276. suspendedStatus,
  277. isICQ,
  278. icq_affiliations_currentCode1,
  279. icq_affiliations_currentCode2,
  280. icq_affiliations_currentCode3,
  281. icq_affiliations_currentKeyword1,
  282. icq_affiliations_currentKeyword2,
  283. icq_affiliations_currentKeyword3,
  284. icq_affiliations_pastCode1,
  285. icq_affiliations_pastCode2,
  286. icq_affiliations_pastCode3,
  287. icq_affiliations_pastKeyword1,
  288. icq_affiliations_pastKeyword2,
  289. icq_affiliations_pastKeyword3,
  290. icq_basicInfo_address,
  291. icq_basicInfo_cellPhone,
  292. icq_basicInfo_city,
  293. icq_basicInfo_countryCode,
  294. icq_basicInfo_emailAddress,
  295. icq_basicInfo_fax,
  296. icq_basicInfo_firstName,
  297. icq_basicInfo_gmtOffset,
  298. icq_basicInfo_lastName,
  299. icq_basicInfo_nickName,
  300. icq_basicInfo_phone,
  301. icq_basicInfo_publishEmail,
  302. icq_basicInfo_state,
  303. icq_basicInfo_zipCode,
  304. icq_interests_code1,
  305. icq_interests_code2,
  306. icq_interests_code3,
  307. icq_interests_code4,
  308. icq_interests_keyword1,
  309. icq_interests_keyword2,
  310. icq_interests_keyword3,
  311. icq_interests_keyword4,
  312. icq_moreInfo_birthDay,
  313. icq_moreInfo_birthMonth,
  314. icq_moreInfo_birthYear,
  315. icq_moreInfo_gender,
  316. icq_moreInfo_homePageAddr,
  317. icq_moreInfo_lang1,
  318. icq_moreInfo_lang2,
  319. icq_moreInfo_lang3,
  320. icq_notes,
  321. icq_permissions_authRequired,
  322. icq_workInfo_address,
  323. icq_workInfo_city,
  324. icq_workInfo_company,
  325. icq_workInfo_countryCode,
  326. icq_workInfo_department,
  327. icq_workInfo_fax,
  328. icq_workInfo_occupationCode,
  329. icq_workInfo_phone,
  330. icq_workInfo_position,
  331. icq_workInfo_state,
  332. icq_workInfo_webPage,
  333. icq_workInfo_zipCode,
  334. aim_firstName,
  335. aim_lastName,
  336. aim_middleName,
  337. aim_maidenName,
  338. aim_country,
  339. aim_state,
  340. aim_city,
  341. aim_nickName,
  342. aim_zipCode,
  343. aim_address,
  344. tocConfig
  345. FROM users
  346. WHERE %s
  347. `
  348. q = fmt.Sprintf(q, whereClause)
  349. rows, err := f.db.QueryContext(ctx, q, queryParams...)
  350. if err != nil {
  351. return nil, err
  352. }
  353. defer rows.Close()
  354. var users []User
  355. for rows.Next() {
  356. var u User
  357. var sn string
  358. err := rows.Scan(
  359. &sn,
  360. &u.DisplayScreenName,
  361. &u.EmailAddress,
  362. &u.AuthKey,
  363. &u.StrongMD5Pass,
  364. &u.WeakMD5Pass,
  365. &u.ConfirmStatus,
  366. &u.RegStatus,
  367. &u.SuspendedStatus,
  368. &u.IsICQ,
  369. &u.ICQAffiliations.CurrentCode1,
  370. &u.ICQAffiliations.CurrentCode2,
  371. &u.ICQAffiliations.CurrentCode3,
  372. &u.ICQAffiliations.CurrentKeyword1,
  373. &u.ICQAffiliations.CurrentKeyword2,
  374. &u.ICQAffiliations.CurrentKeyword3,
  375. &u.ICQAffiliations.PastCode1,
  376. &u.ICQAffiliations.PastCode2,
  377. &u.ICQAffiliations.PastCode3,
  378. &u.ICQAffiliations.PastKeyword1,
  379. &u.ICQAffiliations.PastKeyword2,
  380. &u.ICQAffiliations.PastKeyword3,
  381. &u.ICQBasicInfo.Address,
  382. &u.ICQBasicInfo.CellPhone,
  383. &u.ICQBasicInfo.City,
  384. &u.ICQBasicInfo.CountryCode,
  385. &u.ICQBasicInfo.EmailAddress,
  386. &u.ICQBasicInfo.Fax,
  387. &u.ICQBasicInfo.FirstName,
  388. &u.ICQBasicInfo.GMTOffset,
  389. &u.ICQBasicInfo.LastName,
  390. &u.ICQBasicInfo.Nickname,
  391. &u.ICQBasicInfo.Phone,
  392. &u.ICQBasicInfo.PublishEmail,
  393. &u.ICQBasicInfo.State,
  394. &u.ICQBasicInfo.ZIPCode,
  395. &u.ICQInterests.Code1,
  396. &u.ICQInterests.Code2,
  397. &u.ICQInterests.Code3,
  398. &u.ICQInterests.Code4,
  399. &u.ICQInterests.Keyword1,
  400. &u.ICQInterests.Keyword2,
  401. &u.ICQInterests.Keyword3,
  402. &u.ICQInterests.Keyword4,
  403. &u.ICQMoreInfo.BirthDay,
  404. &u.ICQMoreInfo.BirthMonth,
  405. &u.ICQMoreInfo.BirthYear,
  406. &u.ICQMoreInfo.Gender,
  407. &u.ICQMoreInfo.HomePageAddr,
  408. &u.ICQMoreInfo.Lang1,
  409. &u.ICQMoreInfo.Lang2,
  410. &u.ICQMoreInfo.Lang3,
  411. &u.ICQNotes.Notes,
  412. &u.ICQPermissions.AuthRequired,
  413. &u.ICQWorkInfo.Address,
  414. &u.ICQWorkInfo.City,
  415. &u.ICQWorkInfo.Company,
  416. &u.ICQWorkInfo.CountryCode,
  417. &u.ICQWorkInfo.Department,
  418. &u.ICQWorkInfo.Fax,
  419. &u.ICQWorkInfo.OccupationCode,
  420. &u.ICQWorkInfo.Phone,
  421. &u.ICQWorkInfo.Position,
  422. &u.ICQWorkInfo.State,
  423. &u.ICQWorkInfo.WebPage,
  424. &u.ICQWorkInfo.ZIPCode,
  425. &u.AIMDirectoryInfo.FirstName,
  426. &u.AIMDirectoryInfo.LastName,
  427. &u.AIMDirectoryInfo.MiddleName,
  428. &u.AIMDirectoryInfo.MaidenName,
  429. &u.AIMDirectoryInfo.Country,
  430. &u.AIMDirectoryInfo.State,
  431. &u.AIMDirectoryInfo.City,
  432. &u.AIMDirectoryInfo.NickName,
  433. &u.AIMDirectoryInfo.ZIPCode,
  434. &u.AIMDirectoryInfo.Address,
  435. &u.TOCConfig,
  436. )
  437. if err != nil {
  438. return nil, err
  439. }
  440. u.IdentScreenName = NewIdentScreenName(sn)
  441. users = append(users, u)
  442. }
  443. if err = rows.Err(); err != nil {
  444. return nil, err
  445. }
  446. return users, nil
  447. }
  448. func (f SQLiteUserStore) InsertUser(ctx context.Context, u User) error {
  449. if u.DisplayScreenName.IsUIN() && !u.IsICQ {
  450. return errors.New("inserting user with UIN and isICQ=false")
  451. }
  452. q := `
  453. INSERT INTO users (identScreenName, displayScreenName, authKey, weakMD5Pass, strongMD5Pass, isICQ)
  454. VALUES (?, ?, ?, ?, ?, ?)
  455. ON CONFLICT (identScreenName) DO NOTHING
  456. `
  457. result, err := f.db.ExecContext(ctx,
  458. q,
  459. u.IdentScreenName.String(),
  460. u.DisplayScreenName,
  461. u.AuthKey,
  462. u.WeakMD5Pass,
  463. u.StrongMD5Pass,
  464. u.IsICQ,
  465. )
  466. if err != nil {
  467. return err
  468. }
  469. rowsAffected, err := result.RowsAffected()
  470. if err != nil {
  471. return err
  472. }
  473. if rowsAffected == 0 {
  474. return ErrDupUser
  475. }
  476. return nil
  477. }
  478. func (f SQLiteUserStore) DeleteUser(ctx context.Context, screenName IdentScreenName) error {
  479. q := `
  480. DELETE FROM users WHERE identScreenName = ?
  481. `
  482. result, err := f.db.ExecContext(ctx, q, screenName.String())
  483. if err != nil {
  484. return err
  485. }
  486. rowsAffected, err := result.RowsAffected()
  487. if err != nil {
  488. return err
  489. }
  490. if rowsAffected == 0 {
  491. return ErrNoUser
  492. }
  493. return nil
  494. }
  495. func (f SQLiteUserStore) SetUserPassword(ctx context.Context, screenName IdentScreenName, newPassword string) error {
  496. tx, err := f.db.Begin()
  497. if err != nil {
  498. return err
  499. }
  500. defer func() {
  501. if err != nil {
  502. err = errors.Join(err, tx.Rollback())
  503. }
  504. }()
  505. q := `
  506. SELECT
  507. authKey,
  508. isICQ
  509. FROM users
  510. WHERE identScreenName = ?
  511. `
  512. u := User{}
  513. err = tx.QueryRowContext(ctx, q, screenName.String()).Scan(
  514. &u.AuthKey,
  515. &u.IsICQ,
  516. )
  517. if errors.Is(err, sql.ErrNoRows) {
  518. return ErrNoUser
  519. }
  520. if err = u.HashPassword(newPassword); err != nil {
  521. return err
  522. }
  523. q = `
  524. UPDATE users
  525. SET authKey = ?, weakMD5Pass = ?, strongMD5Pass = ?
  526. WHERE identScreenName = ?
  527. `
  528. result, err := tx.ExecContext(ctx, q, u.AuthKey, u.WeakMD5Pass, u.StrongMD5Pass, screenName.String())
  529. if err != nil {
  530. return err
  531. }
  532. rowsAffected, err := result.RowsAffected()
  533. if err != nil {
  534. return err
  535. }
  536. if rowsAffected == 0 {
  537. // it's possible the user didn't change OR the user doesn't exist.
  538. // check if the user exists.
  539. var exists int
  540. err = tx.QueryRowContext(ctx, "SELECT COUNT(*) FROM users WHERE identScreenName = ?", u.IdentScreenName.String()).Scan(&exists)
  541. if err != nil {
  542. return err // Handle possible SQL errors during the select
  543. }
  544. if exists == 0 {
  545. err = ErrNoUser // User does not exist
  546. return err
  547. }
  548. }
  549. return tx.Commit()
  550. }
  551. func (f SQLiteUserStore) Feedbag(ctx context.Context, screenName IdentScreenName) ([]wire.FeedbagItem, error) {
  552. q := `
  553. SELECT
  554. groupID,
  555. itemID,
  556. classID,
  557. name,
  558. attributes
  559. FROM feedbag
  560. WHERE screenName = ?
  561. `
  562. rows, err := f.db.QueryContext(ctx, q, screenName.String())
  563. if err != nil {
  564. return nil, err
  565. }
  566. defer rows.Close()
  567. var items []wire.FeedbagItem
  568. for rows.Next() {
  569. var item wire.FeedbagItem
  570. var attrs []byte
  571. if err := rows.Scan(&item.GroupID, &item.ItemID, &item.ClassID, &item.Name, &attrs); err != nil {
  572. return nil, err
  573. }
  574. if err := wire.UnmarshalBE(&item.TLVLBlock, bytes.NewBuffer(attrs)); err != nil {
  575. return items, err
  576. }
  577. items = append(items, item)
  578. }
  579. return items, nil
  580. }
  581. func (f SQLiteUserStore) FeedbagLastModified(ctx context.Context, screenName IdentScreenName) (time.Time, error) {
  582. var lastModified sql.NullInt64
  583. q := `SELECT MAX(lastModified) FROM feedbag WHERE screenName = ?`
  584. err := f.db.QueryRowContext(ctx, q, screenName.String()).Scan(&lastModified)
  585. return time.Unix(lastModified.Int64, 0), err
  586. }
  587. func (f SQLiteUserStore) FeedbagDelete(ctx context.Context, screenName IdentScreenName, items []wire.FeedbagItem) error {
  588. // todo add transaction
  589. q := `DELETE FROM feedbag WHERE screenName = ? AND itemID = ?`
  590. for _, item := range items {
  591. if _, err := f.db.ExecContext(ctx, q, screenName.String(), item.ItemID); err != nil {
  592. return err
  593. }
  594. }
  595. return nil
  596. }
  597. func (f SQLiteUserStore) FeedbagUpsert(ctx context.Context, screenName IdentScreenName, items []wire.FeedbagItem) error {
  598. q := `
  599. INSERT INTO feedbag (screenName, groupID, itemID, classID, name, attributes, pdMode, lastModified)
  600. VALUES (?, ?, ?, ?, ?, ?, ?, UNIXEPOCH())
  601. ON CONFLICT (screenName, groupID, itemID)
  602. DO UPDATE SET classID = excluded.classID,
  603. name = excluded.name,
  604. attributes = excluded.attributes,
  605. pdMode = excluded.pdMode,
  606. lastModified = UNIXEPOCH()
  607. `
  608. for _, item := range items {
  609. buf := &bytes.Buffer{}
  610. if err := wire.MarshalBE(item.TLVLBlock, buf); err != nil {
  611. return err
  612. }
  613. if item.ClassID == wire.FeedbagClassIdBuddy ||
  614. item.ClassID == wire.FeedbagClassIDPermit ||
  615. item.ClassID == wire.FeedbagClassIDDeny {
  616. // insert screen name identifier
  617. item.Name = NewIdentScreenName(item.Name).String()
  618. }
  619. pdMode := uint8(0)
  620. if item.ClassID == wire.FeedbagClassIdPdinfo {
  621. var hasMode bool
  622. pdMode, hasMode = item.Uint8(wire.FeedbagAttributesPdMode)
  623. if !hasMode {
  624. // by default, QIP sends a PD info item entry with no mode
  625. pdMode = uint8(wire.FeedbagPDModePermitAll)
  626. }
  627. }
  628. _, err := f.db.ExecContext(ctx,
  629. q,
  630. screenName.String(),
  631. item.GroupID,
  632. item.ItemID,
  633. item.ClassID,
  634. item.Name,
  635. buf.Bytes(),
  636. pdMode)
  637. if err != nil {
  638. return err
  639. }
  640. }
  641. return nil
  642. }
  643. func (f SQLiteUserStore) ClearBuddyListRegistry(ctx context.Context) error {
  644. if _, err := f.db.ExecContext(ctx, `DELETE FROM buddyListMode`); err != nil {
  645. return err
  646. }
  647. if _, err := f.db.ExecContext(ctx, `DELETE FROM clientSideBuddyList`); err != nil {
  648. return err
  649. }
  650. return nil
  651. }
  652. func (f SQLiteUserStore) RegisterBuddyList(ctx context.Context, user IdentScreenName) error {
  653. q := `
  654. INSERT INTO buddyListMode (screenName, clientSidePDMode) VALUES(?, ?)
  655. ON CONFLICT (screenName) DO NOTHING
  656. `
  657. _, err := f.db.ExecContext(ctx, q, user.String(), wire.FeedbagPDModePermitAll)
  658. return err
  659. }
  660. func (f SQLiteUserStore) UnregisterBuddyList(ctx context.Context, user IdentScreenName) error {
  661. if _, err := f.db.ExecContext(ctx, `DELETE FROM buddyListMode WHERE screenName = ?`, user.String()); err != nil {
  662. return err
  663. }
  664. if _, err := f.db.ExecContext(ctx, `DELETE FROM clientSideBuddyList WHERE me = ?`, user.String()); err != nil {
  665. return err
  666. }
  667. return nil
  668. }
  669. func (f SQLiteUserStore) UseFeedbag(ctx context.Context, screenName IdentScreenName) error {
  670. q := `
  671. INSERT INTO buddyListMode (screenName, useFeedbag)
  672. VALUES (?, ?)
  673. ON CONFLICT (screenName)
  674. DO UPDATE SET clientSidePDMode = 0,
  675. useFeedbag = true
  676. `
  677. _, err := f.db.ExecContext(ctx, q, screenName.String(), true)
  678. return err
  679. }
  680. func (f SQLiteUserStore) SetPDMode(ctx context.Context, me IdentScreenName, pdMode wire.FeedbagPDMode) error {
  681. alreadySet, err := f.isPDModeEqual(ctx, me, pdMode)
  682. if err != nil {
  683. return fmt.Errorf("isPDModeEqual: %w", err)
  684. }
  685. if alreadySet {
  686. return nil
  687. }
  688. tx, err := f.db.Begin()
  689. if err != nil {
  690. return err
  691. }
  692. defer func() {
  693. _ = tx.Rollback()
  694. }()
  695. if err := setClientSidePDMode(ctx, tx, me, pdMode); err != nil {
  696. return fmt.Errorf("setClientSidePDMode: %w", err)
  697. }
  698. if err := clearClientSidePDFlags(ctx, tx, me, pdMode); err != nil {
  699. return fmt.Errorf("clearClientSidePDFlags: %w", err)
  700. }
  701. if err := clearBlankClientSideBuddies(ctx, tx, me, pdMode); err != nil {
  702. return fmt.Errorf("clearBlankClientSideBuddies: %w", err)
  703. }
  704. if err := tx.Commit(); err != nil {
  705. return fmt.Errorf("commit: %w", err)
  706. }
  707. return nil
  708. }
  709. // isPDModeEqual indicates whether the current permit/deny mode is already set
  710. // to pdMode.
  711. func (f SQLiteUserStore) isPDModeEqual(ctx context.Context, me IdentScreenName, pdMode wire.FeedbagPDMode) (bool, error) {
  712. q := `
  713. SELECT true
  714. FROM buddyListMode
  715. WHERE screenName = ? AND clientSidePDMode = ?
  716. `
  717. var isEqual bool
  718. err := f.db.QueryRowContext(ctx, q, me.String(), pdMode).Scan(&isEqual)
  719. if err != nil && !errors.Is(err, sql.ErrNoRows) {
  720. return false, err
  721. }
  722. return isEqual, nil
  723. }
  724. // setClientSidePDMode sets the permit/deny mode for my client-side buddy list.
  725. func setClientSidePDMode(ctx context.Context, tx *sql.Tx, me IdentScreenName, pdMode wire.FeedbagPDMode) error {
  726. q := `
  727. INSERT INTO buddyListMode (screenName, clientSidePDMode) VALUES(?, ?)
  728. ON CONFLICT (screenName)
  729. DO UPDATE SET clientSidePDMode = excluded.clientSidePDMode
  730. `
  731. _, err := tx.ExecContext(ctx, q, me.String(), pdMode)
  732. if err != nil {
  733. return err
  734. }
  735. return nil
  736. }
  737. // clearBlankClientSideBuddies removes client-side buddy where all flags
  738. // (isBuddy, isPermit, isDeny) are false.
  739. func clearBlankClientSideBuddies(ctx context.Context, tx *sql.Tx, me IdentScreenName, pdMode wire.FeedbagPDMode) error {
  740. q := `
  741. DELETE FROM clientSideBuddyList
  742. WHERE isBuddy IS FALSE
  743. AND isPermit IS FALSE
  744. AND isDeny IS FALSE
  745. AND me = ?
  746. `
  747. _, err := tx.ExecContext(ctx, q, me.String(), pdMode)
  748. return err
  749. }
  750. // clearClientSidePDFlags clears permit/deny flags.
  751. func clearClientSidePDFlags(ctx context.Context, tx *sql.Tx, me IdentScreenName, pdMode wire.FeedbagPDMode) error {
  752. q := `
  753. UPDATE clientSideBuddyList
  754. SET isDeny = false, isPermit = false
  755. WHERE me = ?
  756. `
  757. _, err := tx.ExecContext(ctx, q, me.String(), pdMode)
  758. return err
  759. }
  760. func (f SQLiteUserStore) AddBuddy(ctx context.Context, me IdentScreenName, them IdentScreenName) error {
  761. q := `
  762. INSERT INTO clientSideBuddyList (me, them, isBuddy)
  763. VALUES (?, ?, true)
  764. ON CONFLICT (me, them) DO UPDATE SET isBuddy = true
  765. `
  766. _, err := f.db.ExecContext(ctx, q, me.String(), them.String())
  767. return err
  768. }
  769. func (f SQLiteUserStore) RemoveBuddy(ctx context.Context, me IdentScreenName, them IdentScreenName) error {
  770. q := `
  771. UPDATE clientSideBuddyList
  772. SET isBuddy = false
  773. WHERE me = ?
  774. AND them = ?
  775. `
  776. _, err := f.db.ExecContext(ctx, q, me.String(), them.String())
  777. return err
  778. }
  779. func (f SQLiteUserStore) DenyBuddy(ctx context.Context, me IdentScreenName, them IdentScreenName) error {
  780. q := `
  781. INSERT INTO clientSideBuddyList (me, them, isDeny)
  782. VALUES (?, ?, 1)
  783. ON CONFLICT (me, them) DO UPDATE SET isDeny = 1
  784. `
  785. _, err := f.db.ExecContext(ctx, q, me.String(), them.String())
  786. return err
  787. }
  788. func (f SQLiteUserStore) RemoveDenyBuddy(ctx context.Context, me IdentScreenName, them IdentScreenName) error {
  789. q := `
  790. UPDATE clientSideBuddyList
  791. SET isDeny = false
  792. WHERE me = ?
  793. AND them = ?
  794. `
  795. _, err := f.db.ExecContext(ctx, q, me.String(), them.String())
  796. return err
  797. }
  798. func (f SQLiteUserStore) PermitBuddy(ctx context.Context, me IdentScreenName, them IdentScreenName) error {
  799. q := `
  800. INSERT INTO clientSideBuddyList (me, them, isPermit)
  801. VALUES (?, ?, 1)
  802. ON CONFLICT (me, them) DO UPDATE SET isPermit = 1
  803. `
  804. _, err := f.db.ExecContext(ctx, q, me.String(), them.String())
  805. return err
  806. }
  807. func (f SQLiteUserStore) RemovePermitBuddy(ctx context.Context, me IdentScreenName, them IdentScreenName) error {
  808. q := `
  809. UPDATE clientSideBuddyList
  810. SET isPermit = false
  811. WHERE me = ?
  812. AND them = ?
  813. `
  814. _, err := f.db.ExecContext(ctx, q, me.String(), them.String())
  815. return err
  816. }
  817. func (f SQLiteUserStore) Profile(ctx context.Context, screenName IdentScreenName) (string, error) {
  818. q := `
  819. SELECT IFNULL(body, '')
  820. FROM profile
  821. WHERE screenName = ?
  822. `
  823. var profile string
  824. err := f.db.QueryRowContext(ctx, q, screenName.String()).Scan(&profile)
  825. if err != nil && !errors.Is(err, sql.ErrNoRows) {
  826. return "", err
  827. }
  828. return profile, nil
  829. }
  830. func (f SQLiteUserStore) SetProfile(ctx context.Context, screenName IdentScreenName, body string) error {
  831. q := `
  832. INSERT INTO profile (screenName, body)
  833. VALUES (?, ?)
  834. ON CONFLICT (screenName)
  835. DO UPDATE SET body = excluded.body
  836. `
  837. _, err := f.db.ExecContext(ctx, q, screenName.String(), body)
  838. return err
  839. }
  840. func (f SQLiteUserStore) SetDirectoryInfo(ctx context.Context, screenName IdentScreenName, info AIMNameAndAddr) error {
  841. q := `
  842. UPDATE users SET
  843. aim_firstName = ?,
  844. aim_lastName = ?,
  845. aim_middleName = ?,
  846. aim_maidenName = ?,
  847. aim_country = ?,
  848. aim_state = ?,
  849. aim_city = ?,
  850. aim_nickName = ?,
  851. aim_zipCode = ?,
  852. aim_address = ?
  853. WHERE identScreenName = ?
  854. `
  855. res, err := f.db.ExecContext(ctx,
  856. q,
  857. info.FirstName,
  858. info.LastName,
  859. info.MiddleName,
  860. info.MaidenName,
  861. info.Country,
  862. info.State,
  863. info.City,
  864. info.NickName,
  865. info.ZIPCode,
  866. info.Address,
  867. screenName.String(),
  868. )
  869. if err != nil {
  870. return fmt.Errorf("exec: %w", err)
  871. }
  872. c, err := res.RowsAffected()
  873. if err != nil {
  874. return fmt.Errorf("rows affected: %w", err)
  875. }
  876. if c == 0 {
  877. return ErrNoUser
  878. }
  879. return nil
  880. }
  881. func (f SQLiteUserStore) SetBuddyIcon(ctx context.Context, md5 []byte, image []byte) error {
  882. q := `
  883. INSERT INTO bartItem (hash, body)
  884. VALUES (?, ?)
  885. ON CONFLICT DO NOTHING
  886. `
  887. _, err := f.db.ExecContext(ctx, q, md5, image)
  888. return err
  889. }
  890. func (f SQLiteUserStore) BuddyIcon(ctx context.Context, md5 []byte) ([]byte, error) {
  891. q := `
  892. SELECT body
  893. FROM bartItem
  894. WHERE hash = ?
  895. `
  896. var body []byte
  897. err := f.db.QueryRowContext(ctx, q, md5).Scan(&body)
  898. if errors.Is(err, sql.ErrNoRows) {
  899. err = nil
  900. }
  901. return body, err
  902. }
  903. func (f SQLiteUserStore) ChatRoomByCookie(ctx context.Context, chatCookie string) (ChatRoom, error) {
  904. chatRoom := ChatRoom{}
  905. q := `
  906. SELECT exchange, name, created, creator
  907. FROM chatRoom
  908. WHERE lower(cookie) = lower(?)
  909. `
  910. var creator string
  911. err := f.db.QueryRowContext(ctx, q, chatCookie).Scan(
  912. &chatRoom.exchange,
  913. &chatRoom.name,
  914. &chatRoom.createTime,
  915. &creator,
  916. )
  917. if errors.Is(err, sql.ErrNoRows) {
  918. err = fmt.Errorf("%w: %s", ErrChatRoomNotFound, chatCookie)
  919. }
  920. chatRoom.creator = NewIdentScreenName(creator)
  921. return chatRoom, err
  922. }
  923. func (f SQLiteUserStore) ChatRoomByName(ctx context.Context, exchange uint16, name string) (ChatRoom, error) {
  924. chatRoom := ChatRoom{
  925. exchange: exchange,
  926. }
  927. q := `
  928. SELECT name, created, creator
  929. FROM chatRoom
  930. WHERE exchange = ? AND lower(name) = lower(?)
  931. `
  932. var creator string
  933. err := f.db.QueryRowContext(ctx, q, exchange, name).Scan(
  934. &chatRoom.name,
  935. &chatRoom.createTime,
  936. &creator,
  937. )
  938. if errors.Is(err, sql.ErrNoRows) {
  939. err = ErrChatRoomNotFound
  940. }
  941. chatRoom.creator = NewIdentScreenName(creator)
  942. return chatRoom, err
  943. }
  944. func (f SQLiteUserStore) CreateChatRoom(ctx context.Context, chatRoom *ChatRoom) error {
  945. chatRoom.createTime = time.Now().UTC()
  946. q := `
  947. INSERT INTO chatRoom (cookie, exchange, name, created, creator)
  948. VALUES (?, ?, ?, ?, ?)
  949. `
  950. _, err := f.db.ExecContext(ctx,
  951. q,
  952. chatRoom.Cookie(),
  953. chatRoom.Exchange(),
  954. chatRoom.Name(),
  955. chatRoom.createTime,
  956. chatRoom.Creator().String(),
  957. )
  958. if err != nil {
  959. if strings.Contains(err.Error(), "constraint failed") {
  960. err = ErrDupChatRoom
  961. }
  962. err = fmt.Errorf("CreateChatRoom: %w", err)
  963. }
  964. return err
  965. }
  966. func (f SQLiteUserStore) AllChatRooms(ctx context.Context, exchange uint16) ([]ChatRoom, error) {
  967. q := `
  968. SELECT created, creator, name
  969. FROM chatRoom
  970. WHERE exchange = ?
  971. ORDER BY created ASC
  972. `
  973. rows, err := f.db.QueryContext(ctx, q, exchange)
  974. if err != nil {
  975. return nil, err
  976. }
  977. defer rows.Close()
  978. var users []ChatRoom
  979. for rows.Next() {
  980. cr := ChatRoom{
  981. exchange: exchange,
  982. }
  983. var creator string
  984. if err := rows.Scan(&cr.createTime, &creator, &cr.name); err != nil {
  985. return nil, err
  986. }
  987. cr.creator = NewIdentScreenName(creator)
  988. users = append(users, cr)
  989. }
  990. if err := rows.Err(); err != nil {
  991. return nil, err
  992. }
  993. return users, nil
  994. }
  995. func (f SQLiteUserStore) UpdateDisplayScreenName(ctx context.Context, displayScreenName DisplayScreenName) error {
  996. q := `
  997. UPDATE users
  998. SET displayScreenName = ?
  999. WHERE identScreenName = ?
  1000. `
  1001. _, err := f.db.ExecContext(ctx, q, displayScreenName.String(), displayScreenName.IdentScreenName().String())
  1002. return err
  1003. }
  1004. func (f SQLiteUserStore) UpdateEmailAddress(ctx context.Context, screenName IdentScreenName, emailAddress *mail.Address) error {
  1005. q := `
  1006. UPDATE users
  1007. SET emailAddress = ?
  1008. WHERE identScreenName = ?
  1009. `
  1010. _, err := f.db.ExecContext(ctx, q, emailAddress.Address, screenName.String())
  1011. return err
  1012. }
  1013. func (f SQLiteUserStore) EmailAddress(ctx context.Context, screenName IdentScreenName) (*mail.Address, error) {
  1014. q := `
  1015. SELECT emailAddress
  1016. FROM users
  1017. WHERE identScreenName = ?
  1018. `
  1019. var emailAddress string
  1020. err := f.db.QueryRowContext(ctx, q, screenName.String()).Scan(&emailAddress)
  1021. // username isn't found for some reason
  1022. if err != nil && !errors.Is(err, sql.ErrNoRows) {
  1023. return nil, err
  1024. }
  1025. e, err := mail.ParseAddress(emailAddress)
  1026. if err != nil {
  1027. return nil, fmt.Errorf("%w: %w", ErrNoEmailAddress, err)
  1028. }
  1029. return e, nil
  1030. }
  1031. func (f SQLiteUserStore) UpdateRegStatus(ctx context.Context, screenName IdentScreenName, regStatus uint16) error {
  1032. q := `
  1033. UPDATE users
  1034. SET regStatus = ?
  1035. WHERE identScreenName = ?
  1036. `
  1037. _, err := f.db.ExecContext(ctx, q, regStatus, screenName.String())
  1038. return err
  1039. }
  1040. func (f SQLiteUserStore) RegStatus(ctx context.Context, screenName IdentScreenName) (uint16, error) {
  1041. q := `
  1042. SELECT regStatus
  1043. FROM users
  1044. WHERE identScreenName = ?
  1045. `
  1046. var regStatus uint16
  1047. err := f.db.QueryRowContext(ctx, q, screenName.String()).Scan(&regStatus)
  1048. // username isn't found for some reason
  1049. if err != nil && !errors.Is(err, sql.ErrNoRows) {
  1050. return 0, err
  1051. }
  1052. return regStatus, nil
  1053. }
  1054. func (f SQLiteUserStore) UpdateConfirmStatus(ctx context.Context, screenName IdentScreenName, confirmStatus bool) error {
  1055. q := `
  1056. UPDATE users
  1057. SET confirmStatus = ?
  1058. WHERE identScreenName = ?
  1059. `
  1060. _, err := f.db.ExecContext(ctx, q, confirmStatus, screenName.String())
  1061. return err
  1062. }
  1063. func (f SQLiteUserStore) ConfirmStatus(ctx context.Context, screenName IdentScreenName) (bool, error) {
  1064. q := `
  1065. SELECT confirmStatus
  1066. FROM users
  1067. WHERE identScreenName = ?
  1068. `
  1069. var confirmStatus bool
  1070. err := f.db.QueryRowContext(ctx, q, screenName.String()).Scan(&confirmStatus)
  1071. // username isn't found for some reason
  1072. if err != nil && !errors.Is(err, sql.ErrNoRows) {
  1073. return false, err
  1074. }
  1075. return confirmStatus, nil
  1076. }
  1077. func (f SQLiteUserStore) UpdateSuspendedStatus(ctx context.Context, suspendedStatus uint16, screenName IdentScreenName) error {
  1078. q := `
  1079. UPDATE users
  1080. SET suspendedStatus = ?
  1081. WHERE identScreenName = ?
  1082. `
  1083. _, err := f.db.ExecContext(ctx, q, suspendedStatus, screenName.String())
  1084. return err
  1085. }
  1086. func (f SQLiteUserStore) SetWorkInfo(ctx context.Context, name IdentScreenName, data ICQWorkInfo) error {
  1087. q := `
  1088. UPDATE users SET
  1089. icq_workInfo_company = ?,
  1090. icq_workInfo_department = ?,
  1091. icq_workInfo_occupationCode = ?,
  1092. icq_workInfo_position = ?,
  1093. icq_workInfo_address = ?,
  1094. icq_workInfo_city = ?,
  1095. icq_workInfo_countryCode = ?,
  1096. icq_workInfo_fax = ?,
  1097. icq_workInfo_phone = ?,
  1098. icq_workInfo_state = ?,
  1099. icq_workInfo_webPage = ?,
  1100. icq_workInfo_zipCode = ?
  1101. WHERE identScreenName = ?
  1102. `
  1103. res, err := f.db.ExecContext(ctx,
  1104. q,
  1105. data.Company,
  1106. data.Department,
  1107. data.OccupationCode,
  1108. data.Position,
  1109. data.Address,
  1110. data.City,
  1111. data.CountryCode,
  1112. data.Fax,
  1113. data.Phone,
  1114. data.State,
  1115. data.WebPage,
  1116. data.ZIPCode,
  1117. name.String(),
  1118. )
  1119. if err != nil {
  1120. return fmt.Errorf("exec: %w", err)
  1121. }
  1122. c, err := res.RowsAffected()
  1123. if err != nil {
  1124. return fmt.Errorf("rows affected: %w", err)
  1125. }
  1126. if c == 0 {
  1127. return ErrNoUser
  1128. }
  1129. return nil
  1130. }
  1131. func (f SQLiteUserStore) SetMoreInfo(ctx context.Context, name IdentScreenName, data ICQMoreInfo) error {
  1132. q := `
  1133. UPDATE users SET
  1134. icq_moreInfo_birthDay = ?,
  1135. icq_moreInfo_birthMonth = ?,
  1136. icq_moreInfo_birthYear = ?,
  1137. icq_moreInfo_gender = ?,
  1138. icq_moreInfo_homePageAddr = ?,
  1139. icq_moreInfo_lang1 = ?,
  1140. icq_moreInfo_lang2 = ?,
  1141. icq_moreInfo_lang3 = ?
  1142. WHERE identScreenName = ?
  1143. `
  1144. res, err := f.db.ExecContext(ctx,
  1145. q,
  1146. data.BirthDay,
  1147. data.BirthMonth,
  1148. data.BirthYear,
  1149. data.Gender,
  1150. data.HomePageAddr,
  1151. data.Lang1,
  1152. data.Lang2,
  1153. data.Lang3,
  1154. name.String(),
  1155. )
  1156. if err != nil {
  1157. return fmt.Errorf("exec: %w", err)
  1158. }
  1159. c, err := res.RowsAffected()
  1160. if err != nil {
  1161. return fmt.Errorf("rows affected: %w", err)
  1162. }
  1163. if c == 0 {
  1164. return ErrNoUser
  1165. }
  1166. return nil
  1167. }
  1168. func (f SQLiteUserStore) SetUserNotes(ctx context.Context, name IdentScreenName, data ICQUserNotes) error {
  1169. q := `
  1170. UPDATE users
  1171. SET icq_notes = ?
  1172. WHERE identScreenName = ?
  1173. `
  1174. res, err := f.db.ExecContext(ctx,
  1175. q,
  1176. data.Notes,
  1177. name.String(),
  1178. )
  1179. if err != nil {
  1180. return fmt.Errorf("exec: %w", err)
  1181. }
  1182. c, err := res.RowsAffected()
  1183. if err != nil {
  1184. return fmt.Errorf("rows affected: %w", err)
  1185. }
  1186. if c == 0 {
  1187. return ErrNoUser
  1188. }
  1189. return nil
  1190. }
  1191. func (f SQLiteUserStore) SetInterests(ctx context.Context, name IdentScreenName, data ICQInterests) error {
  1192. q := `
  1193. UPDATE users SET
  1194. icq_interests_code1 = ?,
  1195. icq_interests_keyword1 = ?,
  1196. icq_interests_code2 = ?,
  1197. icq_interests_keyword2 = ?,
  1198. icq_interests_code3 = ?,
  1199. icq_interests_keyword3 = ?,
  1200. icq_interests_code4 = ?,
  1201. icq_interests_keyword4 = ?
  1202. WHERE identScreenName = ?
  1203. `
  1204. res, err := f.db.ExecContext(ctx,
  1205. q,
  1206. data.Code1,
  1207. data.Keyword1,
  1208. data.Code2,
  1209. data.Keyword2,
  1210. data.Code3,
  1211. data.Keyword3,
  1212. data.Code4,
  1213. data.Keyword4,
  1214. name.String(),
  1215. )
  1216. if err != nil {
  1217. return fmt.Errorf("exec: %w", err)
  1218. }
  1219. c, err := res.RowsAffected()
  1220. if err != nil {
  1221. return fmt.Errorf("rows affected: %w", err)
  1222. }
  1223. if c == 0 {
  1224. return ErrNoUser
  1225. }
  1226. return nil
  1227. }
  1228. func (f SQLiteUserStore) SetAffiliations(ctx context.Context, name IdentScreenName, data ICQAffiliations) error {
  1229. q := `
  1230. UPDATE users SET
  1231. icq_affiliations_currentCode1 = ?,
  1232. icq_affiliations_currentKeyword1 = ?,
  1233. icq_affiliations_currentCode2 = ?,
  1234. icq_affiliations_currentKeyword2 = ?,
  1235. icq_affiliations_currentCode3 = ?,
  1236. icq_affiliations_currentKeyword3 = ?,
  1237. icq_affiliations_pastCode1 = ?,
  1238. icq_affiliations_pastKeyword1 = ?,
  1239. icq_affiliations_pastCode2 = ?,
  1240. icq_affiliations_pastKeyword2 = ?,
  1241. icq_affiliations_pastCode3 = ?,
  1242. icq_affiliations_pastKeyword3 = ?
  1243. WHERE identScreenName = ?
  1244. `
  1245. res, err := f.db.ExecContext(ctx,
  1246. q,
  1247. data.CurrentCode1,
  1248. data.CurrentKeyword1,
  1249. data.CurrentCode2,
  1250. data.CurrentKeyword2,
  1251. data.CurrentCode3,
  1252. data.CurrentKeyword3,
  1253. data.PastCode1,
  1254. data.PastKeyword1,
  1255. data.PastCode2,
  1256. data.PastKeyword2,
  1257. data.PastCode3,
  1258. data.PastKeyword3,
  1259. name.String(),
  1260. )
  1261. if err != nil {
  1262. return fmt.Errorf("exec: %w", err)
  1263. }
  1264. c, err := res.RowsAffected()
  1265. if err != nil {
  1266. return fmt.Errorf("rows affected: %w", err)
  1267. }
  1268. if c == 0 {
  1269. return ErrNoUser
  1270. }
  1271. return nil
  1272. }
  1273. func (f SQLiteUserStore) SetBasicInfo(ctx context.Context, name IdentScreenName, data ICQBasicInfo) error {
  1274. q := `
  1275. UPDATE users SET
  1276. icq_basicInfo_cellPhone = ?,
  1277. icq_basicInfo_countryCode = ?,
  1278. icq_basicInfo_emailAddress = ?,
  1279. icq_basicInfo_firstName = ?,
  1280. icq_basicInfo_gmtOffset = ?,
  1281. icq_basicInfo_address = ?,
  1282. icq_basicInfo_city = ?,
  1283. icq_basicInfo_fax = ?,
  1284. icq_basicInfo_phone = ?,
  1285. icq_basicInfo_state = ?,
  1286. icq_basicInfo_lastName = ?,
  1287. icq_basicInfo_nickName = ?,
  1288. icq_basicInfo_publishEmail = ?,
  1289. icq_basicInfo_zipCode = ?
  1290. WHERE identScreenName = ?
  1291. `
  1292. res, err := f.db.ExecContext(ctx,
  1293. q,
  1294. data.CellPhone,
  1295. data.CountryCode,
  1296. data.EmailAddress,
  1297. data.FirstName,
  1298. data.GMTOffset,
  1299. data.Address,
  1300. data.City,
  1301. data.Fax,
  1302. data.Phone,
  1303. data.State,
  1304. data.LastName,
  1305. data.Nickname,
  1306. data.PublishEmail,
  1307. data.ZIPCode,
  1308. name.String(),
  1309. )
  1310. if err != nil {
  1311. return fmt.Errorf("exec: %w", err)
  1312. }
  1313. c, err := res.RowsAffected()
  1314. if err != nil {
  1315. return fmt.Errorf("rows affected: %w", err)
  1316. }
  1317. if c == 0 {
  1318. return ErrNoUser
  1319. }
  1320. return nil
  1321. }
  1322. func (f SQLiteUserStore) SaveMessage(ctx context.Context, offlineMessage OfflineMessage) error {
  1323. buf := &bytes.Buffer{}
  1324. if err := wire.MarshalBE(offlineMessage.Message, buf); err != nil {
  1325. return fmt.Errorf("marshal: %w", err)
  1326. }
  1327. q := `
  1328. INSERT INTO offlineMessage (sender, recipient, message, sent)
  1329. VALUES (?, ?, ?, ?)
  1330. `
  1331. _, err := f.db.ExecContext(ctx,
  1332. q,
  1333. offlineMessage.Sender.String(),
  1334. offlineMessage.Recipient.String(),
  1335. buf.Bytes(),
  1336. offlineMessage.Sent,
  1337. )
  1338. return err
  1339. }
  1340. func (f SQLiteUserStore) RetrieveMessages(ctx context.Context, recip IdentScreenName) ([]OfflineMessage, error) {
  1341. q := `
  1342. SELECT
  1343. sender,
  1344. message,
  1345. sent
  1346. FROM offlineMessage
  1347. WHERE recipient = ?
  1348. `
  1349. rows, err := f.db.QueryContext(ctx, q, recip.String())
  1350. if err != nil {
  1351. return nil, err
  1352. }
  1353. defer rows.Close()
  1354. var messages []OfflineMessage
  1355. for rows.Next() {
  1356. var sender string
  1357. var buf []byte
  1358. var sent time.Time
  1359. if err := rows.Scan(&sender, &buf, &sent); err != nil {
  1360. return nil, err
  1361. }
  1362. var msg wire.SNAC_0x04_0x06_ICBMChannelMsgToHost
  1363. if err := wire.UnmarshalBE(&msg, bytes.NewBuffer(buf)); err != nil {
  1364. return nil, fmt.Errorf("unmarshal: %w", err)
  1365. }
  1366. messages = append(messages, OfflineMessage{
  1367. Sender: NewIdentScreenName(sender),
  1368. Recipient: recip,
  1369. Message: msg,
  1370. Sent: sent,
  1371. })
  1372. }
  1373. if err := rows.Err(); err != nil {
  1374. return nil, err
  1375. }
  1376. return messages, nil
  1377. }
  1378. func (f SQLiteUserStore) DeleteMessages(ctx context.Context, recip IdentScreenName) error {
  1379. q := `
  1380. DELETE FROM offlineMessage WHERE recipient = ?
  1381. `
  1382. _, err := f.db.ExecContext(ctx, q, recip.String())
  1383. return err
  1384. }
  1385. func (f SQLiteUserStore) BuddyIconMetadata(ctx context.Context, screenName IdentScreenName) (*wire.BARTID, error) {
  1386. q := `
  1387. SELECT
  1388. groupID,
  1389. itemID,
  1390. classID,
  1391. name,
  1392. attributes
  1393. FROM feedBag
  1394. WHERE screenname = ? AND name = ? AND classID = ?
  1395. `
  1396. var item wire.FeedbagItem
  1397. var attrs []byte
  1398. err := f.db.QueryRowContext(ctx, q, screenName.String(), wire.BARTTypesBuddyIcon, wire.FeedbagClassIdBart).Scan(&item.GroupID, &item.ItemID, &item.ClassID, &item.Name, &attrs)
  1399. if errors.Is(err, sql.ErrNoRows) {
  1400. return nil, nil
  1401. }
  1402. if err != nil {
  1403. return nil, err
  1404. }
  1405. if err := wire.UnmarshalBE(&item.TLVLBlock, bytes.NewBuffer(attrs)); err != nil {
  1406. return nil, err
  1407. }
  1408. b, hasBuf := item.Bytes(wire.FeedbagAttributesBartInfo)
  1409. if !hasBuf {
  1410. return nil, errors.New("unable to extract icon payload")
  1411. }
  1412. bartInfo := wire.BARTInfo{}
  1413. if err := wire.UnmarshalBE(&bartInfo, bytes.NewBuffer(b)); err != nil {
  1414. return nil, err
  1415. }
  1416. return &wire.BARTID{
  1417. Type: wire.BARTTypesBuddyIcon,
  1418. BARTInfo: wire.BARTInfo{
  1419. Flags: bartInfo.Flags,
  1420. Hash: bartInfo.Hash,
  1421. },
  1422. }, nil
  1423. }
  1424. func (f SQLiteUserStore) SetKeywords(ctx context.Context, screenName IdentScreenName, keywords [5]string) error {
  1425. q := `
  1426. WITH interests AS (SELECT CASE WHEN name = ? THEN id ELSE NULL END AS aim_keyword1,
  1427. CASE WHEN name = ? THEN id ELSE NULL END AS aim_keyword2,
  1428. CASE WHEN name = ? THEN id ELSE NULL END AS aim_keyword3,
  1429. CASE WHEN name = ? THEN id ELSE NULL END AS aim_keyword4,
  1430. CASE WHEN name = ? THEN id ELSE NULL END AS aim_keyword5
  1431. FROM aimKeyword
  1432. WHERE name IN (?, ?, ?, ?, ?))
  1433. UPDATE users
  1434. SET aim_keyword1 = (SELECT aim_keyword1 FROM interests WHERE aim_keyword1 IS NOT NULL),
  1435. aim_keyword2 = (SELECT aim_keyword2 FROM interests WHERE aim_keyword2 IS NOT NULL),
  1436. aim_keyword3 = (SELECT aim_keyword3 FROM interests WHERE aim_keyword3 IS NOT NULL),
  1437. aim_keyword4 = (SELECT aim_keyword4 FROM interests WHERE aim_keyword4 IS NOT NULL),
  1438. aim_keyword5 = (SELECT aim_keyword5 FROM interests WHERE aim_keyword5 IS NOT NULL)
  1439. WHERE identScreenName = ?
  1440. `
  1441. _, err := f.db.ExecContext(ctx, q,
  1442. keywords[0], keywords[1], keywords[2], keywords[3], keywords[4],
  1443. keywords[0], keywords[1], keywords[2], keywords[3], keywords[4],
  1444. screenName.String())
  1445. return err
  1446. }
  1447. func (f SQLiteUserStore) Categories(ctx context.Context) ([]Category, error) {
  1448. q := `SELECT id, name FROM aimKeywordCategory ORDER BY name`
  1449. rows, err := f.db.QueryContext(ctx, q)
  1450. if err != nil {
  1451. return nil, err
  1452. }
  1453. defer rows.Close()
  1454. var categories []Category
  1455. for rows.Next() {
  1456. category := Category{}
  1457. if err := rows.Scan(&category.ID, &category.Name); err != nil {
  1458. return nil, err
  1459. }
  1460. categories = append(categories, category)
  1461. }
  1462. if err := rows.Err(); err != nil {
  1463. return nil, err
  1464. }
  1465. return categories, nil
  1466. }
  1467. func (f SQLiteUserStore) CreateCategory(ctx context.Context, name string) (Category, error) {
  1468. tx, err := f.db.Begin()
  1469. if err != nil {
  1470. return Category{}, err
  1471. }
  1472. defer tx.Rollback()
  1473. q := `INSERT INTO aimKeywordCategory (name) VALUES (?)`
  1474. res, err := tx.ExecContext(ctx, q, name)
  1475. if err != nil {
  1476. if sqliteErr, ok := err.(*sqlite.Error); ok && sqliteErr.Code() == lib.SQLITE_CONSTRAINT_UNIQUE {
  1477. err = ErrKeywordCategoryExists
  1478. }
  1479. return Category{}, err
  1480. }
  1481. id, err := res.LastInsertId()
  1482. if err != nil {
  1483. return Category{}, err
  1484. }
  1485. if id > math.MaxUint8 {
  1486. return Category{}, errTooManyCategories
  1487. }
  1488. if err := tx.Commit(); err != nil {
  1489. return Category{}, err
  1490. }
  1491. return Category{
  1492. ID: uint8(id),
  1493. Name: name,
  1494. }, nil
  1495. }
  1496. func (f SQLiteUserStore) DeleteCategory(ctx context.Context, categoryID uint8) error {
  1497. q := `DELETE FROM aimKeywordCategory WHERE id = ?`
  1498. res, err := f.db.ExecContext(ctx, q, categoryID)
  1499. if err != nil {
  1500. // Check if the error is a foreign key constraint violation
  1501. if sqliteErr, ok := err.(*sqlite.Error); ok && sqliteErr.Code() == lib.SQLITE_CONSTRAINT_FOREIGNKEY {
  1502. return ErrKeywordInUse
  1503. }
  1504. }
  1505. c, err := res.RowsAffected()
  1506. if err != nil {
  1507. return err
  1508. }
  1509. if c == 0 {
  1510. return ErrKeywordCategoryNotFound
  1511. }
  1512. return nil
  1513. }
  1514. func (f SQLiteUserStore) KeywordsByCategory(ctx context.Context, categoryID uint8) ([]Keyword, error) {
  1515. q := `SELECT id, name FROM aimKeyword WHERE parent = ? ORDER BY name`
  1516. if categoryID == 0 {
  1517. q = `SELECT id, name FROM aimKeyword WHERE parent IS NULL ORDER BY name`
  1518. }
  1519. rows, err := f.db.QueryContext(ctx, q, categoryID)
  1520. if err != nil {
  1521. return nil, err
  1522. }
  1523. defer rows.Close()
  1524. var keywords []Keyword
  1525. for rows.Next() {
  1526. keyword := Keyword{}
  1527. if err := rows.Scan(&keyword.ID, &keyword.Name); err != nil {
  1528. return nil, err
  1529. }
  1530. keywords = append(keywords, keyword)
  1531. }
  1532. if err := rows.Err(); err != nil {
  1533. return nil, err
  1534. }
  1535. if len(keywords) == 0 {
  1536. var exists int
  1537. err = f.db.QueryRow("SELECT COUNT(*) FROM aimKeywordCategory WHERE id = ?", categoryID).Scan(&exists)
  1538. if err != nil {
  1539. return nil, err
  1540. }
  1541. if exists == 0 {
  1542. return nil, ErrKeywordCategoryNotFound
  1543. }
  1544. }
  1545. return keywords, nil
  1546. }
  1547. func (f SQLiteUserStore) CreateKeyword(ctx context.Context, name string, categoryID uint8) (Keyword, error) {
  1548. tx, err := f.db.Begin()
  1549. if err != nil {
  1550. return Keyword{}, err
  1551. }
  1552. defer tx.Rollback()
  1553. q := `INSERT INTO aimKeyword (name, parent) VALUES (?, ?)`
  1554. var parent interface{} = nil
  1555. if categoryID != 0 {
  1556. parent = categoryID
  1557. }
  1558. res, err := tx.ExecContext(ctx, q, name, parent)
  1559. if err != nil {
  1560. if sqliteErr, ok := err.(*sqlite.Error); ok && sqliteErr.Code() == lib.SQLITE_CONSTRAINT_UNIQUE {
  1561. err = ErrKeywordExists
  1562. } else if sqliteErr, ok := err.(*sqlite.Error); ok && sqliteErr.Code() == lib.SQLITE_CONSTRAINT_FOREIGNKEY {
  1563. err = ErrKeywordCategoryNotFound
  1564. }
  1565. return Keyword{}, err
  1566. }
  1567. id, err := res.LastInsertId()
  1568. if err != nil {
  1569. return Keyword{}, err
  1570. }
  1571. if id > math.MaxUint8 {
  1572. return Keyword{}, errTooManyKeywords
  1573. }
  1574. if err := tx.Commit(); err != nil {
  1575. return Keyword{}, err
  1576. }
  1577. return Keyword{
  1578. ID: uint8(id),
  1579. Name: name,
  1580. }, nil
  1581. }
  1582. func (f SQLiteUserStore) DeleteKeyword(ctx context.Context, id uint8) error {
  1583. q := `DELETE FROM aimKeyword WHERE id = ?`
  1584. res, err := f.db.ExecContext(ctx, q, id)
  1585. if err != nil {
  1586. // Check if the error is a foreign key constraint violation
  1587. if sqliteErr, ok := err.(*sqlite.Error); ok && sqliteErr.Code() == lib.SQLITE_CONSTRAINT_FOREIGNKEY {
  1588. return ErrKeywordInUse
  1589. }
  1590. }
  1591. c, err := res.RowsAffected()
  1592. if err != nil {
  1593. return err
  1594. }
  1595. if c == 0 {
  1596. return ErrKeywordNotFound
  1597. }
  1598. return nil
  1599. }
  1600. // InterestList returns a list of keywords grouped by category used to render
  1601. // the AIM directory interests list. The list is made up of 3 types of elements:
  1602. //
  1603. // Categories
  1604. //
  1605. // ID: The category ID
  1606. // Cookie: The category name
  1607. // Type: [wire.ODirKeywordCategory]
  1608. //
  1609. // Keywords
  1610. //
  1611. // ID: The parent category ID
  1612. // Cookie: The keyword name
  1613. // Type: [wire.ODirKeyword]
  1614. //
  1615. // Top-level Keywords
  1616. //
  1617. // ID: 0 (does not have a parent category)
  1618. // Cookie: The keyword name
  1619. // Type: [wire.ODirKeyword]
  1620. //
  1621. // Keywords are grouped contiguously by category and preceded by the category
  1622. // name. Top-level keywords appear by themselves. Categories and top-level
  1623. // keywords are sorted alphabetically. Keyword groups are sorted alphabetically.
  1624. //
  1625. // Conceptually, the list looks like this:
  1626. //
  1627. // > Animals (top-level keyword, id=0)
  1628. // > Artificial Intelligence (keyword, id=3)
  1629. // > Cybersecurity (keyword, id=3)
  1630. // > Music (category, id=1)
  1631. // > Jazz (keyword, id=1)
  1632. // > Rock (keyword, id=1)
  1633. // > Sports (category, id=2)
  1634. // > Basketball (keyword, id=2)
  1635. // > Soccer (keyword, id=2)
  1636. // > Tennis (keyword, id=2)
  1637. // > Technology (category, id=3)
  1638. // > Zoology (top-level keyword, id=0)
  1639. func (f SQLiteUserStore) InterestList(ctx context.Context) ([]wire.ODirKeywordListItem, error) {
  1640. q := `
  1641. WITH categories AS (
  1642. SELECT
  1643. name AS grouping,
  1644. id,
  1645. 0 AS sortPrio,
  1646. name
  1647. FROM aimKeywordCategory
  1648. UNION
  1649. SELECT
  1650. IFNULL(akc.name, ak.name) AS grouping,
  1651. IFNULL(ak.parent, 0) AS id,
  1652. CASE WHEN ak.parent IS NULL THEN 1 ELSE 2 END AS sortPrio,
  1653. ak.name
  1654. FROM aimKeyword ak
  1655. LEFT JOIN aimKeywordCategory akc ON akc.id = ak.parent
  1656. ORDER BY 1, 3, 4
  1657. )
  1658. SELECT
  1659. id,
  1660. sortPrio,
  1661. name
  1662. FROM categories
  1663. `
  1664. rows, err := f.db.QueryContext(ctx, q)
  1665. if err != nil {
  1666. return nil, err
  1667. }
  1668. defer rows.Close()
  1669. var list []wire.ODirKeywordListItem
  1670. for rows.Next() {
  1671. msg := wire.ODirKeywordListItem{}
  1672. var sortPrio int
  1673. if err := rows.Scan(&msg.ID, &sortPrio, &msg.Name); err != nil {
  1674. return nil, err
  1675. }
  1676. switch sortPrio {
  1677. case 0:
  1678. msg.Type = wire.ODirKeywordCategory
  1679. case 1, 2:
  1680. msg.Type = wire.ODirKeyword
  1681. }
  1682. list = append(list, msg)
  1683. }
  1684. if err := rows.Err(); err != nil {
  1685. return nil, err
  1686. }
  1687. return list, nil
  1688. }
  1689. func (f SQLiteUserStore) SetTOCConfig(ctx context.Context, user IdentScreenName, config string) error {
  1690. q := `
  1691. UPDATE users
  1692. SET tocConfig = ?
  1693. WHERE identScreenName = ?
  1694. `
  1695. res, err := f.db.ExecContext(ctx,
  1696. q,
  1697. config,
  1698. user.String(),
  1699. )
  1700. if err != nil {
  1701. return fmt.Errorf("exec: %w", err)
  1702. }
  1703. c, err := res.RowsAffected()
  1704. if err != nil {
  1705. return fmt.Errorf("rows affected: %w", err)
  1706. }
  1707. if c == 0 {
  1708. return ErrNoUser
  1709. }
  1710. return nil
  1711. }