user_store.go 47 KB

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