Compare commits

...

5 Commits

Author SHA1 Message Date
DebaucheryLibrarian ce78e07444 1.137.0 2020-10-19 02:02:49 +02:00
DebaucheryLibrarian 593ce27312 Added rudimentary scene and entity scene remove. 2020-10-19 02:02:21 +02:00
DebaucheryLibrarian 2536405dba Fixed entity API database query. 2020-10-18 00:01:34 +02:00
DebaucheryLibrarian ca22aedaaa Added rudimentary API documentation to README. 2020-10-17 22:54:00 +02:00
DebaucheryLibrarian e6c52002f0 Added tags and entities to REST API.. 2020-10-16 23:00:03 +02:00
19 changed files with 617 additions and 269 deletions

View File

@ -87,6 +87,22 @@ To generate thumbnails for new logos and tag photos, install ImageMagick and run
* `--no-save`: Do not store retrieved information in local database, forcing re-fetch. * `--no-save`: Do not store retrieved information in local database, forcing re-fetch.
* `--level`: Change log level to `silly`, `verbose`, `info`, `warn` or `error`. * `--level`: Change log level to `silly`, `verbose`, `info`, `warn` or `error`.
### API
A GraphQL API is available at `/graphql`, and a REST API is available at the following `GET` endpoints:
* `/api/scenes`: Fetch the latest releases. Supports search with `query` or `q` parameter;
* `/api/scenes/{ID}`: Fetch scene by ID.
* `/api/actors`: Fetch actors. Search `query` or `q` parameter required.
* `/api/actors/{ID|slug}`: Fetch detailed actor by ID or slug.
* `/api/entities`: Fetch networks and channels. Use the `type` parameter to filter for either `channel`s or `network`s.
* `/api/entities/{ID|slug}`: Fetch detailed network or channel by ID. To fetch by slug, the `type` parameter must specify either `channel` or `network`.
* `/api/channels`: Fetch channel entities. Supports the `q` or `query` parameter for searching.
* `/api/channels/{ID|slug}`: Fetch detailed channel by ID or slug.
* `/api/networks`: Fetch networks. Supports a `q` or `query` parameter for searching.
* `/api/networks/{ID|slug}`: Fetch detailed network by ID or slug.
* `/api/tags`: Fetch tags.
* `/api/tags/{ID|slug|name}`: Fetch detailed tag by ID, slug or name.
## Supported networks & sites ## Supported networks & sites
896 channels on 64 networks, continuously expanding! 896 channels on 64 networks, continuously expanding!

View File

@ -84,6 +84,8 @@ async function mounted() {
'fingering', 'fingering',
'anal-fingering', 'anal-fingering',
'titty-fucking', 'titty-fucking',
'fisting',
'anal-fisting',
], ],
cumshot: [ cumshot: [
'facial', 'facial',

View File

@ -658,7 +658,8 @@ exports.up = knex => Promise.resolve()
table.integer('release_id', 16) table.integer('release_id', 16)
.notNullable() .notNullable()
.references('id') .references('id')
.inTable('releases'); .inTable('releases')
.onDelete('cascade');
table.integer('actor_id', 12) table.integer('actor_id', 12)
.notNullable() .notNullable()
@ -674,7 +675,8 @@ exports.up = knex => Promise.resolve()
table.integer('release_id', 16) table.integer('release_id', 16)
.notNullable() .notNullable()
.references('id') .references('id')
.inTable('releases'); .inTable('releases')
.onDelete('cascade');
table.integer('director_id', 8) table.integer('director_id', 8)
.notNullable() .notNullable()
@ -687,7 +689,8 @@ exports.up = knex => Promise.resolve()
table.integer('release_id', 16) table.integer('release_id', 16)
.notNullable() .notNullable()
.references('id') .references('id')
.inTable('releases'); .inTable('releases')
.onDelete('cascade');
table.text('media_id', 21) table.text('media_id', 21)
.notNullable() .notNullable()
@ -700,7 +703,8 @@ exports.up = knex => Promise.resolve()
table.integer('release_id', 16) table.integer('release_id', 16)
.notNullable() .notNullable()
.references('id') .references('id')
.inTable('releases'); .inTable('releases')
.onDelete('cascade');
table.text('media_id', 21) table.text('media_id', 21)
.notNullable() .notNullable()
@ -713,7 +717,8 @@ exports.up = knex => Promise.resolve()
table.integer('release_id', 16) table.integer('release_id', 16)
.notNullable() .notNullable()
.references('id') .references('id')
.inTable('releases'); .inTable('releases')
.onDelete('cascade');
table.text('media_id', 21) table.text('media_id', 21)
.notNullable() .notNullable()
@ -726,7 +731,8 @@ exports.up = knex => Promise.resolve()
table.integer('release_id', 16) table.integer('release_id', 16)
.notNullable() .notNullable()
.references('id') .references('id')
.inTable('releases'); .inTable('releases')
.onDelete('cascade');
table.text('media_id', 21) table.text('media_id', 21)
.notNullable() .notNullable()
@ -739,7 +745,8 @@ exports.up = knex => Promise.resolve()
table.integer('release_id', 16) table.integer('release_id', 16)
.notNullable() .notNullable()
.references('id') .references('id')
.inTable('releases'); .inTable('releases')
.onDelete('cascade');
table.text('media_id', 21) table.text('media_id', 21)
.notNullable() .notNullable()
@ -757,14 +764,16 @@ exports.up = knex => Promise.resolve()
table.integer('release_id', 16) table.integer('release_id', 16)
.notNullable() .notNullable()
.references('id') .references('id')
.inTable('releases'); .inTable('releases')
.onDelete('cascade');
table.unique(['tag_id', 'release_id']); table.unique(['tag_id', 'release_id']);
})) }))
.then(() => knex.schema.createTable('releases_search', (table) => { .then(() => knex.schema.createTable('releases_search', (table) => {
table.integer('release_id', 16) table.integer('release_id', 16)
.references('id') .references('id')
.inTable('releases'); .inTable('releases')
.onDelete('cascade');
})) }))
.then(() => knex.schema.createTable('movies', (table) => { .then(() => knex.schema.createTable('movies', (table) => {
table.increments('id', 16); table.increments('id', 16);

346
package-lock.json generated
View File

@ -1,6 +1,6 @@
{ {
"name": "traxxx", "name": "traxxx",
"version": "1.136.0", "version": "1.137.0",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
@ -1563,10 +1563,12 @@
"dev": true "dev": true
}, },
"ansi-escapes": { "ansi-escapes": {
"version": "3.2.0", "version": "4.3.1",
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz",
"integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", "integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==",
"dev": true "requires": {
"type-fest": "^0.11.0"
}
}, },
"ansi-regex": { "ansi-regex": {
"version": "2.1.1", "version": "2.1.1",
@ -2501,8 +2503,7 @@
"chardet": { "chardet": {
"version": "0.7.0", "version": "0.7.0",
"resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz",
"integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA=="
"dev": true
}, },
"cheerio": { "cheerio": {
"version": "1.0.0-rc.3", "version": "1.0.0-rc.3",
@ -2588,19 +2589,17 @@
"integrity": "sha512-AF9fOBZPflNFm2wtq5BNqLr2ZmOB0nwgYQn0tgfbk5OYDMmLFbP+FrelmGK5apvp67ybbpUVTBZZffgR2yv+CQ==" "integrity": "sha512-AF9fOBZPflNFm2wtq5BNqLr2ZmOB0nwgYQn0tgfbk5OYDMmLFbP+FrelmGK5apvp67ybbpUVTBZZffgR2yv+CQ=="
}, },
"cli-cursor": { "cli-cursor": {
"version": "2.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz",
"integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==",
"dev": true,
"requires": { "requires": {
"restore-cursor": "^2.0.0" "restore-cursor": "^3.1.0"
} }
}, },
"cli-width": { "cli-width": {
"version": "2.2.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz",
"integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw=="
"dev": true
}, },
"cliui": { "cliui": {
"version": "5.0.0", "version": "5.0.0",
@ -3706,12 +3705,33 @@
"text-table": "^0.2.0" "text-table": "^0.2.0"
}, },
"dependencies": { "dependencies": {
"ansi-escapes": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz",
"integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==",
"dev": true
},
"ansi-regex": { "ansi-regex": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
"integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
"dev": true "dev": true
}, },
"cli-cursor": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz",
"integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=",
"dev": true,
"requires": {
"restore-cursor": "^2.0.0"
}
},
"cli-width": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz",
"integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==",
"dev": true
},
"debug": { "debug": {
"version": "4.1.1", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
@ -3721,6 +3741,100 @@
"ms": "^2.1.1" "ms": "^2.1.1"
} }
}, },
"figures": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz",
"integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=",
"dev": true,
"requires": {
"escape-string-regexp": "^1.0.5"
}
},
"inquirer": {
"version": "6.5.2",
"resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.5.2.tgz",
"integrity": "sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ==",
"dev": true,
"requires": {
"ansi-escapes": "^3.2.0",
"chalk": "^2.4.2",
"cli-cursor": "^2.1.0",
"cli-width": "^2.0.0",
"external-editor": "^3.0.3",
"figures": "^2.0.0",
"lodash": "^4.17.12",
"mute-stream": "0.0.7",
"run-async": "^2.2.0",
"rxjs": "^6.4.0",
"string-width": "^2.1.0",
"strip-ansi": "^5.1.0",
"through": "^2.3.6"
},
"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==",
"dev": true
},
"strip-ansi": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
"integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
"dev": true,
"requires": {
"ansi-regex": "^4.1.0"
}
}
}
},
"is-fullwidth-code-point": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
"integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
"dev": true
},
"mimic-fn": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz",
"integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==",
"dev": true
},
"mute-stream": {
"version": "0.0.7",
"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz",
"integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=",
"dev": true
},
"onetime": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz",
"integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=",
"dev": true,
"requires": {
"mimic-fn": "^1.0.0"
}
},
"restore-cursor": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz",
"integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=",
"dev": true,
"requires": {
"onetime": "^2.0.0",
"signal-exit": "^3.0.2"
}
},
"string-width": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
"integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
"dev": true,
"requires": {
"is-fullwidth-code-point": "^2.0.0",
"strip-ansi": "^4.0.0"
}
},
"strip-ansi": { "strip-ansi": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
@ -4308,7 +4422,6 @@
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz",
"integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==",
"dev": true,
"requires": { "requires": {
"chardet": "^0.7.0", "chardet": "^0.7.0",
"iconv-lite": "^0.4.24", "iconv-lite": "^0.4.24",
@ -4319,7 +4432,6 @@
"version": "0.4.24", "version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
"dev": true,
"requires": { "requires": {
"safer-buffer": ">= 2.1.2 < 3" "safer-buffer": ">= 2.1.2 < 3"
} }
@ -4447,10 +4559,9 @@
"dev": true "dev": true
}, },
"figures": { "figures": {
"version": "2.0.0", "version": "3.2.0",
"resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz",
"integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==",
"dev": true,
"requires": { "requires": {
"escape-string-regexp": "^1.0.5" "escape-string-regexp": "^1.0.5"
} }
@ -5993,74 +6104,104 @@
"integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw=="
}, },
"inquirer": { "inquirer": {
"version": "6.5.2", "version": "7.3.3",
"resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.5.2.tgz", "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz",
"integrity": "sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ==", "integrity": "sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==",
"dev": true,
"requires": { "requires": {
"ansi-escapes": "^3.2.0", "ansi-escapes": "^4.2.1",
"chalk": "^2.4.2", "chalk": "^4.1.0",
"cli-cursor": "^2.1.0", "cli-cursor": "^3.1.0",
"cli-width": "^2.0.0", "cli-width": "^3.0.0",
"external-editor": "^3.0.3", "external-editor": "^3.0.3",
"figures": "^2.0.0", "figures": "^3.0.0",
"lodash": "^4.17.12", "lodash": "^4.17.19",
"mute-stream": "0.0.7", "mute-stream": "0.0.8",
"run-async": "^2.2.0", "run-async": "^2.4.0",
"rxjs": "^6.4.0", "rxjs": "^6.6.0",
"string-width": "^2.1.0", "string-width": "^4.1.0",
"strip-ansi": "^5.1.0", "strip-ansi": "^6.0.0",
"through": "^2.3.6" "through": "^2.3.6"
}, },
"dependencies": { "dependencies": {
"ansi-regex": { "ansi-regex": {
"version": "3.0.0", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
"integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg=="
"dev": true },
"ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"requires": {
"color-convert": "^2.0.1"
}
},
"chalk": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
"requires": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
}
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"requires": {
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
},
"emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
},
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
}, },
"is-fullwidth-code-point": { "is-fullwidth-code-point": {
"version": "2.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="
"dev": true },
"lodash": {
"version": "4.17.20",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
"integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA=="
}, },
"string-width": { "string-width": {
"version": "2.1.1", "version": "4.2.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz",
"integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==",
"dev": true,
"requires": { "requires": {
"is-fullwidth-code-point": "^2.0.0", "emoji-regex": "^8.0.0",
"strip-ansi": "^4.0.0" "is-fullwidth-code-point": "^3.0.0",
}, "strip-ansi": "^6.0.0"
"dependencies": {
"strip-ansi": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
"integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
"dev": true,
"requires": {
"ansi-regex": "^3.0.0"
}
}
} }
}, },
"strip-ansi": { "strip-ansi": {
"version": "5.2.0", "version": "6.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
"integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
"dev": true,
"requires": { "requires": {
"ansi-regex": "^4.1.0" "ansi-regex": "^5.0.0"
}, }
"dependencies": { },
"ansi-regex": { "supports-color": {
"version": "4.1.0", "version": "7.2.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true "requires": {
} "has-flag": "^4.0.0"
} }
} }
} }
@ -7224,10 +7365,9 @@
} }
}, },
"mimic-fn": { "mimic-fn": {
"version": "1.2.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
"integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="
"dev": true
}, },
"mimic-response": { "mimic-response": {
"version": "2.1.0", "version": "2.1.0",
@ -7381,10 +7521,9 @@
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
}, },
"mute-stream": { "mute-stream": {
"version": "0.0.7", "version": "0.0.8",
"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz",
"integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA=="
"dev": true
}, },
"nan": { "nan": {
"version": "2.14.0", "version": "2.14.0",
@ -7937,12 +8076,11 @@
"integrity": "sha1-+M33eISCb+Tf+T46nMN7HkSAdC4=" "integrity": "sha1-+M33eISCb+Tf+T46nMN7HkSAdC4="
}, },
"onetime": { "onetime": {
"version": "2.0.1", "version": "5.1.2",
"resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
"integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
"dev": true,
"requires": { "requires": {
"mimic-fn": "^1.0.0" "mimic-fn": "^2.1.0"
} }
}, },
"opn": { "opn": {
@ -9325,12 +9463,11 @@
"integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=" "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo="
}, },
"restore-cursor": { "restore-cursor": {
"version": "2.0.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz",
"integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==",
"dev": true,
"requires": { "requires": {
"onetime": "^2.0.0", "onetime": "^5.1.0",
"signal-exit": "^3.0.2" "signal-exit": "^3.0.2"
} }
}, },
@ -9358,13 +9495,9 @@
} }
}, },
"run-async": { "run-async": {
"version": "2.3.0", "version": "2.4.1",
"resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz",
"integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ=="
"dev": true,
"requires": {
"is-promise": "^2.1.0"
}
}, },
"run-queue": { "run-queue": {
"version": "1.0.3", "version": "1.0.3",
@ -9376,10 +9509,9 @@
} }
}, },
"rxjs": { "rxjs": {
"version": "6.5.4", "version": "6.6.3",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.4.tgz", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.3.tgz",
"integrity": "sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q==", "integrity": "sha512-trsQc+xYYXZ3urjOiJOuCOa5N3jAZ3eiSpQB5hIT8zGlL2QfnHLJ2r7GMkBGuIausdJN1OneaI6gQlsqNHHmZQ==",
"dev": true,
"requires": { "requires": {
"tslib": "^1.9.0" "tslib": "^1.9.0"
} }
@ -10896,7 +11028,6 @@
"version": "0.0.33", "version": "0.0.33",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
"integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
"dev": true,
"requires": { "requires": {
"os-tmpdir": "~1.0.2" "os-tmpdir": "~1.0.2"
} }
@ -11186,6 +11317,11 @@
"prelude-ls": "~1.1.2" "prelude-ls": "~1.1.2"
} }
}, },
"type-fest": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz",
"integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ=="
},
"type-is": { "type-is": {
"version": "1.6.18", "version": "1.6.18",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",

View File

@ -1,6 +1,6 @@
{ {
"name": "traxxx", "name": "traxxx",
"version": "1.136.0", "version": "1.137.0",
"description": "All the latest porn releases in one place", "description": "All the latest porn releases in one place",
"main": "src/app.js", "main": "src/app.js",
"scripts": { "scripts": {
@ -95,6 +95,7 @@
"fs-extra": "^7.0.1", "fs-extra": "^7.0.1",
"graphile-utils": "^4.5.6", "graphile-utils": "^4.5.6",
"iconv-lite": "^0.5.1", "iconv-lite": "^0.5.1",
"inquirer": "^7.3.3",
"jsdom": "^16.3.0", "jsdom": "^16.3.0",
"knex": "^0.21.5", "knex": "^0.21.5",
"knex-migrate": "^1.7.4", "knex-migrate": "^1.7.4",

Binary file not shown.

After

Width:  |  Height:  |  Size: 564 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

View File

@ -590,6 +590,7 @@ const tagPosters = [
['airtight', 7, 'Lana Rhoades in "Gangbang Me 3" for HardX'], ['airtight', 7, 'Lana Rhoades in "Gangbang Me 3" for HardX'],
['anal', 0, 'Adriana Chechik in "Manuel Creampies Their Asses 3" for Jules Jordan'], ['anal', 0, 'Adriana Chechik in "Manuel Creampies Their Asses 3" for Jules Jordan'],
['anal-creampie', 2, 'Lana Rhoades in "Lana\'s Anal Workout" for HardX'], ['anal-creampie', 2, 'Lana Rhoades in "Lana\'s Anal Workout" for HardX'],
['anal-fisting', 1, 'Jane Wilde fisting Alexis Tae in "Jane Wilde is AGAPE" for Evil Angel'],
['ass-eating', 1, 'Aidra Fox and Cassidy Klein in "Lesbian Anal Yoga" for LesbianX'], ['ass-eating', 1, 'Aidra Fox and Cassidy Klein in "Lesbian Anal Yoga" for LesbianX'],
['asian', 0, 'Vina Sky for Erotica X'], ['asian', 0, 'Vina Sky for Erotica X'],
['atm', 2, 'Jureka Del Mar in "Stretched Out" for Her Limit'], ['atm', 2, 'Jureka Del Mar in "Stretched Out" for Her Limit'],

View File

@ -10,6 +10,8 @@ const fetchUpdates = require('./updates');
const { fetchScenes, fetchMovies } = require('./deep'); const { fetchScenes, fetchMovies } = require('./deep');
const { storeScenes, storeMovies, updateReleasesSearch } = require('./store-releases'); const { storeScenes, storeMovies, updateReleasesSearch } = require('./store-releases');
const { scrapeActors } = require('./actors'); const { scrapeActors } = require('./actors');
const { flushEntities } = require('./entities');
const { deleteScenes } = require('./releases');
const getFileEntries = require('./utils/file-entries'); const getFileEntries = require('./utils/file-entries');
async function init() { async function init() {
@ -22,6 +24,14 @@ async function init() {
await updateReleasesSearch(); await updateReleasesSearch();
} }
if (argv.flushNetworks || argv.flushChannels) {
await flushEntities(argv.flushNetworks, argv.flushChannels);
}
if (argv.delete) {
await deleteScenes(argv.delete);
}
const actorsFromFile = argv.actorsFile && await getFileEntries(argv.actorsFile); const actorsFromFile = argv.actorsFile && await getFileEntries(argv.actorsFile);
const actorNames = (argv.actors || []).concat(actorsFromFile || []); const actorNames = (argv.actors || []).concat(actorsFromFile || []);

View File

@ -238,6 +238,21 @@ const { argv } = yargs
type: 'boolean', type: 'boolean',
default: false, default: false,
}) })
.option('flush-channels', {
describe: 'Delete all scenes and movies from channels.',
type: 'array',
alias: 'flush-channel',
})
.option('flush-networks', {
describe: 'Delete all scenes and movies from network.',
type: 'array',
alias: 'flush-network',
})
.option('delete', {
describe: 'Remove scenes by ID.',
type: 'array',
alias: 'remove',
})
.coerce('after', interpretAfter) .coerce('after', interpretAfter)
.coerce('actors-update', interpretAfter); .coerce('actors-update', interpretAfter);

View File

@ -1,10 +1,12 @@
'use strict'; 'use strict';
const config = require('config'); const config = require('config');
const inquirer = require('inquirer');
const logger = require('./logger')(__filename);
const argv = require('./argv'); const argv = require('./argv');
const knex = require('./knex'); const knex = require('./knex');
const whereOr = require('./utils/where-or'); const { deleteScenes } = require('./releases');
function curateEntity(entity, includeParameters = false) { function curateEntity(entity, includeParameters = false) {
if (!entity) { if (!entity) {
@ -18,7 +20,6 @@ function curateEntity(entity, includeParameters = false) {
description: entity.description, description: entity.description,
slug: entity.slug, slug: entity.slug,
type: entity.type, type: entity.type,
parameters: includeParameters ? entity.parameters : null,
parent: curateEntity(entity.parent, includeParameters), parent: curateEntity(entity.parent, includeParameters),
} : {}; } : {};
@ -29,6 +30,19 @@ function curateEntity(entity, includeParameters = false) {
}, includeParameters)); }, includeParameters));
} }
if (entity.tags) {
curatedEntity.tags = entity.tags.map(tag => ({
id: tag.id,
name: tag.name,
slug: tag.slug,
priority: tag.priority,
}));
}
if (includeParameters) {
curatedEntity.parameters = entity.parameters;
}
return curatedEntity; return curatedEntity;
} }
@ -102,33 +116,139 @@ async function fetchIncludedEntities() {
return curatedNetworks; return curatedNetworks;
} }
async function fetchChannels(queryObject) { async function fetchEntity(entityId, type) {
const sites = await knex('sites') const entity = await knex('entities')
.where(builder => whereOr(queryObject, 'sites', builder)) .select(knex.raw(`
.select( entities.*,
'sites.*', COALESCE(json_agg(children) FILTER (WHERE children.id IS NOT NULL), '[]') as children,
'networks.name as network_name', 'networks.slug as network_slug', 'networks.url as network_url', 'networks.description as network_description', 'networks.parameters as network_parameters', COALESCE(json_agg(tags) FILTER (WHERE tags.id IS NOT NULL), '[]') as tags,
) row_to_json(parents) as parent
.leftJoin('networks', 'sites.network_id', 'networks.id') `))
.limit(100); .modify((queryBuilder) => {
if (Number(entityId)) {
queryBuilder.where('entities.id', entityId);
return;
}
return curateEntities(sites); if (type) {
queryBuilder
.where('entities.slug', entityId)
.where('entities.type', type);
return;
}
throw new Error('Invalid ID or unspecified entity type');
})
.leftJoin('entities as parents', 'parents.id', 'entities.parent_id')
.leftJoin('entities as children', 'children.parent_id', 'entities.id')
.leftJoin('entities_tags', 'entities_tags.entity_id', 'entities.id')
.leftJoin('tags', 'tags.id', 'entities_tags.tag_id')
.groupBy('entities.id', 'parents.id')
.first();
return curateEntity(entity);
} }
async function fetchChannelsFromReleases() { async function fetchEntities(type, limit) {
const sites = await knex('releases') const entities = await knex('entities')
.select('site_id', '') .select(knex.raw(`
.leftJoin('sites', 'sites.id', 'releases.site_id') entities.*,
.groupBy('sites.id') COALESCE(json_agg(tags) FILTER (WHERE tags.id IS NOT NULL), '[]') as tags,
.limit(100); row_to_json(parents) as parent
`))
.modify((queryBuilder) => {
if (type) {
queryBuilder.where('entities.type', type);
}
})
.leftJoin('entities as parents', 'parents.id', 'entities.parent_id')
.leftJoin('entities_tags', 'entities_tags.entity_id', 'entities.id')
.leftJoin('tags', 'tags.id', 'entities_tags.tag_id')
.groupBy('entities.id', 'parents.id')
.limit(limit || 100);
return curateEntities(sites); return curateEntities(entities);
}
async function searchEntities(query, type, limit) {
const entities = knex
.select(knex.raw(`
entities.id, entities.name, entities.slug, entities.type, entities.url, entities.description,
COALESCE(json_agg(tags) FILTER (WHERE tags.id IS NOT NULL), '[]') as tags,
row_to_json(parents) as parent
`))
.from(knex.raw('search_entities(?) as entities', [query]))
.modify((queryBuilder) => {
if (type) {
queryBuilder.where('entities.type', type);
}
})
.leftJoin('entities as parents', 'parents.id', 'entities.parent_id')
.leftJoin('entities_tags', 'entities_tags.entity_id', 'entities.id')
.leftJoin('tags', 'tags.id', 'entities_tags.tag_id')
.groupBy('entities.id', 'entities.name', 'entities.slug', 'entities.type', 'entities.url', 'entities.description', 'parents.id')
.limit(limit || 100);
return curateEntities(await entities);
}
async function flushEntities(networkSlugs = [], channelSlugs = []) {
const entitySlugs = networkSlugs.concat(channelSlugs).join(', ');
const entityQuery = knex
.withRecursive('selected_entities', knex.raw(`
SELECT entities.*
FROM entities
WHERE
entities.slug = ANY(:networkSlugs)
AND entities.type = 'network'
OR (entities.slug = ANY(:channelSlugs)
AND entities.type = 'channel')
UNION ALL
SELECT entities.*
FROM entities
INNER JOIN selected_entities ON selected_entities.id = entities.parent_id
`, {
networkSlugs,
channelSlugs,
}));
const sceneIds = await entityQuery
.clone()
.select('releases.id')
.distinct('releases.id')
.whereNotNull('releases.id')
.from('selected_entities')
.leftJoin('releases', 'releases.entity_id', 'selected_entities.id')
.pluck('releases.id');
if (sceneIds.length === 0) {
logger.info(`No scenes or movies found to remove for ${entitySlugs}`);
return;
}
const confirmed = await inquirer.prompt([{
type: 'confirm',
name: 'flushEntities',
message: `You are about to remove ${sceneIds.length} scenes for ${entitySlugs}. Are you sure?`,
default: false,
}]);
if (!confirmed.flushEntities) {
logger.warn(`Confirmation rejected, not flushing scenes for: ${entitySlugs}`);
return;
}
await deleteScenes(sceneIds);
} }
module.exports = { module.exports = {
curateEntity, curateEntity,
curateEntities, curateEntities,
fetchIncludedEntities, fetchIncludedEntities,
fetchChannels, fetchEntity,
fetchChannelsFromReleases, fetchEntities,
searchEntities,
flushEntities,
}; };

View File

@ -1,81 +0,0 @@
'use strict';
const knex = require('./knex');
const whereOr = require('./utils/where-or');
const { fetchSites } = require('./sites');
async function curateNetwork(network, includeParameters = false, includeSites = true, includeStudios = false) {
const curatedNetwork = {
id: network.id,
name: network.name,
url: network.url,
description: network.description,
slug: network.slug,
parameters: includeParameters ? network.parameters : null,
};
if (includeSites) {
curatedNetwork.sites = await fetchSites({ network_id: network.id });
}
if (includeStudios) {
const studios = await knex('studios').where({ network_id: network.id });
curatedNetwork.studios = studios.map(studio => ({
id: studio.id,
name: studio.name,
url: studio.url,
description: studio.description,
slug: studio.slug,
}));
}
return curatedNetwork;
}
function curateNetworks(releases) {
return Promise.all(releases.map(async release => curateNetwork(release)));
}
async function findNetworkByUrl(url) {
const { hostname } = new URL(url);
const domain = hostname.replace(/^www./, '');
const network = await knex('networks')
.where('networks.url', 'like', `%${domain}`)
.orWhere('networks.url', url)
.first();
if (network) {
return curateNetwork(network, true);
}
return null;
}
async function fetchNetworks(queryObject) {
const releases = await knex('networks')
.where(builder => whereOr(queryObject, 'networks', builder))
.limit(100);
return curateNetworks(releases);
}
async function fetchNetworksFromReleases() {
const releases = await knex('releases')
.select('site_id', '')
.leftJoin('sites', 'sites.id', 'releases.site_id')
.leftJoin('networks', 'networks.id', 'sites.network_id')
.groupBy('networks.id')
.limit(100);
return curateNetworks(releases);
}
module.exports = {
curateNetwork,
curateNetworks,
fetchNetworks,
fetchNetworksFromReleases,
findNetworkByUrl,
};

View File

@ -121,9 +121,18 @@ async function searchReleases(query, limit = 100) {
return releases.map(release => curateRelease(release)); return releases.map(release => curateRelease(release));
} }
async function deleteScenes(sceneIds) {
await knex('releases')
.whereIn('id', sceneIds)
.delete();
// TODO: wipe media without associations, clean disk
}
module.exports = { module.exports = {
curateRelease, curateRelease,
fetchRelease, fetchRelease,
fetchReleases, fetchReleases,
searchReleases, searchReleases,
deleteScenes,
}; };

View File

@ -4,6 +4,75 @@ const knex = require('./knex');
const slugify = require('./utils/slugify'); const slugify = require('./utils/slugify');
const bulkInsert = require('./utils/bulk-insert'); const bulkInsert = require('./utils/bulk-insert');
function curateTagMedia(media) {
if (!media) {
return null;
}
return {
id: media.id,
path: media.path,
thumbnail: media.thumbnail,
lazy: media.lazy,
comment: media.comment,
credit: media.credit,
};
}
function curateTag(tag) {
if (!tag) {
return null;
}
const curatedTag = {
id: tag.id,
name: tag.name,
slug: tag.slug,
description: tag.description,
priority: tag.priority,
group: curateTag(tag.group),
aliasFor: curateTag(tag.alias),
aliases: (tag.aliases || []).map(aliasTag => curateTag(aliasTag)),
};
if (tag.poster) {
curatedTag.poster = curateTagMedia(tag.poster);
}
if (tag.photos) {
curatedTag.photos = tag.photos.map(photo => curateTagMedia(photo));
}
return curatedTag;
}
function withRelations(queryBuilder, withMedia) {
queryBuilder
.select(knex.raw(`
tags.*,
COALESCE(json_agg(DISTINCT aliases) FILTER (WHERE aliases.id IS NOT NULL), '[]') as aliases,
row_to_json(tags_groups) as group,
row_to_json(roots) as alias
`))
.leftJoin('tags_groups', 'tags_groups.id', 'tags.group_id')
.leftJoin('tags as roots', 'roots.id', 'tags.alias_for')
.leftJoin('tags as aliases', 'aliases.alias_for', 'tags.id')
.groupBy('tags.id', 'tags_groups.id', 'roots.id');
if (withMedia) {
queryBuilder
.select(knex.raw(`
row_to_json(posters) as poster,
COALESCE(json_agg(DISTINCT photos) FILTER (WHERE photos.id IS NOT NULL), '[]') as photos
`))
.leftJoin('tags_posters', 'tags_posters.tag_id', 'tags.id')
.leftJoin('tags_photos', 'tags_photos.tag_id', 'tags.id')
.leftJoin('media as posters', 'posters.id', 'tags_posters.media_id')
.leftJoin('media as photos', 'photos.id', 'tags_photos.media_id')
.groupBy('posters.id');
}
}
async function matchReleaseTags(releases) { async function matchReleaseTags(releases) {
const rawTags = releases const rawTags = releases
.map(release => release.tags).flat() .map(release => release.tags).flat()
@ -82,6 +151,34 @@ async function associateReleaseTags(releases, type = 'release') {
await bulkInsert(`${type}s_tags`, tagAssociations, false); await bulkInsert(`${type}s_tags`, tagAssociations, false);
} }
async function fetchTag(tagId) {
const tag = await knex('tags')
.modify(queryBuilder => withRelations(queryBuilder, true))
.where((builder) => {
if (Number(tagId)) {
builder.where('tags.id', tagId);
return;
}
builder
.where('tags.name', tagId)
.orWhere('tags.slug', tagId);
})
.first();
return curateTag(tag);
}
async function fetchTags(limit = 100) {
const tags = await knex('tags')
.modify(queryBuilder => withRelations(queryBuilder, false))
.limit(limit);
return tags.map(tag => curateTag(tag));
}
module.exports = { module.exports = {
associateReleaseTags, associateReleaseTags,
fetchTag,
fetchTags,
}; };

29
src/web/entities.js Normal file
View File

@ -0,0 +1,29 @@
'use strict';
const { fetchEntity, fetchEntities, searchEntities } = require('../entities');
async function fetchEntityApi(req, res, type) {
const entity = await fetchEntity(req.params.entityId, type || req.query.type);
if (entity) {
res.send({ entity });
return;
}
res.status(404).send({ entity: null });
}
async function fetchEntitiesApi(req, res, type) {
const query = req.query.query || req.query.q;
const entities = query
? await searchEntities(query, type || req.query.type, req.query.limit)
: await fetchEntities(type || req.query.type, req.query.limit);
res.send({ entities });
}
module.exports = {
fetchEntity: fetchEntityApi,
fetchEntities: fetchEntitiesApi,
};

View File

@ -1,26 +0,0 @@
'use strict';
const { fetchNetworks, fetchNetworksFromReleases } = require('../networks');
async function fetchNetworksApi(req, res) {
const networkId = typeof req.params.networkId === 'number' ? req.params.networkId : undefined; // null will literally include NULL results
const networkSlug = typeof req.params.networkId === 'string' ? req.params.networkId : undefined;
const networks = await fetchNetworks({
id: networkId,
slug: networkSlug,
});
res.send(networks);
}
async function fetchNetworksFromReleasesApi(req, res) {
const networks = await fetchNetworksFromReleases();
res.send(networks);
}
module.exports = {
fetchNetworks: fetchNetworksApi,
fetchNetworksFromReleases: fetchNetworksFromReleasesApi,
};

View File

@ -26,6 +26,16 @@ const {
fetchActors, fetchActors,
} = require('./actors'); } = require('./actors');
const {
fetchEntity,
fetchEntities,
} = require('./entities');
const {
fetchTag,
fetchTags,
} = require('./tags');
async function initServer() { async function initServer() {
const app = express(); const app = express();
const router = Router(); const router = Router();
@ -78,6 +88,21 @@ async function initServer() {
router.get('/api/actors', fetchActors); router.get('/api/actors', fetchActors);
router.get('/api/actors/:actorId', fetchActor); router.get('/api/actors/:actorId', fetchActor);
router.get('/api/entities', async (req, res) => fetchEntities(req, res, null));
router.get('/api/entities/:entityId', async (req, res) => fetchEntity(req, res, null));
router.get('/api/channels', async (req, res) => fetchEntities(req, res, 'channel'));
router.get('/api/channels/:entityId', async (req, res) => fetchEntity(req, res, 'channel'));
router.get('/api/networks', async (req, res) => fetchEntities(req, res, 'network'));
router.get('/api/networks/:entityId', async (req, res) => fetchEntity(req, res, 'network'));
router.get('/api/studios', async (req, res) => fetchEntities(req, res, 'studio'));
router.get('/api/studios/:entityId', async (req, res) => fetchEntity(req, res, 'studio'));
router.get('/api/tags', fetchTags);
router.get('/api/tags/:tagId', fetchTag);
router.get('*', (req, res) => { router.get('*', (req, res) => {
res.render(path.join(__dirname, '../../assets/index.ejs'), { res.render(path.join(__dirname, '../../assets/index.ejs'), {
env: JSON.stringify({ env: JSON.stringify({

View File

@ -1,40 +1,25 @@
'use strict'; 'use strict';
const { fetchTags } = require('../tags'); const { fetchTag, fetchTags } = require('../tags');
async function fetchTagsApi(req, res) { async function fetchTagApi(req, res) {
const tagId = typeof req.params.tagId === 'number' ? req.params.tagId : undefined; // null will literally include NULL results const tag = await fetchTag(req.params.tagId);
const tagSlug = typeof req.params.tagId === 'string' ? req.params.tagId : undefined;
if (tagId || tagSlug) { if (tag) {
const tags = await fetchTags({ res.send({ tag });
id: tagId,
slug: tagSlug,
}, null, req.query.limit);
if (tags.length > 0) {
res.send(tags[0]);
return;
}
res.status(404).send();
return; return;
} }
const query = {}; res.status(404).send({ tag: null });
const groupsQuery = {}; }
if (req.query.priority) query.priority = req.query.priority.split(','); async function fetchTagsApi(req, res) {
if (req.query.slug) query.slug = req.query.slug.split(','); const tags = await fetchTags(req.query.limit);
if (req.query.group) {
groupsQuery.slug = req.query.group.split(',');
}
const tags = await fetchTags(query, groupsQuery, req.query.limit); res.send({ tags });
res.send(tags);
} }
module.exports = { module.exports = {
fetchTag: fetchTagApi,
fetchTags: fetchTagsApi, fetchTags: fetchTagsApi,
}; };