Skip to content

Commit

Permalink
Merge pull request #1369 from balena-io/otaviojacobi/adds-organizatio…
Browse files Browse the repository at this point in the history
…n-logo-model

Add organization logo to organization
  • Loading branch information
flowzone-app[bot] authored Nov 13, 2023
2 parents e220432 + 251e835 commit 7094406
Show file tree
Hide file tree
Showing 7 changed files with 90 additions and 7 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
"@types/chai-as-promised": "^7.1.4",
"@types/lodash": "^4.14.195",
"@types/memoizee": "^0.4.7",
"@types/mime": "^3.0.3",
"@types/mocha": "^10.0.1",
"@types/ndjson": "^2.0.0",
"@types/sinon": "^10.0.6",
Expand Down Expand Up @@ -132,6 +133,7 @@
"handlebars": "^4.7.7",
"lodash": "^4.17.21",
"memoizee": "^0.4.15",
"mime": "^3.0.0",
"ndjson": "^2.0.0",
"p-throttle": "^4.1.1",
"pinejs-client-core": "^6.12.0",
Expand Down
17 changes: 12 additions & 5 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -373,22 +373,29 @@ export const getSdk = function ($opts?: SdkOptions) {
* @memberof balena
*
* @description
* The utils instance used internally. This should not be necessary
* in normal usage, but can be useful to handle some specific cases.
* The utils instance offers some convenient features for clients.
*
* @example
* balena.utils.mergePineOptions(
* { $expand: { device: { $select: ['id'] } } },
* { $expand: { device: { $select: ['name'] } } },
* );
*
* @example
* // Creating a new WebResourceFile in case 'File' API is not available.
* new balena.utils.BalenaWebResourceFile(
* [fs.readFileSync('./file.tgz')],
* 'file.tgz'
* );
*/
Object.defineProperty(sdk, 'utils', {
enumerable: true,
configurable: true,
get() {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { mergePineOptions } = require('./util') as typeof import('./util');
return { mergePineOptions };
const { mergePineOptions, BalenaWebResourceFile } =
// eslint-disable-next-line @typescript-eslint/no-var-requires
require('./util') as typeof import('./util');
return { mergePineOptions, BalenaWebResourceFile };
},
});

Expand Down
25 changes: 25 additions & 0 deletions src/models/organization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,31 @@ const getOrganizationModel = function (
* balena.models.organization.create({ name:'MyOrganization' }).then(function(organization) {
* console.log(organization);
* });
*
* @example
* balena.models.organization.create({
* name:'MyOrganization',
* logo_image: new balena.utils.BalenaWebResourceFile(
* [fs.readFileSync('./img.jpeg')],
* 'img.jpeg'
* );
* })
* .then(function(organization) {
* console.log(organization);
* });
*
* @example
* balena.models.organization.create({
* name:'MyOrganization',
* // Only in case File API is avaialable (most browsers and Node 20+)
* logo_image: new File(
* imageContent,
* 'img.jpeg'
* );
* })
* .then(function(organization) {
* console.log(organization);
* });
*/
const create = function (
organization: BalenaSdk.PineSubmitBody<BalenaSdk.Organization>,
Expand Down
2 changes: 2 additions & 0 deletions src/types/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type {
OptionalNavigationResource,
ReverseNavigationResource,
ConceptTypeNavigationResource,
WebResource,
} from '../../typings/pinejs-client-core';
import type { AnyObject } from '../../typings/utils';

Expand Down Expand Up @@ -84,6 +85,7 @@ export interface Organization {
handle: string;
has_past_due_invoice_since__date: string | null;
is_frozen: boolean;
logo_image: WebResource;

application: ReverseNavigationResource<Application>;
/** includes__organization_membership */
Expand Down
15 changes: 15 additions & 0 deletions src/util/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import * as errors from 'balena-errors';
import type * as Pine from '../../typings/pinejs-client-core';
import type { IfDefined } from '../../typings/utils';
import type { WebResourceFile } from 'balena-request';
import * as mime from 'mime';

export interface BalenaUtils {
mergePineOptions: typeof mergePineOptions;
BalenaWebResourceFile: typeof BalenaWebResourceFile;
}

export const notImplemented = () => {
Expand Down Expand Up @@ -349,3 +352,15 @@ export const limitedMap = <T, U>(
}
});
};

export class BalenaWebResourceFile extends Blob implements WebResourceFile {
public name: string;
constructor(blobParts: BlobPart[], name: string, options?: BlobPropertyBag) {
const opts = {
...options,
type: options?.type ?? mime.getType(name) ?? undefined,
};
super(blobParts, opts);
this.name = name;
}
}
24 changes: 23 additions & 1 deletion tests/integration/models/organization.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { expect } from 'chai';
import parallel from 'mocha.parallel';
import * as superagent from 'superagent';
import {
balena,
credentials,
Expand Down Expand Up @@ -98,6 +99,27 @@ describe('Organization model', function () {
.that.is.not.equal(ctx.newOrg1.handle);
ctx.newOrg2 = org;
});

it('should be able to create an organization with a logo', async function () {
const org = await balena.models.organization.create({
name: 'org-with-logo',
logo_image: new balena.utils.BalenaWebResourceFile(
[Buffer.from('this is a test\n')],
'orglogo.png',
),
});

const fetchedOrg = await balena.models.organization.get(org.id, {
$select: ['id', 'logo_image'],
});
expect(fetchedOrg)
.to.have.nested.property('logo_image.href')
.that.is.a('string');

const res = await superagent.get(fetchedOrg.logo_image.href);
expect(res.status).to.equal(200);
expect(res.headers['content-length']).to.equal('15');
});
});
});

Expand All @@ -108,7 +130,7 @@ describe('Organization model', function () {
$orderby: 'id asc',
});
expect(orgs).to.be.an('array');
expect(orgs).to.have.lengthOf(3);
expect(orgs).to.have.lengthOf(4);
const [org1, org2, org3] = orgs;
expect(org1).to.deep.match(ctx.userInitialOrg);
expect(org2).to.deep.match(ctx.newOrg1);
Expand Down
12 changes: 11 additions & 1 deletion typings/pinejs-client-core.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { WebResourceFile } from 'balena-request';
import type {
AnyObject,
PropsAssignableWithType,
Expand Down Expand Up @@ -143,6 +144,14 @@ export type PostResult<T> = SelectResultObject<
Exclude<StringKeyof<T>, PropsOfType<T, ReverseNavigationResource<object>>>
>;

export type WebResource = {
filename: string;
href: string;
content_type?: string;
content_disposition?: string;
size?: number;
};

// based on https://github.com/balena-io/pinejs-client-js/blob/master/core.d.ts

type RawFilter =
Expand Down Expand Up @@ -379,10 +388,11 @@ export type ODataOptionsStrict<T> = Omit<
export type ODataOptionsWithFilter<T> = ODataOptions<T> &
Required<Pick<ODataOptions<T>, '$filter'>>;

export type ReplaceWebResource<K> = K extends WebResource ? WebResourceFile : K;
export type SubmitBody<T> = {
[k in keyof T]?: T[k] extends AssociatedResource<object>
? number | null
: T[k];
: ReplaceWebResource<T[k]>;
};

type BaseResourceId =
Expand Down

0 comments on commit 7094406

Please sign in to comment.