mirror of
https://github.com/aljazceru/landscape-template.git
synced 2026-02-23 15:34:21 +01:00
Merge branch 'dev' into feature/list-your-product-ui
This commit is contained in:
@@ -28,6 +28,11 @@ declare global {
|
||||
}
|
||||
|
||||
export interface NexusGenInputs {
|
||||
ImageInput: { // input type
|
||||
id?: string | null; // String
|
||||
name?: string | null; // String
|
||||
url: string; // String!
|
||||
}
|
||||
MakerRoleInput: { // input type
|
||||
id: number; // Int!
|
||||
level: NexusGenEnums['RoleLevelEnum']; // RoleLevelEnum!
|
||||
@@ -36,7 +41,7 @@ export interface NexusGenInputs {
|
||||
id: number; // Int!
|
||||
}
|
||||
ProfileDetailsInput: { // input type
|
||||
avatar?: string | null; // String
|
||||
avatar?: NexusGenInputs['ImageInput'] | null; // ImageInput
|
||||
bio?: string | null; // String
|
||||
discord?: string | null; // String
|
||||
email?: string | null; // String
|
||||
@@ -59,7 +64,7 @@ export interface NexusGenInputs {
|
||||
}
|
||||
StoryInputType: { // input type
|
||||
body: string; // String!
|
||||
cover_image?: string | null; // String
|
||||
cover_image?: NexusGenInputs['ImageInput'] | null; // ImageInput
|
||||
id?: number | null; // Int
|
||||
is_published?: boolean | null; // Boolean
|
||||
tags: string[]; // [String!]!
|
||||
@@ -95,7 +100,6 @@ export interface NexusGenScalars {
|
||||
|
||||
export interface NexusGenObjects {
|
||||
Author: { // root type
|
||||
avatar: string; // String!
|
||||
id: number; // Int!
|
||||
join_date: NexusGenScalars['Date']; // Date!
|
||||
lightning_address?: string | null; // String
|
||||
@@ -111,7 +115,6 @@ export interface NexusGenObjects {
|
||||
applicants_count: number; // Int!
|
||||
applications: NexusGenRootTypes['BountyApplication'][]; // [BountyApplication!]!
|
||||
body: string; // String!
|
||||
cover_image?: string | null; // String
|
||||
createdAt: NexusGenScalars['Date']; // Date!
|
||||
deadline: string; // String!
|
||||
excerpt: string; // String!
|
||||
@@ -129,7 +132,6 @@ export interface NexusGenObjects {
|
||||
workplan: string; // String!
|
||||
}
|
||||
Category: { // root type
|
||||
cover_image?: string | null; // String
|
||||
icon?: string | null; // String
|
||||
id: number; // Int!
|
||||
title: string; // String!
|
||||
@@ -154,7 +156,6 @@ export interface NexusGenObjects {
|
||||
title: string; // String!
|
||||
}
|
||||
Hackathon: { // root type
|
||||
cover_image: string; // String!
|
||||
description: string; // String!
|
||||
end_date: NexusGenScalars['Date']; // Date!
|
||||
id: number; // Int!
|
||||
@@ -181,7 +182,6 @@ export interface NexusGenObjects {
|
||||
}
|
||||
Mutation: {};
|
||||
MyProfile: { // root type
|
||||
avatar: string; // String!
|
||||
bio?: string | null; // String
|
||||
discord?: string | null; // String
|
||||
email?: string | null; // String
|
||||
@@ -213,13 +213,10 @@ export interface NexusGenObjects {
|
||||
votes_count: number; // Int!
|
||||
}
|
||||
Project: { // root type
|
||||
cover_image: string; // String!
|
||||
description: string; // String!
|
||||
id: number; // Int!
|
||||
lightning_address?: string | null; // String
|
||||
lnurl_callback_url?: string | null; // String
|
||||
screenshots: string[]; // [String!]!
|
||||
thumbnail_image: string; // String!
|
||||
title: string; // String!
|
||||
votes_count: number; // Int!
|
||||
website: string; // String!
|
||||
@@ -237,7 +234,6 @@ export interface NexusGenObjects {
|
||||
}
|
||||
Story: { // root type
|
||||
body: string; // String!
|
||||
cover_image?: string | null; // String
|
||||
createdAt: NexusGenScalars['Date']; // Date!
|
||||
excerpt: string; // String!
|
||||
id: number; // Int!
|
||||
@@ -254,13 +250,11 @@ export interface NexusGenObjects {
|
||||
title: string; // String!
|
||||
}
|
||||
Tournament: { // root type
|
||||
cover_image: string; // String!
|
||||
description: string; // String!
|
||||
end_date: NexusGenScalars['Date']; // Date!
|
||||
id: number; // Int!
|
||||
location: string; // String!
|
||||
start_date: NexusGenScalars['Date']; // Date!
|
||||
thumbnail_image: string; // String!
|
||||
title: string; // String!
|
||||
website: string; // String!
|
||||
}
|
||||
@@ -268,7 +262,6 @@ export interface NexusGenObjects {
|
||||
description: string; // String!
|
||||
ends_at: NexusGenScalars['Date']; // Date!
|
||||
id: number; // Int!
|
||||
image: string; // String!
|
||||
location: string; // String!
|
||||
starts_at: NexusGenScalars['Date']; // Date!
|
||||
title: string; // String!
|
||||
@@ -280,7 +273,6 @@ export interface NexusGenObjects {
|
||||
question: string; // String!
|
||||
}
|
||||
TournamentJudge: { // root type
|
||||
avatar: string; // String!
|
||||
company: string; // String!
|
||||
name: string; // String!
|
||||
}
|
||||
@@ -296,7 +288,6 @@ export interface NexusGenObjects {
|
||||
}
|
||||
TournamentPrize: { // root type
|
||||
amount: string; // String!
|
||||
image: string; // String!
|
||||
title: string; // String!
|
||||
}
|
||||
TournamentProjectsResponse: { // root type
|
||||
@@ -305,7 +296,6 @@ export interface NexusGenObjects {
|
||||
projects: NexusGenRootTypes['Project'][]; // [Project!]!
|
||||
}
|
||||
User: { // root type
|
||||
avatar: string; // String!
|
||||
bio?: string | null; // String
|
||||
discord?: string | null; // String
|
||||
github?: string | null; // String
|
||||
|
||||
@@ -115,6 +115,12 @@ type Hackathon {
|
||||
website: String!
|
||||
}
|
||||
|
||||
input ImageInput {
|
||||
id: String
|
||||
name: String
|
||||
url: String!
|
||||
}
|
||||
|
||||
type LnurlDetails {
|
||||
commentAllowed: Int
|
||||
maxSendable: Int
|
||||
@@ -219,7 +225,7 @@ type PostComment {
|
||||
}
|
||||
|
||||
input ProfileDetailsInput {
|
||||
avatar: String
|
||||
avatar: ImageInput
|
||||
bio: String
|
||||
discord: String
|
||||
email: String
|
||||
@@ -331,7 +337,7 @@ type Story implements PostBase {
|
||||
|
||||
input StoryInputType {
|
||||
body: String!
|
||||
cover_image: String
|
||||
cover_image: ImageInput
|
||||
id: Int
|
||||
is_published: Boolean
|
||||
tags: [String!]!
|
||||
|
||||
@@ -4,7 +4,8 @@ const {
|
||||
extendType,
|
||||
nonNull,
|
||||
} = require('nexus');
|
||||
const { prisma } = require('../../../prisma')
|
||||
const { prisma } = require('../../../prisma');
|
||||
const { resolveImgObjectToUrl } = require('../../../utils/resolveImageUrl');
|
||||
|
||||
|
||||
const Category = objectType({
|
||||
@@ -12,7 +13,11 @@ const Category = objectType({
|
||||
definition(t) {
|
||||
t.nonNull.int('id');
|
||||
t.nonNull.string('title');
|
||||
t.string('cover_image');
|
||||
t.string('cover_image', {
|
||||
async resolve(parent) {
|
||||
return prisma.category.findUnique({ where: { id: parent.id } }).cover_image_rel().then(resolveImgObjectToUrl)
|
||||
}
|
||||
});
|
||||
t.string('icon');
|
||||
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ const {
|
||||
nonNull,
|
||||
} = require('nexus');
|
||||
const { prisma } = require('../../../prisma');
|
||||
const { resolveImgObjectToUrl } = require('../../../utils/resolveImageUrl');
|
||||
|
||||
|
||||
|
||||
@@ -15,7 +16,11 @@ const Hackathon = objectType({
|
||||
t.nonNull.int('id');
|
||||
t.nonNull.string('title');
|
||||
t.nonNull.string('description');
|
||||
t.nonNull.string('cover_image');
|
||||
t.nonNull.string('cover_image', {
|
||||
async resolve(parent) {
|
||||
return prisma.hackathon.findUnique({ where: { id: parent.id } }).cover_image_rel().then(resolveImgObjectToUrl)
|
||||
}
|
||||
});
|
||||
t.nonNull.date('start_date');
|
||||
t.nonNull.date('end_date');
|
||||
t.nonNull.string('location');
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
const scalars = require('./_scalars')
|
||||
const misc = require('./misc')
|
||||
const category = require('./category')
|
||||
const project = require('./project')
|
||||
const vote = require('./vote')
|
||||
@@ -10,6 +11,7 @@ const donation = require('./donation')
|
||||
const tag = require('./tag')
|
||||
|
||||
module.exports = {
|
||||
...misc,
|
||||
...tag,
|
||||
...scalars,
|
||||
...category,
|
||||
|
||||
19
api/functions/graphql/types/misc.js
Normal file
19
api/functions/graphql/types/misc.js
Normal file
@@ -0,0 +1,19 @@
|
||||
const { objectType, extendType, inputObjectType } = require("nexus");
|
||||
const { prisma } = require('../../../prisma');
|
||||
|
||||
const ImageInput = inputObjectType({
|
||||
name: 'ImageInput',
|
||||
definition(t) {
|
||||
t.string('id');
|
||||
t.string('name');
|
||||
t.nonNull.string('url');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
module.exports = {
|
||||
// Types
|
||||
ImageInput,
|
||||
|
||||
// Queries
|
||||
}
|
||||
@@ -15,6 +15,9 @@ const { prisma } = require('../../../prisma');
|
||||
const { getUserByPubKey } = require('../../../auth/utils/helperFuncs');
|
||||
const { ApolloError } = require('apollo-server-lambda');
|
||||
const { marked } = require('marked');
|
||||
const { resolveImgObjectToUrl } = require('../../../utils/resolveImageUrl');
|
||||
const { ImageInput } = require('./misc');
|
||||
const { deleteImage } = require('../../../services/imageUpload.service');
|
||||
|
||||
|
||||
const POST_TYPE = enumType({
|
||||
@@ -37,7 +40,11 @@ const Author = objectType({
|
||||
definition(t) {
|
||||
t.nonNull.int('id');
|
||||
t.nonNull.string('name');
|
||||
t.nonNull.string('avatar');
|
||||
t.nonNull.string('avatar', {
|
||||
async resolve(parent) {
|
||||
return prisma.user.findUnique({ where: { id: parent.id } }).avatar_rel().then(resolveImgObjectToUrl)
|
||||
}
|
||||
});
|
||||
t.nonNull.date('join_date');
|
||||
|
||||
t.string('lightning_address');
|
||||
@@ -71,7 +78,11 @@ const Story = objectType({
|
||||
t.nonNull.string('type', {
|
||||
resolve: () => t.typeName
|
||||
});
|
||||
t.string('cover_image');
|
||||
t.string('cover_image', {
|
||||
async resolve(parent) {
|
||||
return prisma.story.findUnique({ where: { id: parent.id } }).cover_image_rel().then(resolveImgObjectToUrl)
|
||||
}
|
||||
});
|
||||
t.nonNull.list.nonNull.field('comments', {
|
||||
type: "PostComment",
|
||||
resolve: (parent) => []
|
||||
@@ -111,7 +122,9 @@ const StoryInputType = inputObjectType({
|
||||
t.int('id');
|
||||
t.nonNull.string('title');
|
||||
t.nonNull.string('body');
|
||||
t.string('cover_image');
|
||||
t.field('cover_image', {
|
||||
type: ImageInput
|
||||
})
|
||||
t.nonNull.list.nonNull.string('tags');
|
||||
t.boolean('is_published')
|
||||
}
|
||||
@@ -342,6 +355,64 @@ const getPostById = extendType({
|
||||
}
|
||||
})
|
||||
|
||||
const addCoverImage = async (providerImageId) => {
|
||||
const newCoverImage = await prisma.hostedImage.findFirst({
|
||||
where: {
|
||||
provider_image_id: providerImageId
|
||||
}
|
||||
})
|
||||
|
||||
if (!newCoverImage) throw new ApolloError("New cover image not found")
|
||||
|
||||
await prisma.hostedImage.update({
|
||||
where: {
|
||||
id: newCoverImage.id
|
||||
},
|
||||
data: {
|
||||
is_used: true
|
||||
}
|
||||
})
|
||||
|
||||
return newCoverImage
|
||||
}
|
||||
|
||||
const getHostedImageIdsFromBody = async (body, oldBodyImagesIds = null) => {
|
||||
let bodyImageIds = []
|
||||
|
||||
const regex = /(?:!\[(.*?)\]\((.*?)\))/g
|
||||
let match;
|
||||
while ((match = regex.exec(body))) {
|
||||
const [, , value] = match
|
||||
|
||||
// Useful for old external images in case of duplicates. We need to be sure we are targeting an image from the good story.
|
||||
const where = oldBodyImagesIds ? {
|
||||
AND: [
|
||||
{ url: value },
|
||||
{ id: { in: oldBodyImagesIds } }
|
||||
]
|
||||
} :
|
||||
{
|
||||
url: value,
|
||||
}
|
||||
|
||||
const hostedImage = await prisma.hostedImage.findFirst({
|
||||
where
|
||||
})
|
||||
if (hostedImage) {
|
||||
bodyImageIds.push(hostedImage.id)
|
||||
await prisma.hostedImage.update({
|
||||
where: {
|
||||
id: hostedImage.id
|
||||
},
|
||||
data: {
|
||||
is_used: true
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
return bodyImageIds
|
||||
}
|
||||
|
||||
const createStory = extendType({
|
||||
type: 'Mutation',
|
||||
definition(t) {
|
||||
@@ -358,20 +429,64 @@ const createStory = extendType({
|
||||
|
||||
let was_published = false;
|
||||
|
||||
|
||||
// TODO: validate post data
|
||||
|
||||
let coverImage = null
|
||||
let bodyImageIds = []
|
||||
|
||||
// Edit story
|
||||
if (id) {
|
||||
const oldPost = await prisma.story.findFirst({
|
||||
where: { id },
|
||||
select: {
|
||||
user_id: true,
|
||||
is_published: true
|
||||
is_published: true,
|
||||
cover_image_id: true,
|
||||
body_image_ids: true
|
||||
}
|
||||
})
|
||||
was_published = oldPost.is_published;
|
||||
if (user.id !== oldPost.user_id)
|
||||
throw new ApolloError("Not post author")
|
||||
}
|
||||
// TODO: validate post data
|
||||
if (user.id !== oldPost.user_id) throw new ApolloError("Not post author")
|
||||
|
||||
// Body images
|
||||
bodyImageIds = await getHostedImageIdsFromBody(body, oldPost.body_image_ids)
|
||||
|
||||
// Old cover image is found
|
||||
if (oldPost.cover_image_id) {
|
||||
const oldCoverImage = await prisma.hostedImage.findFirst({
|
||||
where: {
|
||||
id: oldPost.cover_image_id
|
||||
}
|
||||
})
|
||||
|
||||
// New cover image
|
||||
if (cover_image?.id && cover_image.id !== oldCoverImage?.provider_image_id) {
|
||||
await deleteImage(oldCoverImage.id)
|
||||
coverImage = await addCoverImage(cover_image.id)
|
||||
} else {
|
||||
coverImage = oldCoverImage
|
||||
}
|
||||
} else {
|
||||
// No old image found and new cover image
|
||||
if (cover_image?.id) {
|
||||
coverImage = await addCoverImage(cover_image.id)
|
||||
}
|
||||
}
|
||||
|
||||
// Remove unused body images
|
||||
const unusedImagesIds = oldPost.body_image_ids.filter(x => !bodyImageIds.includes(x));
|
||||
unusedImagesIds.map(async i => await deleteImage(i))
|
||||
|
||||
} else {
|
||||
// Body images
|
||||
bodyImageIds = await getHostedImageIdsFromBody(body)
|
||||
|
||||
// New story and new cover image
|
||||
if (cover_image?.id) {
|
||||
coverImage = await addCoverImage(cover_image.id)
|
||||
}
|
||||
}
|
||||
|
||||
// Preprocess & insert
|
||||
const htmlBody = marked.parse(body);
|
||||
@@ -383,6 +498,16 @@ const createStory = extendType({
|
||||
.replace(/"/g, '"')
|
||||
;
|
||||
|
||||
|
||||
const coverImageRel = coverImage ? {
|
||||
cover_image_rel: {
|
||||
connect:
|
||||
{
|
||||
id: coverImage ? coverImage.id : null
|
||||
}
|
||||
}
|
||||
} : {}
|
||||
|
||||
if (id) {
|
||||
await prisma.story.update({
|
||||
where: { id },
|
||||
@@ -398,7 +523,7 @@ const createStory = extendType({
|
||||
data: {
|
||||
title,
|
||||
body,
|
||||
cover_image,
|
||||
cover_image: '',
|
||||
excerpt,
|
||||
is_published: was_published || is_published,
|
||||
tags: {
|
||||
@@ -415,16 +540,17 @@ const createStory = extendType({
|
||||
}
|
||||
})
|
||||
},
|
||||
body_image_ids: bodyImageIds,
|
||||
...coverImageRel
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
return prisma.story.create({
|
||||
return await prisma.story.create({
|
||||
data: {
|
||||
title,
|
||||
body,
|
||||
cover_image,
|
||||
cover_image: '',
|
||||
excerpt,
|
||||
is_published,
|
||||
tags: {
|
||||
@@ -445,7 +571,9 @@ const createStory = extendType({
|
||||
connect: {
|
||||
id: user.id,
|
||||
}
|
||||
}
|
||||
},
|
||||
body_image_ids: bodyImageIds,
|
||||
...coverImageRel
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -470,17 +598,39 @@ const deleteStory = extendType({
|
||||
const oldPost = await prisma.story.findFirst({
|
||||
where: { id },
|
||||
select: {
|
||||
user_id: true
|
||||
user_id: true,
|
||||
body_image_ids: true,
|
||||
cover_image_id: true
|
||||
}
|
||||
})
|
||||
if (user.id !== oldPost.user_id)
|
||||
throw new ApolloError("Not post author")
|
||||
|
||||
return prisma.story.delete({
|
||||
const deletedPost = await prisma.story.delete({
|
||||
where: {
|
||||
id
|
||||
}
|
||||
})
|
||||
|
||||
const coverImage = await prisma.hostedImage.findMany({
|
||||
where: {
|
||||
OR: [
|
||||
{ id: oldPost.cover_image_id },
|
||||
{
|
||||
id: {
|
||||
in: oldPost.body_image_ids
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
provider_image_id: true
|
||||
}
|
||||
})
|
||||
coverImage.map(async i => await deleteImage(i.id))
|
||||
|
||||
return deletedPost
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
@@ -8,6 +8,7 @@ const {
|
||||
inputObjectType,
|
||||
} = require('nexus')
|
||||
const { prisma } = require('../../../prisma');
|
||||
const { resolveImgObjectToUrl } = require('../../../utils/resolveImageUrl');
|
||||
|
||||
const { paginationArgs, getLnurlDetails, lightningAddressToLnurl } = require('./helpers');
|
||||
const { MakerRole } = require('./users');
|
||||
@@ -19,9 +20,30 @@ const Project = objectType({
|
||||
t.nonNull.int('id');
|
||||
t.nonNull.string('title');
|
||||
t.nonNull.string('description');
|
||||
t.nonNull.string('cover_image');
|
||||
t.nonNull.string('thumbnail_image');
|
||||
t.nonNull.list.nonNull.string('screenshots');
|
||||
t.nonNull.string('cover_image', {
|
||||
async resolve(parent) {
|
||||
return prisma.project.findUnique({ where: { id: parent.id } }).cover_image_rel().then(resolveImgObjectToUrl)
|
||||
}
|
||||
});
|
||||
t.nonNull.string('thumbnail_image', {
|
||||
async resolve(parent) {
|
||||
return prisma.project.findUnique({ where: { id: parent.id } }).thumbnail_image_rel().then(resolveImgObjectToUrl)
|
||||
}
|
||||
});
|
||||
t.nonNull.list.nonNull.string('screenshots', {
|
||||
async resolve(parent) {
|
||||
if (!parent.screenshots_ids) return null
|
||||
const imgObject = await prisma.hostedImage.findMany({
|
||||
where: {
|
||||
id: { in: parent.screenshots_ids }
|
||||
}
|
||||
});
|
||||
|
||||
return imgObject.map(img => {
|
||||
return resolveImgObjectToUrl(img);
|
||||
});
|
||||
}
|
||||
});
|
||||
t.nonNull.string('website');
|
||||
t.string('lightning_address');
|
||||
t.string('lnurl_callback_url');
|
||||
|
||||
@@ -9,6 +9,7 @@ const {
|
||||
booleanArg,
|
||||
} = require('nexus');
|
||||
const { getUserByPubKey } = require('../../../auth/utils/helperFuncs');
|
||||
const { resolveImgObjectToUrl } = require('../../../utils/resolveImageUrl');
|
||||
const { prisma } = require('../../../prisma');
|
||||
const { paginationArgs, removeNulls } = require('./helpers');
|
||||
|
||||
@@ -19,7 +20,11 @@ const TournamentPrize = objectType({
|
||||
definition(t) {
|
||||
t.nonNull.string('title');
|
||||
t.nonNull.string('amount');
|
||||
t.nonNull.string('image');
|
||||
t.nonNull.string('image', {
|
||||
async resolve(parent) {
|
||||
return prisma.tournamentPrize.findUnique({ where: { id: parent.id } }).image_rel().then(resolveImgObjectToUrl)
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
@@ -28,7 +33,11 @@ const TournamentJudge = objectType({
|
||||
definition(t) {
|
||||
t.nonNull.string('name');
|
||||
t.nonNull.string('company');
|
||||
t.nonNull.string('avatar');
|
||||
t.nonNull.string('avatar', {
|
||||
async resolve(parent) {
|
||||
return prisma.tournamentJudge.findUnique({ where: { id: parent.id } }).avatar_rel().then(resolveImgObjectToUrl)
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
@@ -74,7 +83,11 @@ const TournamentEvent = objectType({
|
||||
definition(t) {
|
||||
t.nonNull.int('id');
|
||||
t.nonNull.string('title');
|
||||
t.nonNull.string('image');
|
||||
t.nonNull.string('image', {
|
||||
async resolve(parent) {
|
||||
return prisma.tournamentEvent.findUnique({ where: { id: parent.id } }).image_rel().then(resolveImgObjectToUrl)
|
||||
}
|
||||
});
|
||||
t.nonNull.string('description');
|
||||
t.nonNull.date('starts_at');
|
||||
t.nonNull.date('ends_at');
|
||||
@@ -91,8 +104,16 @@ const Tournament = objectType({
|
||||
t.nonNull.int('id');
|
||||
t.nonNull.string('title');
|
||||
t.nonNull.string('description');
|
||||
t.nonNull.string('thumbnail_image');
|
||||
t.nonNull.string('cover_image');
|
||||
t.nonNull.string('thumbnail_image', {
|
||||
async resolve(parent) {
|
||||
return prisma.tournament.findUnique({ where: { id: parent.id } }).thumbnail_image_rel().then(resolveImgObjectToUrl)
|
||||
}
|
||||
});
|
||||
t.nonNull.string('cover_image', {
|
||||
async resolve(parent) {
|
||||
return prisma.tournament.findUnique({ where: { id: parent.id } }).cover_image_rel().then(resolveImgObjectToUrl)
|
||||
}
|
||||
});
|
||||
t.nonNull.date('start_date');
|
||||
t.nonNull.date('end_date');
|
||||
t.nonNull.string('location');
|
||||
|
||||
0
api/functions/graphql/types/tournaments.js
Normal file
0
api/functions/graphql/types/tournaments.js
Normal file
@@ -3,7 +3,10 @@ const { prisma } = require('../../../prisma');
|
||||
const { objectType, extendType, intArg, nonNull, inputObjectType, stringArg, interfaceType, list, enumType } = require("nexus");
|
||||
const { getUserByPubKey } = require("../../../auth/utils/helperFuncs");
|
||||
const { removeNulls } = require("./helpers");
|
||||
const { ImageInput } = require('./misc');
|
||||
const { Tournament } = require('./tournament');
|
||||
const { resolveImgObjectToUrl } = require('../../../utils/resolveImageUrl');
|
||||
const { deleteImage } = require('../../../services/imageUpload.service');
|
||||
|
||||
|
||||
|
||||
@@ -13,7 +16,11 @@ const BaseUser = interfaceType({
|
||||
definition(t) {
|
||||
t.nonNull.int('id');
|
||||
t.nonNull.string('name');
|
||||
t.nonNull.string('avatar');
|
||||
t.nonNull.string('avatar', {
|
||||
async resolve(parent) {
|
||||
return prisma.user.findUnique({ where: { id: parent.id } }).avatar_rel().then(resolveImgObjectToUrl)
|
||||
}
|
||||
});
|
||||
t.nonNull.date('join_date');
|
||||
t.string('role');
|
||||
t.string('jobTitle')
|
||||
@@ -289,7 +296,9 @@ const ProfileDetailsInput = inputObjectType({
|
||||
name: 'ProfileDetailsInput',
|
||||
definition(t) {
|
||||
t.string('name');
|
||||
t.string('avatar');
|
||||
t.field('avatar', {
|
||||
type: ImageInput
|
||||
})
|
||||
t.string('email')
|
||||
t.string('jobTitle')
|
||||
t.string('lightning_address')
|
||||
@@ -317,14 +326,48 @@ const updateProfileDetails = extendType({
|
||||
throw new Error("You have to login");
|
||||
// TODO: validate new data
|
||||
|
||||
// ----------------
|
||||
// Check if the user uploaded a new image, and if so,
|
||||
// remove the old one from the hosting service, then replace it with this one
|
||||
// ----------------
|
||||
let avatarId = user.avatar_id;
|
||||
if (args.data.avatar.id) {
|
||||
const newAvatarProviderId = args.data.avatar.id;
|
||||
const newAvatar = await prisma.hostedImage.findFirst({
|
||||
where: {
|
||||
provider_image_id: newAvatarProviderId
|
||||
}
|
||||
})
|
||||
|
||||
if (newAvatar && newAvatar.id !== user.avatar_id) {
|
||||
avatarId = newAvatar.id;
|
||||
|
||||
await prisma.hostedImage.update({
|
||||
where: {
|
||||
id: newAvatar.id
|
||||
},
|
||||
data: {
|
||||
is_used: true
|
||||
}
|
||||
});
|
||||
|
||||
await deleteImage(user.avatar_id)
|
||||
}
|
||||
}
|
||||
|
||||
// Preprocess & insert
|
||||
|
||||
return prisma.user.update({
|
||||
where: {
|
||||
id: user.id,
|
||||
},
|
||||
data: removeNulls(args.data)
|
||||
data: removeNulls({
|
||||
...args.data,
|
||||
avatar_id: avatarId,
|
||||
|
||||
//hack to remove avatar from args.data
|
||||
// can be removed later with a schema data validator
|
||||
avatar: '',
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
@@ -85,11 +85,21 @@ const loginHandler = async (req, res) => {
|
||||
const nostr_prv_key = generatePrivateKey();
|
||||
const nostr_pub_key = getPublicKey(nostr_prv_key);
|
||||
|
||||
const avatar = await prisma.hostedImage.create({
|
||||
data: {
|
||||
filename: 'avatar.svg',
|
||||
provider: 'external',
|
||||
is_used: true,
|
||||
url: `https://avatars.dicebear.com/api/bottts/${key}.svg`,
|
||||
provider_image_id: ''
|
||||
}
|
||||
})
|
||||
|
||||
const createdUser = await prisma.user.create({
|
||||
data: {
|
||||
pubKey: key,
|
||||
name: key,
|
||||
avatar: `https://avatars.dicebear.com/api/bottts/${key}.svg`,
|
||||
avatar_id: avatar.id,
|
||||
nostr_prv_key,
|
||||
nostr_pub_key,
|
||||
},
|
||||
|
||||
@@ -5,11 +5,10 @@ const extractKeyFromCookie = require('../../utils/extractKeyFromCookie')
|
||||
const { getUserByPubKey } = require('../../auth/utils/helperFuncs')
|
||||
const { getDirectUploadUrl } = require('../../services/imageUpload.service')
|
||||
const { prisma } = require('../../prisma')
|
||||
const { getUrlFromProvider } = require('../../utils/resolveImageUrl')
|
||||
|
||||
const postUploadImageUrl = async (req, res) => {
|
||||
|
||||
return res.status(404).send("This api is in progress");
|
||||
|
||||
const userPubKey = await extractKeyFromCookie(req.headers.cookie ?? req.headers.Cookie)
|
||||
const user = await getUserByPubKey(userPubKey)
|
||||
|
||||
@@ -22,11 +21,16 @@ const postUploadImageUrl = async (req, res) => {
|
||||
try {
|
||||
const uploadUrl = await getDirectUploadUrl()
|
||||
|
||||
await prisma.hostedImage.create({
|
||||
data: { id: uploadUrl.id, filename },
|
||||
const hostedImage = await prisma.hostedImage.create({
|
||||
data: {
|
||||
filename,
|
||||
url: getUrlFromProvider(uploadUrl.provider, uploadUrl.id),
|
||||
provider_image_id: uploadUrl.id,
|
||||
provider: uploadUrl.provider
|
||||
},
|
||||
})
|
||||
|
||||
return res.status(200).json(uploadUrl)
|
||||
return res.status(200).json({ id: hostedImage.id, uploadURL: uploadUrl.uploadURL })
|
||||
} catch (error) {
|
||||
res.status(500).send('Unexpected error happened, please try again')
|
||||
}
|
||||
|
||||
@@ -1,11 +1,21 @@
|
||||
const { CONSTS } = require('../utils')
|
||||
const axios = require('axios')
|
||||
const FormData = require('form-data')
|
||||
const { prisma } = require('../prisma')
|
||||
|
||||
const BASE_URL = 'https://api.cloudflare.com/client/v4'
|
||||
|
||||
const operationUrls = {
|
||||
'image.uploadUrl': `${BASE_URL}/accounts/${CONSTS.CLOUDFLARE_IMAGE_ACCOUNT_ID}/images/v2/direct_upload`,
|
||||
'image.delete': `${BASE_URL}/accounts/${CONSTS.CLOUDFLARE_IMAGE_ACCOUNT_ID}/images/v1/`,
|
||||
}
|
||||
|
||||
function getAxiosConfig() {
|
||||
return {
|
||||
headers: {
|
||||
Authorization: `Bearer ${CONSTS.CLOUDFLARE_IMAGE_API_KEY}`,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
async function getDirectUploadUrl() {
|
||||
@@ -27,9 +37,62 @@ async function getDirectUploadUrl() {
|
||||
throw new Error(result.data, { cause: result.data.errors })
|
||||
}
|
||||
|
||||
return result.data.result
|
||||
const data = result.data.result
|
||||
|
||||
return { id: data.id, uploadURL: data.uploadURL, provider: 'cloudflare' }
|
||||
}
|
||||
|
||||
async function deleteImageFromProvider(providerImageId) {
|
||||
try {
|
||||
const url = operationUrls['image.delete'] + providerImageId
|
||||
const result = await axios.delete(url, getAxiosConfig())
|
||||
|
||||
if (!result.data.success) {
|
||||
throw new Error(result.data, { cause: result.data.errors })
|
||||
}
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteImage(hostedImageId) {
|
||||
if (!hostedImageId) throw new Error("argument 'hostedImageId' must be provider")
|
||||
|
||||
const hostedImage = await prisma.hostedImage.findFirst({
|
||||
where: {
|
||||
id: hostedImageId,
|
||||
},
|
||||
})
|
||||
|
||||
if (!hostedImage) throw new Error(`No HostedImage row found for HostedImage.id=${hostedImageId}`)
|
||||
if (hostedImage.provider_image_id && hostedImage.provider_image_id === '')
|
||||
throw new Error(`Field 'provider_image_id' for HostedImage.id=${hostedImageId} must not be empty. Current value '${hostedImage.provider_image_id}'`)
|
||||
|
||||
// Set is_used to false in case of deletion fail from the hosting image provider. The scheduled job will try to delete the HostedImage row
|
||||
await prisma.hostedImage.update({
|
||||
where: {
|
||||
id: hostedImage.id,
|
||||
},
|
||||
data: {
|
||||
is_used: false,
|
||||
},
|
||||
})
|
||||
|
||||
if (hostedImage.provider_image_id && hostedImage.provider_image_id !== '') {
|
||||
deleteImageFromProvider(hostedImage.provider_image_id)
|
||||
.then(async () => {
|
||||
await prisma.hostedImage.delete({
|
||||
where: {
|
||||
id: hostedImageId,
|
||||
},
|
||||
})
|
||||
})
|
||||
.catch((error) => console.error(error))
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getDirectUploadUrl,
|
||||
deleteImage,
|
||||
deleteImageFromProvider,
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ const JWT_SECRET = process.env.JWT_SECRET
|
||||
const LNURL_AUTH_HOST = process.env.LNURL_AUTH_HOST
|
||||
const CLOUDFLARE_IMAGE_ACCOUNT_ID = process.env.CLOUDFLARE_IMAGE_ACCOUNT_ID
|
||||
const CLOUDFLARE_IMAGE_API_KEY = process.env.CLOUDFLARE_IMAGE_API_KEY
|
||||
const CLOUDFLARE_IMAGE_ACCOUNT_HASH = process.env.CLOUDFLARE_IMAGE_ACCOUNT_HASH
|
||||
|
||||
const CONSTS = {
|
||||
JWT_SECRET,
|
||||
@@ -10,6 +11,7 @@ const CONSTS = {
|
||||
LNURL_AUTH_HOST,
|
||||
CLOUDFLARE_IMAGE_ACCOUNT_ID,
|
||||
CLOUDFLARE_IMAGE_API_KEY,
|
||||
CLOUDFLARE_IMAGE_ACCOUNT_HASH
|
||||
}
|
||||
|
||||
module.exports = CONSTS
|
||||
|
||||
45
api/utils/resolveImageUrl.js
Normal file
45
api/utils/resolveImageUrl.js
Normal file
@@ -0,0 +1,45 @@
|
||||
const { CLOUDFLARE_IMAGE_ACCOUNT_HASH } = require('./consts')
|
||||
|
||||
const PROVIDERS = [
|
||||
{
|
||||
name: 'cloudflare',
|
||||
prefixUrl: `https://imagedelivery.net/${CLOUDFLARE_IMAGE_ACCOUNT_HASH}/`,
|
||||
variants: [
|
||||
{
|
||||
default: true,
|
||||
name: 'public',
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
/**
|
||||
* resolveImgObjectToUrl
|
||||
* @param {object} imgObject
|
||||
* @param {string} variant - List to be defined. DEFAULT TO 'public'
|
||||
* @returns {string} image url
|
||||
*/
|
||||
function resolveImgObjectToUrl(imgObject, variant = null) {
|
||||
if (!imgObject) return null;
|
||||
|
||||
if (imgObject.provider === 'external') {
|
||||
return imgObject.url
|
||||
}
|
||||
|
||||
return getUrlFromProvider(imgObject.provider, imgObject.provider_image_id, variant)
|
||||
}
|
||||
|
||||
function getUrlFromProvider(provider, providerImageId, variant = null) {
|
||||
const p = PROVIDERS.find((p) => p.name === provider)
|
||||
|
||||
if (p) {
|
||||
if (p && p.name === 'cloudflare') {
|
||||
const variantName = variant ?? p.variants.find((v) => v.default).name
|
||||
return p.prefixUrl + providerImageId + '/' + variantName
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('Hosting images provider not supported')
|
||||
}
|
||||
|
||||
module.exports = { resolveImgObjectToUrl, getUrlFromProvider }
|
||||
Reference in New Issue
Block a user