user_store.go 49 KB

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