user_store.go 50 KB

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