mirror of
https://github.com/captbaritone/webamp.git
synced 2026-08-01 22:45:18 +00:00
Move Tweet GraphQL to model
This commit is contained in:
parent
d1611afbfb
commit
ba7989db9c
9 changed files with 99 additions and 108 deletions
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue