Skip to content

Commit

Permalink
feat(asset): create asset functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
intellix committed May 21, 2019
1 parent 7caab46 commit 0c7e9a9
Show file tree
Hide file tree
Showing 9 changed files with 317 additions and 3 deletions.
2 changes: 2 additions & 0 deletions packages/prime-core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,14 @@
"@primecms/field-slice": "^0.3.4-beta.0",
"@primecms/field-string": "^0.3.4-beta.0",
"apollo-server-express": "2.4.0",
"aws-sdk": "^2.459.0",
"class-validator": "0.9.1",
"cors": "2.8.5",
"dataloader": "1.4.0",
"debug": "4.1.1",
"dotenv": "6.2.0",
"express": "4.16.4",
"file-type": "^11.0.0",
"graphql": "^14.1.1",
"graphql-iso-date": "3.6.1",
"graphql-type-json": "^0.2.1",
Expand Down
62 changes: 62 additions & 0 deletions packages/prime-core/src/entities/Asset.ts
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 packages/prime-core/src/modules/external/interfaces/Upload.ts
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;
}
2 changes: 2 additions & 0 deletions packages/prime-core/src/modules/internal/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { Connection } from 'typeorm';
import { pubSub } from '../../utils/pubSub';
import { createAccounts } from '../accounts';
import { AccessTokenResolver } from './resolvers/AccessTokenResolver';
import { AssetResolver } from './resolvers/AssetResolver';
import { DocumentResolver } from './resolvers/DocumentResolver';
import { PrimeResolver } from './resolvers/PrimeResolver';
import { ReleaseResolver } from './resolvers/ReleaseResolver';
Expand All @@ -29,6 +30,7 @@ export const createInternal = async (connection: Connection) => {
const schema = await buildTypeDefsAndResolvers({
resolvers: [
AccessTokenResolver,
AssetResolver,
DocumentResolver,
PrimeResolver,
ReleaseResolver,
Expand Down
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 packages/prime-core/src/modules/internal/resolvers/AssetResolver.ts
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,
});
}
}
9 changes: 9 additions & 0 deletions packages/prime-core/src/modules/internal/types/AssetInput.ts
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>;
}
55 changes: 55 additions & 0 deletions packages/prime-core/src/utils/S3Uploader.ts
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
}
}
Loading

0 comments on commit 0c7e9a9

Please sign in to comment.