
Security News
Rust RFC Proposes a Security Tab on crates.io for RustSec Advisories
Rustās crates.io team is advancing an RFC to add a Security tab that surfaces RustSec vulnerability and unsoundness advisories directly on crate pages.
@produce8/p8-cube-helper
Advanced tools
[](https://github.com/semantic-release/semantic-release) [ utilizes domain driven design.
Domain-Driven Design (DDD) is an approach to software development that focuses on aligning the software design with the business domain it represents. It emphasizes understanding the domain and using that understanding to drive the design and implementation of the software system.
Domain-driven Design (DDD) typically involves the following layers:
These layers work together to create a software system that aligns with the business domain and promotes maintainability, extensibility, and flexibility.
/src
/common
/ddd
/decorators
/enum
/exceptions
/guards
/interface
/port
/utils
/core
/application
/services
/ports
/domain
/entities
/aggregates
/services
/infrastructure
/adapters
/mappers
/interface
/controllers
/presenters
/dtos
/queries
/commands
<feature-name>|<entity-name>[.<request>|<response>].<type>.ts
Example:
- DTOs:
- get-app-metrics-totals-whole-team.request.dto.ts
- get-app-metrics-totals-whole-team.response.dto.ts
- Controller:
- get-app-metrics-totals-whole-team.controller.ts
- Composite Controller:
- get-app-metrics-totals-whole-team.composite-controller.ts
- Query:
- get-app-metrics-totals-whole-team.query.ts
- Command:
- expand-app-metrics-totals-whole-team.command.ts
- Entity:
- app-metrics-totals-whole-team.entity.ts
- Mapper:
- app-metrics-totals-whole-team.mapper.ts
- Presenter:
- get-app-metrics-totals-whole-team.presenter.ts
- Application service:
- get-app-metrics-totals-whole-team.application-service.ts
- Application service:
- analytics-activity.port.ts
- Domain service:
- get-app-metrics-totals-whole-team.domain-service.ts
Before writing any code, sit down with the frontend developer and work through the Figma mockups for a given feature to produce interfaces that define the minimal logically grouped queries that can be performed. If a component consists of; a chart, an average for this period value and an all time average value, the data that needs to be queried for these respective sub components canāt be queried together. These define your domains!
These domains define the controllers you will then build in the cube helper package.
Heres an example of a minimal interface:
interface CubeRequestConfig {
token: string;
}
interface GetAppMetricsTotalsWholeTeamRequestDto {
params: {
startDate: string;
endDate: string;
teamId: string;
userIds: string[];
};
config: CubeRequestConfig;
}
interface GetAppMetricsTotalsWholeTeamResponseDto {
timeInApp: Number;
timeInCalls: Number;
}
Once the minimal interfaces (domains) have been defined, you can then use those interfaces to extrapolate the rest of your feature. Those interfaces become the request and response DTOs used by your controllers.
A complete feature consists of the following components:
Example:
interface CubeRequestConfig {
token: string;
}
interface GetAppMetricsTotalsWholeTeamRequestDto {
params: {
startDate: string;
endDate: string;
teamId: string;
userIds: string[];
};
config: CubeRequestConfig;
}
interface GetAppMetricsTotalsWholeTeamResponseDto {
timeInApp: Number;
timeInCalls: Number;
}
Example:
export class GetAppMetricsTotalWholeTeamController {
constructor(
protected readonly logManager: LogManager,
protected readonly query: GetAppMetricsTotalWholeTeamQuery,
protected readonly mapper: AppMetricsTotalWholeTeamMapper
) {}
async get(
props: GetAppMetricsTotalsWholeTeamRequestDto
): Promise<GetAppMetricsTotalsWholeTeamResponseDto> {
try {
const result = await this.query.execute(props);
return this.mapper.toResponse(result);
} catch (error) {
throw new Error(
`AppMetricsTotalWholeTeamComponentController.get encountered an error ${error}`
);
}
}
}
Example:
export class GetAppMetricsTotalWholeTeamQuery {
constructor(
protected readonly logManager: LogManager,
protected readonly service: GetAppMetricsTotalWholeTeamApplicationService
) {}
async execute(
props: GetAppMetricsTotalsWholeTeamRequestDto
): Promise<AppMetricsTotalWholeTeamEntity> {
try {
return await this.service.execute(props);
} catch (error) {
throw new InternalServerErrorException(error);
}
}
}
export class ExpandAppMetricsTotalWholeTeamCommand {
constructor(
protected readonly logManager: LogManager,
// this can be an application service if you are sending data to some external
// system. if you never need to leave the domain layer, a domain
// service is just fine!
protected readonly service: ExpandAppMetricsTotalWholeTeamDomainService
) {}
async execute(
props: ExpansAppMetricsTotalsWholeTeamRequestDto
): Promise<AppMetricsTotalWholeTeamEntity> {
try {
return await this.service.execute(props);
} catch (error) {
throw new InternalServerErrorException(error);
}
}
}
Example:
export class GetAppMetricsTotalWholeTeamApplicationService {
constructor(
protected readonly logManager: LogManager,
protected readonly port: AnalyticsActivityPort,
protected readonly domainService: GetAppMetricsTotalWholeTeamDomainService
) {}
async execute(
props: GetAppMetricsTotalsWholeTeamRequestDto
): Promise<AppMetricsTotalWholeTeamEntity> {
try {
const result: AppMetricsTotalWholeTeamEntity[] =
await this.port.getAppMetricsTotalsWholeTeam({
startDate: props.params.startDate,
endDate: props.params.endDate,
teamId: props.params.teamId,
})
return this.domainService({ entity: result });
} catch (error) {
throw new Error(
`GetAppMetricsTotalWholeTeamService.execute encountered an error ${error}`
);
}
}
}
Example:
import { Guard } from 'src/common/guards/guard';
export class AnalyticsActivityPort {
constructor(
protected readonly logManager: LogManager,
protected readonly adapter: CubeAdapter
) {
}
async getAppMetricsTotalsWholeTeam(props:{
startDate: string,
endDate: string,
teamId: string
}): Promise<AppMetricsTotalWholeTeamEntity> {
try {
const result: IAppMetricsTotalWholeTeamPersistenceDto[] =
await this.adapter.query(props.token, {
measures: [
IAnalyticsEventEnum.Count,
IAnalyticsEventEnum.TotalDuration,
],
timeDimensions: [
{
dimension: IAnalyticsEventEnum.LocalStartTime,
dateRange: [props.startDate, props.endDate],
},
],
filters: [
{
member: IAnalyticsEventEnum.EventType,
operator: 'equals',
values: [
AnalyticsEventTypes.INTERNAL,
AnalyticsEventTypes.EXTERNAL,
],
},
],
});
if (result.length > 0) {
const filteredResults = await Guard.filterValidResponses(
IAppMetricsTotalWholeTeamPersistenceDto,
result
);
const [entity] = filteredResults.map((record) => {
return SelfCalendarScoreMapper.toDomain(record);
});
return entity;
}
return null;
} catch (error) {
throw new InternalServerErrorException(error);
}
}
}
The Domain Service is for orchestrating relationships between domain objects: ie Entities and Aggregate Roots - as opposed to the Application Service which interfaces with external data
export interface GetAppMetricsTotalWholeTeamDomainServiceProps {
entity: AppMetricsTotalWholeTeamEntity
}
export class GetAppMetricsTotalWholeTeamDomainService {
constructor(protected readonly logManager: LogManager) {}
execute(
props: GetAppMetricsTotalWholeTeamDomainServiceProps
): AppMetricsTotalWholeTeamEntity {
try {
// perform hydration on missing values
let entity = props.entity
if (!entity) {
AppMetricsTotalWholeTeamEntity.create({
timeInApp: 0,
timeInCalls: 0
}
)
}
// if the entity needs to access any values from the FE rather than from Cube values
// add them with setter methods here
// if an aggregate root is returned from the application service,
// construct it out of the inputted entities and return here.
return entity
} catch (error) {
throw new Error(
`GetAppMetricsTotalWholeTeamDomainService.execute encountered an error ${error}`
);
}
}
}
Example:
interface AppMetricsTotalWholeTeamEntityProps {
timeInApp: number;
timeInCalls: number;
}
class AppMetricsTotalWholeTeamEntity extends Entity<AppMetricsTotalWholeTeamEntityProps> {
protected _id: string;
protected _someSecretInternalValue: number;
static create(
props: AppMetricsTotalWholeTeamEntityProps,
entityId?: string
): AppMetricsTotalWholeTeamEntity {
const id = entityId || v4();
const user = new AppMetricsTotalWholeTeamEntity({
id,
props,
});
return user;
}
public validate(): void {}
get timeInApp() {
return this.props.timeInApp;
}
get timeInCalls() {
return this.props.timeInCalls;
}
// setters can be used in the following approach where data needs to be
// drilled down from the interface layer or another domain entity.
setSomeSecretInternalValue(value: number): void {
this._someSecretInternalValue = value;
}
get timeInCallsMinusSecretValue(): number {
return this.props.timeInCalls - this._someSecretInternalValue;
}
// logic such as mathematical calculations can be achieved as such.
// these can then be invoked in the toResponse method of the mapper.
get someMathematicalCalculation() {
return Math.round(this.timeInApp / this.timeInCalls)
}
}
Example:
export class IAppMetricsTotalWholeTeamPersistenceDto {
@IsNotEmpty()
[ITeamAnalyticsActivityV2Enum.TotalDuration]: string;
@IsNotEmpty()
[ITeamAnalyticsActivityV2Enum.TotalCallDuration]: string;
}
export class AppMetricsTotalWholeTeamMapper
implements
Mapper<
IAppMetricsTotalWholeTeamPersistenceDto, // <-- persistence
AppMetricsTotalWholeTeamEntity, // <-- entity
GetAppMetricsTotalsWholeTeamResponseDto // <-- dto
>
{
toPersistence(
entity: AppMetricsTotalWholeTeamEntity
): IAppMetricsTotalWholeTeamPersistenceDto {
return AppMetricsTotalWholeTeamMapper.toPersistence(entity);
}
static toPersistence(
entity: AppMetricsTotalWholeTeamEntity
): IAppMetricsTotalWholeTeamPersistenceDto {
return {
[ITeamAnalyticsActivityV2Enum.TotalDuration]: String(entity.timeInApp),
[ITeamAnalyticsActivityV2Enum.TotalCallDuration]: String(
entity.timeInCalls
),
};
}
toDomain(
record: IAppMetricsTotalWholeTeamPersistenceResponse
): AppMetricsTotalWholeTeamEntity {
return AppMetricsTotalWholeTeamMapper.toDomain(record);
}
static toDomain(
record: IAppMetricsTotalWholeTeamPersistenceResponse
): AppMetricsTotalWholeTeamEntity {
const entity = AppMetricsTotalWholeTeamEntity.create({
timeInApp: Number(record[ITeamAnalyticsActivityV2Enum.TotalDuration]),
timeInCalls: Number(
record[ITeamAnalyticsActivityV2Enum.TotalCallDuration]
),
});
return entity;
}
toResponse(
entity: AppMetricsTotalWholeTeamEntity
): GetAppMetricsTotalsWholeTeamResponseDto {
return AppMetricsTotalWholeTeamMapper.toResponse(entity);
}
static toResponse(
entity: AppMetricsTotalWholeTeamEntity
): GetAppMetricsTotalsWholeTeamResponseDto {
return {
timeInApp: entity.timeInApp,
timeInCalls: entity.timeInCalls,
// someAvgValue: entity.someMathematicalCalculation < -- math operations delayed as long as possible
};
}
}
Example:
type ScoreAggregateRoots =
| SelfDigitalIntensityScoreAggregateRoot
| SelfCalendarScoreAggregateRoot
| SelfDigitalWorkingHoursScoreAggregateRoot
| SelfFocusScoreAggregateRoot
| SelfTimeInCallsScoreAggregateRoot
| SelfScreentimeScoreAggregateRoot;
export interface GetSelfAllScoresPresenterProps
extends Record<string, ScoreAggregateRoots> {
digitalIntensityScore: SelfDigitalIntensityScoreAggregateRoot;
digitalWorkingHoursScore: SelfDigitalWorkingHoursScoreAggregateRoot;
focusScore: SelfFocusScoreAggregateRoot;
screentimeScore: SelfScreentimeScoreAggregateRoot;
calendarScore: SelfCalendarScoreAggregateRoot;
timeInCallsScore: SelfTimeInCallsScoreAggregateRoot;
}
export class GetSelfAllScoresPresenter
implements
Presenter<
ScoreAggregateRoots,
Omit<SelfGetAllScoresResponseDto, 'startDate' | 'endDate'>
>
{
constructor(
protected readonly logManager: LogManager,
protected readonly digitalIntensityMapper: SelfDigitalIntensityScoreAggregateRootMapper,
protected readonly digitalWorkingHoursMapper: SelfDigitalWorkingHoursScoreAggregateRootMapper,
protected readonly focusMapper: SelfFocusScoreAggregateRootMapper,
protected readonly screentimeMapper: SelfScreentimeScoreAggregateRootMapper,
protected readonly calendarMapper: SelfCalendarScoreAggregateRootMapper,
protected readonly timeInCallsMapper: SelfTimeInCallsScoreAggregateRootMapper
) {}
/**
* Remove legacy 'hasData' property from the response once the frontend is updated to handle empty data
*/
toResponse(
entities: GetSelfAllScoresPresenterProps
): Omit<SelfGetAllScoresResponseDto, 'startDate' | 'endDate'> {
try {
const digitalIntensity = this.digitalIntensityMapper.toResponse(
entities.digitalIntensityScore
);
const digitalWorkingHours = this.digitalWorkingHoursMapper.toResponse(
entities.digitalWorkingHoursScore
);
const focus = this.focusMapper.toResponse(entities.focusScore);
const screentime = this.screentimeMapper.toResponse(
entities.screentimeScore
);
const calendar = this.calendarMapper.toResponse(entities.calendarScore);
const timeInCalls = this.timeInCallsMapper.toResponse(
entities.timeInCallsScore
);
const hasData =
!digitalIntensity ||
!digitalWorkingHours ||
!focus ||
!screentime ||
!calendar ||
!timeInCalls
? false
: true;
return {
hasData,
digitalIntensity,
workingHours: digitalWorkingHours,
focus,
screenTime: screentime,
timeInMeetings: calendar,
timeInCalls,
};
} catch (error) {
throw new Error(
`GetSelfAllScoresPresenter.toResponse encountered an error ${error}`
);
}
}
}
Example
export class GetAppMetricsTotalWholeTeamCompositeController {
constructor(
protected readonly logManager: LogManager,
protected readonly queryOne: GetAppMetricsTotalWholeTeamQuery,
protected readonly queryTwo: GetAppMetricsTotalWholeTeamQuery,
protected readonly presenter: AppMetricsTotalWholeTeamPresenter
) {}
async get(
props: GetAppMetricsTotalsWholeTeamRequestDto
): Promise<GetAppMetricsTotalsWholeTeamResponseDto> {
try {
const [resultOne, resultTwo] = await Promise.all([
this.queryOne.execute(props),
this.queryTwo.execute(props)
])
return this.presenter.toResponse({resultOne, resultTwo});
} catch (error) {
throw new Error(
`GetAppMetricsTotalWholeTeamCompositeController.get encountered an error ${error}`
);
}
}
}
As we currently have partial support for the legacy codebase (under the /lib directory in the cube helper package), we must inject any new controllers into the pre-existing client class for Frontend enablement.
To achieve this, weāve defined an interface to store new feature methods for the client to use:
interface IReforgedCubeHelperMethods {...}
This interface is then extended in the entry point interface for the cube helper package library. Under /lib/index.ts:
interface ICubeQueryHelper extends IReforgedCubeHelperMethods {...}
When implementing a new feature, ensure you expose it to the frontend by adding an appropriately named method to the Reforged methods interface and implementing it in the cube query helper class.
Example:
export class CubeQueryHelper implements ICubeQueryHelper {
...
async getAppMetricsTotalsWholeTeam(
params: GetAppMetricsTotalsWholeTeamRequestDto
): Promise<GetAppMetricsTotalsWholeTeamResponseDto> {
try {
const mapper = new AppMetricsTotalWholeTeamMapper();
const service = new GetAppMetricsTotalWholeTeamService(
this.logManager,
this.adapter,
mapper
);
const query = new GetAppMetricsTotalWholeTeamQuery(
this.logManager,
service
);
const controller = new GetAppMetricsTotalWholeTeamController(
this.logManager,
query,
mapper
);
return await controller.get(params);
} catch (error) {
this.logManager.error(
'TeamMetricsController.getAppMetricsTotalsWholeTeam encountered an error',
error
);
}
};
...
}
<component-location>/test/unit/
<feature-name>|<entity-name>.<type>.test.ts
Example:
describe('Given GetAppMetricsTotalWholeTeamService', () => {
const mockLogManager = mock<LogManager>();
const mockAdapter = mock<CubeAdapter>();
const mockMapper = mock<AppMetricsTotalWholeTeamMapper>();
const sut = new GetAppMetricsTotalWholeTeamService(
mockLogManager,
mockAdapter,
mockMapper
);
afterEach(() => {
jest.clearAllMocks();
});
describe('Given valid input', () => {
it('Should return without error', async () => {
mockAdapter.query.mockResolvedValueOnce([
{
[ITeamAnalyticsActivityV2Enum.TotalDuration]: '1',
[ITeamAnalyticsActivityV2Enum.TotalCallDuration]: '1',
[ITeamAnalyticsActivityV2Enum.UserCount]: '1',
},
]);
const entity = AppMetricsTotalWholeTeamEntity.create({
timeInApp: 1,
timeInCalls: 1,
});
mockMapper.toDomain.mockReturnValueOnce(entity);
const result = await sut.execute({
params: {
startDate: '2024-01-01',
endDate: '2024-01-01',
teamId: '',
userIds: [''],
},
config: { token: '' },
});
expect(result.equals(entity)).toEqual(true);
expect(mockAdapter.query).toHaveBeenCalled();
expect(mockMapper.toDomain).toHaveBeenCalled();
});
it('Should return error service throws error', async () => {
mockAdapter.query.mockRejectedValueOnce('some error');
await expect(
sut.execute({
params: {
startDate: '2024-01-01',
endDate: '2024-01-01',
teamId: '',
userIds: [''],
},
config: { token: '' },
})
).rejects.toThrow();
});
});
});
Large teams working together on a project that requires a consistent release cycle creates friction if said release cycle is managed by humans. Humans are naturally fallible.
Using a consistent, standard commit message approach, an automated solution can be introduced that allows for semantic releases to happen without the need for human input (beyond merging the odd pull request).
Integrate one or more tools to enable automated semantic version releases as part of our CI/CD pipeline.
To integrate this solution with an existing project, here's what you'll need:
npm install --save-dev semantic-release husky @commitlint/{cli,config-conventional}
or
yarn add --dev semantic-release husky @commitlint/{cli,config-conventional}
.npmrc file with the following command:
echo "access=public" >> .npmrc
npx husky init
echo "npx --no -- commitlint --edit \\$1" > .husky/commit-msg
echo "export default { extends: ["@commitlint/config-conventional"] };" > commitlint.config.ts
.github/workflows/release.yml:
name: Release
on:
push:
branches:
- main ## or master
permissions:
contents: read ## for checkout
jobs:
release:
name: Release
runs-on: ubuntu-latest
permissions:
contents: write ## to be able to publish a GitHub release
issues: write ## to be able to comment on released issues
pull-requests: write ## to be able to comment on released pull requests
id-token: write ## to enable use of OIDC for npm provenance
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: "lts/*"
- name: Install dependencies
run: npm clean-install
- name: Verify the integrity of provenance attestations and registry signatures for installed dependencies
run: npm audit signatures
- name: Release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
run: npx semantic-release
When a pull request is merged into main, the Github workflow Release action is triggered which invokes semantic-release. This traverses the commit history and compiles a release based on the commit convention.
<type>(<scope>): <short summary>
ā ā ā
ā ā āā⫸ Summary in present tense. Not capitalized. No period at the end.
ā ā
ā āā⫸ Commit Scope(Optional): animations|bazel|benchpress|common|compiler|compiler-cli|core|
ā elements|forms|http|language-service|localize|platform-browser|
ā platform-browser-dynamic|platform-server|router|service-worker|
ā upgrade|zone.js|packaging|changelog|docs-infra|migrations|
ā devtools
ā
āā⫸ Commit Type: build|ci|docs|feat|fix|perf|refactor|test
The following commit message patterns will trigger a release of their given scope:
fix(pencil): stop graphite breaking when too much pressure applied
feat(pencil): stop graphite breaking when too much pressure applied
(Note that the BREAKING CHANGE: token must be in the footer of the commit)
feat(pencil): stop graphite breaking when too much pressure applied
BREAKING CHANGE: The graphiteWidth option has been removed.
The default graphite width of 10mm is always used for performance reasons.
| Feature | Deadline | Complete | |
|---|---|---|---|
| Dependency container for dependency injection solution | TBD | yes/ not currently implemented | |
| Code linting (https://www.npmjs.com/package/eslint-plugin-hexagonal-architecture) | TBD | ||
| CLI tool for dev experience | TBD | ||
| class validator @Sasha Mahalia | 2024-04-05 | yes | |
| semantic-release | |||
| commitlint | https://commitlint.js.org/guides/local-setup.html |
Semantic Release
https://semantic-release.gitbook.io/semantic-release
https://commitlint.js.org/guides/getting-started.html
https://www.conventionalcommits.org/en/v1.0.0/
top level domain entity is the entity that returns data to the client. This is achieved via the use of a Controller and Mapper. This can be an Entity or and AggregateRoottoResponse mapper method as it's not needed. However, if the given entity is also a top level domain entity then it does indeed need a complete mapper interface, as it will return a response interface to the client.operator does not exist: character varying < timestamp with time zone or similar is a Postgres error that refers to having a mismatch between the operation a Cube query is trying to perform with the datatype that isnāt a timestamp. Converting the offending postgres datatype - ie āvarcharā to ātimestampā will solve the issue.test/integration folder.FAQs
[](https://github.com/semantic-release/semantic-release) [
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Rustās crates.io team is advancing an RFC to add a Security tab that surfaces RustSec vulnerability and unsoundness advisories directly on crate pages.

Security News
/Research
Socket found a Rust typosquat (finch-rust) that loads sha-rust to steal credentials, using impersonation and an unpinned dependency to auto-deliver updates.

Research
/Security Fundamentals
A pair of typosquatted Go packages posing as Googleās UUID library quietly turn helper functions into encrypted exfiltration channels to a paste site, putting developer and CI data at risk.