diff --git a/assets/components/actors/actor.vue b/assets/components/actors/actor.vue index 8792cd80..2b4eaa96 100644 --- a/assets/components/actors/actor.vue +++ b/assets/components/actors/actor.vue @@ -188,7 +188,6 @@

{{ actor.description }}

@@ -23,7 +23,7 @@ class="nav-link" :class="{ active: active === 'networks' }" > - Networks + Networks @@ -33,7 +33,7 @@ class="nav-link" :class="{ active: active === 'tags' }" > - Tags + Tags @@ -83,8 +83,9 @@ export default { } .nav-link { - display: inline-flex; + display: flex; align-items: center; + justify-content: center; padding: 1rem; border-bottom: solid 5px transparent; color: $shadow; @@ -114,4 +115,24 @@ export default { } } } + +@media(max-width: $breakpoint0) { + .nav-label { + display: none; + } + + .nav .nolist { + display: flex; + } + + .nav, + .nav-item { + flex-grow: 1; + } + + .nav-link { + + + } +} diff --git a/assets/components/networks/networks.vue b/assets/components/networks/networks.vue index c50f0e06..508dcb6c 100644 --- a/assets/components/networks/networks.vue +++ b/assets/components/networks/networks.vue @@ -31,7 +31,7 @@ export default { + diff --git a/assets/components/tags/tags.vue b/assets/components/tags/tags.vue index 6ef541d4..379d7fba 100644 --- a/assets/components/tags/tags.vue +++ b/assets/components/tags/tags.vue @@ -1,10 +1,44 @@ @@ -12,7 +46,42 @@ import Tag from '../tile/tag.vue'; async function mounted() { - this.tags = await this.$store.dispatch('fetchTags', { priority: [9] }); + const tags = await this.$store.dispatch('fetchTags', { + slug: [ + 'airtight', + 'anal', + 'double-anal', + 'double-penetration', + 'double-vaginal', + 'da-tp', + 'dv-tp', + 'triple-anal', + 'blowbang', + 'gangbang', + 'mff', + 'mfm', + 'orgy', + 'asian', + 'caucasian', + 'ebony', + 'interracial', + 'latina', + 'anal-creampie', + 'bukkake', + 'creampie', + 'facial', + 'oral-creampie', + 'swallowing', + ], + }); + + this.tags = tags.reduce((acc, tag) => { + if (acc[tag.group.slug]) { + return { ...acc, [tag.group.slug]: [...acc[tag.group.slug], tag] }; + } + + return { ...acc, [tag.group.slug]: [tag] }; + }, {}); } export default { @@ -21,7 +90,7 @@ export default { }, data() { return { - tags: [], + tags: {}, }; }, mounted, @@ -30,9 +99,12 @@ export default { diff --git a/assets/components/tile/tag.vue b/assets/components/tile/tag.vue index cefe46a5..3923ec3c 100644 --- a/assets/components/tile/tag.vue +++ b/assets/components/tile/tag.vue @@ -4,15 +4,14 @@ :title="tag.name" class="tile" > + {{ tag.name }} + - - {{ tag.name }} @@ -24,11 +23,6 @@ export default { default: null, }, }, - data() { - return { - imageAvailable: true, - }; - }, }; @@ -36,7 +30,8 @@ export default { @import 'theme'; .tile { - background: $background; + color: $text-contrast; + background: $profile; display: flex; flex-direction: column; align-items: center; @@ -53,20 +48,13 @@ export default { } .title { - color: $text; + height: 100%; display: flex; align-items: center; justify-content: center; font-size: 1rem; - font-weight: bold; padding: .5rem 1rem; -} - -.title { - color: $text; - height: 100%; - display: flex; - align-items: center; - margin: 0; + font-weight: bold; + text-transform: capitalize; } diff --git a/assets/css/_theme.scss b/assets/css/_theme.scss index 83ab8070..ca20dffc 100644 --- a/assets/css/_theme.scss +++ b/assets/css/_theme.scss @@ -1,4 +1,5 @@ /* $primary: #ff886c; */ +$breakpoint0: 540px; $breakpoint: 720px; $breakpoint2: 900px; $breakpoint3: 1200px; diff --git a/assets/js/api.js b/assets/js/api.js index bb1904c2..0f0cc945 100644 --- a/assets/js/api.js +++ b/assets/js/api.js @@ -1,7 +1,8 @@ import config from 'config'; async function get(endpoint, query = {}) { - const q = new URLSearchParams(query).toString(); + const curatedQuery = Object.entries(query).reduce((acc, [key, value]) => (value ? { ...acc, [key]: value } : acc), {}); // remove empty values + const q = new URLSearchParams(curatedQuery).toString(); const res = await fetch(`${config.api.url}${endpoint}?${q}`, { method: 'GET', diff --git a/assets/js/tags/actions.js b/assets/js/tags/actions.js index 6a608ed2..8da5bc78 100644 --- a/assets/js/tags/actions.js +++ b/assets/js/tags/actions.js @@ -1,12 +1,23 @@ import { get } from '../api'; function initTagsActions(store, _router) { - async function fetchTags({ _commit }, { tagId, limit = 100, priority }) { + async function fetchTags({ _commit }, { + tagId, + limit = 100, + slug, + group, + priority, + }) { if (tagId) { return get(`/tags/${tagId}`); } - return get('/tags', { limit, priority }); + return get('/tags', { + limit, + slug, + priority, + group, + }); } async function fetchTagReleases({ _commit }, tagId) { diff --git a/migrations/20190325001339_releases.js b/migrations/20190325001339_releases.js index 74dc1aa9..6d76dc6e 100644 --- a/migrations/20190325001339_releases.js +++ b/migrations/20190325001339_releases.js @@ -228,6 +228,7 @@ exports.up = knex => Promise.resolve() table.string('quality', 6); table.string('hash'); + table.text('comment'); table.string('source', 1000); table.unique(['domain', 'target_id', 'role', 'hash']); diff --git a/package-lock.json b/package-lock.json index d536915b..71c96baf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9693,6 +9693,124 @@ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" }, + "showdown": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/showdown/-/showdown-1.9.1.tgz", + "integrity": "sha512-9cGuS382HcvExtf5AHk7Cb4pAeQQ+h0eTr33V1mu+crYWV4KvWAw6el92bDrqGEk5d46Ai/fhbEUwqJ/mTCNEA==", + "requires": { + "yargs": "^14.2" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" + }, + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "requires": { + "locate-path": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.1.tgz", + "integrity": "sha512-85Tk+90UCVWvbDavCLKPOLC9vvY8OwEX/RtKF+/1OADJMVlFfEHOiMTPVyxg7mk/dKa+ipdHm0OUkTvCpMTuwg==", + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "requires": { + "ansi-regex": "^4.1.0" + } + }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + } + }, + "yargs": { + "version": "14.2.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-14.2.2.tgz", + "integrity": "sha512-/4ld+4VV5RnrynMhPZJ/ZpOCGSCeghMykZ3BhdFBDa9Wy/RH6uEGNWDJog+aUlq+9OM1CFTgtYRW5Is1Po9NOA==", + "requires": { + "cliui": "^5.0.0", + "decamelize": "^1.2.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^15.0.0" + } + }, + "yargs-parser": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-15.0.0.tgz", + "integrity": "sha512-xLTUnCMc4JhxrPEPUYD5IBR1mWCK/aT6+RJ/K29JY2y1vD+FhtgKK0AXRWvI262q3QSffAQuTouFIKUuHX89wQ==", + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, "sigmund": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", diff --git a/package.json b/package.json index 0edaca5d..07cfcb52 100644 --- a/package.json +++ b/package.json @@ -86,6 +86,7 @@ "react": "^16.8.6", "react-dom": "^16.8.6", "sharp": "^0.23.2", + "showdown": "^1.9.1", "tough-cookie": "^3.0.1", "tty-table": "^2.7.0", "url-pattern": "^1.0.3", diff --git a/public/css/style.css b/public/css/style.css index d1b81c67..36dab8a0 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -629,7 +629,7 @@ .networks[data-v-4709d404] { display: grid; - grid-template-columns: repeat(auto-fit, 15rem); + grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)); grid-gap: 1rem; padding: 1rem; } @@ -644,9 +644,6 @@ .photos .avatar-link[data-v-0a0430c7] { display: none; } -.photo-link[data-v-0a0430c7] { - height: 15rem; -} .photo[data-v-0a0430c7] { width: 100%; height: 100%; @@ -923,26 +920,57 @@ } /* $primary: #ff886c; */ +.description a { + color: #cc4466; + text-decoration: inherit; +} +.description a:hover { + color: #ff6c88; +} + +/* $primary: #ff886c; */ +.tag[data-v-7f130e7f] { + display: flex; + flex-direction: row; + flex-grow: 1; + justify-content: stretch; +} +.sidebar[data-v-7f130e7f] { + background: #222; + color: #fff; + width: 25rem; + box-sizing: border-box; + padding: 1rem; +} .poster[data-v-7f130e7f] { - width: 30rem; - height: 18rem; + width: 100%; + height: 15rem; -o-object-fit: cover; object-fit: cover; } .title[data-v-7f130e7f] { - display: inline-block; - padding: 1rem; - margin: 0 .5rem 0 0; + padding: 0; + margin: 1rem 0; text-transform: capitalize; } .title .icon[data-v-7f130e7f] { + fill: #fff; width: 1.25rem; height: 1.25rem; } +.description[data-v-7f130e7f] { + padding: 0; + margin: 0 0 1rem 0; + line-height: 1.5; +} +.photo[data-v-7f130e7f] { + width: 100%; +} /* $primary: #ff886c; */ .tile[data-v-602c6fd8] { - background: #fff; + color: #fff; + background: #222; display: flex; flex-direction: column; align-items: center; @@ -958,27 +986,23 @@ object-fit: cover; } .title[data-v-602c6fd8] { - color: #222; + height: 100%; display: flex; align-items: center; justify-content: center; font-size: 1rem; - font-weight: bold; padding: .5rem 1rem; -} -.title[data-v-602c6fd8] { - color: #222; - height: 100%; - display: flex; - align-items: center; - margin: 0; + font-weight: bold; + text-transform: capitalize; } .tags[data-v-66fa6284] { + padding: 1rem; +} +.tiles[data-v-66fa6284] { display: grid; grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr)); grid-gap: .5rem; - padding: 1rem; } /* $primary: #ff886c; */ @@ -1146,8 +1170,9 @@ body { display: inline-block; } .nav-link[data-v-10b7ec04] { - display: inline-flex; + display: flex; align-items: center; + justify-content: center; padding: 1rem; border-bottom: solid 5px transparent; color: rgba(0, 0, 0, 0.5); @@ -1172,6 +1197,18 @@ body { .nav-link:hover:not(.active) .icon[data-v-10b7ec04] { fill: #ff6c88; } +@media (max-width: 540px) { +.nav-label[data-v-10b7ec04] { + display: none; +} +.nav .nolist[data-v-10b7ec04] { + display: flex; +} +.nav[data-v-10b7ec04], + .nav-item[data-v-10b7ec04] { + flex-grow: 1; +} +} /* $primary: #ff886c; */ .container { diff --git a/public/img/logos/pervcity/analoverdose.png b/public/img/logos/pervcity/analoverdose.png new file mode 100644 index 00000000..ab589e09 Binary files /dev/null and b/public/img/logos/pervcity/analoverdose.png differ diff --git a/public/img/logos/pervcity/bangingbeauties.png b/public/img/logos/pervcity/bangingbeauties.png new file mode 100644 index 00000000..f708ce57 Binary files /dev/null and b/public/img/logos/pervcity/bangingbeauties.png differ diff --git a/public/img/logos/pervcity/chocolatebjs.png b/public/img/logos/pervcity/chocolatebjs.png new file mode 100644 index 00000000..1dd521f6 Binary files /dev/null and b/public/img/logos/pervcity/chocolatebjs.png differ diff --git a/public/img/logos/pervcity/network.png b/public/img/logos/pervcity/network.png index e6907c83..6b6b2984 100644 Binary files a/public/img/logos/pervcity/network.png and b/public/img/logos/pervcity/network.png differ diff --git a/public/img/logos/pervcity/oraloverdose.png b/public/img/logos/pervcity/oraloverdose.png new file mode 100644 index 00000000..516cc9c8 Binary files /dev/null and b/public/img/logos/pervcity/oraloverdose.png differ diff --git a/public/img/logos/pervcity/upherasshole.png b/public/img/logos/pervcity/upherasshole.png new file mode 100644 index 00000000..2d8328a3 Binary files /dev/null and b/public/img/logos/pervcity/upherasshole.png differ diff --git a/public/img/tags/airtight.jpg b/public/img/tags/airtight.jpg deleted file mode 100644 index 0cd22fe3..00000000 Binary files a/public/img/tags/airtight.jpg and /dev/null differ diff --git a/public/img/tags/airtight_thumb.jpg b/public/img/tags/airtight_thumb.jpg deleted file mode 100644 index 815de4e6..00000000 Binary files a/public/img/tags/airtight_thumb.jpg and /dev/null differ diff --git a/public/img/tags/anal.jpg b/public/img/tags/anal.jpg deleted file mode 100644 index c52ad57f..00000000 Binary files a/public/img/tags/anal.jpg and /dev/null differ diff --git a/public/img/tags/anal_thumb.jpg b/public/img/tags/anal_thumb.jpg deleted file mode 100644 index e5d10351..00000000 Binary files a/public/img/tags/anal_thumb.jpg and /dev/null differ diff --git a/public/img/tags/bdsm.jpg b/public/img/tags/bdsm.jpg deleted file mode 100644 index 21b40081..00000000 Binary files a/public/img/tags/bdsm.jpg and /dev/null differ diff --git a/public/img/tags/bdsm_thumb.jpg b/public/img/tags/bdsm_thumb.jpg deleted file mode 100644 index 4bbbb415..00000000 Binary files a/public/img/tags/bdsm_thumb.jpg and /dev/null differ diff --git a/public/img/tags/double-anal.jpg b/public/img/tags/double-anal.jpg deleted file mode 100644 index 37b415fc..00000000 Binary files a/public/img/tags/double-anal.jpg and /dev/null differ diff --git a/public/img/tags/double-anal_thumb.jpg b/public/img/tags/double-anal_thumb.jpg deleted file mode 100644 index 1126f772..00000000 Binary files a/public/img/tags/double-anal_thumb.jpg and /dev/null differ diff --git a/public/img/tags/double-penetration.jpg b/public/img/tags/double-penetration.jpg deleted file mode 100644 index 3b21b8cc..00000000 Binary files a/public/img/tags/double-penetration.jpg and /dev/null differ diff --git a/public/img/tags/double-penetration_thumb.jpg b/public/img/tags/double-penetration_thumb.jpg deleted file mode 100644 index 105bdaa7..00000000 Binary files a/public/img/tags/double-penetration_thumb.jpg and /dev/null differ diff --git a/public/img/tags/double-vaginal.jpg b/public/img/tags/double-vaginal.jpg deleted file mode 100644 index f129c538..00000000 Binary files a/public/img/tags/double-vaginal.jpg and /dev/null differ diff --git a/public/img/tags/double-vaginal_thumb.jpg b/public/img/tags/double-vaginal_thumb.jpg deleted file mode 100644 index bee76b43..00000000 Binary files a/public/img/tags/double-vaginal_thumb.jpg and /dev/null differ diff --git a/public/img/tags/gangbang.jpg b/public/img/tags/gangbang.jpg deleted file mode 100644 index f3cb80d4..00000000 Binary files a/public/img/tags/gangbang.jpg and /dev/null differ diff --git a/public/img/tags/gangbang_thumb.jpg b/public/img/tags/gangbang_thumb.jpg deleted file mode 100644 index 87841fbf..00000000 Binary files a/public/img/tags/gangbang_thumb.jpg and /dev/null differ diff --git a/public/img/tags/interracial.jpg b/public/img/tags/interracial.jpg deleted file mode 100644 index 95493de0..00000000 Binary files a/public/img/tags/interracial.jpg and /dev/null differ diff --git a/public/img/tags/interracial_thumb.jpg b/public/img/tags/interracial_thumb.jpg deleted file mode 100644 index 834cfdeb..00000000 Binary files a/public/img/tags/interracial_thumb.jpg and /dev/null differ diff --git a/public/img/tags/triple-penetration.jpg b/public/img/tags/triple-penetration.jpg deleted file mode 100644 index 7b9daddb..00000000 Binary files a/public/img/tags/triple-penetration.jpg and /dev/null differ diff --git a/public/img/tags/triple-penetration_thumb.jpg b/public/img/tags/triple-penetration_thumb.jpg deleted file mode 100644 index 7826f75e..00000000 Binary files a/public/img/tags/triple-penetration_thumb.jpg and /dev/null differ diff --git a/seeds/03_studios.js b/seeds/02_studios.js similarity index 100% rename from seeds/03_studios.js rename to seeds/02_studios.js diff --git a/seeds/02_tags.js b/seeds/03_tags.js similarity index 86% rename from seeds/02_tags.js rename to seeds/03_tags.js index de82dbca..67c5fb7d 100644 --- a/seeds/02_tags.js +++ b/seeds/03_tags.js @@ -17,6 +17,10 @@ const groups = [ slug: 'ethnicity', name: 'Ethnicity', }, + { + slug: 'finish', + name: 'Finish', + }, { slug: 'group', name: 'Group sex', @@ -65,7 +69,7 @@ function getTags(groupsMap) { name: 'airtight', slug: 'airtight', alias_for: null, - description: 'A cock in every penetrable hole (of a woman); one in the mouth, one in the vagina, and one in the asshole.', + description: 'Stuffing one cock in her ass, one in her pussy, and one in her mouth, filling all of her penetrable holes and sealing her airtight like a figurative balloon. In other words, simultaneously getting [double penetrated](/tag/double-penetration), and giving a [blowjob](/tag/blowjob) or getting [facefucked](/tag/facefuck). Being airtight implies being [gangbanged](/tag/gangbang).', priority: 9, group_id: groupsMap.penetration, }, @@ -74,24 +78,21 @@ function getTags(groupsMap) { slug: 'amateur', alias_for: null, }, - { - name: 'american', - slug: 'american', - alias_for: null, - group_id: groupsMap.ethnicity, - }, { name: 'anal creampie', slug: 'anal-creampie', + priority: 7, alias_for: null, description: 'Ejaculating into the asshole.', + group_id: groupsMap.finish, }, { name: 'anal', slug: 'anal', - description: 'Penetrating the asshole with a (real) dick.', + description: 'Taking a cock in the asshole.', priority: 9, alias_for: null, + group_id: groupsMap.penetration, }, { name: 'ass fingering', @@ -102,6 +103,7 @@ function getTags(groupsMap) { { name: 'anal fisting', slug: 'anal-fisting', + description: 'Shoving an entire hand into the asshole.', alias_for: null, }, { @@ -112,11 +114,13 @@ function getTags(groupsMap) { { name: 'anal toys', slug: 'anal-toys', + description: 'Stuffing a toy, such as a dildo or buttplug, into the ass', alias_for: null, }, { name: 'asian', slug: 'asian', + priority: 7, alias_for: null, group_id: groupsMap.ethnicity, }, @@ -129,7 +133,8 @@ function getTags(groupsMap) { { name: 'ass to mouth', slug: 'ass-to-mouth', - priority: 8, + priority: 6, + description: 'Sucking off a cock right after anal, giving your own or someone else`s asshole a second hand taste.', alias_for: null, }, { @@ -157,6 +162,7 @@ function getTags(groupsMap) { { name: 'BDSM', slug: 'bdsm', + priority: 8, alias_for: null, }, { @@ -203,11 +209,14 @@ function getTags(groupsMap) { { name: 'blowjob', slug: 'blowjob', + priority: 7, alias_for: null, }, { name: 'blowbang', slug: 'blowbang', + priority: 9, + description: 'Pleasuring a gang of three or more cocks by sucking and jerking off as many cocks as they can, often getting [facefucked](/tag/facefuck), groped and rubbed out, and followed by a [bukkake](/tag/bukkake). If they are getting fucked, it is a [gangbang](/tag/gangbang).', alias_for: null, group_id: groupsMap.group, }, @@ -225,7 +234,10 @@ function getTags(groupsMap) { { name: 'bukkake', slug: 'bukkake', + priority: 8, + description: 'Getting ejaculated on the face by a group of three or more men, often following a [blowbang](/tag/blowbang) or [gangbang](/tag/gangbang).', alias_for: null, + group_id: groupsMap.finish, }, { name: 'cheerleader', @@ -256,7 +268,10 @@ function getTags(groupsMap) { { name: 'creampie', slug: 'creampie', + priority: 8, + description: 'Ejaculalating into her pussy, often shown visibly dripping out afterwards.', alias_for: null, + group_id: groupsMap.finish, }, { name: 'cum licking', @@ -284,13 +299,25 @@ function getTags(groupsMap) { alias_for: null, }, { - name: 'double anal penetration', + name: 'double anal', slug: 'double-anal', + description: 'Two cocks in the ass at the same time. If there\'s a third cock in her pussy, it is [double anal TP](/tag/da-tp).', + priority: 8, alias_for: null, + group_id: groupsMap.penetration, + }, + { + name: 'triple anal', + slug: 'triple-anal', + description: 'Getting fucked in the ass by not one, two but *three* cocks at the same time.', + priority: 7, + alias_for: null, + group_id: groupsMap.penetration, }, { name: 'deepthroat', slug: 'deepthroat', + priority: 7, alias_for: null, }, { @@ -298,6 +325,8 @@ function getTags(groupsMap) { slug: 'double-penetration', priority: 9, alias_for: null, + description: 'Fucking two cocks at once, with one in her ass, and one in her pussy. If she has another cock in her mouth, she is [airtight](/tag/airtight).', + group_id: groupsMap.penetration, }, { name: 'dungeon', @@ -305,9 +334,12 @@ function getTags(groupsMap) { alias_for: null, }, { - name: 'double vaginal penetration', + name: 'double vaginal', slug: 'double-vaginal', + description: 'Fucking her pussy with two cocks at the same time. If there\'s a third cock in her asshole, it is [double vaginal TP](/tag/dv-tp).', + priority: 8, alias_for: null, + group_id: groupsMap.penetration, }, { name: 'double blowjob', @@ -328,6 +360,7 @@ function getTags(groupsMap) { { name: 'ebony', slug: 'ebony', + priority: 7, alias_for: null, group_id: groupsMap.ethnicity, }, @@ -341,15 +374,10 @@ function getTags(groupsMap) { slug: 'enhanced-boobs', alias_for: null, }, - { - name: 'European', - slug: 'european', - alias_for: null, - group_id: groupsMap.ethnicity, - }, { name: 'facefuck', slug: 'facefuck', + priority: 9, alias_for: null, group_id: groupsMap.position, }, @@ -363,6 +391,7 @@ function getTags(groupsMap) { name: 'facial', slug: 'facial', alias_for: null, + group_id: groupsMap.finish, }, { name: 'feet', @@ -385,8 +414,10 @@ function getTags(groupsMap) { alias_for: null, }, { - name: 'FMF threesome', - slug: 'fmf', + name: 'MFF threesome', + slug: 'mff', + priority: 9, + description: 'A threesome with two women and one guy, in which the women have sex with eachother.', alias_for: null, group_id: groupsMap.group, }, @@ -398,10 +429,19 @@ function getTags(groupsMap) { { name: 'gangbang', slug: 'gangbang', + description: 'A group of three or more guys fucking a woman, at least two at the same time, often but not necessarily involving a [blowbang](/tag/blowbang), [double penetration](/tag/airtight) and [airtight](/tag/airtight). If she only gets fucked by one guy at a time, it might be considered a [trainbang](/tag/trainbang) instead. In a reverse gangbang, multiple women fuck one man.', alias_for: null, priority: 9, group_id: groupsMap.group, }, + { + name: 'trainbang', + slug: 'trainbang', + description: 'A group of three or more guys fucking a woman as in a [gangbang](/tag/gangbang), but one after the other, and never at the same time.', + priority: 7, + alias_for: null, + group_id: groupsMap.group, + }, { name: 'gapes', slug: 'gapes', @@ -435,12 +475,6 @@ function getTags(groupsMap) { alias_for: null, group_id: groupsMap.clothing, }, - { - name: 'hungarian', - slug: 'hungarian', - alias_for: null, - group_id: groupsMap.ethnicity, - }, { name: 'humiliation', slug: 'humiliation', @@ -456,6 +490,7 @@ function getTags(groupsMap) { slug: 'interracial', priority: 9, alias_for: null, + group_id: groupsMap.ethnicity, }, { name: 'kissing', @@ -470,7 +505,9 @@ function getTags(groupsMap) { { name: 'Latina', slug: 'latina', + priority: 7, alias_for: null, + group_id: groupsMap.ethnicity, }, { name: 'leather', @@ -480,6 +517,7 @@ function getTags(groupsMap) { { name: 'lesbian', slug: 'lesbian', + priority: 9, alias_for: null, }, { @@ -513,6 +551,8 @@ function getTags(groupsMap) { { name: 'MFM threesome', slug: 'mfm', + priority: 9, + description: 'Two men fucking one woman, but not eachother. Typically involves a \'spitroast\', where one guy gets a blowjob and the other fucks her pussy.', alias_for: null, group_id: groupsMap.group, }, @@ -542,11 +582,15 @@ function getTags(groupsMap) { { name: 'oral creampie', slug: 'oral-creampie', + priority: 7, alias_for: null, + group_id: groupsMap.finish, }, { name: 'orgy', slug: 'orgy', + priority: 9, + description: 'A group of (at least four) people having sex with eachother. If only one person is getting fucked, it is probably a [gangbang](/tag/gangbang).', alias_for: null, group_id: groupsMap.group, }, @@ -610,14 +654,9 @@ function getTags(groupsMap) { { name: 'rough', slug: 'rough', + priority: 7, alias_for: null, }, - { - name: 'russian', - slug: 'russian', - alias_for: null, - group_id: groupsMap.ethnicity, - }, { name: 'saliva', slug: 'saliva', @@ -729,6 +768,7 @@ function getTags(groupsMap) { name: 'swallowing', slug: 'swallowing', alias_for: null, + group_id: groupsMap.finish, }, { name: 'tattoo', @@ -764,9 +804,27 @@ function getTags(groupsMap) { priority: 10, alias_for: null, }, + { + name: 'double anal TP', + slug: 'da-tp', + priority: 7, + description: 'Triple penetration with two cocks in the ass, and one in the pussy. Also see [double vaginal TP](/tag/dv-tp).', + group_id: groupsMap.penetration, + alias_for: null, + }, + { + name: 'double vaginal TP', + slug: 'dv-tp', + priority: 7, + description: 'Triple penetration with two cocks in the pussy, and one in the ass. Also see [double anal TP](/tag/da-tp).', + group_id: groupsMap.penetration, + alias_for: null, + }, { name: 'triple penetration', slug: 'triple-penetration', + priority: 7, + description: 'Three cocks fucking her from behind at the same time. This can be either [double anal TP](/tag/da-tp), or [double vaginal TP](/tag/dv-tp).', alias_for: null, }, { @@ -795,8 +853,9 @@ function getTags(groupsMap) { alias_for: null, }, { - name: 'white', - slug: 'white', + name: 'caucasian', + slug: 'caucasian', + priority: 7, alias_for: null, group_id: groupsMap.ethnicity, }, @@ -878,11 +937,11 @@ function getTagAliases(tagsMap) { }, { name: 'fmf', - alias_for: tagsMap.fmf, + alias_for: tagsMap.mff, }, { name: 'ffm', - alias_for: tagsMap.fmf, + alias_for: tagsMap.mff, }, { name: 'bgb', @@ -1112,6 +1171,10 @@ function getTagAliases(tagsMap) { name: 'double anal penetration (dap)', alias_for: tagsMap['double-anal'], }, + { + name: 'tap', + alias_for: tagsMap['triple-anal'], + }, { name: 'dpp', alias_for: tagsMap['double-vaginal'], @@ -1436,6 +1499,10 @@ function getTagAliases(tagsMap) { name: 'whipping', alias_for: tagsMap['corporal-punishment'], }, + { + name: 'white', + alias_for: tagsMap.caucasian, + }, { name: 'work', alias_for: tagsMap.office, diff --git a/seeds/04_media.js b/seeds/04_media.js new file mode 100644 index 00000000..e869cc74 --- /dev/null +++ b/seeds/04_media.js @@ -0,0 +1,220 @@ +const upsert = require('../src/utils/upsert'); + +function getMedia(tagsMap) { + return [ + { + path: 'tags/airtight/poster.jpeg', + target_id: tagsMap.airtight, + role: 'poster', + comment: 'Jynx Maze in "Pump My Ass Full of Cum 3" for Jules Jordan', + }, + { + path: 'tags/airtight/2.jpeg', + target_id: tagsMap.airtight, + comment: 'Dakota Skye in "Dakota Goes Nuts" for ArchAngel', + }, + { + path: 'tags/airtight/1.jpeg', + target_id: tagsMap.airtight, + comment: 'Chloe Amour in "DP Masters 4" for Jules Jordan', + }, + { + path: 'tags/airtight/0.jpeg', + domain: 'tags', + target_id: tagsMap.airtight, + comment: 'Sheena Shaw in "Ass Worship 14" for Jules Jordan', + }, + { + path: 'tags/anal/poster.jpeg', + target_id: tagsMap.anal, + role: 'poster', + comment: '', + }, + { + path: 'tags/double-penetration/poster.jpeg', + target_id: tagsMap['double-penetration'], + role: 'poster', + comment: '', + }, + { + path: 'tags/double-anal/poster.jpeg', + target_id: tagsMap['double-anal'], + role: 'poster', + comment: '', + }, + { + path: 'tags/double-vaginal/poster.jpeg', + target_id: tagsMap['double-vaginal'], + role: 'poster', + comment: '', + }, + { + path: 'tags/da-tp/poster.jpeg', + target_id: tagsMap['da-tp'], + role: 'poster', + comment: 'Ninel Mojado aka Mira Cuckold in GIO063 for LegalPorno', + }, + { + path: 'tags/da-tp/1.jpeg', + target_id: tagsMap['da-tp'], + role: 'photo', + comment: 'Francys Belle in SZ1702 for LegalPorno', + }, + { + path: 'tags/da-tp/2.jpeg', + target_id: tagsMap['da-tp'], + role: 'photo', + comment: 'Angel Smalls in GIO408 for LegalPorno', + }, + { + path: 'tags/dv-tp/poster.jpeg', + target_id: tagsMap['dv-tp'], + role: 'poster', + comment: 'Juelz Ventura in "Gangbanged 5" for Elegant Angel', + }, + { + path: 'tags/triple-anal/poster.jpeg', + target_id: tagsMap['triple-anal'], + role: 'poster', + comment: 'Kristy Black in SZ1986 for LegalPorno', + }, + { + path: 'tags/triple-anal/1.jpeg', + target_id: tagsMap['triple-anal'], + role: 'photo', + comment: 'Natasha Teen in SZ2098 for LegalPorno', + }, + { + path: 'tags/triple-anal/2.jpeg', + target_id: tagsMap['triple-anal'], + role: 'photo', + comment: 'Kira Thorn in GIO1018 for LegalPorno"', + }, + { + path: 'tags/blowbang/poster.jpeg', + target_id: tagsMap.blowbang, + role: 'poster', + comment: '', + }, + { + path: 'tags/gangbang/poster.jpeg', + target_id: tagsMap.gangbang, + role: 'poster', + comment: '', + }, + { + path: 'tags/gangbang/1.jpeg', + target_id: tagsMap.gangbang, + role: 'photo', + comment: 'Ginger Lynn in "Gangbang Mystique", a photoset shot by Suze Randall for Puritan No. 10, 1984. This photo pushed the boundaries of pornography at the time, as depicting a woman \'fully occupied\' was unheard of.', + }, + { + path: 'tags/mff/poster.jpeg', + target_id: tagsMap.mff, + role: 'poster', + comment: '', + }, + { + path: 'tags/mfm/poster.jpeg', + target_id: tagsMap.mfm, + role: 'poster', + comment: '', + }, + { + path: 'tags/orgy/poster.jpeg', + target_id: tagsMap.orgy, + role: 'poster', + comment: '', + }, + { + path: 'tags/asian/poster.jpeg', + target_id: tagsMap.asian, + role: 'poster', + comment: '', + }, + { + path: 'tags/caucasian/poster.jpeg', + target_id: tagsMap.caucasian, + role: 'poster', + comment: '', + }, + { + path: 'tags/ebony/poster.jpeg', + target_id: tagsMap.ebony, + role: 'poster', + comment: '', + }, + { + path: 'tags/latina/poster.jpeg', + target_id: tagsMap.latina, + role: 'poster', + comment: '', + }, + { + path: 'tags/interracial/poster.jpeg', + target_id: tagsMap.interracial, + role: 'poster', + comment: '', + }, + { + path: 'tags/facial/poster.jpeg', + target_id: tagsMap.facial, + role: 'poster', + comment: '', + }, + { + path: 'tags/bukkake/poster.jpeg', + target_id: tagsMap.bukkake, + role: 'poster', + comment: '', + }, + { + path: 'tags/swallowing/poster.jpeg', + target_id: tagsMap.swallowing, + role: 'poster', + comment: '', + }, + { + path: 'tags/creampie/poster.jpeg', + target_id: tagsMap.creampie, + role: 'poster', + comment: '', + }, + { + path: 'tags/anal-creampie/poster.jpeg', + target_id: tagsMap['anal-creampie'], + role: 'poster', + comment: '', + }, + { + path: 'tags/oral-creampie/poster.jpeg', + target_id: tagsMap['oral-creampie'], + role: 'poster', + comment: '', + }, + ] + .map((file, index) => ({ + ...file, + thumbnail: file.thumbnail || file.path.replace('.jpeg', '_thumb.jpeg'), + mime: 'image/jpeg', + index, + domain: file.domain || 'tags', + role: file.role || 'photo', + })); +} + +/* eslint-disable max-len */ +exports.seed = knex => Promise.resolve() + .then(async () => { + const [duplicates, tags] = await Promise.all([ + knex('media').where('domain', 'tags'), + knex('tags').where('alias_for', null), + ]); + + const duplicatesByPath = duplicates.reduce((acc, file) => ({ ...acc, [file.path]: file }), {}); + const tagsMap = tags.reduce((acc, { id, slug }) => ({ ...acc, [slug]: id }), {}); + + const media = getMedia(tagsMap); + + return upsert('media', media, duplicatesByPath, 'path', knex); + }); diff --git a/seeds/04_countries.js b/seeds/05_countries.js similarity index 100% rename from seeds/04_countries.js rename to seeds/05_countries.js diff --git a/src/actors.js b/src/actors.js index ee140301..7de45948 100644 --- a/src/actors.js +++ b/src/actors.js @@ -375,28 +375,31 @@ async function scrapeBasicActors() { return scrapeActors(basicActors.map(actor => actor.name)); } -async function associateActors(release, releaseId) { - const actorEntries = await knex('actors').whereIn('name', release.actors); - - const newActors = release.actors - .map(actorName => actorName.trim()) - .filter(actorName => !actorEntries.some(actor => actor.name === actorName)); - - const [newActorEntries, associatedActors] = await Promise.all([ - Promise.all(newActors.map(async actorName => storeActor({ name: actorName }))), - knex('actors_associated').where('release_id', releaseId), +async function associateActors(mappedActors, releases) { + const [existingActorEntries, existingAssociationEntries] = await Promise.all([ + knex('actors').whereIn('name', Object.keys(mappedActors)), + knex('actors_associated').whereIn('release_id', releases.map(release => release.id)), ]); - const newlyAssociatedActors = actorEntries - .concat(newActorEntries) - .filter(actorEntry => !associatedActors.some(actor => actorEntry.id === actor.id)) - .map(actor => ({ - release_id: releaseId, - actor_id: actor.id, - })); + const associations = await Promise.map(Object.entries(mappedActors), async ([actorName, releaseIds]) => { + const actorEntry = existingActorEntries.find(actor => actor.name === actorName) + || await storeActor({ name: actorName }); - await knex('actors_associated') - .insert(newlyAssociatedActors); + return releaseIds + .map(releaseId => ({ + release_id: releaseId, + actor_id: actorEntry.id, + })) + .filter(association => !existingAssociationEntries + // remove associations already in database + .some(associationEntry => associationEntry.actor_id === association.actor_id + && associationEntry.release_id === association.release_id)); + }); + + await Promise.all([ + knex('actors_associated').insert(associations.flat()), + scrapeBasicActors(), + ]); } module.exports = { diff --git a/src/media.js b/src/media.js index b10aa65a..becf7540 100644 --- a/src/media.js +++ b/src/media.js @@ -29,7 +29,7 @@ async function getThumbnail(buffer) { } async function createReleaseMediaDirectory(release, releaseId) { - if (release.poster || (release.photos && release.photos.length)) { + if (release.poster || (release.photos && release.photos.length) || release.trailer) { await fs.mkdir( path.join(config.media.path, 'releases', release.site.network.slug, release.site.slug, releaseId.toString()), { recursive: true }, @@ -133,7 +133,7 @@ async function storePhotos(release, releaseId) { return null; } }, { - concurrency: 2, + concurrency: 10, }); await knex('media') @@ -225,7 +225,7 @@ async function storeAvatars(profile, actor) { return null; } }, { - concurrency: 2, + concurrency: 10, }); const avatars = files.filter(file => file); diff --git a/src/releases.js b/src/releases.js index 09253705..70ba3055 100644 --- a/src/releases.js +++ b/src/releases.js @@ -197,7 +197,6 @@ async function storeReleaseAssets(release, releaseId) { await createReleaseMediaDirectory(release, releaseId); await Promise.all([ - associateActors(release, releaseId), associateTags(release, releaseId), storePhotos(release, releaseId), storePoster(release, releaseId), @@ -222,36 +221,59 @@ async function storeRelease(release) { }) .returning('*'); - await storeReleaseAssets(release, existingRelease.id); + // await storeReleaseAssets(release, existingRelease.id); console.log(`Updated release "${release.title}" (${existingRelease.id}, ${release.site.name})`); - return updatedRelease || existingRelease; + return updatedRelease ? updatedRelease.id : existingRelease.id; } const [releaseEntry] = await knex('releases') .insert(curatedRelease) .returning('*'); - await storeReleaseAssets(release, releaseEntry.id); + // await storeReleaseAssets(release, releaseEntry.id); console.log(`Stored release "${release.title}" (${releaseEntry.id}, ${release.site.name})`); return releaseEntry.id; } async function storeReleases(releases) { - return Promise.map(releases, async (release) => { + const storedReleases = await Promise.map(releases, async (release) => { try { const releaseId = await storeRelease(release); - return releaseId; + return { + id: releaseId, + ...release, + }; } catch (error) { console.error(error); return null; } }, { - concurrency: 2, + concurrency: 10, }); + + const actors = storedReleases.reduce((acc, release) => { + release.actors.forEach((actor) => { + const trimmedActor = actor.trim(); + + if (acc[trimmedActor]) { + acc[trimmedActor] = acc[trimmedActor].concat(release.id); + return; + } + + acc[trimmedActor] = [release.id]; + }); + + return acc; + }, {}); + + await Promise.all([ + associateActors(actors, storedReleases), + Promise.all(storedReleases.map(async release => storeReleaseAssets(release, release.id))), + ]); } module.exports = { diff --git a/src/scrape-release.js b/src/scrape-release.js index 2b4de7ae..e33e471c 100644 --- a/src/scrape-release.js +++ b/src/scrape-release.js @@ -7,7 +7,6 @@ const scrapers = require('./scrapers/scrapers'); const { storeReleases } = require('./releases'); const { findSiteByUrl } = require('./sites'); const { findNetworkByUrl } = require('./networks'); -const { scrapeBasicActors } = require('./actors'); async function findSite(url, release) { const site = (release && release.site) || await findSiteByUrl(url); @@ -50,7 +49,6 @@ async function scrapeRelease(url, release, deep = false) { if (!deep && argv.save) { // don't store release when called by site scraper const [releaseId] = await storeReleases([scene]); - await scrapeBasicActors(); console.log(`http://${config.web.host}:${config.web.port}/scene/${releaseId}`); } diff --git a/src/scrape-sites.js b/src/scrape-sites.js index 67cc9dbc..4aaeaf32 100644 --- a/src/scrape-sites.js +++ b/src/scrape-sites.js @@ -9,7 +9,6 @@ const { fetchIncludedSites } = require('./sites'); const scrapers = require('./scrapers/scrapers'); const scrapeRelease = require('./scrape-release'); const { storeReleases } = require('./releases'); -const { scrapeBasicActors } = require('./actors'); function getAfterDate() { return moment @@ -103,40 +102,39 @@ async function scrapeSiteReleases(scraper, site) { } async function scrapeReleases() { - const sites = await fetchIncludedSites(); + const networks = await fetchIncludedSites(); - console.log(`Found ${sites.length} sites in database`); - - await Promise.map(sites, async (site) => { + const scrapedReleases = await Promise.map(networks, async network => Promise.map(network.sites, async (site) => { const scraper = scrapers.releases[site.slug] || scrapers.releases[site.network.slug]; if (!scraper) { console.warn(`No scraper found for '${site.name}' (${site.slug})`); - return; + return []; } try { - const siteReleases = await scrapeSiteReleases(scraper, site); - const siteActors = siteReleases.reduce((acc, release) => [...acc, ...release.actors], []); - - console.log(siteActors); - - if (argv.save) { - await storeReleases(siteReleases); - } + return await scrapeSiteReleases(scraper, site); } catch (error) { if (argv.debug) { console.error(`${site.id}: Failed to scrape releases`, error); - return; } console.warn(`${site.id}: Failed to scrape releases`); + + return []; } }, { + // 2 network sites at a time concurrency: 2, + }), + { + // 5 networks at a time + concurrency: 5, }); - await scrapeBasicActors(); + if (argv.save) { + await storeReleases(scrapedReleases.flat(2)); + } } module.exports = scrapeReleases; diff --git a/src/sites.js b/src/sites.js index b2c9218d..03884fdc 100644 --- a/src/sites.js +++ b/src/sites.js @@ -72,6 +72,25 @@ async function findSiteByUrl(url) { return null; } +function sitesByNetwork(sites) { + const networks = sites.reduce((acc, site) => { + if (acc[site.network.slug]) { + acc[site.network.slug].sites = acc[site.network.slug].sites.concat(site); + + return acc; + } + + acc[site.network.slug] = { + ...site.network, + sites: [site], + }; + + return acc; + }, {}); + + return Object.values(networks); +} + async function fetchSitesFromArgv() { const rawSites = await knex('sites') .select('sites.*', 'networks.name as network_name', 'networks.slug as network_slug', 'networks.parameters as network_parameters') @@ -79,7 +98,10 @@ async function fetchSitesFromArgv() { .orWhereIn('networks.slug', argv.networks || []) .leftJoin('networks', 'sites.network_id', 'networks.id'); - return curateSites(rawSites, true); + const curatedSites = await curateSites(rawSites, true); + console.log(`Found ${curatedSites.length} sites in database`); + + return sitesByNetwork(curatedSites); } async function fetchSitesFromConfig() { @@ -94,7 +116,10 @@ async function fetchSitesFromConfig() { .orWhereIn('network_id', networkIds) .leftJoin('networks', 'sites.network_id', 'networks.id'); - return curateSites(rawSites, true); + const curatedSites = await curateSites(rawSites, true); + console.log(`Found ${curatedSites.length} sites in database`); + + return sitesByNetwork(curatedSites); } async function fetchIncludedSites() { diff --git a/src/tags.js b/src/tags.js index 3f6ad1c4..ce68ef93 100644 --- a/src/tags.js +++ b/src/tags.js @@ -4,13 +4,21 @@ const knex = require('./knex'); const whereOr = require('./utils/where-or'); async function curateTag(tag) { - const aliases = await knex('tags').where({ alias_for: tag.id }); + const [aliases, media] = await Promise.all([ + knex('tags').where({ alias_for: tag.id }), + knex('media') + .where('domain', 'tags') + .andWhere('target_id', tag.id) + .orderBy('index'), + ]); return { id: tag.id, name: tag.name, slug: tag.slug, description: tag.description, + poster: media.find(photo => photo.role === 'poster'), + photos: media.filter(photo => photo.role === 'photo'), group: { id: tag.group_id, name: tag.group_name, @@ -31,15 +39,20 @@ async function associateTags(release, releaseId) { return; } - await knex('tags_associated').insert(release.tags.map(tagId => ({ - tag_id: tagId, - release_id: releaseId, - }))); + try { + await knex('tags_associated').insert(release.tags.map(tagId => ({ + tag_id: tagId, + release_id: releaseId, + }))); + } catch (error) { + console.log(release, error); + } } -async function fetchTags(queryObject, limit = 100) { +async function fetchTags(queryObject, groupsQueryObject, limit = 100) { const tags = await knex('tags') .where(builder => whereOr(queryObject, 'tags', builder)) + .orWhere(builder => whereOr(groupsQueryObject, 'tags_groups', builder)) .andWhere({ 'tags.alias_for': null }) .select( 'tags.*', diff --git a/src/utils/escape-html.js b/src/utils/escape-html.js new file mode 100644 index 00000000..845f3864 --- /dev/null +++ b/src/utils/escape-html.js @@ -0,0 +1,10 @@ +function escapeHtml(text) { + return text + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); +} + +module.exports = escapeHtml; diff --git a/src/web/tags.js b/src/web/tags.js index 405ea7c0..315bc9ce 100644 --- a/src/web/tags.js +++ b/src/web/tags.js @@ -10,7 +10,7 @@ async function fetchTagsApi(req, res) { const tags = await fetchTags({ id: tagId, slug: tagSlug, - }, req.query.limit); + }, null, req.query.limit); if (tags.length > 0) { res.send(tags[0]); @@ -21,9 +21,16 @@ async function fetchTagsApi(req, res) { return; } - const tags = await fetchTags({ - priority: req.query.priority.split(','), - }, req.query.limit); + const query = {}; + const groupsQuery = {}; + + if (req.query.priority) query.priority = req.query.priority.split(','); + if (req.query.slug) query.slug = req.query.slug.split(','); + if (req.query.group) { + groupsQuery.slug = req.query.group.split(','); + } + + const tags = await fetchTags(query, groupsQuery, req.query.limit); res.send(tags); }