2023-05-30 22:29:54 +00:00
'use strict' ;
const config = require ( 'config' ) ;
2023-06-03 00:51:42 +00:00
const fs = require ( 'fs' ) ;
const path = require ( 'path' ) ;
2023-05-30 22:29:54 +00:00
const moment = require ( 'moment' ) ;
const Promise = require ( 'bluebird' ) ;
2023-06-03 00:51:42 +00:00
const bhttp = require ( 'bhttp' ) ;
2023-05-30 22:29:54 +00:00
const { nanoid } = require ( 'nanoid/non-secure' ) ;
const AWS = require ( 'aws-sdk' ) ;
const { graphql } = require ( '../web/graphql' ) ;
const knex = require ( '../knex' ) ;
const args = require ( '../argv' ) ;
const endpoint = new AWS . Endpoint ( 's3.eu-central-1.wasabisys.com' ) ;
const s3 = new AWS . S3 ( {
// region: 'eu-central-1',
endpoint ,
credentials : {
accessKeyId : config . s3 . accessKey ,
secretAccessKey : config . s3 . secretKey ,
} ,
} ) ;
// NOT TRANSFERRED, unutilized on old server: production location, availabile qualities, actor alias for, actor entry id, chapter posters, chapter photos
2023-06-03 00:51:42 +00:00
const sceneFields = `
2023-05-30 22:29:54 +00:00
entryId
shootId
title
url
date
datePrecision
productionDate
description
duration
entity {
slug
type
}
studio {
slug
}
2023-06-03 00:51:42 +00:00
movies : moviesScenesBySceneId {
movie {
title
entryId
entity {
slug
type
}
}
}
2023-05-30 22:29:54 +00:00
actors : releasesActors {
actor {
name
slug
entryId
entity {
slug
type
}
}
}
directors : releasesDirectors {
director {
2023-06-03 00:51:42 +00:00
name
2023-05-30 22:29:54 +00:00
slug
entryId
entity {
slug
type
}
}
}
tags : releasesTags {
tag {
slug
}
}
chapters ( orderBy : TIME _ASC ) {
index
time
duration
title
description
tags : chaptersTags {
tag {
slug
}
}
}
poster : releasesPoster {
media {
hash
path
thumbnail
lazy
s3 : isS3
mime
index
width
height
size
source
sourcePage
}
}
photos : releasesPhotos {
media {
hash
path
thumbnail
lazy
s3 : isS3
mime
index
width
height
size
source
sourcePage
}
}
covers : releasesCovers {
media {
hash
path
thumbnail
lazy
s3 : isS3
mime
index
width
height
size
source
sourcePage
}
}
trailer : releasesTrailer {
media {
hash
path
thumbnail
lazy
s3 : isS3
mime
index
width
height
size
source
sourcePage
}
}
teaser : releasesTeaser {
media {
hash
path
thumbnail
lazy
s3 : isS3
mime
index
width
height
size
source
sourcePage
}
}
createdAt
` ;
2023-06-03 00:51:42 +00:00
const movieFields = `
entryId
title
url
date
datePrecision
entity {
slug
type
}
poster : moviesPoster {
media {
hash
path
thumbnail
lazy
s3 : isS3
mime
index
width
height
size
source
sourcePage
}
}
covers : moviesCovers {
media {
hash
path
thumbnail
lazy
s3 : isS3
mime
index
width
height
size
source
sourcePage
}
}
createdAt
` ;
2023-05-30 22:29:54 +00:00
async function save ( ) {
const limit = args . limit || 1000 ;
2023-06-03 22:23:45 +00:00
const offset = args . start || 0 ;
2023-05-30 22:29:54 +00:00
const { releases } = await graphql ( `
2023-06-03 00:51:42 +00:00
query SearchScenes (
2023-05-30 22:29:54 +00:00
$limit : Int = 20
$offset : Int = 0
) {
releases (
first : $limit
offset : $offset
orderBy : DATE _DESC
) {
2023-06-03 00:51:42 +00:00
$ { sceneFields }
}
}
` , {
limit ,
offset ,
} , 'owner' ) ;
const { movies } = await graphql ( `
query SearchScenes (
$limit : Int = 20
$offset : Int = 0
) {
movies (
first : $limit
offset : $offset
orderBy : DATE _DESC
) {
$ { movieFields }
2023-05-30 22:29:54 +00:00
}
}
` , {
limit ,
offset ,
2023-06-03 00:51:42 +00:00
} , 'owner' ) ;
2023-05-30 22:29:54 +00:00
2023-06-03 20:41:15 +00:00
const filename = ` export- ${ offset } - ${ offset + limit } - ${ moment ( ) . format ( 'YYYY-MM-DD_hh_mm_ss' ) } .json ` ;
2023-05-30 22:29:54 +00:00
2023-06-03 20:41:15 +00:00
let savedScenes = 0 ;
let savedMovies = 0 ;
2023-06-03 00:51:42 +00:00
2023-06-03 20:41:15 +00:00
await releases . reduce ( async ( chain , release ) => {
await chain ;
const entry = JSON . stringify ( {
... release ,
type : 'release' ,
2023-06-03 22:23:45 +00:00
description : release . description . replace ( /\n/g , ' ' ) ,
2023-06-03 20:41:15 +00:00
actors : release . actors . filter ( Boolean ) . map ( ( { actor } ) => actor ) ,
directors : release . directors . filter ( Boolean ) . map ( ( { director } ) => director ) ,
studio : release . studio ? . slug ,
tags : release . tags . map ( ( { tag } ) => tag ? . slug ) . filter ( Boolean ) ,
movies : release . movies ? . map ( ( { movie } ) => movie ) || [ ] ,
chapters : release . chapters . filter ( Boolean ) . map ( ( chapter ) => ( {
... chapter ,
tags : chapter . tags . map ( ( { tag } ) => tag ? . slug ) . filter ( Boolean ) ,
} ) ) ,
poster : release . poster ? . media ,
trailer : release . trailer ? . media ,
teaser : release . teaser ? . media ,
photos : release . photos . filter ( Boolean ) . map ( ( { media } ) => media ) ,
covers : release . covers . filter ( Boolean ) . map ( ( { media } ) => media ) ,
} ) ;
await fs . promises . appendFile ( filename , ` ${ entry } \n ` ) ;
2023-05-30 22:29:54 +00:00
2023-06-03 20:41:15 +00:00
savedScenes += 1 ;
} , Promise . resolve ( ) ) ;
await movies . reduce ( async ( chain , movie ) => {
await chain ;
const entry = JSON . stringify ( {
... movie ,
type : 'movie' ,
poster : movie . poster ? . media ,
covers : movie . covers . filter ( Boolean ) . map ( ( { media } ) => media ) ,
} ) ;
await fs . promises . appendFile ( filename , ` ${ entry } \n ` ) ;
savedMovies += 1 ;
} , Promise . resolve ( ) ) ;
2023-05-30 22:29:54 +00:00
2023-06-03 20:41:15 +00:00
console . log ( ` Saved ${ savedScenes } scenes and ${ savedMovies } movies to ${ filename } ` ) ;
2023-05-30 22:29:54 +00:00
process . exit ( ) ;
}
async function addReleaseTags ( release , context ) {
if ( release . tags . length === 0 ) {
return ;
}
await knex ( 'releases_tags' ) . insert ( release . tags . map ( ( tag ) => ( {
tag _id : context . tagIdsBySlug [ tag ] ,
release _id : release . id ,
original _tag : tag ,
} ) ) ) ;
}
async function addNewActor ( actor , entity , context ) {
const [ actorId ] = await knex ( 'actors' )
. insert ( {
name : actor . name ,
slug : actor . slug ,
entity _id : entity ? . id ,
batch _id : context . batchId ,
} )
. returning ( 'id' ) ;
return actorId ;
}
async function addReleaseActors ( release , context , target = 'actor' ) {
await release [ ` ${ target } s ` ] . reduce ( async ( chain , actor ) => {
await chain ;
const entity = actor . entity
? await knex ( 'entities' ) . where ( actor . entity ) . first ( )
: null ;
if ( actor . entity && ! entity ) {
throw new Error ( ` Actor ${ actor . slug } contains non-existent ${ release . entity . type } ' ${ release . entity . slug } ' ` ) ;
}
const existingActor = await knex ( 'actors' )
. where ( 'slug' , actor . slug )
. where ( ( builder ) => {
if ( entity ) {
builder . where ( 'entity_id' , entity . id ) ;
return ;
}
builder . whereNull ( 'entity_id' ) ;
} )
. first ( ) ;
const actorId = existingActor ? . id
|| await addNewActor ( actor , entity , context ) ;
await knex ( ` releases_ ${ target } s ` ) . insert ( {
release _id : release . id ,
[ ` ${ target } _id ` ] : actorId ,
} ) ;
} , Promise . resolve ( ) ) ;
}
async function addReleaseDirectors ( release , context ) {
return addReleaseActors ( release , context , 'director' ) ;
}
async function addReleaseChapters ( release , context ) {
await release . chapters . reduce ( async ( chain , chapter ) => {
await chain ;
const [ chapterId ] = await knex ( 'chapters' )
. insert ( {
release _id : release . id ,
index : chapter . index ,
time : chapter . time ,
duration : chapter . duration ,
description : chapter . description ,
} )
. returning ( 'id' ) ;
if ( chapter . tags . length > 0 ) {
await knex ( 'chapters_tags' ) . insert ( chapter . tags . map ( ( tag ) => ( {
tag _id : context . tagIdsBySlug [ tag ] ,
chapter _id : chapterId ,
original _tag : tag ,
} ) ) ) ;
}
} , Promise . resolve ( ) ) ;
}
2023-06-03 00:51:42 +00:00
const dirs = {
path : '' ,
thumbnail : 'thumbs' ,
lazy : 'lazy' ,
} ;
async function transferMedia ( media , target ) {
return [ 'path' , 'thumbnail' , 'lazy' ] . reduce ( async ( chain , type ) => {
await chain ;
const filename = ` ${ media . hash } ${ path . extname ( media [ type ] ) } ` ;
const filepath = path . join ( target , dirs [ type ] , filename ) ;
const temp = path . join ( 'media/temp' , filepath ) ;
2023-06-03 19:51:09 +00:00
const url = new URL ( media [ type ] , ` ${ media . s3 ? config . media . transferSources . s3 : config . media . transferSources . local } / ` ) . href ;
2023-06-03 00:51:42 +00:00
console . log ( 'Transferring media' , url ) ;
const res = await bhttp . get ( url , { stream : true } ) ;
if ( res . statusCode !== 200 ) {
console . warn ( ` Missing ${ target } ${ url } ` ) ;
return ;
}
await fs . promises . mkdir ( path . dirname ( temp ) , { recursive : true } ) ;
await new Promise ( ( resolve , reject ) => {
const fileStream = fs . createWriteStream ( temp ) ;
res . pipe ( fileStream ) ;
res . on ( 'error' , ( ) => { reject ( ) ; } ) ;
fileStream . on ( 'finish' , ( ) => { resolve ( ) ; } ) ;
fileStream . on ( 'error' , ( ) => { reject ( ) ; } ) ;
} ) ;
2023-06-03 19:51:09 +00:00
await s3 . upload ( {
Bucket : config . s3 . bucket ,
Body : fs . createReadStream ( temp ) ,
Key : filepath ,
ContentType : media . mime ,
} ) . promise ( ) ;
2023-06-03 00:51:42 +00:00
2023-06-03 19:51:09 +00:00
await fs . promises . unlink ( temp ) ;
2023-06-03 00:51:42 +00:00
} , Promise . resolve ( ) ) ;
}
2023-05-30 22:29:54 +00:00
async function addReleaseMedia ( medias , release , target ) {
2023-06-03 22:23:45 +00:00
await medias . filter ( Boolean ) . reduce ( async ( chain , media ) => {
await chain ;
2023-06-03 00:51:42 +00:00
const existingMedia = await knex ( 'media' )
. where ( 'hash' , media . hash )
. first ( ) ;
const id = existingMedia ? . id || nanoid ( ) ;
2023-05-30 22:29:54 +00:00
2023-06-03 00:51:42 +00:00
if ( ! existingMedia ) {
2023-05-30 22:29:54 +00:00
await knex ( 'media' ) . insert ( {
id ,
hash : media . hash ,
2023-06-03 00:51:42 +00:00
path : path . join ( target , '' , ` ${ media . hash } ${ path . extname ( media . path ) } ` ) ,
thumbnail : path . join ( target , 'thumbs' , ` ${ media . hash } ${ path . extname ( media . thumbnail ) } ` ) ,
lazy : path . join ( target , 'lazy' , ` ${ media . hash } ${ path . extname ( media . lazy ) } ` ) ,
// is_s3: media.s3,
is _s3 : true ,
2023-05-30 22:29:54 +00:00
index : media . index ,
mime : media . mime ,
size : media . size ,
width : media . width ,
height : media . height ,
source : media . source ,
source _page : media . sourcePage ,
} ) ;
2023-06-03 19:51:09 +00:00
await transferMedia ( media , target ) ;
2023-05-30 22:29:54 +00:00
}
2023-06-03 00:51:42 +00:00
await knex ( ` ${ release . type } s_ ${ target } ` ) . insert ( {
[ ` ${ release . type } _id ` ] : release . id ,
media _id : id ,
} ) ;
2023-06-03 22:23:45 +00:00
} , Promise . resolve ( ) ) ;
2023-05-30 22:29:54 +00:00
}
2023-06-03 00:51:42 +00:00
async function linkMovieScenes ( release , context ) {
await release . movies . reduce ( async ( chain , linkedMovie ) => {
await chain ;
const movie = context . movies . find ( ( storedMovie ) => storedMovie . entryId === linkedMovie . entryId
&& storedMovie . entity . slug === linkedMovie . entity . slug
&& storedMovie . entity . type === linkedMovie . entity . type ) ;
console . log ( 'movie' , linkedMovie , movie ) ;
if ( ! movie ) {
throw new Error ( ` Missing ${ linkedMovie . entity . slug } movie ' ${ linkedMovie . title } ' in ' ${ release . title } ' ` ) ;
}
await knex ( 'movies_scenes' ) . insert ( {
movie _id : movie . id ,
scene _id : release . id ,
} ) ;
} , Promise . resolve ( ) ) ;
}
2023-05-30 22:29:54 +00:00
async function addRelease ( release , context ) {
2023-06-03 00:51:42 +00:00
const existingRelease = await knex ( ` ${ release . type } s ` )
2023-06-03 22:49:21 +00:00
. select ( ` ${ release . type } s.* ` , 'entities.name as entity_name' )
2023-06-03 00:51:42 +00:00
. leftJoin ( 'entities' , 'entities.id' , ` ${ release . type } s.entity_id ` )
2023-05-30 22:29:54 +00:00
. where ( 'entry_id' , release . entryId )
. where ( 'entities.slug' , release . entity . slug )
. where ( 'entities.type' , release . entity . type )
. first ( ) ;
if ( existingRelease ) {
2023-06-03 22:42:29 +00:00
console . log ( ` Skipping ${ release . entity . slug } release " ${ release . title } ", already in database ` ) ;
2023-06-03 22:49:21 +00:00
return {
... release ,
id : existingRelease . id ,
entityName : existingRelease . entity _name ,
} ;
2023-05-30 22:29:54 +00:00
}
const [ entity ] = await Promise . all ( [
2023-06-03 22:23:45 +00:00
knex ( 'entities' ) . select ( [ 'id' , 'name' ] ) . where ( release . entity ) . first ( ) ,
2023-05-30 22:29:54 +00:00
] ) ;
if ( ! entity ) {
throw new Error ( ` Release contains non-existent ${ release . entity . type } ' ${ release . entity . slug } ' ` ) ;
}
2023-06-03 00:51:42 +00:00
const [ releaseEntry ] = await knex ( ` ${ release . type } s ` )
2023-05-30 22:29:54 +00:00
. insert ( {
entry _id : release . entryId ,
entity _id : entity . id ,
url : release . url ,
title : release . title ,
slug : release . slug ,
date : release . date ,
date _precision : release . datePrecision ,
created _batch _id : context . batchId ,
updated _batch _id : context . batchId ,
2023-06-03 00:51:42 +00:00
... ( release . type === 'scene' && {
shoot _id : release . shootId ,
studio _id : context . studioIdsBySlug [ release . studio ] ,
production _date : release . productionDate ,
description : release . description ,
duration : release . duration ,
} ) ,
2023-05-30 22:29:54 +00:00
} )
2023-06-03 00:51:42 +00:00
. returning ( [ 'id' , 'entry_id' ] ) ;
2023-05-30 22:29:54 +00:00
2023-06-03 00:51:42 +00:00
const releaseWithId = {
... release ,
id : releaseEntry . id ,
2023-06-03 22:23:45 +00:00
entityName : entity . name ,
2023-06-03 00:51:42 +00:00
} ;
2023-05-30 22:29:54 +00:00
await Promise . all ( [
addReleaseMedia ( [ releaseWithId . poster ] , releaseWithId , 'posters' , context ) ,
2023-06-03 00:51:42 +00:00
... ( release . type === 'release' ? [
addReleaseTags ( releaseWithId , context ) ,
addReleaseActors ( releaseWithId , context ) ,
addReleaseDirectors ( releaseWithId , context ) ,
addReleaseChapters ( releaseWithId , context ) ,
addReleaseMedia ( releaseWithId . photos , releaseWithId , 'photos' , context ) ,
linkMovieScenes ( releaseWithId , context ) ,
] : [ ] ) ,
... ( release . type === 'movie' ? [
addReleaseMedia ( releaseWithId . covers , releaseWithId , 'covers' , context ) ,
] : [ ] ) ,
2023-05-30 22:29:54 +00:00
] ) ;
2023-06-03 00:51:42 +00:00
return releaseWithId ;
2023-05-30 22:29:54 +00:00
}
async function load ( ) {
2023-06-03 00:51:42 +00:00
const file = await fs . promises . readFile ( args . file , 'utf8' ) ;
2023-06-03 22:23:45 +00:00
const start = args . start || 0 ;
const end = args . limit ? start + args . limit : Infinity ;
2023-06-03 20:41:15 +00:00
const releases = file . split ( '\n' )
. filter ( Boolean )
. map ( ( data ) => JSON . parse ( data ) )
2023-06-03 19:51:09 +00:00
. filter ( ( release ) => ( args . entity ? release . entity . slug === args . entity : true ) )
2023-06-03 22:23:45 +00:00
. slice ( start , end ) ;
if ( releases . length === 0 ) {
console . log ( 'Nothing to load' ) ;
return ;
}
2023-05-30 22:29:54 +00:00
const [ batchId ] = await knex ( 'batches' ) . insert ( { comment : ` import ${ args . file } ` } ) . returning ( 'id' ) ;
2023-06-03 00:51:42 +00:00
const aggTags = Array . from ( new Set ( releases . filter ( ( release ) => release . type === 'release' ) . flatMap ( ( release ) => [ ... release . tags , ... release . chapters . flatMap ( ( chapter ) => chapter . tags ) ] ) . filter ( Boolean ) ) ) ;
2023-05-30 22:29:54 +00:00
const aggStudios = Array . from ( new Set ( releases . map ( ( release ) => release . studio ) . filter ( Boolean ) ) ) ;
const tags = await knex ( 'tags' )
. select ( 'id' , 'slug' )
. whereIn ( 'slug' , aggTags ) ;
const studios = await knex ( 'entities' )
. select ( 'id' , 'slug' )
. where ( 'type' , 'studio' )
. whereIn ( 'slug' , aggStudios ) ;
const tagIdsBySlug = Object . fromEntries ( tags . map ( ( tag ) => [ tag . slug , tag . id ] ) ) ;
const studioIdsBySlug = Object . fromEntries ( studios . map ( ( studio ) => [ studio . slug , studio . id ] ) ) ;
2023-06-03 00:51:42 +00:00
const addedMovies = await releases . filter ( ( release ) => release . type === 'movie' ) . reduce ( async ( chain , release ) => {
const acc = await chain ;
const movie = await addRelease ( release , { batchId , tagIdsBySlug , studioIdsBySlug } ) ;
2023-06-03 22:23:45 +00:00
console . log ( ` Loaded ' ${ movie . entityName } ' movie " ${ movie . title } " ` ) ;
2023-06-03 00:51:42 +00:00
return acc . concat ( movie ) ;
} , Promise . resolve ( [ ] ) ) ;
const addedScenes = await releases . filter ( ( release ) => release . type === 'release' ) . reduce ( async ( chain , release ) => {
2023-05-30 22:29:54 +00:00
const acc = await chain ;
2023-06-03 00:51:42 +00:00
const scene = await addRelease ( release , { batchId , movies : addedMovies , tagIdsBySlug , studioIdsBySlug } ) ;
2023-05-30 22:29:54 +00:00
2023-06-03 22:23:45 +00:00
console . log ( ` Loaded ' ${ scene . entityName } ' scene " ${ scene . title } " ` ) ;
2023-06-03 19:51:09 +00:00
return acc . concat ( ! ! scene ) ;
2023-05-30 22:29:54 +00:00
} , Promise . resolve ( [ ] ) ) ;
2023-06-03 00:51:42 +00:00
console . log ( ` Loaded ${ addedMovies . filter ( Boolean ) . length } / ${ releases . filter ( ( release ) => release . type === 'movie' ) . length } movies in batch ${ batchId } ` ) ;
console . log ( ` Loaded ${ addedScenes . filter ( Boolean ) . length } / ${ releases . filter ( ( release ) => release . type === 'release' ) . length } scenes in batch ${ batchId } ` ) ;
2023-05-30 22:29:54 +00:00
process . exit ( ) ;
}
( {
save ,
load ,
} ) [ args . _ ] ( ) ;