Skip to content

Commit

Permalink
style: fix
Browse files Browse the repository at this point in the history
  • Loading branch information
joscha committed Jul 29, 2024
1 parent 727df0c commit b01509b
Show file tree
Hide file tree
Showing 16 changed files with 512 additions and 22 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ examples, etc.
- ❌ Interactions
- ❌ Relationship Strengths
- ❌ Notes
- Entity Files
- [Entity Files](src/v1/entity_files.ts)
- ❌ Reminders
- ❌ Webhooks
-[Whoami](src/v1/auth.ts)
Expand Down
4 changes: 3 additions & 1 deletion deno.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"@std/assert": "jsr:@std/assert@^0.226.0",
"@std/fs": "jsr:@std/fs@^0.229.1",
"@std/path": "jsr:@std/path@^0.225.2",
"@std/streams": "jsr:@std/streams@^1.0.0",
"@std/testing": "jsr:@std/testing@^0.225.0",
"axios": "npm:axios@^1.7.2",
"axios-mock-adapter": "https://esm.sh/[email protected]",
Expand All @@ -12,7 +13,8 @@
"tasks": {
"build": "deno run -A scripts/build_npm.ts",
"check": "deno check src/**/*.ts",
"test": "deno test --allow-env=API_KEY,NODE_EXTRA_CA_CERTS --allow-net=api.affinity.co --doc --allow-read=./src/v1/tests/ src/**/tests/",
"test": "deno test --allow-env=API_KEY,NODE_EXTRA_CA_CERTS --allow-net --doc --allow-read=./src/v1/tests/ src/**/tests/",
"test:single": "deno test --allow-env=API_KEY,NODE_EXTRA_CA_CERTS --allow-net --doc --allow-read=./src/v1/tests/",
"test:coverage": "deno task test --coverage=cov_profile && deno coverage cov_profile --html",
"watch": "deno task test --no-clear-screen --watch --shuffle --parallel",
"snapshot-update": "deno task test --allow-write=./src/v1/tests/__snapshots__ -- --update",
Expand Down
54 changes: 52 additions & 2 deletions deno.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

207 changes: 207 additions & 0 deletions src/v1/entity_files.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
import type { AxiosInstance } from 'axios'
import { defaultTransformers } from './axios_default_transformers.ts'
import { FieldBase } from './fields.ts'
import { FieldValueType } from './lists.ts'
import type { DateTime, Replace, RequireOnlyOne } from './types.ts'
import { entityFilesUrl, fieldsUrl } from './urls.ts'
import type { PagedRequest } from './paged_request.ts'
import type { PagedResponse } from './paged_response.ts'
export type { DateTime } from './types.ts'
import { Readable } from 'node:stream'
import { EntityRequestFilter } from './field_value_changes.ts'
import { assert } from '@std/assert'

type EntityFileRaw = {
/** The unique identifier of the entity file object. */
id: number
/** The name of the file. */
name: string
/** The size of the file in bytes. */
size: string
/** The unique identifier of the person corresponding to the entity file. */
person_id: number | null
/** The unique identifier of the organization corresponding to the entity file. */
organization_id: number | null
/** The unique identifier of the opportunity corresponding to the entity file. */
opportunity_id: number | null
/** The unique identifier of the user who created the entity file. */
uploader_id: number
/** The time when the entity file was created. */
created_at: DateTime
}

type EntityFile = Replace<EntityFileRaw, {
created_at: Date
}>

/**
* Represents the request parameters for retrieving entity files.
*/
type AllEntityFileRequest =
& {
/**
* A unique ID that represents a Person whose associated files should be retrieved.
*/
person_id?: number
/**
* A unique ID that represents an Organization whose associated files should be retrieved.
*/
organization_id?: number
/**
* A unique ID that represents an Opportunity whose associated files should be retrieved.
*/
opportunity_id?: number
}
& PagedRequest

type PagedEntityFileResponseRaw =
& {
entity_files: EntityFileRaw[]
}
& PagedResponse

type PagedEntityFileResponse = Replace<PagedEntityFileResponseRaw, {
entity_files: EntityFile[]
}>

type UploadEntityFileRequest =
& {
files: File[]
}
& RequireOnlyOne<EntityRequestFilter, keyof EntityRequestFilter>

/**
* Entity files are files uploaded to a relevant entity.
* Possible files, for example, would be a pitch deck for an opportunity or a physical mail correspondence for a person.
*/
export class EntityFiles {
/** @hidden */
constructor(private readonly axios: AxiosInstance) {
}

private static transformEntityFile(file: EntityFileRaw): EntityFile {
return {
...file,
created_at: new Date(file.created_at),
}
}

/**
* Returns all entity files within your organization.
*/
async all(params?: AllEntityFileRequest): Promise<PagedEntityFileResponse> {
const response = await this.axios.get<PagedEntityFileResponse>(
entityFilesUrl(),
{
params,
transformResponse: [
...defaultTransformers(),
(json: PagedEntityFileResponseRaw) => {
return {
...json,
entity_files: json.entity_files.map(
EntityFiles.transformEntityFile,
),
}
},
],
},
)
return response.data
}

/**
* Fetches an entity with a specified `entity_file_id`.
*/
async get(entity_file_id: EntityFile['id']): Promise<EntityFile> {
const response = await this.axios.get<EntityFile>(
entityFilesUrl(entity_file_id),
{
transformResponse: [
...defaultTransformers(),
EntityFiles.transformEntityFile,
],
},
)
return response.data
}

/**
* Downloads the entity file with the specified `entity_file_id`.
*
* @example
* ```typescript
* import { promises as fsPromises } from 'node:fs';
* const fileResponseStream = affinity.entityFiles.download(123);
* await fsPromises.writeFile(filePath, fileResponseStream);
* ```
*/
async download(entity_file_id: EntityFile['id']): Promise<Readable> {
const response = await this.axios.get(
entityFilesUrl(entity_file_id, true),
{
responseType: 'stream',
// The download location of entity files is provided via a redirect from Affinity
maxRedirects: 5,
},
)
return response.data
}

/**
* Uploads files attached to the entity with the given id.
*
* The file will display on the entity's profile, provided that the entity is not a person internal to the user's organization.
*
* @example
* ```typescript
* const file = fs.createReadStream('example.pdf');
* const entityFile = await affinity.entityFiles.upload({
* files: [file],
* person_id: 123,
* });
* ```
*/
async upload(params: UploadEntityFileRequest): Promise<boolean> {
const formData = new FormData()
const { files } = params
assert(files.length, 'At least one file must be provided')
if (files.length === 1) {
// Append the file as 'file' if only one file is provided
// it's a bit odd that the Affinity API expects the file to be sent in a different
// parameter, but maybe there is an implementation detail that treats multiple files
// differently to a single one, so we're complying with the API here
const [file] = files
formData.append('file', file, file.name)
} else {
files.forEach((file) => {
formData.append('files[]', file, file.name)
})
}
if (params.person_id) {
formData.append('person_id', params.person_id.toString())
} else if (params.organization_id) {
formData.append(
'organization_id',
params.organization_id.toString(),
)
} else if (params.opportunity_id) {
formData.append('opportunity_id', params.opportunity_id.toString())
} else {
throw new Error(
'One of person_id, organization_id or opportunity_id must be provided',
)
}

const response = await this.axios.post<{ success: boolean }>(
entityFilesUrl(),
formData,
{
headers: {
'Content-Type': 'multipart/form-data',
},
},
)
return response.data.success === true
}
}
11 changes: 7 additions & 4 deletions src/v1/field_value_changes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,7 @@ export type FieldValueChange = Replace<FieldValueChangeRaw, {

export type FieldValueChangeResponse = FieldValueChange[]

/**
* Only one of these properties can be present at a time
*/
export interface GetFieldValueChangesRequestFilter {
export interface EntityRequestFilter {
/**
* A unique ID that represents a person object whose field value changes are to be retrieved.
*/
Expand All @@ -101,6 +98,12 @@ export interface GetFieldValueChangesRequestFilter {
* A unique ID that represents an opportunity object whose field value changes are to be retrieved.
*/
opportunity_id: number
}

/**
* Only one of these properties can be present at a time
*/
export interface GetFieldValueChangesRequestFilter extends EntityRequestFilter {
/**
* A unique ID that represents a list entry object whose field value changes are to be retrieved.
*/
Expand Down
Loading

0 comments on commit b01509b

Please sign in to comment.