Try to get uploads to work
This commit is contained in:
parent
7ccc951107
commit
04101bf674
|
@ -72,6 +72,7 @@ declare namespace GQL {
|
|||
location: ILocation;
|
||||
occurrences: Array<IEventOccurrence | null> | null;
|
||||
tags: Array<IEventTag | null> | null;
|
||||
images: IEventImages | null;
|
||||
}
|
||||
|
||||
interface IInfoOnCalendarEventArguments {
|
||||
|
@ -127,6 +128,19 @@ declare namespace GQL {
|
|||
text: string;
|
||||
}
|
||||
|
||||
interface IEventImages {
|
||||
__typename: 'EventImages';
|
||||
thumb: IImage | null;
|
||||
cover: IImage | null;
|
||||
poster: IImage | null;
|
||||
gallery: Array<IImage> | null;
|
||||
}
|
||||
|
||||
interface IImage {
|
||||
__typename: 'Image';
|
||||
url: string;
|
||||
}
|
||||
|
||||
interface IUserRole {
|
||||
__typename: 'UserRole';
|
||||
user: IUser;
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
"date-fns": "^2.7.0",
|
||||
"dotenv": "^8.2.0",
|
||||
"express": "^4.17.1",
|
||||
"fs-capacitor": "^5.0.0",
|
||||
"graphql": "^14.4.2",
|
||||
"graphql-import": "^0.7.1",
|
||||
"graphql-tag": "^2.10.1",
|
||||
|
|
|
@ -21,7 +21,6 @@ interface UploadImageOptions {
|
|||
interface FileMeta {
|
||||
filename: string
|
||||
mimetype: string
|
||||
encoding: string
|
||||
}
|
||||
|
||||
// General pattern for filename:
|
||||
|
@ -38,22 +37,36 @@ export default class ImageBucketService {
|
|||
this.gcsBucket = new Storage().bucket(this.options.gcsBucketName)
|
||||
|
||||
// quick fail in case of bad credentials
|
||||
this.gcsBucket.getFiles()
|
||||
this.gcsBucket.getFiles().catch(err => {
|
||||
console.error("Error accessing bucket", err)
|
||||
})
|
||||
}
|
||||
|
||||
saveLocally(stream: ReadableStream, fileMeta: FileMeta): string {
|
||||
const splitFileName = fileMeta.filename.split(".")
|
||||
addRandomHashToFilename = (filename: string) => {
|
||||
const splitFileName = filename.split(".")
|
||||
const randomSequence = randomstring({ length: 6 })
|
||||
splitFileName.reverse()
|
||||
splitFileName.splice(1, 0, randomstring(6))
|
||||
splitFileName.splice(1, 0, randomSequence)
|
||||
splitFileName.reverse()
|
||||
const targetFileName = splitFileName.join(".")
|
||||
}
|
||||
|
||||
async saveLocally(
|
||||
stream: ReadableStream,
|
||||
fileMeta: FileMeta
|
||||
): Promise<string> {
|
||||
const targetFileName = this.addRandomHashToFilename(fileMeta.filename)
|
||||
const targetFullPath = `${this.options.tmpLocalPath}/${targetFileName}`
|
||||
|
||||
fs.writeFileSync(targetFullPath, stream, {
|
||||
encoding: fileMeta.encoding,
|
||||
console.log('::STREAM::',typeof stream, JSON.stringify(stream))
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.writeFile(targetFullPath, stream, err => {
|
||||
if (err) {
|
||||
console.error("error writing file", err)
|
||||
reject(err)
|
||||
}
|
||||
resolve(targetFullPath)
|
||||
})
|
||||
})
|
||||
|
||||
return targetFullPath
|
||||
}
|
||||
|
||||
async deleteLocally(path: string) {
|
||||
|
@ -61,31 +74,40 @@ export default class ImageBucketService {
|
|||
}
|
||||
|
||||
async uploadToBucket(file: FileUpload, options: UploadImageOptions) {
|
||||
const localTempPath = this.saveLocally(file.createReadStream(), {
|
||||
encoding: file.encoding,
|
||||
const localTempPath = await this.saveLocally(file.createReadStream(), {
|
||||
filename: file.filename,
|
||||
mimetype: file.mimetype,
|
||||
})
|
||||
console.log("local save call terminated", localTempPath)
|
||||
|
||||
const prefix = `e/${options.eventId}/${options.imageType}_`
|
||||
const [existingFiles] = await this.gcsBucket.getFiles({
|
||||
prefix,
|
||||
})
|
||||
const highest: number = existingFiles
|
||||
.map(file => parseInt(file.name.split(prefix)[1]))
|
||||
.sort()
|
||||
.reverse()[0]
|
||||
console.log("existing filenames", existingFiles.map(file => file.name))
|
||||
|
||||
const existingIds: number[] = existingFiles.map(file =>
|
||||
parseInt(file.name.split(prefix)[1])
|
||||
)
|
||||
|
||||
const highest = Math.max(0, ...existingIds)
|
||||
const next = `${highest}`.padStart(4, "0")
|
||||
const bucketDestination = `${prefix}${next}`
|
||||
|
||||
const [bucketFile, _]: [
|
||||
File,
|
||||
Metadata
|
||||
] = await this.gcsBucket.upload(localTempPath, {
|
||||
destination: bucketDestination,
|
||||
})
|
||||
console.log(
|
||||
"will bucket upload from",
|
||||
localTempPath,
|
||||
"to",
|
||||
bucketDestination
|
||||
)
|
||||
const [bucketFile]: [File, Metadata] = await this.gcsBucket.upload(
|
||||
localTempPath,
|
||||
{
|
||||
destination: bucketDestination,
|
||||
}
|
||||
)
|
||||
|
||||
this.deleteLocally(localTempPath) // don't care about result
|
||||
// this.deleteLocally(localTempPath) // don't care about result
|
||||
return `${this.options.buckerPublicURLBase}/${bucketFile.name}`
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,10 @@ import { User } from "../Auth/User.entity";
|
|||
import { FileUpload } from "graphql-upload";
|
||||
import { EventImage, ImageType } from "./EventImage.entity";
|
||||
|
||||
const canChangeEvent = async (event: Event, user: User) => {
|
||||
const canChangeEvent = async (event: Event, user?: User) => {
|
||||
if (!user) {
|
||||
return false
|
||||
}
|
||||
const roles = (await user.roles).map(role => role.type)
|
||||
if (roles.includes("admin") || roles.includes("embassador")) {
|
||||
return true
|
||||
|
@ -24,6 +27,7 @@ export const EventImageResolvers = (
|
|||
context: Context,
|
||||
info
|
||||
) => {
|
||||
console.log('Mutation resolver: setEventCoverImage')
|
||||
const event = await Event.findOne(req.input.id)
|
||||
if (!event) {
|
||||
throw new Error(`Event with id ${req.input.id} not found`)
|
||||
|
@ -34,15 +38,15 @@ export const EventImageResolvers = (
|
|||
)
|
||||
}
|
||||
|
||||
console.log('will get upload form input')
|
||||
const fileUpload: FileUpload = await req.input.file
|
||||
fileUpload.filename
|
||||
console.log('got a file upload', JSON.stringify(fileUpload))
|
||||
const coverImageURL = await imageBucketService.uploadToBucket(
|
||||
fileUpload,
|
||||
{
|
||||
fileMeta: {
|
||||
filename: fileUpload.filename,
|
||||
mimetype: fileUpload.mimetype,
|
||||
encoding: fileUpload.encoding,
|
||||
},
|
||||
imageType: ImageType.Cover,
|
||||
eventId: req.input.id,
|
||||
|
|
122
src/graphql.ts
122
src/graphql.ts
|
@ -1,17 +1,18 @@
|
|||
import {ApolloServer} from 'apollo-server-express'
|
||||
import {makeExecutableSchema} from "graphql-tools"
|
||||
import EventsResolvers from './Events/eventsResolvers'
|
||||
import UserResolvers from './Auth/userResolvers'
|
||||
import {importSchema} from 'graphql-import'
|
||||
import { ApolloServer } from "apollo-server-express"
|
||||
import { makeExecutableSchema } from "graphql-tools"
|
||||
import EventsResolvers from "./Events/eventsResolvers"
|
||||
import UserResolvers from "./Auth/userResolvers"
|
||||
import { importSchema } from "graphql-import"
|
||||
import AuthResolvers from "./Auth/authResolvers"
|
||||
import {Connection} from "typeorm"
|
||||
import {PostOffice} from "./post_office"
|
||||
import {Session} from "./Auth/Session.entity"
|
||||
import {Context} from "./@types/graphql-utils"
|
||||
import { Connection } from "typeorm"
|
||||
import { PostOffice } from "./post_office"
|
||||
import { Session } from "./Auth/Session.entity"
|
||||
import { Context } from "./@types/graphql-utils"
|
||||
import SessionManager from "./Auth/SessionManager"
|
||||
import { tagResolvers } from "./Calendar/tagsResolvers";
|
||||
import ImageBucketService from "./Image/ImageBucketService";
|
||||
import { EventImageResolvers } from "./Image/eventImageResolvers";
|
||||
import { tagResolvers } from "./Calendar/tagsResolvers"
|
||||
import ImageBucketService from "./Image/ImageBucketService"
|
||||
import { EventImageResolvers } from "./Image/eventImageResolvers"
|
||||
import { GraphQLUpload } from "graphql-upload"
|
||||
|
||||
interface Dependencies {
|
||||
typeormConnection: Connection
|
||||
|
@ -24,35 +25,44 @@ interface Dependencies {
|
|||
|
||||
const resolvers = {
|
||||
Query: {},
|
||||
Mutation: {}
|
||||
Mutation: {},
|
||||
}
|
||||
|
||||
export const createServer = async (dependencies: Dependencies) => {
|
||||
|
||||
export const createServer = async (
|
||||
dependencies: Dependencies
|
||||
): Promise<ApolloServer> => {
|
||||
const authResolvers = new AuthResolvers({
|
||||
sendEmail: dependencies.sendEmail,
|
||||
emailTargetDomain: dependencies.domain,
|
||||
sessionManager: dependencies.sessionManager
|
||||
sessionManager: dependencies.sessionManager,
|
||||
})
|
||||
|
||||
const eventImageResolvers = EventImageResolvers(dependencies.imageBucketService)
|
||||
const typeDefs = importSchema(__dirname + '/schema.graphql')
|
||||
|
||||
const { Query: EventQueryResolvers, Mutation: EventResolversMutation,...eventResolvers} = EventsResolvers
|
||||
const { Query: UserQueryResolvers, Mutation: UserMutationResolvers, ...userResolvers} = UserResolvers
|
||||
const eventImageResolvers = EventImageResolvers(
|
||||
dependencies.imageBucketService
|
||||
)
|
||||
const typeDefs = importSchema(__dirname + "/schema.graphql")
|
||||
const {
|
||||
Query: EventQueryResolvers,
|
||||
Mutation: EventResolversMutation,
|
||||
...eventResolvers
|
||||
} = EventsResolvers
|
||||
const {
|
||||
Query: UserQueryResolvers,
|
||||
Mutation: UserMutationResolvers,
|
||||
...userResolvers
|
||||
} = UserResolvers
|
||||
|
||||
const schema = makeExecutableSchema({
|
||||
typeDefs: [
|
||||
typeDefs
|
||||
],
|
||||
typeDefs: [typeDefs],
|
||||
resolvers: {
|
||||
Upload: GraphQLUpload,
|
||||
Query: {
|
||||
...resolvers.Query,
|
||||
...EventQueryResolvers,
|
||||
...authResolvers.Query,
|
||||
...UserQueryResolvers,
|
||||
...UserQueryResolvers,
|
||||
...tagResolvers.Query,
|
||||
...eventImageResolvers.Query
|
||||
...eventImageResolvers.Query,
|
||||
},
|
||||
Mutation: {
|
||||
...resolvers.Mutation,
|
||||
|
@ -60,13 +70,13 @@ export const createServer = async (dependencies: Dependencies) => {
|
|||
...authResolvers.Mutation,
|
||||
...UserMutationResolvers,
|
||||
...tagResolvers.Mutation,
|
||||
...eventImageResolvers.Mutation
|
||||
...eventImageResolvers.Mutation,
|
||||
},
|
||||
UserSession: {
|
||||
...authResolvers.UserSession
|
||||
...authResolvers.UserSession,
|
||||
},
|
||||
EventTag: {
|
||||
...tagResolvers.EventTag
|
||||
...tagResolvers.EventTag,
|
||||
},
|
||||
...eventResolvers,
|
||||
...userResolvers,
|
||||
|
@ -75,30 +85,38 @@ export const createServer = async (dependencies: Dependencies) => {
|
|||
|
||||
return new ApolloServer({
|
||||
schema,
|
||||
context: dependencies.customContext ? dependencies.customContext : async (a) => {
|
||||
const ctx: Context = {
|
||||
req: a.req
|
||||
}
|
||||
let authToken
|
||||
if (a.req.header('authentication')) {
|
||||
authToken = a.req.header('authentication')
|
||||
}
|
||||
if (a.req.headers.cookie) {
|
||||
console.log('a.req.headers.cookie', a.req.headers.cookie)
|
||||
const cookies: any = a.req.headers.cookie.split(';').map(s => s.trim().split('=')).reduce((acc, val)=> {acc[val[0]] = val[1]; return acc},{})
|
||||
context: dependencies.customContext
|
||||
? dependencies.customContext
|
||||
: async a => {
|
||||
const ctx: Context = {
|
||||
req: a.req,
|
||||
}
|
||||
let authToken
|
||||
if (a.req.header("authentication")) {
|
||||
authToken = a.req.header("authentication")
|
||||
}
|
||||
if (a.req.headers.cookie) {
|
||||
console.log("a.req.headers.cookie", a.req.headers.cookie)
|
||||
const cookies: any = a.req.headers.cookie
|
||||
.split(";")
|
||||
.map(s => s.trim().split("="))
|
||||
.reduce((acc, val) => {
|
||||
acc[val[0]] = val[1]
|
||||
return acc
|
||||
}, {})
|
||||
|
||||
if (cookies.authentication) {
|
||||
authToken = cookies.authentication as string
|
||||
}
|
||||
}
|
||||
if (authToken) {
|
||||
const session = await Session.findOne({hash: authToken})
|
||||
if (session) {
|
||||
ctx.user = session.user
|
||||
}
|
||||
}
|
||||
if (cookies.authentication) {
|
||||
authToken = cookies.authentication as string
|
||||
}
|
||||
}
|
||||
if (authToken) {
|
||||
const session = await Session.findOne({ hash: authToken })
|
||||
if (session) {
|
||||
ctx.user = session.user
|
||||
}
|
||||
}
|
||||
|
||||
return ctx
|
||||
}
|
||||
return ctx
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
45
src/index.ts
45
src/index.ts
|
@ -1,21 +1,21 @@
|
|||
require('dotenv').config()
|
||||
require("dotenv").config()
|
||||
|
||||
import { createServer } from "./graphql"
|
||||
import typeormConfig from './ormconfig'
|
||||
import {createConnection} from "typeorm"
|
||||
import { sendEmail} from "./post_office"
|
||||
import * as config from './config'
|
||||
import express from 'express'
|
||||
import typeormConfig from "./ormconfig"
|
||||
import { createConnection } from "typeorm"
|
||||
import { sendEmail } from "./post_office"
|
||||
import * as config from "./config"
|
||||
import express from "express"
|
||||
import authHttpHandlers from "./Auth/authHttpHandlers"
|
||||
import SessionManager from "./Auth/SessionManager"
|
||||
import morgan from 'morgan'
|
||||
import ImageBucketService from "./Image/ImageBucketService";
|
||||
import morgan from "morgan"
|
||||
import ImageBucketService from "./Image/ImageBucketService"
|
||||
|
||||
const start = async () => {
|
||||
const sessionManager = new SessionManager({
|
||||
sendEmail: sendEmail,
|
||||
emailTargetDomain: config.domain,
|
||||
emailSenderDomain: config.mailgun.domain
|
||||
emailSenderDomain: config.mailgun.domain,
|
||||
})
|
||||
|
||||
const imageBucketService = new ImageBucketService({
|
||||
|
@ -23,32 +23,39 @@ const start = async () => {
|
|||
tmpLocalPath: config.eventImage.imageTempLocalPath,
|
||||
gcsBucketName: config.eventImage.imageCGSBucketName,
|
||||
})
|
||||
console.log(`Starting with db: ${typeormConfig.database} and config:\n ${JSON.stringify(typeormConfig,null,'\t')}`)
|
||||
console.log(
|
||||
`Starting with db: ${typeormConfig.database} and config:\n ${JSON.stringify(
|
||||
typeormConfig,
|
||||
null,
|
||||
"\t"
|
||||
)}`
|
||||
)
|
||||
const connection = await createConnection(typeormConfig)
|
||||
console.log('Will look for migrations and run them')
|
||||
console.log("Will look for migrations and run them")
|
||||
await connection.runMigrations()
|
||||
console.log('No more migrations to run')
|
||||
console.log("No more migrations to run")
|
||||
|
||||
const server = await createServer({
|
||||
typeormConnection: connection,
|
||||
sendEmail,
|
||||
domain: config.domain,
|
||||
sessionManager,
|
||||
imageBucketService
|
||||
imageBucketService,
|
||||
})
|
||||
|
||||
const authHandlers = authHttpHandlers(sessionManager)
|
||||
|
||||
const app = express()
|
||||
app.use(express.json())
|
||||
app.use(morgan('combined'))
|
||||
app.use(morgan("combined"))
|
||||
app.post("/api/signup", authHandlers.signupHandler)
|
||||
app.post("/api/login", authHandlers.loginHandler)
|
||||
app.post("/api/init-session", authHandlers.initializeSessionHandler)
|
||||
app.get("/api/signout", authHandlers.signoutHandler)
|
||||
|
||||
app.post('/api/signup', authHandlers.signupHandler)
|
||||
app.post('/api/login', authHandlers.loginHandler)
|
||||
app.post('/api/init-session', authHandlers.initializeSessionHandler)
|
||||
app.get('/api/signout', authHandlers.signoutHandler)
|
||||
server.applyMiddleware({ app })
|
||||
|
||||
app.listen({port: 4000}, () => {
|
||||
app.listen({ port: 4000 }, () => {
|
||||
console.log(`🚀 Server ready at localhost:4000/${server.graphqlPath}`)
|
||||
})
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ type UserRole {
|
|||
scalar Date
|
||||
scalar RoleType
|
||||
scalar Translations
|
||||
scalar Upload
|
||||
|
||||
type UserSession {
|
||||
hash: String!
|
||||
|
@ -41,6 +42,16 @@ input RequestInviteInput {
|
|||
# Events
|
||||
# ------
|
||||
|
||||
type Image {
|
||||
url: String!
|
||||
}
|
||||
|
||||
type EventImages {
|
||||
thumb: Image
|
||||
cover: Image
|
||||
poster: Image
|
||||
gallery: [Image!]
|
||||
}
|
||||
|
||||
type CalendarEvent {
|
||||
id: ID!
|
||||
|
@ -52,6 +63,7 @@ type CalendarEvent {
|
|||
location: Location!
|
||||
occurrences: [EventOccurrence]
|
||||
tags: [EventTag]
|
||||
images: EventImages
|
||||
}
|
||||
|
||||
type EventTag {
|
||||
|
@ -196,7 +208,6 @@ input DeleteEventTagInput {
|
|||
id: ID!
|
||||
}
|
||||
|
||||
scalar Upload
|
||||
input EventImageUploadInput {
|
||||
id: ID!
|
||||
file: Upload!
|
||||
|
|
|
@ -2343,6 +2343,11 @@ fs-capacitor@^2.0.4:
|
|||
resolved "https://registry.yarnpkg.com/fs-capacitor/-/fs-capacitor-2.0.4.tgz#5a22e72d40ae5078b4fe64fe4d08c0d3fc88ad3c"
|
||||
integrity sha512-8S4f4WsCryNw2mJJchi46YgB6CR5Ze+4L1h8ewl9tEpL4SJ3ZO+c/bS4BWhB8bK+O3TMqhuZarTitd0S0eh2pA==
|
||||
|
||||
fs-capacitor@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/fs-capacitor/-/fs-capacitor-5.0.0.tgz#0d98d1278d5dd3247411f51ccd2a0bdcfe72320a"
|
||||
integrity sha512-M1CNCxyeL81Tyr6qpxBgRJ3GEHKrzFdpen7ttoDd9KtRbf74myW7Kb39QHbNzo9rf/2CNJlEUbAKnfi7N40Pdg==
|
||||
|
||||
fs-minipass@^1.2.5:
|
||||
version "1.2.6"
|
||||
resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.6.tgz#2c5cc30ded81282bfe8a0d7c7c1853ddeb102c07"
|
||||
|
|
Loading…
Reference in New Issue