store.ts 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. import { DB } from "https://deno.land/x/sqlite@v3.1.1/mod.ts";
  2. import { User, Tweet, TweetSchema } from "./twitter.ts";
  3. export abstract class Store {
  4. saveTweet(tweet: Tweet) {
  5. this.saveSingleTweet(tweet);
  6. this.saveUser(tweet.user);
  7. if (tweet.quoted_status !== undefined) {
  8. this.saveTweet(tweet.quoted_status);
  9. }
  10. if (tweet.retweeted_status !== undefined) {
  11. this.saveTweet(tweet.retweeted_status);
  12. }
  13. }
  14. abstract saveSingleTweet(tweet: Tweet): void;
  15. abstract saveUser(user: User): void;
  16. abstract getLatestTweet(authorId: string): Tweet | null;
  17. abstract getLatestTweets(authorId: string, olderThanId: string, newerThan: Date): Tweet[];
  18. abstract getAuthorId(username: string): string | null;
  19. }
  20. function dateToEpoch(dateStr: string | Date) {
  21. const date = typeof dateStr === 'string' ? new Date(dateStr) : dateStr;
  22. return Math.floor(date.getTime() / 1000);
  23. }
  24. export class SqliteStore extends Store {
  25. private db: DB;
  26. constructor(path: string) {
  27. super();
  28. this.db = new DB(path);
  29. this.db.query(`
  30. CREATE TABLE IF NOT EXISTS tweets (
  31. id INTEGER PRIMARY KEY,
  32. author_id INTEGER NOT NULL,
  33. created_at INTEGER NOT NULL,
  34. tweet TEXT NOT NULL,
  35. fetched_at INTEGER NOT NULL
  36. );
  37. `);
  38. this.db.query(`
  39. CREATE TABLE IF NOT EXISTS users (
  40. id INTEGER PRIMARY KEY,
  41. username TEXT NOT NULL UNIQUE
  42. );
  43. `);
  44. }
  45. saveSingleTweet(tweet: Tweet) {
  46. this.db.query("INSERT OR IGNORE INTO tweets VALUES (?, ?, ?, ?, ?)", [
  47. BigInt(tweet.id_str),
  48. BigInt(tweet.user.id_str),
  49. dateToEpoch(tweet.created_at),
  50. JSON.stringify(tweet),
  51. dateToEpoch(new Date()),
  52. ]);
  53. }
  54. saveUser(user: User) {
  55. this.db.query("INSERT OR REPLACE INTO users VALUES (?, ?)", [
  56. BigInt(user.id_str),
  57. user.screen_name,
  58. ]);
  59. }
  60. getLatestTweet(authorId: string): Tweet | null {
  61. const result = this.db.query<[string]>(`
  62. SELECT tweet FROM tweets
  63. WHERE author_id = ?
  64. ORDER BY id DESC
  65. LIMIT 1
  66. `, [ BigInt(authorId) ]);
  67. if (result.length === 0) {
  68. return null;
  69. } else {
  70. const tweet = JSON.parse(result[0][0]);
  71. return TweetSchema.parse(tweet);
  72. }
  73. }
  74. getLatestTweets(authorId: string, olderThanId: string, newerThan: Date): Tweet[] {
  75. const result = this.db.query<[string]>(`
  76. SELECT tweet FROM tweets
  77. WHERE author_id = ?
  78. AND id < ?
  79. AND created_at > ?
  80. ORDER BY id DESC
  81. `, [ BigInt(authorId), BigInt(olderThanId), dateToEpoch(newerThan) ]);
  82. return result.map(([ tweetStr ]) => {
  83. const tweet = JSON.parse(tweetStr);
  84. return TweetSchema.parse(tweet);
  85. });
  86. }
  87. getAuthorId(username: string): string | null {
  88. const result = this.db.query<[BigInt]>(`
  89. SELECT id FROM users
  90. WHERE username = ?
  91. ORDER BY id DESC
  92. LIMIT 1
  93. `, [ username ]);
  94. if (result.length === 0) {
  95. return null;
  96. } else {
  97. return result[0][0].toString();
  98. }
  99. }
  100. }