Normalized database. Updated seed files. Simplified seed upsert.

This commit is contained in:
2019-12-19 02:35:07 +01:00
parent 9b17add4e2
commit 31aee71edb
8 changed files with 578 additions and 530 deletions

View File

@@ -125,10 +125,4 @@ const networks = [
];
exports.seed = knex => Promise.resolve()
.then(async () => {
// find network IDs
const duplicates = await knex('networks').select('*');
const duplicatesBySlug = duplicates.reduce((acc, network) => ({ ...acc, [network.slug]: network }), {});
return upsert('networks', networks, duplicatesBySlug, 'slug', knex);
});
.then(async () => upsert('networks', networks, 'slug', knex));

View File

@@ -2428,15 +2428,10 @@ function getSites(networksMap) {
/* eslint-disable max-len */
exports.seed = knex => Promise.resolve()
.then(async () => {
const [duplicates, networks] = await Promise.all([
knex('sites').select('*'),
knex('networks').select('*'),
]);
const duplicatesBySlug = duplicates.reduce((acc, site) => ({ ...acc, [site.slug]: site }), {});
const networks = await knex('networks').select('*');
const networksMap = networks.reduce((acc, { id, slug }) => ({ ...acc, [slug]: id }), {});
const sites = getSites(networksMap);
return upsert('sites', sites, duplicatesBySlug, 'slug', knex);
return upsert('sites', sites, 'slug', knex);
});

View File

@@ -1,5 +1,3 @@
'use strict';
const upsert = require('../src/utils/upsert');
function getStudios(networksMap) {
@@ -9,133 +7,133 @@ function getStudios(networksMap) {
slug: 'gonzocom',
name: 'Gonzo.com',
url: 'https://www.legalporno.com/studios/gonzo_com',
network_id: networksMap['legalporno'],
network_id: networksMap.legalporno,
},
{
slug: 'giorgiograndi',
name: 'Giorgio Grandi',
url: 'https://www.legalporno.com/studios/giorgio-grandi',
network_id: networksMap['legalporno'],
network_id: networksMap.legalporno,
},
{
slug: 'hardpornworld',
name: 'Hard Porn World',
url: 'https://www.legalporno.com/studios/hard-porn-world',
network_id: networksMap['legalporno'],
network_id: networksMap.legalporno,
},
{
slug: 'interracialvision',
name: 'Interracial Vision',
url: 'https://www.legalporno.com/studios/interracial-vision',
network_id: networksMap['legalporno'],
network_id: networksMap.legalporno,
},
{
slug: 'giorgioslab',
name: 'Giorgio\'s Lab',
url: 'https://www.legalporno.com/studios/giorgio--s-lab',
network_id: networksMap['legalporno'],
network_id: networksMap.legalporno,
},
{
slug: 'americananal',
name: 'American Anal',
url: 'https://www.legalporno.com/studios/american-anal',
network_id: networksMap['legalporno'],
network_id: networksMap.legalporno,
},
{
slug: 'assablanca',
name: 'Assablanca',
url: 'https://www.legalporno.com/studios/assablanca',
network_id: networksMap['legalporno'],
network_id: networksMap.legalporno,
},
{
slug: 'focus',
name: 'Focus',
url: 'https://www.legalporno.com/studios/focus',
network_id: networksMap['legalporno'],
network_id: networksMap.legalporno,
},
{
slug: 'analforever',
name: 'Anal Forever',
url: 'https://www.legalporno.com/studios/anal-forever',
network_id: networksMap['legalporno'],
network_id: networksMap.legalporno,
},
{
slug: 'gonzoinbrazil',
name: 'Gonzo in Brazil',
url: 'https://www.legalporno.com/studios/gonzo-in-brazil',
network_id: networksMap['legalporno'],
network_id: networksMap.legalporno,
},
{
slug: 'mranal',
name: 'Mr Anal',
url: 'https://www.legalporno.com/studios/mr-anal',
network_id: networksMap['legalporno'],
network_id: networksMap.legalporno,
},
{
slug: 'tarrawhite',
name: 'Tarra White',
url: 'https://www.legalporno.com/studios/tarra-white',
network_id: networksMap['legalporno'],
network_id: networksMap.legalporno,
},
{
slug: 'sineplexsos',
name: 'Sineplex SOS',
url: 'https://www.legalporno.com/studios/sineplex-sos',
network_id: networksMap['legalporno'],
network_id: networksMap.legalporno,
},
{
slug: 'fmodels',
name: 'F Models',
url: 'https://www.legalporno.com/studios/f-models',
network_id: networksMap['legalporno'],
network_id: networksMap.legalporno,
},
{
slug: 'sineplexcz',
name: 'Sineplex CZ',
url: 'https://www.legalporno.com/studios/sineplex-cz',
network_id: networksMap['legalporno'],
network_id: networksMap.legalporno,
},
{
slug: 'gg',
name: 'GG',
url: 'https://www.legalporno.com/studios/gg',
network_id: networksMap['legalporno'],
network_id: networksMap.legalporno,
},
{
slug: 'firstgape',
name: 'First Gape',
url: 'https://www.legalporno.com/studios/first-gape',
network_id: networksMap['legalporno'],
network_id: networksMap.legalporno,
},
{
slug: 'omargalantiproductions',
name: 'Omar Galanti Productions',
url: 'https://www.legalporno.com/studios/omar-galanti-productions',
network_id: networksMap['legalporno'],
network_id: networksMap.legalporno,
},
{
slug: 'norestfortheass',
name: 'No Rest For The Ass',
url: 'https://www.legalporno.com/studios/no-rest-for-the-ass',
network_id: networksMap['legalporno'],
network_id: networksMap.legalporno,
},
{
slug: 'hairygonzo',
name: 'Hairy Gonzo',
url: 'https://www.legalporno.com/studios/hairy-gonzo',
network_id: networksMap['legalporno'],
network_id: networksMap.legalporno,
},
{
slug: 'sineplexclassic',
name: 'Sineplex Classic',
url: 'https://www.legalporno.com/studios/sineplex-classic',
network_id: networksMap['legalporno'],
network_id: networksMap.legalporno,
},
{
slug: 'sinemale',
name: 'Sinemale',
url: 'https://www.legalporno.com/studios/sinemale',
network_id: networksMap['legalporno'],
network_id: networksMap.legalporno,
},
];
}
@@ -143,15 +141,10 @@ function getStudios(networksMap) {
/* eslint-disable max-len */
exports.seed = knex => Promise.resolve()
.then(async () => {
const [duplicates, networks] = await Promise.all([
knex('studios').select('*'),
knex('networks').select('*'),
]);
const duplicatesBySlug = duplicates.reduce((acc, studio) => ({ ...acc, [studio.slug]: studio }), {});
const networks = await knex('networks').select('*');
const networksMap = networks.reduce((acc, { id, slug }) => ({ ...acc, [slug]: id }), {});
const studios = getStudios(networksMap);
return upsert('studios', studios, duplicatesBySlug, 'slug', knex);
return upsert('studios', studios, 'slug', knex);
});

View File

@@ -69,7 +69,7 @@ function getTags(groupsMap) {
name: 'airtight',
slug: 'airtight',
alias_for: null,
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).',
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).', /* eslint-disable-line max-len */
priority: 9,
group_id: groupsMap.penetration,
},
@@ -429,7 +429,7 @@ 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.',
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.', /* eslint-disable-line max-len */
alias_for: null,
priority: 9,
group_id: groupsMap.group,
@@ -1542,35 +1542,20 @@ function getTagAliases(tagsMap) {
}
exports.seed = knex => Promise.resolve()
.then(async () => upsert('tags_groups', groups, 'slug', knex))
.then(async () => {
const duplicates = await knex('tags_groups').select('*');
const duplicatesBySlug = duplicates.reduce((acc, group) => ({ ...acc, [group.slug]: group }), {});
return upsert('tags_groups', groups, duplicatesBySlug, 'slug', knex);
})
.then(async () => {
const [duplicates, groupEntries] = await Promise.all([
knex('tags').select('*'),
knex('tags_groups').select('*'),
]);
const duplicatesBySlug = duplicates.reduce((acc, tag) => ({ ...acc, [tag.slug]: tag }), {});
const groupEntries = knex('tags').select('*');
const groupsMap = groupEntries.reduce((acc, { id, slug }) => ({ ...acc, [slug]: id }), {});
const tags = getTags(groupsMap);
return upsert('tags', tags, duplicatesBySlug, 'slug', knex);
return upsert('tags', tags, 'slug', knex);
})
.then(async () => {
const [duplicates, tags] = await Promise.all([
knex('tags').select('*').whereNotNull('alias_for'),
knex('tags').select('*').where({ alias_for: null }),
]);
const duplicatesByName = duplicates.reduce((acc, tag) => ({ ...acc, [tag.name]: tag }), {});
const tags = await knex('tags').select('*').where({ alias_for: null });
const tagsMap = tags.reduce((acc, { id, slug }) => ({ ...acc, [slug]: id }), {});
const tagAliases = getTagAliases(tagsMap);
return upsert('tags', tagAliases, duplicatesByName, 'name', knex);
return upsert('tags', tagAliases, 'name', knex);
});

View File

@@ -1,257 +1,241 @@
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/0.jpeg',
target_id: tagsMap['da-tp'],
role: 'poster',
comment: 'Natasha Teen in LegalPorno SZ2164',
},
{
path: 'tags/da-tp/3.jpeg',
target_id: tagsMap['da-tp'],
role: 'photo',
comment: 'Evelina Darling in GIO294',
},
{
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/da-tp/4.jpeg',
target_id: tagsMap['da-tp'],
role: 'photo',
comment: 'Ninel Mojado aka Mira Cuckold in GIO063 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/dv-tp/0.jpeg',
target_id: tagsMap['dv-tp'],
role: 'photo',
comment: 'Luna Rival in LegalPorno SZ1490',
},
{
path: 'tags/tattoo/poster.jpeg',
target_id: tagsMap.tattoo,
role: 'poster',
comment: 'Kali Roses in "Goes All In For Anal" for Hussie Pass',
},
{
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/gangbang/2.jpeg',
target_id: tagsMap.gangbang,
role: 'photo',
comment: 'Riley Reid\'s double anal in "The Gangbang of Riley Reid" for Jules Jordan',
},
{
path: 'tags/gangbang/3.jpeg',
target_id: tagsMap.gangbang,
role: 'photo',
comment: 'Kelsi Monroe in "Brazzers House 2, Day 2" for Brazzers',
},
{
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',
target: { [file.domain || 'tags']: file.target_id },
role: file.role || 'photo',
}));
}
const tagPosters = [
{
path: 'tags/airtight/poster.jpeg',
tagSlug: 'airtight',
comment: 'Jynx Maze in "Pump My Ass Full of Cum 3" for Jules Jordan',
},
{
path: 'tags/anal/poster.jpeg',
tagSlug: 'anal',
comment: '',
},
{
path: 'tags/double-penetration/poster.jpeg',
tagSlug: 'double-penetration',
comment: '',
},
{
path: 'tags/double-anal/poster.jpeg',
tagSlug: 'double-anal',
comment: '',
},
{
path: 'tags/double-vaginal/poster.jpeg',
tagSlug: 'double-vaginal',
comment: '',
},
{
path: 'tags/tattoo/poster.jpeg',
tagSlug: 'tattoo',
comment: 'Kali Roses in "Goes All In For Anal" for Hussie Pass',
},
{
path: 'tags/triple-anal/poster.jpeg',
tagSlug: 'triple-anal',
comment: 'Kristy Black in SZ1986 for LegalPorno',
},
{
path: 'tags/blowbang/poster.jpeg',
tagSlug: 'blowbang',
comment: '',
},
{
path: 'tags/gangbang/poster.jpeg',
tagSlug: 'gangbang',
comment: '',
},
{
path: 'tags/mff/poster.jpeg',
tagSlug: 'mff',
comment: '',
},
{
path: 'tags/mfm/poster.jpeg',
tagSlug: 'mfm',
comment: '',
},
{
path: 'tags/orgy/poster.jpeg',
tagSlug: 'orgy',
comment: '',
},
{
path: 'tags/asian/poster.jpeg',
tagSlug: 'asian',
comment: '',
},
{
path: 'tags/caucasian/poster.jpeg',
tagSlug: 'caucasian',
comment: '',
},
{
path: 'tags/ebony/poster.jpeg',
tagSlug: 'ebony',
comment: '',
},
{
path: 'tags/latina/poster.jpeg',
tagSlug: 'latina',
comment: '',
},
{
path: 'tags/interracial/poster.jpeg',
tagSlug: 'interracial',
comment: '',
},
{
path: 'tags/facial/poster.jpeg',
tagSlug: 'facial',
comment: '',
},
{
path: 'tags/bukkake/poster.jpeg',
tagSlug: 'bukkake',
comment: '',
},
{
path: 'tags/swallowing/poster.jpeg',
tagSlug: 'swallowing',
comment: '',
},
{
path: 'tags/creampie/poster.jpeg',
tagSlug: 'creampie',
comment: '',
},
{
path: 'tags/anal-creampie/poster.jpeg',
tagSlug: 'anal-creampie',
comment: '',
},
{
path: 'tags/oral-creampie/poster.jpeg',
tagSlug: 'oral-creampie',
comment: '',
},
]
.map((file, index) => ({
...file,
thumbnail: file.thumbnail || file.path.replace('.jpeg', '_thumb.jpeg'),
mime: 'image/jpeg',
index,
}));
const tagPhotos = [
{
path: 'tags/airtight/2.jpeg',
tagSlug: 'airtight',
comment: 'Dakota Skye in "Dakota Goes Nuts" for ArchAngel',
},
{
path: 'tags/airtight/1.jpeg',
tagSlug: 'airtight',
comment: 'Chloe Amour in "DP Masters 4" for Jules Jordan',
},
{
path: 'tags/airtight/0.jpeg',
domain: 'tags',
tagSlug: 'airtight',
comment: 'Sheena Shaw in "Ass Worship 14" for Jules Jordan',
},
{
path: 'tags/da-tp/0.jpeg',
tagSlug: 'da-tp',
comment: 'Natasha Teen in LegalPorno SZ2164',
},
{
path: 'tags/da-tp/3.jpeg',
tagSlug: 'da-tp',
comment: 'Evelina Darling in GIO294',
},
{
path: 'tags/da-tp/1.jpeg',
tagSlug: 'da-tp',
comment: 'Francys Belle in SZ1702 for LegalPorno',
},
{
path: 'tags/da-tp/2.jpeg',
tagSlug: 'da-tp',
comment: 'Angel Smalls in GIO408 for LegalPorno',
},
{
path: 'tags/da-tp/4.jpeg',
tagSlug: 'da-tp',
comment: 'Ninel Mojado aka Mira Cuckold in GIO063 for LegalPorno',
},
{
path: 'tags/dv-tp/poster.jpeg',
tagSlug: 'dv-tp',
comment: 'Juelz Ventura in "Gangbanged 5" for Elegant Angel',
},
{
path: 'tags/dv-tp/0.jpeg',
tagSlug: 'dv-tp',
comment: 'Luna Rival in LegalPorno SZ1490',
},
{
path: 'tags/triple-anal/1.jpeg',
tagSlug: 'triple-anal',
comment: 'Natasha Teen in SZ2098 for LegalPorno',
},
{
path: 'tags/triple-anal/2.jpeg',
tagSlug: 'triple-anal',
comment: 'Kira Thorn in GIO1018 for LegalPorno',
},
{
path: 'tags/gangbang/1.jpeg',
tagSlug: 'gangbang',
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/gangbang/2.jpeg',
tagSlug: 'gangbang',
comment: 'Riley Reid\'s double anal in "The Gangbang of Riley Reid" for Jules Jordan',
},
{
path: 'tags/gangbang/3.jpeg',
tagSlug: 'gangbang',
comment: 'Kelsi Monroe in "Brazzers House 2, Day 2" for Brazzers',
},
]
.map((file, index) => ({
...file,
thumbnail: file.thumbnail || file.path.replace('.jpeg', '_thumb.jpeg'),
mime: 'image/jpeg',
index,
}));
/* 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 tagMedia = tagPosters.concat(tagPhotos);
const tags = await knex('tags').whereIn('slug', tagMedia.map(item => item.tagSlug));
const { inserted, updated } = await upsert('media', tagMedia.map(({
path, thumbnail, mime, index, comment,
}) => ({
path, thumbnail, mime, index, comment,
})), 'path', knex);
const tagIdsBySlug = tags.reduce((acc, tag) => ({ ...acc, [tag.slug]: tag.id }), {});
const mediaIdsByPath = inserted.concat(updated).reduce((acc, item) => ({ ...acc, [item.path]: item.id }), {});
const tagPosterEntries = tagPosters.map(poster => ({
tag_id: tagIdsBySlug[poster.tagSlug],
media_id: mediaIdsByPath[poster.path],
}));
const tagPhotoEntries = tagPhotos.map(photo => ({
tag_id: tagIdsBySlug[photo.tagSlug],
media_id: mediaIdsByPath[photo.path],
}));
return Promise.all([
upsert('tags_posters', tagPosterEntries, 'tag_id', knex),
upsert('tags_photos', tagPhotoEntries, 'tag_id', knex),
]);
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);
});

View File

@@ -1755,9 +1755,4 @@ const countries = [
];
exports.seed = knex => knex('countries')
.then(async () => {
const duplicates = await knex('countries').select('*');
const duplicatesByAlpha2 = duplicates.reduce((acc, country) => ({ ...acc, [country.alpha2]: country }), {});
return upsert('countries', countries, duplicatesByAlpha2, 'alpha2', knex);
});
.then(async () => upsert('countries', countries, 'alpha2', knex));