mirror of
https://github.com/captbaritone/webamp.git
synced 2026-07-18 00:55:54 +00:00
Add support for SQLite based search
This commit is contained in:
parent
68b8b22a49
commit
faee5410ac
7 changed files with 498 additions and 213 deletions
|
|
@ -1,5 +1,5 @@
|
|||
import { Router } from "express";
|
||||
import { createHandler } from "graphql-http/lib/use/express";
|
||||
import { createYoga, YogaInitialContext } from "graphql-yoga";
|
||||
|
||||
// import DEFAULT_QUERY from "./defaultQuery";
|
||||
import { getSchema } from "./schema";
|
||||
|
|
@ -15,34 +15,14 @@ export function getUserContext(ctx: Ctx): UserContext {
|
|||
|
||||
const router = Router();
|
||||
|
||||
router.use(
|
||||
"/",
|
||||
createHandler<Ctx>({
|
||||
schema: getSchema(),
|
||||
context: (req) => {
|
||||
return req.raw;
|
||||
},
|
||||
/*
|
||||
graphiql: {
|
||||
defaultQuery: DEFAULT_QUERY,
|
||||
},*/
|
||||
// graphqlHTTP({
|
||||
// schema: getSchema(),
|
||||
// graphiql: {
|
||||
// defaultQuery: DEFAULT_QUERY,
|
||||
// },
|
||||
// customFormatErrorFn: (error) => {
|
||||
// console.error(error);
|
||||
// return {
|
||||
// message: error.message,
|
||||
// locations: error.locations,
|
||||
// stack: error.stack ? error.stack.split("\n") : [],
|
||||
// path: error.path,
|
||||
// };
|
||||
// },
|
||||
// extensions,
|
||||
// }) as RequestHandler
|
||||
})
|
||||
);
|
||||
const yoga = createYoga({
|
||||
schema: getSchema(),
|
||||
context: (ctx: YogaInitialContext) => {
|
||||
return ctx.req;
|
||||
},
|
||||
});
|
||||
|
||||
// Bind GraphQL Yoga to the graphql endpoint to avoid rendering the playground on any path
|
||||
router.use("", yoga);
|
||||
|
||||
export default router;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import { Int } from "grats";
|
||||
import { Ctx } from "..";
|
||||
import SkinModel from "../../../data/SkinModel";
|
||||
import UserContext from "../../../data/UserContext";
|
||||
import ClassicSkinResolver from "./ClassicSkinResolver";
|
||||
|
|
@ -7,6 +6,7 @@ import { ISkin } from "./CommonSkinResolver";
|
|||
import ModernSkinResolver from "./ModernSkinResolver";
|
||||
import algoliasearch from "algoliasearch";
|
||||
import * as Skins from "../../../data/skins";
|
||||
import { knex } from "../../../db";
|
||||
|
||||
// These keys are already in the web client, so they are not secret at all.
|
||||
const client = algoliasearch("HQ9I5Z6IM5", "6466695ec3f624a5fccf46ec49680e51");
|
||||
|
|
@ -54,30 +54,62 @@ export async function fetch_skin_by_md5(
|
|||
* @gqlQueryField
|
||||
*/
|
||||
export async function search_skins(
|
||||
{
|
||||
query,
|
||||
first = 10,
|
||||
offset = 0,
|
||||
}: { query: string; first?: Int; offset?: Int },
|
||||
query: string,
|
||||
first: Int = 10,
|
||||
offset: Int = 0,
|
||||
ctx: UserContext
|
||||
): Promise<Array<ISkin | null>> {
|
||||
if (first > 1000) {
|
||||
throw new Error("Can only query 1000 records via search.");
|
||||
}
|
||||
|
||||
const results: { hits: { md5: string }[] } = await index.search(query, {
|
||||
attributesToRetrieve: ["md5"],
|
||||
length: first,
|
||||
offset,
|
||||
});
|
||||
const skins = await knex("skin_search")
|
||||
.select("skin_md5")
|
||||
.leftJoin("skins", "skin_search.skin_md5", "skins.md5")
|
||||
.where("skins.skin_type", "in", [1, 2])
|
||||
.limit(first)
|
||||
.offset(offset)
|
||||
.whereRaw("skin_search MATCH ?", query);
|
||||
|
||||
return Promise.all(
|
||||
results.hits.map(async (hit) => {
|
||||
const model = await SkinModel.fromMd5Assert(ctx, hit.md5);
|
||||
skins.map(async (hit) => {
|
||||
const model = await SkinModel.fromMd5Assert(ctx, hit.skin_md5);
|
||||
return SkinResolver.fromModel(model);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search the database using the Algolia search index used by the Museum.
|
||||
*
|
||||
* Useful for locating a particular skin.
|
||||
* @gqlQueryField
|
||||
*/
|
||||
export async function search_classic_skins(
|
||||
query: string,
|
||||
first: Int = 10,
|
||||
offset: Int = 0,
|
||||
ctx: UserContext
|
||||
): Promise<Array<ClassicSkinResolver | null>> {
|
||||
if (first > 1000) {
|
||||
throw new Error("Can only query 1000 records via search.");
|
||||
}
|
||||
|
||||
const skins = await knex("skin_search")
|
||||
.select("skin_md5")
|
||||
.leftJoin("skins", "skin_search.skin_md5", "skins.md5")
|
||||
.where("skins.skin_type", "=", 1)
|
||||
.limit(first)
|
||||
.offset(offset)
|
||||
.whereRaw("skin_search MATCH ?", query);
|
||||
|
||||
return Promise.all(
|
||||
skins.map(async (hit) => {
|
||||
const model = await SkinModel.fromMd5Assert(ctx, hit.skin_md5);
|
||||
return new ClassicSkinResolver(model);
|
||||
})
|
||||
);
|
||||
}
|
||||
/**
|
||||
* A random skin that needs to be reviewed
|
||||
* @gqlQueryField */
|
||||
|
|
|
|||
|
|
@ -413,6 +413,12 @@ type Query {
|
|||
"""
|
||||
Search the database using the Algolia search index used by the Museum.
|
||||
|
||||
Useful for locating a particular skin.
|
||||
"""
|
||||
search_classic_skins(first: Int! = 10, offset: Int! = 0, query: String!): [ClassicSkin] @semanticNonNull
|
||||
"""
|
||||
Search the database using the Algolia search index used by the Museum.
|
||||
|
||||
Useful for locating a particular skin.
|
||||
"""
|
||||
search_skins(first: Int! = 10, offset: Int! = 0, query: String!): [Skin] @semanticNonNull
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { defaultFieldResolver, GraphQLSchema, GraphQLObjectType, GraphQLString,
|
|||
import { getUserContext as getUserContext } from "./index";
|
||||
import { fetch_archive_file_by_md5 as queryFetch_archive_file_by_md5Resolver } from "./../../data/ArchiveFileModel";
|
||||
import { fetch_internet_archive_item_by_identifier as queryFetch_internet_archive_item_by_identifierResolver } from "./../../data/IaItemModel";
|
||||
import { fetch_skin_by_md5 as queryFetch_skin_by_md5Resolver, search_skins as querySearch_skinsResolver, skin_to_review as querySkin_to_reviewResolver } from "./resolvers/SkinResolver";
|
||||
import { fetch_skin_by_md5 as queryFetch_skin_by_md5Resolver, search_classic_skins as querySearch_classic_skinsResolver, search_skins as querySearch_skinsResolver, skin_to_review as querySkin_to_reviewResolver } from "./resolvers/SkinResolver";
|
||||
import { fetch_tweet_by_url as queryFetch_tweet_by_urlResolver } from "./../../data/TweetModel";
|
||||
import { me as queryMeResolver } from "./resolvers/UserResolver";
|
||||
import { archive_files as modernSkinArchive_filesResolver, average_color as modernSkinAverage_colorResolver, download_url as modernSkinDownload_urlResolver, filename as modernSkinFilenameResolver, id as modernSkinIdResolver, internet_archive_item as modernSkinInternet_archive_itemResolver, md5 as modernSkinMd5Resolver, museum_url as modernSkinMuseum_urlResolver, nsfw as modernSkinNsfwResolver, readme_text as modernSkinReadme_textResolver, reviews as modernSkinReviewsResolver, screenshot_url as modernSkinScreenshot_urlResolver, tweeted as modernSkinTweetedResolver, tweets as modernSkinTweetsResolver, webamp_url as modernSkinWebamp_urlResolver, archive_files as classicSkinArchive_filesResolver, average_color as classicSkinAverage_colorResolver, download_url as classicSkinDownload_urlResolver, filename as classicSkinFilenameResolver, id as classicSkinIdResolver, internet_archive_item as classicSkinInternet_archive_itemResolver, md5 as classicSkinMd5Resolver, museum_url as classicSkinMuseum_urlResolver, nsfw as classicSkinNsfwResolver, readme_text as classicSkinReadme_textResolver, reviews as classicSkinReviewsResolver, screenshot_url as classicSkinScreenshot_urlResolver, tweeted as classicSkinTweetedResolver, tweets as classicSkinTweetsResolver, webamp_url as classicSkinWebamp_urlResolver } from "./resolvers/CommonSkinResolver";
|
||||
|
|
@ -511,6 +511,166 @@ export function getSchema(): GraphQLSchema {
|
|||
};
|
||||
}
|
||||
});
|
||||
const ClassicSkinType: GraphQLObjectType = new GraphQLObjectType({
|
||||
name: "ClassicSkin",
|
||||
description: "A classic Winamp skin",
|
||||
fields() {
|
||||
return {
|
||||
archive_files: {
|
||||
description: "List of files contained within the skin's .wsz archive",
|
||||
name: "archive_files",
|
||||
type: new GraphQLList(ArchiveFileType),
|
||||
resolve(source) {
|
||||
return assertNonNull(classicSkinArchive_filesResolver(source));
|
||||
}
|
||||
},
|
||||
average_color: {
|
||||
description: "String representation (rgb usually) of the skin's average color",
|
||||
name: "average_color",
|
||||
type: GraphQLString,
|
||||
resolve(source) {
|
||||
return classicSkinAverage_colorResolver(source);
|
||||
}
|
||||
},
|
||||
download_url: {
|
||||
description: "URL to download the skin",
|
||||
name: "download_url",
|
||||
type: GraphQLString,
|
||||
resolve(source) {
|
||||
return assertNonNull(classicSkinDownload_urlResolver(source));
|
||||
}
|
||||
},
|
||||
filename: {
|
||||
description: "Filename of skin when uploaded to the Museum. Note: In some cases a skin\nhas been uploaded under multiple names. Here we just pick one.",
|
||||
name: "filename",
|
||||
type: GraphQLString,
|
||||
args: {
|
||||
normalize_extension: {
|
||||
description: "If true, the the correct file extension (.wsz or .wal) will be .\nOtherwise, the original user-uploaded file extension will be used.",
|
||||
name: "normalize_extension",
|
||||
type: new GraphQLNonNull(GraphQLBoolean),
|
||||
defaultValue: false
|
||||
}
|
||||
},
|
||||
resolve(source, args) {
|
||||
return assertNonNull(classicSkinFilenameResolver(source, args));
|
||||
}
|
||||
},
|
||||
has_media_library: {
|
||||
description: "Does the skin include sprite sheets for the media library?",
|
||||
name: "has_media_library",
|
||||
type: GraphQLBoolean,
|
||||
resolve(source, args, context, info) {
|
||||
return assertNonNull(defaultFieldResolver(source, args, context, info));
|
||||
}
|
||||
},
|
||||
id: {
|
||||
description: "GraphQL ID of the skin",
|
||||
name: "id",
|
||||
type: new GraphQLNonNull(GraphQLID),
|
||||
resolve(source) {
|
||||
return classicSkinIdResolver(source);
|
||||
}
|
||||
},
|
||||
internet_archive_item: {
|
||||
description: "The skin's \"item\" at archive.org",
|
||||
name: "internet_archive_item",
|
||||
type: InternetArchiveItemType,
|
||||
resolve(source) {
|
||||
return classicSkinInternet_archive_itemResolver(source);
|
||||
}
|
||||
},
|
||||
last_algolia_index_update_date: {
|
||||
description: "The date on which this skin was last updated in the Algolia search index.\nGiven in simplified extended ISO format (ISO 8601).",
|
||||
name: "last_algolia_index_update_date",
|
||||
type: GraphQLString
|
||||
},
|
||||
md5: {
|
||||
description: "MD5 hash of the skin's file",
|
||||
name: "md5",
|
||||
type: GraphQLString,
|
||||
resolve(source) {
|
||||
return assertNonNull(classicSkinMd5Resolver(source));
|
||||
}
|
||||
},
|
||||
museum_url: {
|
||||
description: "URL of the skin on the Winamp Skin Museum",
|
||||
name: "museum_url",
|
||||
type: GraphQLString,
|
||||
resolve(source) {
|
||||
return classicSkinMuseum_urlResolver(source);
|
||||
}
|
||||
},
|
||||
nsfw: {
|
||||
description: "Has the skin been flagged as \"not safe for work\"?\"",
|
||||
name: "nsfw",
|
||||
type: GraphQLBoolean,
|
||||
resolve(source) {
|
||||
return classicSkinNsfwResolver(source);
|
||||
}
|
||||
},
|
||||
readme_text: {
|
||||
description: "Text of the readme file extracted from the skin",
|
||||
name: "readme_text",
|
||||
type: GraphQLString,
|
||||
resolve(source) {
|
||||
return classicSkinReadme_textResolver(source);
|
||||
}
|
||||
},
|
||||
reviews: {
|
||||
description: "Times that the skin has been reviewed either on the Museum's Tinder-style\nreview page, or via the Discord bot.",
|
||||
name: "reviews",
|
||||
type: new GraphQLList(ReviewType),
|
||||
resolve(source) {
|
||||
return assertNonNull(classicSkinReviewsResolver(source));
|
||||
}
|
||||
},
|
||||
screenshot_url: {
|
||||
description: "URL of a screenshot of the skin",
|
||||
name: "screenshot_url",
|
||||
type: GraphQLString,
|
||||
resolve(source) {
|
||||
return classicSkinScreenshot_urlResolver(source);
|
||||
}
|
||||
},
|
||||
transparent_pixels: {
|
||||
description: "The number of transparent pixels rendered by the skin.",
|
||||
name: "transparent_pixels",
|
||||
type: GraphQLInt,
|
||||
resolve(source, args, context, info) {
|
||||
return assertNonNull(defaultFieldResolver(source, args, context, info));
|
||||
}
|
||||
},
|
||||
tweeted: {
|
||||
description: "Has the skin been tweeted?",
|
||||
name: "tweeted",
|
||||
type: GraphQLBoolean,
|
||||
resolve(source) {
|
||||
return assertNonNull(classicSkinTweetedResolver(source));
|
||||
}
|
||||
},
|
||||
tweets: {
|
||||
description: "List of",
|
||||
name: "tweets",
|
||||
type: new GraphQLList(TweetType),
|
||||
resolve(source) {
|
||||
return assertNonNull(classicSkinTweetsResolver(source));
|
||||
}
|
||||
},
|
||||
webamp_url: {
|
||||
description: "URL of webamp.org with the skin loaded",
|
||||
name: "webamp_url",
|
||||
type: GraphQLString,
|
||||
resolve(source) {
|
||||
return classicSkinWebamp_urlResolver(source);
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
interfaces() {
|
||||
return [NodeType, SkinType];
|
||||
}
|
||||
});
|
||||
const SkinsConnectionType: GraphQLObjectType = new GraphQLObjectType({
|
||||
name: "SkinsConnection",
|
||||
description: "A collection of classic Winamp skins",
|
||||
|
|
@ -841,6 +1001,30 @@ export function getSchema(): GraphQLSchema {
|
|||
return queryNodeResolver(args.id, getUserContext(context));
|
||||
}
|
||||
},
|
||||
search_classic_skins: {
|
||||
description: "Search the database using the Algolia search index used by the Museum.\n\nUseful for locating a particular skin.",
|
||||
name: "search_classic_skins",
|
||||
type: new GraphQLList(ClassicSkinType),
|
||||
args: {
|
||||
first: {
|
||||
name: "first",
|
||||
type: new GraphQLNonNull(GraphQLInt),
|
||||
defaultValue: 10
|
||||
},
|
||||
offset: {
|
||||
name: "offset",
|
||||
type: new GraphQLNonNull(GraphQLInt),
|
||||
defaultValue: 0
|
||||
},
|
||||
query: {
|
||||
name: "query",
|
||||
type: new GraphQLNonNull(GraphQLString)
|
||||
}
|
||||
},
|
||||
resolve(_source, args, context) {
|
||||
return assertNonNull(querySearch_classic_skinsResolver(args.query, args.first, args.offset, getUserContext(context)));
|
||||
}
|
||||
},
|
||||
search_skins: {
|
||||
description: "Search the database using the Algolia search index used by the Museum.\n\nUseful for locating a particular skin.",
|
||||
name: "search_skins",
|
||||
|
|
@ -862,7 +1046,7 @@ export function getSchema(): GraphQLSchema {
|
|||
}
|
||||
},
|
||||
resolve(_source, args, context) {
|
||||
return assertNonNull(querySearch_skinsResolver(args, getUserContext(context)));
|
||||
return assertNonNull(querySearch_skinsResolver(args.query, args.first, args.offset, getUserContext(context)));
|
||||
}
|
||||
},
|
||||
skin_to_review: {
|
||||
|
|
@ -1143,166 +1327,6 @@ export function getSchema(): GraphQLSchema {
|
|||
};
|
||||
}
|
||||
});
|
||||
const ClassicSkinType: GraphQLObjectType = new GraphQLObjectType({
|
||||
name: "ClassicSkin",
|
||||
description: "A classic Winamp skin",
|
||||
fields() {
|
||||
return {
|
||||
archive_files: {
|
||||
description: "List of files contained within the skin's .wsz archive",
|
||||
name: "archive_files",
|
||||
type: new GraphQLList(ArchiveFileType),
|
||||
resolve(source) {
|
||||
return assertNonNull(classicSkinArchive_filesResolver(source));
|
||||
}
|
||||
},
|
||||
average_color: {
|
||||
description: "String representation (rgb usually) of the skin's average color",
|
||||
name: "average_color",
|
||||
type: GraphQLString,
|
||||
resolve(source) {
|
||||
return classicSkinAverage_colorResolver(source);
|
||||
}
|
||||
},
|
||||
download_url: {
|
||||
description: "URL to download the skin",
|
||||
name: "download_url",
|
||||
type: GraphQLString,
|
||||
resolve(source) {
|
||||
return assertNonNull(classicSkinDownload_urlResolver(source));
|
||||
}
|
||||
},
|
||||
filename: {
|
||||
description: "Filename of skin when uploaded to the Museum. Note: In some cases a skin\nhas been uploaded under multiple names. Here we just pick one.",
|
||||
name: "filename",
|
||||
type: GraphQLString,
|
||||
args: {
|
||||
normalize_extension: {
|
||||
description: "If true, the the correct file extension (.wsz or .wal) will be .\nOtherwise, the original user-uploaded file extension will be used.",
|
||||
name: "normalize_extension",
|
||||
type: new GraphQLNonNull(GraphQLBoolean),
|
||||
defaultValue: false
|
||||
}
|
||||
},
|
||||
resolve(source, args) {
|
||||
return assertNonNull(classicSkinFilenameResolver(source, args));
|
||||
}
|
||||
},
|
||||
has_media_library: {
|
||||
description: "Does the skin include sprite sheets for the media library?",
|
||||
name: "has_media_library",
|
||||
type: GraphQLBoolean,
|
||||
resolve(source, args, context, info) {
|
||||
return assertNonNull(defaultFieldResolver(source, args, context, info));
|
||||
}
|
||||
},
|
||||
id: {
|
||||
description: "GraphQL ID of the skin",
|
||||
name: "id",
|
||||
type: new GraphQLNonNull(GraphQLID),
|
||||
resolve(source) {
|
||||
return classicSkinIdResolver(source);
|
||||
}
|
||||
},
|
||||
internet_archive_item: {
|
||||
description: "The skin's \"item\" at archive.org",
|
||||
name: "internet_archive_item",
|
||||
type: InternetArchiveItemType,
|
||||
resolve(source) {
|
||||
return classicSkinInternet_archive_itemResolver(source);
|
||||
}
|
||||
},
|
||||
last_algolia_index_update_date: {
|
||||
description: "The date on which this skin was last updated in the Algolia search index.\nGiven in simplified extended ISO format (ISO 8601).",
|
||||
name: "last_algolia_index_update_date",
|
||||
type: GraphQLString
|
||||
},
|
||||
md5: {
|
||||
description: "MD5 hash of the skin's file",
|
||||
name: "md5",
|
||||
type: GraphQLString,
|
||||
resolve(source) {
|
||||
return assertNonNull(classicSkinMd5Resolver(source));
|
||||
}
|
||||
},
|
||||
museum_url: {
|
||||
description: "URL of the skin on the Winamp Skin Museum",
|
||||
name: "museum_url",
|
||||
type: GraphQLString,
|
||||
resolve(source) {
|
||||
return classicSkinMuseum_urlResolver(source);
|
||||
}
|
||||
},
|
||||
nsfw: {
|
||||
description: "Has the skin been flagged as \"not safe for work\"?\"",
|
||||
name: "nsfw",
|
||||
type: GraphQLBoolean,
|
||||
resolve(source) {
|
||||
return classicSkinNsfwResolver(source);
|
||||
}
|
||||
},
|
||||
readme_text: {
|
||||
description: "Text of the readme file extracted from the skin",
|
||||
name: "readme_text",
|
||||
type: GraphQLString,
|
||||
resolve(source) {
|
||||
return classicSkinReadme_textResolver(source);
|
||||
}
|
||||
},
|
||||
reviews: {
|
||||
description: "Times that the skin has been reviewed either on the Museum's Tinder-style\nreview page, or via the Discord bot.",
|
||||
name: "reviews",
|
||||
type: new GraphQLList(ReviewType),
|
||||
resolve(source) {
|
||||
return assertNonNull(classicSkinReviewsResolver(source));
|
||||
}
|
||||
},
|
||||
screenshot_url: {
|
||||
description: "URL of a screenshot of the skin",
|
||||
name: "screenshot_url",
|
||||
type: GraphQLString,
|
||||
resolve(source) {
|
||||
return classicSkinScreenshot_urlResolver(source);
|
||||
}
|
||||
},
|
||||
transparent_pixels: {
|
||||
description: "The number of transparent pixels rendered by the skin.",
|
||||
name: "transparent_pixels",
|
||||
type: GraphQLInt,
|
||||
resolve(source, args, context, info) {
|
||||
return assertNonNull(defaultFieldResolver(source, args, context, info));
|
||||
}
|
||||
},
|
||||
tweeted: {
|
||||
description: "Has the skin been tweeted?",
|
||||
name: "tweeted",
|
||||
type: GraphQLBoolean,
|
||||
resolve(source) {
|
||||
return assertNonNull(classicSkinTweetedResolver(source));
|
||||
}
|
||||
},
|
||||
tweets: {
|
||||
description: "List of",
|
||||
name: "tweets",
|
||||
type: new GraphQLList(TweetType),
|
||||
resolve(source) {
|
||||
return assertNonNull(classicSkinTweetsResolver(source));
|
||||
}
|
||||
},
|
||||
webamp_url: {
|
||||
description: "URL of webamp.org with the skin loaded",
|
||||
name: "webamp_url",
|
||||
type: GraphQLString,
|
||||
resolve(source) {
|
||||
return classicSkinWebamp_urlResolver(source);
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
interfaces() {
|
||||
return [NodeType, SkinType];
|
||||
}
|
||||
});
|
||||
return new GraphQLSchema({
|
||||
query: QueryType,
|
||||
mutation: MutationType,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,88 @@
|
|||
import * as Knex from "knex";
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
await knex.raw(`
|
||||
-- Step 1: Create the FTS table
|
||||
CREATE VIRTUAL TABLE skin_search USING fts5(
|
||||
skin_md5,
|
||||
readme_text,
|
||||
file_names
|
||||
);
|
||||
|
||||
-- Step 2: Populate the FTS table with existing data
|
||||
INSERT INTO skin_search(skin_md5, readme_text, file_names)
|
||||
SELECT
|
||||
s.md5,
|
||||
s.readme_text,
|
||||
GROUP_CONCAT(f.file_path, ' ')
|
||||
FROM
|
||||
skins s
|
||||
LEFT JOIN
|
||||
files f ON s.md5 = f.skin_md5
|
||||
GROUP BY
|
||||
s.md5;
|
||||
|
||||
-- Step 3: Create triggers to keep the FTS index updated
|
||||
|
||||
-- Trigger for inserting into the skins table
|
||||
CREATE TRIGGER after_skin_insert AFTER INSERT ON skins
|
||||
BEGIN
|
||||
INSERT INTO skin_search(skin_md5, readme_text)
|
||||
VALUES (NEW.md5, NEW.readme_text);
|
||||
END;
|
||||
|
||||
-- Trigger for updating skins readme_text
|
||||
CREATE TRIGGER after_skin_update AFTER UPDATE OF readme_text ON skins
|
||||
BEGIN
|
||||
UPDATE skin_search
|
||||
SET readme_text = NEW.readme_text
|
||||
WHERE skin_md5 = NEW.md5;
|
||||
END;
|
||||
|
||||
-- Trigger for inserting into the files table
|
||||
CREATE TRIGGER after_file_insert AFTER INSERT ON files
|
||||
BEGIN
|
||||
UPDATE skin_search
|
||||
SET file_names = (
|
||||
SELECT GROUP_CONCAT(file_path, ' ')
|
||||
FROM files
|
||||
WHERE skin_md5 = NEW.skin_md5
|
||||
)
|
||||
WHERE skin_md5 = NEW.skin_md5;
|
||||
END;
|
||||
|
||||
-- Trigger for updating file_path in files
|
||||
CREATE TRIGGER after_file_update AFTER UPDATE OF file_path ON files
|
||||
BEGIN
|
||||
UPDATE skin_search
|
||||
SET file_names = (
|
||||
SELECT GROUP_CONCAT(file_path, ' ')
|
||||
FROM files
|
||||
WHERE skin_md5 = NEW.skin_md5
|
||||
)
|
||||
WHERE skin_md5 = NEW.skin_md5;
|
||||
END;
|
||||
|
||||
-- Trigger for deleting files
|
||||
CREATE TRIGGER after_file_delete AFTER DELETE ON files
|
||||
BEGIN
|
||||
UPDATE skin_search
|
||||
SET file_names = (
|
||||
SELECT GROUP_CONCAT(file_path, ' ')
|
||||
FROM files
|
||||
WHERE skin_md5 = OLD.skin_md5
|
||||
)
|
||||
WHERE skin_md5 = OLD.skin_md5;
|
||||
END;`);
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
await knex.raw(`
|
||||
DROP TRIGGER after_skin_insert;
|
||||
DROP TRIGGER after_skin_update;
|
||||
DROP TRIGGER after_file_insert;
|
||||
DROP TRIGGER after_file_update;
|
||||
DROP TRIGGER after_file_delete;
|
||||
DROP TABLE skin_search;
|
||||
`);
|
||||
}
|
||||
|
|
@ -23,6 +23,7 @@
|
|||
"fast-xml-parser": "^4.2.2",
|
||||
"graphql": "^16.8.1",
|
||||
"graphql-http": "^1.22.1",
|
||||
"graphql-yoga": "^5.10.10",
|
||||
"imagemin": "^7.0.0",
|
||||
"imagemin-optipng": "^7.0.0",
|
||||
"knex": "^0.21.1",
|
||||
|
|
|
|||
170
yarn.lock
170
yarn.lock
|
|
@ -1568,6 +1568,21 @@
|
|||
resolved "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.1.tgz"
|
||||
integrity sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ==
|
||||
|
||||
"@envelop/core@^5.0.2":
|
||||
version "5.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@envelop/core/-/core-5.0.3.tgz#d359dd45853a4d3ab23e9d12187e544cde9ffa9b"
|
||||
integrity sha512-SE3JxL7odst8igN6x77QWyPpXKXz/Hs5o5Y27r+9Br6WHIhkW90lYYVITWIJQ/qYgn5PkpbaVgeFY9rgqQaZ/A==
|
||||
dependencies:
|
||||
"@envelop/types" "5.0.0"
|
||||
tslib "^2.5.0"
|
||||
|
||||
"@envelop/types@5.0.0":
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@envelop/types/-/types-5.0.0.tgz#3ae59b50ec31d4bdcc7bd0b47e9c8cf2ac44b0ff"
|
||||
integrity sha512-IPjmgSc4KpQRlO4qbEDnBEixvtb06WDmjKfi/7fkZaryh5HuOmTtixe1EupQI5XfXO8joc3d27uUZ0QdC++euA==
|
||||
dependencies:
|
||||
tslib "^2.5.0"
|
||||
|
||||
"@esbuild-plugins/node-modules-polyfill@^0.1.4":
|
||||
version "0.1.4"
|
||||
resolved "https://registry.npmjs.org/@esbuild-plugins/node-modules-polyfill/-/node-modules-polyfill-0.1.4.tgz"
|
||||
|
|
@ -2068,6 +2083,18 @@
|
|||
tslib "^2.4.0"
|
||||
value-or-promise "^1.0.12"
|
||||
|
||||
"@graphql-tools/executor@^1.3.7":
|
||||
version "1.3.12"
|
||||
resolved "https://registry.yarnpkg.com/@graphql-tools/executor/-/executor-1.3.12.tgz#d1019279cad02c099850fedd2be2b563a815de66"
|
||||
integrity sha512-FzLXZQJOZHB75SecYFOIEEHw/qcxkRFViw0lVqHpaL07c+GqDxv6VOto0FZCIiV9RgGdyRj3O8lXDCp9Cw1MbA==
|
||||
dependencies:
|
||||
"@graphql-tools/utils" "^10.7.2"
|
||||
"@graphql-typed-document-node/core" "^3.2.0"
|
||||
"@repeaterjs/repeater" "^3.0.4"
|
||||
"@whatwg-node/disposablestack" "^0.0.5"
|
||||
tslib "^2.4.0"
|
||||
value-or-promise "^1.0.12"
|
||||
|
||||
"@graphql-tools/graphql-file-loader@^7.3.7":
|
||||
version "7.5.17"
|
||||
resolved "https://registry.npmjs.org/@graphql-tools/graphql-file-loader/-/graphql-file-loader-7.5.17.tgz"
|
||||
|
|
@ -2128,6 +2155,24 @@
|
|||
"@graphql-tools/utils" "^9.2.1"
|
||||
tslib "^2.4.0"
|
||||
|
||||
"@graphql-tools/merge@^9.0.17":
|
||||
version "9.0.17"
|
||||
resolved "https://registry.yarnpkg.com/@graphql-tools/merge/-/merge-9.0.17.tgz#57c98b74d6553fb0c053d4a121405d6088e119b6"
|
||||
integrity sha512-3K4g8KKbIqfdmK0L5+VtZsqwAeElPkvT5ejiH+KEhn2wyKNCi4HYHxpQk8xbu+dSwLlm9Lhet1hylpo/mWCkuQ==
|
||||
dependencies:
|
||||
"@graphql-tools/utils" "^10.7.2"
|
||||
tslib "^2.4.0"
|
||||
|
||||
"@graphql-tools/schema@^10.0.11":
|
||||
version "10.0.16"
|
||||
resolved "https://registry.yarnpkg.com/@graphql-tools/schema/-/schema-10.0.16.tgz#82b82f9193e708fc0d34f9f3ba80ccc7a057aefa"
|
||||
integrity sha512-G2zgb8hNg9Sx6Z2FSXm57ToNcwMls9A9cUm+EsCrnGGDsryzN5cONYePUpSGj5NCFivVp3o1FT5dg19P/1qeqQ==
|
||||
dependencies:
|
||||
"@graphql-tools/merge" "^9.0.17"
|
||||
"@graphql-tools/utils" "^10.7.2"
|
||||
tslib "^2.4.0"
|
||||
value-or-promise "^1.0.12"
|
||||
|
||||
"@graphql-tools/schema@^9.0.18", "@graphql-tools/schema@^9.0.19":
|
||||
version "9.0.19"
|
||||
resolved "https://registry.npmjs.org/@graphql-tools/schema/-/schema-9.0.19.tgz"
|
||||
|
|
@ -2157,6 +2202,16 @@
|
|||
value-or-promise "^1.0.11"
|
||||
ws "^8.12.0"
|
||||
|
||||
"@graphql-tools/utils@^10.6.2", "@graphql-tools/utils@^10.7.2":
|
||||
version "10.7.2"
|
||||
resolved "https://registry.yarnpkg.com/@graphql-tools/utils/-/utils-10.7.2.tgz#feafb7be9211570037288f5a3cadab76de41a097"
|
||||
integrity sha512-Wn85S+hfkzfVFpXVrQ0hjnePa3p28aB6IdAGCiD1SqBCSMDRzL+OFEtyAyb30nV9Mqflqs9lCqjqlR2puG857Q==
|
||||
dependencies:
|
||||
"@graphql-typed-document-node/core" "^3.1.1"
|
||||
cross-inspect "1.0.1"
|
||||
dset "^3.1.4"
|
||||
tslib "^2.4.0"
|
||||
|
||||
"@graphql-tools/utils@^9.0.0", "@graphql-tools/utils@^9.2.1":
|
||||
version "9.2.1"
|
||||
resolved "https://registry.npmjs.org/@graphql-tools/utils/-/utils-9.2.1.tgz"
|
||||
|
|
@ -2176,11 +2231,36 @@
|
|||
tslib "^2.4.0"
|
||||
value-or-promise "^1.0.12"
|
||||
|
||||
"@graphql-typed-document-node/core@3.2.0", "@graphql-typed-document-node/core@^3.1.1":
|
||||
"@graphql-typed-document-node/core@3.2.0", "@graphql-typed-document-node/core@^3.1.1", "@graphql-typed-document-node/core@^3.2.0":
|
||||
version "3.2.0"
|
||||
resolved "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.2.0.tgz"
|
||||
integrity sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==
|
||||
|
||||
"@graphql-yoga/logger@^2.0.1":
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@graphql-yoga/logger/-/logger-2.0.1.tgz#b3d18371c07bb2fe03417e3920ddcaebb2ee0262"
|
||||
integrity sha512-Nv0BoDGLMg9QBKy9cIswQ3/6aKaKjlTh87x3GiBg2Z4RrjyrM48DvOOK0pJh1C1At+b0mUIM67cwZcFTDLN4sA==
|
||||
dependencies:
|
||||
tslib "^2.8.1"
|
||||
|
||||
"@graphql-yoga/subscription@^5.0.3":
|
||||
version "5.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@graphql-yoga/subscription/-/subscription-5.0.3.tgz#d36bc16d4f9c4ba3b9024133ba959ae2185b2d41"
|
||||
integrity sha512-xLGEataxCULjL9rlTCVFL1IW2E90TnDIL+mE4lZBi9X92jc6s+Q+jk6Ax3I98kryCJh0UMiwjit7CaIIczTiJg==
|
||||
dependencies:
|
||||
"@graphql-yoga/typed-event-target" "^3.0.2"
|
||||
"@repeaterjs/repeater" "^3.0.4"
|
||||
"@whatwg-node/events" "^0.1.0"
|
||||
tslib "^2.8.1"
|
||||
|
||||
"@graphql-yoga/typed-event-target@^3.0.2":
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@graphql-yoga/typed-event-target/-/typed-event-target-3.0.2.tgz#af29ed2a5a84062ffab8e404b335ec4d4c37ceb4"
|
||||
integrity sha512-ZpJxMqB+Qfe3rp6uszCQoag4nSw42icURnBRfFYSOmTgEeOe4rD0vYlbA8spvCu2TlCesNTlEN9BLWtQqLxabA==
|
||||
dependencies:
|
||||
"@repeaterjs/repeater" "^3.0.4"
|
||||
tslib "^2.8.1"
|
||||
|
||||
"@hapi/hoek@^9.0.0", "@hapi/hoek@^9.3.0":
|
||||
version "9.3.0"
|
||||
resolved "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz"
|
||||
|
|
@ -7253,11 +7333,33 @@
|
|||
resolved "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.5.tgz"
|
||||
integrity sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ==
|
||||
|
||||
"@whatwg-node/disposablestack@^0.0.5":
|
||||
version "0.0.5"
|
||||
resolved "https://registry.yarnpkg.com/@whatwg-node/disposablestack/-/disposablestack-0.0.5.tgz#cd646b1ef60a36972e018ab21f412a3539c6deec"
|
||||
integrity sha512-9lXugdknoIequO4OYvIjhygvfSEgnO8oASLqLelnDhkRjgBZhc39shC3QSlZuyDO9bgYSIVa2cHAiN+St3ty4w==
|
||||
dependencies:
|
||||
tslib "^2.6.3"
|
||||
|
||||
"@whatwg-node/events@^0.0.3":
|
||||
version "0.0.3"
|
||||
resolved "https://registry.npmjs.org/@whatwg-node/events/-/events-0.0.3.tgz"
|
||||
integrity sha512-IqnKIDWfXBJkvy/k6tzskWTc2NK3LcqHlb+KHGCrjOCH4jfQckRX0NAiIcC/vIqQkzLYw2r2CTSwAxcrtcD6lA==
|
||||
|
||||
"@whatwg-node/events@^0.1.0":
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@whatwg-node/events/-/events-0.1.2.tgz#23f7c7ad887d7fd448e9ce3261eac9ef319ddd7c"
|
||||
integrity sha512-ApcWxkrs1WmEMS2CaLLFUEem/49erT3sxIVjpzU5f6zmVcnijtDSrhoK2zVobOIikZJdH63jdAXOrvjf6eOUNQ==
|
||||
dependencies:
|
||||
tslib "^2.6.3"
|
||||
|
||||
"@whatwg-node/fetch@^0.10.0", "@whatwg-node/fetch@^0.10.1":
|
||||
version "0.10.3"
|
||||
resolved "https://registry.yarnpkg.com/@whatwg-node/fetch/-/fetch-0.10.3.tgz#b19883b8a1568c5ae09abd550589ec597e685f4b"
|
||||
integrity sha512-jCTL/qYcIW2GihbBRHypQ/Us7saWMNZ5fsumsta+qPY0Pmi1ccba/KRQvgctmQsbP69FWemJSs8zVcFaNwdL0w==
|
||||
dependencies:
|
||||
"@whatwg-node/node-fetch" "^0.7.7"
|
||||
urlpattern-polyfill "^10.0.0"
|
||||
|
||||
"@whatwg-node/fetch@^0.8.0", "@whatwg-node/fetch@^0.8.1":
|
||||
version "0.8.8"
|
||||
resolved "https://registry.npmjs.org/@whatwg-node/fetch/-/fetch-0.8.8.tgz"
|
||||
|
|
@ -7280,6 +7382,24 @@
|
|||
fast-url-parser "^1.1.3"
|
||||
tslib "^2.3.1"
|
||||
|
||||
"@whatwg-node/node-fetch@^0.7.7":
|
||||
version "0.7.7"
|
||||
resolved "https://registry.yarnpkg.com/@whatwg-node/node-fetch/-/node-fetch-0.7.7.tgz#6c1752e2e16cfac93fdef2ef4f852b7a50dd15ed"
|
||||
integrity sha512-BDbIMOenThOTFDBLh1WscgBNAxfDAdAdd9sMG8Ff83hYxApJVbqEct38bUAj+zn8bTsfBx/lyfnVOTyq5xUlvg==
|
||||
dependencies:
|
||||
"@whatwg-node/disposablestack" "^0.0.5"
|
||||
busboy "^1.6.0"
|
||||
tslib "^2.6.3"
|
||||
|
||||
"@whatwg-node/server@^0.9.64":
|
||||
version "0.9.65"
|
||||
resolved "https://registry.yarnpkg.com/@whatwg-node/server/-/server-0.9.65.tgz#abba6a790bfe7cf9a7ea1301c1e12743504f8c83"
|
||||
integrity sha512-CnYTFEUJkbbAcuBXnXirVIgKBfs2YA6sSGjxeq07AUiyXuoQ0fbvTIQoteMglmn09QeGzcH/l0B7nIml83xvVw==
|
||||
dependencies:
|
||||
"@whatwg-node/disposablestack" "^0.0.5"
|
||||
"@whatwg-node/fetch" "^0.10.0"
|
||||
tslib "^2.6.3"
|
||||
|
||||
"@xtuc/ieee754@^1.2.0":
|
||||
version "1.2.0"
|
||||
resolved "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz"
|
||||
|
|
@ -10617,6 +10737,13 @@ cross-fetch@3.1.5:
|
|||
dependencies:
|
||||
node-fetch "2.6.7"
|
||||
|
||||
cross-inspect@1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/cross-inspect/-/cross-inspect-1.0.1.tgz#15f6f65e4ca963cf4cc1a2b5fef18f6ca328712b"
|
||||
integrity sha512-Pcw1JTvZLSJH83iiGWt6fRcT+BjZlCDRVwYLbUcHzv/CRpB7r0MlSrGbIyQvVSNyGnbt7G4AXuyCiDR3POvZ1A==
|
||||
dependencies:
|
||||
tslib "^2.4.0"
|
||||
|
||||
cross-spawn@^5.0.1:
|
||||
version "5.1.0"
|
||||
resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz"
|
||||
|
|
@ -11835,6 +11962,11 @@ dset@^3.1.2:
|
|||
resolved "https://registry.npmjs.org/dset/-/dset-3.1.3.tgz"
|
||||
integrity sha512-20TuZZHCEZ2O71q9/+8BwKwZ0QtD9D8ObhrihJPr+vLLYlSuAU3/zL4cSlgbfeoGHTjCSJBa7NGcrF9/Bx/WJQ==
|
||||
|
||||
dset@^3.1.4:
|
||||
version "3.1.4"
|
||||
resolved "https://registry.yarnpkg.com/dset/-/dset-3.1.4.tgz#f8eaf5f023f068a036d08cd07dc9ffb7d0065248"
|
||||
integrity sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA==
|
||||
|
||||
duplexer2@0.0.2:
|
||||
version "0.0.2"
|
||||
resolved "https://registry.npmjs.org/duplexer2/-/duplexer2-0.0.2.tgz"
|
||||
|
|
@ -15134,16 +15266,28 @@ graphql-ws@5.12.1:
|
|||
resolved "https://registry.npmjs.org/graphql-ws/-/graphql-ws-5.12.1.tgz"
|
||||
integrity sha512-umt4f5NnMK46ChM2coO36PTFhHouBrK9stWWBczERguwYrGnPNxJ9dimU6IyOBfOkC6Izhkg4H8+F51W/8CYDg==
|
||||
|
||||
graphql@^16.8.1:
|
||||
graphql-yoga@^5.10.10:
|
||||
version "5.10.10"
|
||||
resolved "https://registry.yarnpkg.com/graphql-yoga/-/graphql-yoga-5.10.10.tgz#5384d7e391d4310a6eb8d03fb9dff18795badd90"
|
||||
integrity sha512-0KF0mxKeedMBYOSVLbJh7GJJwrObhBktr77SuDdZPmVA+OtdC9Xef+gYHsk7EQDeBPodgsA99pmd/tL9j0d4zg==
|
||||
dependencies:
|
||||
"@envelop/core" "^5.0.2"
|
||||
"@graphql-tools/executor" "^1.3.7"
|
||||
"@graphql-tools/schema" "^10.0.11"
|
||||
"@graphql-tools/utils" "^10.6.2"
|
||||
"@graphql-yoga/logger" "^2.0.1"
|
||||
"@graphql-yoga/subscription" "^5.0.3"
|
||||
"@whatwg-node/fetch" "^0.10.1"
|
||||
"@whatwg-node/server" "^0.9.64"
|
||||
dset "^3.1.4"
|
||||
lru-cache "^10.0.0"
|
||||
tslib "^2.8.1"
|
||||
|
||||
graphql@16.8.1, graphql@^16.8.1, graphql@^16.9.0:
|
||||
version "16.8.1"
|
||||
resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.8.1.tgz#1930a965bef1170603702acdb68aedd3f3cf6f07"
|
||||
integrity sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw==
|
||||
|
||||
graphql@^16.9.0:
|
||||
version "16.10.0"
|
||||
resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.10.0.tgz#24c01ae0af6b11ea87bf55694429198aaa8e220c"
|
||||
integrity sha512-AjqGKbDGUFRKIRCP9tCKiIGHyriz2oHEbPIbEtcSLSs4YjReZOIPQQWek4+6hjw62H9QShXHyaGivGiYVLeYFQ==
|
||||
|
||||
grats@^0.0.31:
|
||||
version "0.0.31"
|
||||
resolved "https://registry.yarnpkg.com/grats/-/grats-0.0.31.tgz#f094786584a0f2fb345b38e6c37330d3f4f391cd"
|
||||
|
|
@ -19413,6 +19557,11 @@ lpad-align@^1.0.1:
|
|||
longest "^1.0.0"
|
||||
meow "^3.3.0"
|
||||
|
||||
lru-cache@^10.0.0:
|
||||
version "10.4.3"
|
||||
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119"
|
||||
integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==
|
||||
|
||||
lru-cache@^10.2.0:
|
||||
version "10.2.0"
|
||||
resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz"
|
||||
|
|
@ -27653,6 +27802,11 @@ tslib@^2.0.0, tslib@^2.0.1, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.2.0, tslib@^2.3
|
|||
resolved "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz"
|
||||
integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==
|
||||
|
||||
tslib@^2.6.3, tslib@^2.8.1:
|
||||
version "2.8.1"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f"
|
||||
integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==
|
||||
|
||||
tsscmp@1.0.6:
|
||||
version "1.0.6"
|
||||
resolved "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz"
|
||||
|
|
@ -28278,7 +28432,7 @@ url@^0.11.0:
|
|||
punycode "^1.4.1"
|
||||
qs "^6.11.2"
|
||||
|
||||
urlpattern-polyfill@10.0.0:
|
||||
urlpattern-polyfill@10.0.0, urlpattern-polyfill@^10.0.0:
|
||||
version "10.0.0"
|
||||
resolved "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.0.0.tgz"
|
||||
integrity sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue