Move Tweet GraphQL to model

This commit is contained in:
Jordan Eldredge 2023-08-29 13:46:56 -07:00
parent d1611afbfb
commit ba7989db9c
9 changed files with 99 additions and 108 deletions

View file

@ -1,7 +1,6 @@
import { Int } from "grats";
import TweetModel from "../../data/TweetModel";
import { knex } from "../../db";
import TweetResolver from "./resolvers/TweetResolver";
/** @gqlEnum */
export type TweetsSortOption = "LIKES" | "RETWEETS";
@ -44,13 +43,7 @@ export default class TweetsConnection {
* The list of tweets
* @gqlField
*/
async nodes(args: never, ctx): Promise<Array<TweetResolver | null>> {
const tweets = await this._getQuery()
.select()
.limit(this._first)
.offset(this._offset);
return tweets.map((tweet) => {
return new TweetResolver(new TweetModel(ctx, tweet));
});
async nodes(args: never, ctx): Promise<Array<TweetModel | null>> {
return this._getQuery().select().limit(this._first).offset(this._offset);
}
}

View file

@ -3,9 +3,9 @@ import { NodeResolver, toId } from "./NodeResolver";
import ReviewResolver from "./ReviewResolver";
import path from "path";
import { ID, Int } from "grats";
import TweetResolver from "./TweetResolver";
import ArchiveFileResolver from "./ArchiveFileResolver";
import InternetArchiveItemResolver from "./InternetArchiveItemResolver";
import TweetModel from "../../../data/TweetModel";
/**
* A classic Winamp skin
@ -63,7 +63,7 @@ export default class ClassicSkinResolver
/**
* List of @winampskins tweets that mentioned the skin.
* @gqlField */
tweets(): Promise<Array<TweetResolver | null>> {
tweets(): Promise<Array<TweetModel | null>> {
return super.tweets();
}

View file

@ -3,7 +3,7 @@ import SkinModel from "../../../data/SkinModel";
import ArchiveFileResolver from "./ArchiveFileResolver";
import InternetArchiveItemResolver from "./InternetArchiveItemResolver";
import ReviewResolver from "./ReviewResolver";
import TweetResolver from "./TweetResolver";
import TweetModel from "../../../data/TweetModel";
/**
* A Winamp skin. Could be modern or classic.
@ -57,7 +57,7 @@ export interface ISkin {
* List of @winampskins tweets that mentioned the skin.
* @gqlField
*/
tweets(): Promise<Array<TweetResolver | null>>;
tweets(): Promise<Array<TweetModel | null>>;
/**
* List of files contained within the skin's .wsz archive
@ -124,9 +124,8 @@ export default abstract class CommonSkinResolver {
tweeted(): Promise<boolean> {
return this._model.tweeted();
}
async tweets(): Promise<Array<TweetResolver | null>> {
const tweets = await this._model.getTweets();
return tweets.map((tweetModel) => new TweetResolver(tweetModel));
async tweets(): Promise<Array<TweetModel | null>> {
return this._model.getTweets();
}
async archive_files(): Promise<Array<ArchiveFileResolver | null>> {
const files = await this._model.getArchiveFiles();

View file

@ -6,8 +6,8 @@ import { ID } from "grats";
import ReviewResolver from "./ReviewResolver";
import InternetArchiveItemResolver from "./InternetArchiveItemResolver";
import ArchiveFileResolver from "./ArchiveFileResolver";
import TweetResolver from "./TweetResolver";
import { XMLParser } from "fast-xml-parser";
import TweetModel from "../../../data/TweetModel";
/**
* A "modern" Winamp skin. These skins use the `.wal` file extension and are free-form.
@ -71,7 +71,7 @@ export default class ModernSkinResolver
/**
* List of @winampskins tweets that mentioned the skin.
* @gqlField */
async tweets(): Promise<Array<TweetResolver | null>> {
async tweets(): Promise<Array<TweetModel | null>> {
return super.tweets();
}

View file

@ -2,7 +2,6 @@ import IaItemModel from "../../../data/IaItemModel";
import SkinModel from "../../../data/SkinModel";
import TweetModel from "../../../data/TweetModel";
import SkinResolver from "../resolvers/SkinResolver";
import TweetResolver from "../resolvers/TweetResolver";
import UserResolver from "../resolvers/UserResolver";
import SkinsConnection from "../SkinsConnection";
import TweetsConnection, { TweetsSortOption } from "../TweetsConnection";
@ -75,12 +74,8 @@ class RootResolver extends MutationResolver {
async fetch_tweet_by_url(
{ url }: { url: string },
{ ctx }
): Promise<TweetResolver | null> {
const tweet = await TweetModel.fromAnything(ctx, url);
if (tweet == null) {
return null;
}
return new TweetResolver(tweet);
): Promise<TweetModel | null> {
return TweetModel.fromAnything(ctx, url);
}
/**

View file

@ -1,50 +0,0 @@
import { Int } from "grats";
import TweetModel from "../../../data/TweetModel";
import { ISkin } from "./CommonSkinResolver";
import SkinResolver from "./SkinResolver";
/**
* A tweet made by @winampskins mentioning a Winamp skin
* @gqlType Tweet
*/
export default class TweetResolver {
_model: TweetModel;
constructor(model: TweetModel) {
this._model = model;
}
/**
* URL of the tweet. **Note:** Early on in the bot's life we just recorded
* _which_ skins were tweeted, not any info about the actual tweet. This means we
* don't always know the URL of the tweet.
* @gqlField
*/
url(): string | null {
return this._model.getUrl();
}
/**
* Number of likes the tweet has received. Updated nightly. (Note: Recent likes on older tweets may not be reflected here)
* @gqlField
*/
likes(): Int {
return this._model.getLikes();
}
/**
* Number of retweets the tweet has received. Updated nightly. (Note: Recent retweets on older tweets may not be reflected here)
* @gqlField
*/
retweets(): Int {
return this._model.getRetweets();
}
/**
* The skin featured in this Tweet
* @gqlField
*/
async skin(): Promise<ISkin | null> {
const skin = await this._model.getSkin();
if (skin == null) {
return null;
}
return SkinResolver.fromModel(skin);
}
}

View file

@ -116,9 +116,13 @@ type DatabaseStatistics {
**Note:** Generally skins that have been marked NSFW are also marked as rejected.
"""
rejected_skins_count: Int
"""The number of skins that have been approved for tweeting, but not yet tweeted."""
"""
The number of skins that have been approved for tweeting, but not yet tweeted.
"""
tweetable_skins_count: Int
"""The number of skins in the Museum that have been tweeted by @winampskins"""
"""
The number of skins in the Museum that have been tweeted by @winampskins
"""
tweeted_skins_count: Int
"""The total number of classic skins in the Museum's database"""
unique_classic_skins_count: Int
@ -132,7 +136,9 @@ type DatabaseStatistics {
pipeline gets stuck.
"""
uploads_pending_processing_count: Int
"""Number of skins that have been uploaded to the Museum via the web interface."""
"""
Number of skins that have been uploaded to the Museum via the web interface.
"""
web_uploads_count: Int
}
@ -147,7 +153,9 @@ type InternetArchiveItem {
last_metadata_scrape_date_UNSTABLE: String
"""URL to get the Internet Archive's metadata for this item in JSON form."""
metadata_url: String
"""Our cached version of the metadata avaliable at \`metadata_url\` (above)"""
"""
Our cached version of the metadata avaliable at \`metadata_url\` (above)
"""
raw_metadata_json: String
"""The skin that this item contains"""
skin: Skin
@ -233,7 +241,9 @@ type Mutation {
in.
"""
request_nsfw_review_for_skin(md5: String!): Boolean
"""Send a message to the admin of the site. Currently this appears in Discord."""
"""
Send a message to the admin of the site. Currently this appears in Discord.
"""
send_feedback(email: String, message: String!, url: String): Boolean
"""Mutations for the upload flow"""
upload: UploadMutations
@ -366,25 +376,6 @@ interface Skin {
webamp_url: String @deprecated(reason: "Needed for migration")
}
"""A collection of classic Winamp skins"""
type SkinsConnection {
"""The total number of skins matching the filter"""
count: Int
"""The list of skins"""
nodes: [Skin]
}
enum SkinsFilterOption {
APPROVED
NSFW
REJECTED
TWEETED
}
enum SkinsSortOption {
MUSEUM
}
"""Information about an attempt to upload a skin to the Museum."""
type SkinUpload {
id: String
@ -412,12 +403,35 @@ enum SkinUploadStatus {
URL_REQUESTED
}
"""A collection of classic Winamp skins"""
type SkinsConnection {
"""The total number of skins matching the filter"""
count: Int
"""The list of skins"""
nodes: [Skin]
}
enum SkinsFilterOption {
APPROVED
NSFW
REJECTED
TWEETED
}
enum SkinsSortOption {
MUSEUM
}
"""A tweet made by @winampskins mentioning a Winamp skin"""
type Tweet {
"""Number of likes the tweet has received. Updated nightly. (Note: Recent likes on older tweets may not be reflected here)"""
likes: Int
"""Number of retweets the tweet has received. Updated nightly. (Note: Recent retweets on older tweets may not be reflected here)"""
retweets: Int
"""
Number of likes the tweet has received. Updated nightly. (Note: Recent likes on older tweets may not be reflected here)
"""
likes: Int @methodName(name: "getLikes")
"""
Number of retweets the tweet has received. Updated nightly. (Note: Recent retweets on older tweets may not be reflected here)
"""
retweets: Int @methodName(name: "getRetweets")
"""The skin featured in this Tweet"""
skin: Skin
"""
@ -425,7 +439,7 @@ type Tweet {
_which_ skins were tweeted, not any info about the actual tweet. This means we
don't always know the URL of the tweet.
"""
url: String
url: String @methodName(name: "getUrl")
}
"""A collection of tweets made by the @winampskins bot"""
@ -480,4 +494,4 @@ input UploadUrlRequest {
type User {
username: String
}
}

View file

@ -4,11 +4,18 @@ import DataLoader from "dataloader";
import { knex } from "../db";
import { TWEET_SNOWFLAKE_REGEX } from "../utils";
import SkinModel from "./SkinModel";
import { Int } from "grats";
import { ISkin } from "../api/graphql/resolvers/CommonSkinResolver";
import SkinResolver from "../api/graphql/resolvers/SkinResolver";
export type TweetDebugData = {
row: TweetRow;
};
/**
* A tweet made by @winampskins mentioning a Winamp skin
* @gqlType Tweet
*/
export default class TweetModel {
constructor(readonly ctx: UserContext, readonly row: TweetRow) {}
@ -49,14 +56,29 @@ export default class TweetModel {
return this.row.tweet_id || null;
}
/**
* URL of the tweet. **Note:** Early on in the bot's life we just recorded
* _which_ skins were tweeted, not any info about the actual tweet. This means we
* don't always know the URL of the tweet.
* @gqlField url
*/
getUrl(): string | null {
const tweetId = this.getTweetId();
return tweetId ? `https://twitter.com/winampskins/status/${tweetId}` : null;
}
getLikes(): number {
/**
* Number of likes the tweet has received. Updated nightly. (Note: Recent likes on older tweets may not be reflected here)
* @gqlField likes
*/
getLikes(): Int {
return this.row.likes ?? 0;
}
getRetweets(): number {
/**
* Number of retweets the tweet has received. Updated nightly. (Note: Recent retweets on older tweets may not be reflected here)
* @gqlField retweets
*/
getRetweets(): Int {
return this.row.retweets ?? 0;
}
@ -64,6 +86,18 @@ export default class TweetModel {
return SkinModel.fromMd5Assert(this.ctx, this.getMd5());
}
/**
* The skin featured in this Tweet
* @gqlField
*/
async skin(): Promise<ISkin | null> {
const skin = await this.getSkin();
if (skin == null) {
return null;
}
return SkinResolver.fromModel(skin);
}
async debug(): Promise<TweetDebugData> {
return {
row: this.row,

View file

@ -37,14 +37,20 @@ export default class Media {
// Via: https://stackoverflow.com/a/43395068/1263117
// TODO #leak
if (this._context.state === "suspended") {
const resume = async () => {
console.log("Suspended on init");
const resume = async (e) => {
console.log("Resuming", e);
await this._context.resume();
console.log("Resuming");
if (this._context.state === "running") {
console.log("Runnign!");
// TODO: Add this to the disposable
document.body.removeEventListener("touchend", resume, false);
document.body.removeEventListener("click", resume, false);
document.body.removeEventListener("keydown", resume, false);
} else {
console.log("Not running");
}
};