-
Notifications
You must be signed in to change notification settings - Fork 114
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(asset): create asset functionality
- Loading branch information
Showing
9 changed files
with
317 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
import { User } from '@accounts/typeorm'; | ||
import { GraphQLFloat } from 'graphql'; | ||
import { Field, ID, ObjectType } from 'type-graphql'; | ||
import { | ||
Column, | ||
CreateDateColumn, | ||
Entity, | ||
ManyToOne, | ||
PrimaryGeneratedColumn, | ||
UpdateDateColumn, | ||
} from 'typeorm'; | ||
|
||
@Entity() | ||
@ObjectType() | ||
export class Asset { | ||
@Field(type => ID) | ||
@PrimaryGeneratedColumn('uuid') | ||
public id: string; | ||
|
||
@CreateDateColumn() | ||
@Field({ nullable: true }) | ||
public createdAt: Date; | ||
|
||
@UpdateDateColumn() | ||
@Field({ nullable: true }) | ||
public updatedAt: Date; | ||
|
||
@Column({ type: 'timestamp', nullable: true }) | ||
@Field({ nullable: true }) | ||
public deletedAt: Date; | ||
|
||
@Column({ nullable: false }) | ||
@Field({ nullable: false }) | ||
public url: string; | ||
|
||
@Column({ nullable: false }) | ||
@Field({ nullable: false }) | ||
public mimeType: string; | ||
|
||
@Column({ nullable: false }) | ||
@Field({ nullable: false }) | ||
public fileName: string; | ||
|
||
@Column({ nullable: false }) | ||
@Field({ nullable: false }) | ||
public fileSize: number; | ||
|
||
@Column('float', { nullable: true }) | ||
@Field(type => GraphQLFloat, { nullable: true }) | ||
public width: number; | ||
|
||
@Column('float', { nullable: true }) | ||
@Field(type => GraphQLFloat, { nullable: true }) | ||
public height: number; | ||
|
||
@Column({ nullable: true }) | ||
@Field({ nullable: true }) | ||
public userId?: string; | ||
|
||
@ManyToOne(type => User, { nullable: true, onDelete: 'SET NULL' }) | ||
public user: User; | ||
} |
23 changes: 23 additions & 0 deletions
23
packages/prime-core/src/modules/external/interfaces/Upload.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import { ReadStream } from 'fs'; | ||
|
||
/** | ||
* File upload details, resolved from an `Upload` scalar promise. | ||
* See: https://github.com/jaydenseric/graphql-upload | ||
*/ | ||
export interface Upload { | ||
/** File name */ | ||
filename: string; | ||
|
||
/** File MIME type. Provided by the client and can’t be trusted. */ | ||
mimetype: string; | ||
|
||
/** File stream transfer encoding. */ | ||
encoding: string; | ||
|
||
/** | ||
* createReadStream Returns a Node.js readable stream of the file contents, | ||
* for processing and storing the file. Multiple calls create independent streams. | ||
* Throws if called after all resolvers have resolved, or after an error has interrupted the request. | ||
*/ | ||
createReadStream: () => ReadStream; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
6 changes: 6 additions & 0 deletions
6
packages/prime-core/src/modules/internal/repositories/AssetRepository.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import { EntityRepository } from 'typeorm'; | ||
import { Asset } from '../../../entities/Asset'; | ||
import { DataLoaderRepository } from './DataLoaderRepository'; | ||
|
||
@EntityRepository(Asset) | ||
export class AssetRepository extends DataLoaderRepository<Asset> {} |
102 changes: 102 additions & 0 deletions
102
packages/prime-core/src/modules/internal/resolvers/AssetResolver.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
import { Context } from 'apollo-server-core'; | ||
import { GraphQLResolveInfo } from 'graphql'; | ||
import { | ||
Arg, | ||
Args, | ||
Ctx, | ||
FieldResolver, | ||
ID, | ||
Info, | ||
Mutation, | ||
Query, | ||
registerEnumType, | ||
Resolver, | ||
Root, | ||
} from 'type-graphql'; | ||
import { getRepository } from 'typeorm'; | ||
import { EntityConnection } from 'typeorm-cursor-connection'; | ||
import { InjectRepository } from 'typeorm-typedi-extensions'; | ||
import { Asset } from '../../../entities/Asset'; | ||
import { AssetRepository } from '../repositories/AssetRepository'; | ||
import { AssetInput } from '../types/AssetInput'; | ||
import { ConnectionArgs, createConnectionType } from '../types/createConnectionType'; | ||
import { User } from '../types/User'; | ||
import { Authorized } from '../utils/Authorized'; | ||
|
||
const AssetConnection = createConnectionType(Asset); | ||
|
||
enum AssetOrder { | ||
id_ASC, | ||
id_DESC, | ||
name_ASC, | ||
name_DESC, | ||
} | ||
|
||
registerEnumType(AssetOrder, { | ||
name: 'AssetConnectionOrder', | ||
}); | ||
|
||
@Resolver(of => Asset) | ||
export class AssetResolver { | ||
@InjectRepository(AssetRepository) | ||
private readonly assetRepository: AssetRepository; | ||
|
||
@Authorized() | ||
@Query(returns => Asset, { nullable: true, description: 'Get Asset by ID' }) | ||
public Asset( | ||
@Arg('id', type => ID) id: string, | ||
@Ctx() context: Context, | ||
@Info() info: GraphQLResolveInfo | ||
) { | ||
return this.assetRepository.loadOne(id); | ||
} | ||
|
||
@Authorized() | ||
@Query(returns => AssetConnection, { description: 'Get many Assets' }) | ||
public async allAssets( | ||
@Args() args: ConnectionArgs, | ||
@Arg('order', type => AssetOrder, { defaultValue: 0 }) orderBy: string | ||
) { | ||
const [sort, order]: any = orderBy.split('_'); | ||
const connection = await new EntityConnection(args, { | ||
repository: this.assetRepository, | ||
sortOptions: [{ sort, order }], | ||
}); | ||
return { | ||
edges: await connection.edges, | ||
totalCount: await this.assetRepository.count(), | ||
}; | ||
} | ||
|
||
@Authorized() | ||
@Mutation(returns => Asset, { description: 'Create Asset' }) | ||
public async createAsset( | ||
@Arg('input') input: AssetInput, | ||
@Ctx() context: Context | ||
): Promise<Asset> { | ||
// TODO: Add asset on host | ||
const assetInput: Partial<Asset> = {}; | ||
const asset = this.assetRepository.create(assetInput); | ||
await this.assetRepository.save(asset); | ||
return asset; | ||
} | ||
|
||
@Authorized() | ||
@Mutation(returns => Boolean, { description: 'Remove Asset by ID' }) | ||
public async removeAsset( | ||
@Arg('id', type => ID) id: string, | ||
@Ctx() context: Context | ||
): Promise<boolean> { | ||
// TODO: Remove asset from host | ||
const entity = await this.assetRepository.findOneOrFail(id); | ||
return Boolean(await this.assetRepository.remove(entity)); | ||
} | ||
|
||
@FieldResolver(returns => User, { description: 'Get Asset User' }) | ||
public user(@Root() asset: Asset): Promise<User> { | ||
return getRepository(User).findOneOrFail({ | ||
cache: 1000, | ||
where: asset.user, | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import { GraphQLUpload } from 'graphql-upload'; | ||
import { Field, InputType } from 'type-graphql'; | ||
import { Upload } from '../../external/interfaces/Upload'; | ||
|
||
@InputType() | ||
export class AssetInput { | ||
@Field(type => GraphQLUpload, { nullable: false }) | ||
public upload: Promise<Upload>; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
import S3 from 'aws-sdk/clients/s3'; | ||
import fileType from 'file-type'; | ||
import { ReadStream } from 'fs'; | ||
import { Service } from 'typedi'; | ||
|
||
@Service() | ||
export class S3Uploader { | ||
private bucket: string; | ||
private s3: S3; | ||
|
||
constructor() { | ||
if (!process.env.S3_BUCKET) { | ||
throw Error('S3_BUCKET not set'); | ||
} | ||
if (!process.env.S3_REGION) { | ||
throw Error('S3_REGION not set'); | ||
} | ||
if (!process.env.S3_ACCESS_KEY_ID) { | ||
throw Error('S3_ACCESS_KEY_ID not set'); | ||
} | ||
if (!process.env.S3_SECRET_ACCESS_KEY) { | ||
throw Error('S3_SECRET_ACCESS_KEY not set'); | ||
} | ||
|
||
this.bucket = process.env.S3_BUCKET; | ||
this.s3 = new S3({ | ||
region: process.env.S3_REGION, | ||
credentials: { | ||
accessKeyId: process.env.S3_ACCESS_KEY_ID, | ||
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY, | ||
}, | ||
}); | ||
} | ||
|
||
public async upload(key: string, readStream: ReadStream): Promise<string> { | ||
// Detect mime-type | ||
const stream = await fileType.stream(readStream); | ||
const { mime } = stream.fileType!; | ||
|
||
return this.s3 | ||
.upload({ | ||
Bucket: this.bucket, | ||
ACL: 'public-read', | ||
Body: readStream, | ||
Key: key, | ||
ContentType: mime, | ||
}) | ||
.promise() | ||
.then(({ Location }) => Location); | ||
} | ||
|
||
public async delete(key: string): Promise<void> { | ||
// TODO | ||
} | ||
} |
Oops, something went wrong.