@libs-for-dev/nestjs-ddd-library 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/abstract-entity.d.ts +2 -1
- package/dist/abstract-entity.js +4 -7
- package/dist/abstract-value-object.js +3 -7
- package/dist/index.js +3 -19
- package/dist/tests/deep-readonly.d.ts +4 -0
- package/dist/tests/deep-readonly.js +10 -0
- package/dist/value-objects/email.js +8 -12
- package/dist/value-objects/errors/index.d.ts +3 -0
- package/dist/value-objects/errors/index.js +5 -18
- package/dist/value-objects/errors/not-a-money.error.d.ts +4 -0
- package/dist/value-objects/errors/not-a-money.error.js +6 -0
- package/dist/value-objects/errors/not-a-phone-number.error.d.ts +4 -0
- package/dist/value-objects/errors/not-a-phone-number.error.js +6 -0
- package/dist/value-objects/errors/not-an-email.error.js +1 -5
- package/dist/value-objects/errors/not-an-url.error.d.ts +4 -0
- package/dist/value-objects/errors/not-an-url.error.js +6 -0
- package/dist/value-objects/errors/not-an-uuid.error.js +1 -5
- package/dist/value-objects/index.d.ts +3 -0
- package/dist/value-objects/index.js +6 -19
- package/dist/value-objects/money.d.ts +13 -0
- package/dist/value-objects/money.js +34 -0
- package/dist/value-objects/phone-number.d.ts +8 -0
- package/dist/value-objects/phone-number.js +18 -0
- package/dist/value-objects/url.d.ts +8 -0
- package/dist/value-objects/url.js +18 -0
- package/dist/value-objects/uuid.js +10 -14
- package/package.json +22 -19
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import type { IEvent } from '@nestjs/cqrs';
|
|
2
2
|
import { AggregateRoot } from '@nestjs/cqrs';
|
|
3
|
+
import type { DeepReadonly } from './tests/deep-readonly';
|
|
3
4
|
export interface EntityPropsInterface<T> {
|
|
4
5
|
props: T;
|
|
5
6
|
}
|
|
6
7
|
export declare abstract class AbstractEntity<Id, Props, Event extends IEvent = IEvent> extends AggregateRoot<Event> {
|
|
7
8
|
readonly id: Id;
|
|
8
|
-
get props(): Props
|
|
9
|
+
get props(): DeepReadonly<Props>;
|
|
9
10
|
protected propsData: EntityPropsInterface<Props>;
|
|
10
11
|
protected constructor(id: Id, props: Props);
|
|
11
12
|
equals(object: AbstractEntity<Id, Props>): boolean;
|
package/dist/abstract-entity.js
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const cqrs_1 = require("@nestjs/cqrs");
|
|
5
|
-
class AbstractEntity extends cqrs_1.AggregateRoot {
|
|
1
|
+
import { AggregateRoot } from '@nestjs/cqrs';
|
|
2
|
+
import { deepReadonly } from './tests/deep-readonly';
|
|
3
|
+
export class AbstractEntity extends AggregateRoot {
|
|
6
4
|
id;
|
|
7
5
|
get props() {
|
|
8
|
-
return
|
|
6
|
+
return deepReadonly(this.propsData.props);
|
|
9
7
|
}
|
|
10
8
|
propsData;
|
|
11
9
|
constructor(id, props) {
|
|
@@ -20,4 +18,3 @@ class AbstractEntity extends cqrs_1.AggregateRoot {
|
|
|
20
18
|
return this.id === object.id;
|
|
21
19
|
}
|
|
22
20
|
}
|
|
23
|
-
exports.AbstractEntity = AbstractEntity;
|
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
exports.AbstractValueObject = void 0;
|
|
4
|
-
const fast_equals_1 = require("fast-equals");
|
|
5
|
-
class AbstractValueObject {
|
|
1
|
+
import { deepEqual } from 'fast-equals';
|
|
2
|
+
export class AbstractValueObject {
|
|
6
3
|
props;
|
|
7
4
|
constructor(props) {
|
|
8
5
|
this.props = props;
|
|
@@ -11,7 +8,6 @@ class AbstractValueObject {
|
|
|
11
8
|
if (this.constructor.name !== valueObject.constructor.name) {
|
|
12
9
|
return false;
|
|
13
10
|
}
|
|
14
|
-
return
|
|
11
|
+
return deepEqual(valueObject.props, this.props);
|
|
15
12
|
}
|
|
16
13
|
}
|
|
17
|
-
exports.AbstractValueObject = AbstractValueObject;
|
package/dist/index.js
CHANGED
|
@@ -1,19 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
-
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
-
};
|
|
16
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
__exportStar(require("./value-objects"), exports);
|
|
18
|
-
__exportStar(require("./abstract-entity"), exports);
|
|
19
|
-
__exportStar(require("./abstract-value-object"), exports);
|
|
1
|
+
export * from './value-objects';
|
|
2
|
+
export * from './abstract-entity';
|
|
3
|
+
export * from './abstract-value-object';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export const deepReadonly = (object) => {
|
|
2
|
+
const propertyNames = Object.getOwnPropertyNames(object);
|
|
3
|
+
for (const name of propertyNames) {
|
|
4
|
+
const value = object[name];
|
|
5
|
+
if (value !== undefined && typeof value === 'object') {
|
|
6
|
+
deepReadonly(value);
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
return Object.freeze(object);
|
|
10
|
+
};
|
|
@@ -1,22 +1,18 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const abstract_value_object_1 = require("../abstract-value-object");
|
|
7
|
-
const not_an_email_error_1 = require("../value-objects/errors/not-an-email.error");
|
|
8
|
-
class Email extends abstract_value_object_1.AbstractValueObject {
|
|
1
|
+
import { isEmail } from 'class-validator';
|
|
2
|
+
import { Err, Ok } from 'oxide.ts';
|
|
3
|
+
import { AbstractValueObject } from '../abstract-value-object';
|
|
4
|
+
import { NotAnEmailError } from '../value-objects/errors/not-an-email.error';
|
|
5
|
+
export class Email extends AbstractValueObject {
|
|
9
6
|
get value() {
|
|
10
7
|
return this.props.value;
|
|
11
8
|
}
|
|
12
9
|
static create(email) {
|
|
13
10
|
if (!this.isValid(email)) {
|
|
14
|
-
return
|
|
11
|
+
return Err(new NotAnEmailError(email));
|
|
15
12
|
}
|
|
16
|
-
return
|
|
13
|
+
return Ok(new Email({ value: email }));
|
|
17
14
|
}
|
|
18
15
|
static isValid(email) {
|
|
19
|
-
return
|
|
16
|
+
return isEmail(email);
|
|
20
17
|
}
|
|
21
18
|
}
|
|
22
|
-
exports.Email = Email;
|
|
@@ -1,18 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
-
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
-
};
|
|
16
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
__exportStar(require("./not-an-email.error"), exports);
|
|
18
|
-
__exportStar(require("./not-an-uuid.error"), exports);
|
|
1
|
+
export * from './not-a-money.error';
|
|
2
|
+
export * from './not-a-phone-number.error';
|
|
3
|
+
export * from './not-an-email.error';
|
|
4
|
+
export * from './not-an-url.error';
|
|
5
|
+
export * from './not-an-uuid.error';
|
|
@@ -1,10 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.NotAnEmailError = void 0;
|
|
4
|
-
class NotAnEmailError extends Error {
|
|
1
|
+
export class NotAnEmailError extends Error {
|
|
5
2
|
static name = 'NotAnEmailError';
|
|
6
3
|
constructor(email) {
|
|
7
4
|
super(`${email} is not a valid email`);
|
|
8
5
|
}
|
|
9
6
|
}
|
|
10
|
-
exports.NotAnEmailError = NotAnEmailError;
|
|
@@ -1,10 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.NotAnUuidError = void 0;
|
|
4
|
-
class NotAnUuidError extends Error {
|
|
1
|
+
export class NotAnUuidError extends Error {
|
|
5
2
|
static name = 'NotAnUuidError';
|
|
6
3
|
constructor(uuid) {
|
|
7
4
|
super(`${uuid} is not a valid UUID`);
|
|
8
5
|
}
|
|
9
6
|
}
|
|
10
|
-
exports.NotAnUuidError = NotAnUuidError;
|
|
@@ -1,19 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
-
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
-
};
|
|
16
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
__exportStar(require("./errors"), exports);
|
|
18
|
-
__exportStar(require("./email"), exports);
|
|
19
|
-
__exportStar(require("./uuid"), exports);
|
|
1
|
+
export * from './errors';
|
|
2
|
+
export * from './email';
|
|
3
|
+
export * from './money';
|
|
4
|
+
export * from './phone-number';
|
|
5
|
+
export * from './url';
|
|
6
|
+
export * from './uuid';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Result } from 'oxide.ts';
|
|
2
|
+
import { AbstractValueObject } from '../abstract-value-object';
|
|
3
|
+
export interface MoneyPropsInterface {
|
|
4
|
+
amount: number;
|
|
5
|
+
currency: string;
|
|
6
|
+
}
|
|
7
|
+
export declare class Money extends AbstractValueObject<MoneyPropsInterface> {
|
|
8
|
+
get amount(): number;
|
|
9
|
+
get currency(): string;
|
|
10
|
+
get value(): MoneyPropsInterface;
|
|
11
|
+
static create(amount: number, currency: string): Result<Money, Error>;
|
|
12
|
+
private static isValid;
|
|
13
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Err, Ok } from 'oxide.ts';
|
|
2
|
+
import { AbstractValueObject } from '../abstract-value-object';
|
|
3
|
+
import { NotAMoneyError } from '../value-objects/errors/not-a-money.error';
|
|
4
|
+
export class Money extends AbstractValueObject {
|
|
5
|
+
get amount() {
|
|
6
|
+
return this.props.amount;
|
|
7
|
+
}
|
|
8
|
+
get currency() {
|
|
9
|
+
return this.props.currency;
|
|
10
|
+
}
|
|
11
|
+
get value() {
|
|
12
|
+
return {
|
|
13
|
+
amount: this.amount,
|
|
14
|
+
currency: this.currency,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
static create(amount, currency) {
|
|
18
|
+
if (!this.isValid(amount, currency)) {
|
|
19
|
+
return Err(new NotAMoneyError(amount, currency));
|
|
20
|
+
}
|
|
21
|
+
return Ok(new Money({ amount, currency }));
|
|
22
|
+
}
|
|
23
|
+
static isValid(amount, currency) {
|
|
24
|
+
const minimumAmount = 0;
|
|
25
|
+
if (amount < minimumAmount || !Number.isFinite(amount)) {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
const currencyRegex = /^[A-Z]{3}$/u;
|
|
29
|
+
if (!currencyRegex.test(currency)) {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Result } from 'oxide.ts';
|
|
2
|
+
import { AbstractValueObject } from '../abstract-value-object';
|
|
3
|
+
import { NotAPhoneNumberError } from '../value-objects/errors/not-a-phone-number.error';
|
|
4
|
+
export declare class PhoneNumber extends AbstractValueObject<string> {
|
|
5
|
+
get value(): string;
|
|
6
|
+
static create(phoneNumber: string): Result<PhoneNumber, NotAPhoneNumberError>;
|
|
7
|
+
private static isValid;
|
|
8
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { isMobilePhone } from 'class-validator';
|
|
2
|
+
import { Err, Ok } from 'oxide.ts';
|
|
3
|
+
import { AbstractValueObject } from '../abstract-value-object';
|
|
4
|
+
import { NotAPhoneNumberError } from '../value-objects/errors/not-a-phone-number.error';
|
|
5
|
+
export class PhoneNumber extends AbstractValueObject {
|
|
6
|
+
get value() {
|
|
7
|
+
return this.props.value;
|
|
8
|
+
}
|
|
9
|
+
static create(phoneNumber) {
|
|
10
|
+
if (!this.isValid(phoneNumber)) {
|
|
11
|
+
return Err(new NotAPhoneNumberError(phoneNumber));
|
|
12
|
+
}
|
|
13
|
+
return Ok(new PhoneNumber({ value: phoneNumber }));
|
|
14
|
+
}
|
|
15
|
+
static isValid(phoneNumber) {
|
|
16
|
+
return isMobilePhone(phoneNumber);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Result } from 'oxide.ts';
|
|
2
|
+
import { AbstractValueObject } from '../abstract-value-object';
|
|
3
|
+
import { NotAnUrlError } from '../value-objects/errors/not-an-url.error';
|
|
4
|
+
export declare class Url extends AbstractValueObject<string> {
|
|
5
|
+
get value(): string;
|
|
6
|
+
static create(url: string): Result<Url, NotAnUrlError>;
|
|
7
|
+
private static isValid;
|
|
8
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { isURL } from 'class-validator';
|
|
2
|
+
import { Err, Ok } from 'oxide.ts';
|
|
3
|
+
import { AbstractValueObject } from '../abstract-value-object';
|
|
4
|
+
import { NotAnUrlError } from '../value-objects/errors/not-an-url.error';
|
|
5
|
+
export class Url extends AbstractValueObject {
|
|
6
|
+
get value() {
|
|
7
|
+
return this.props.value;
|
|
8
|
+
}
|
|
9
|
+
static create(url) {
|
|
10
|
+
if (!this.isValid(url)) {
|
|
11
|
+
return Err(new NotAnUrlError(url));
|
|
12
|
+
}
|
|
13
|
+
return Ok(new Url({ value: url }));
|
|
14
|
+
}
|
|
15
|
+
static isValid(url) {
|
|
16
|
+
return isURL(url, { require_protocol: true });
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -1,23 +1,19 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const abstract_value_object_1 = require("../abstract-value-object");
|
|
8
|
-
const not_an_uuid_error_1 = require("../value-objects/errors/not-an-uuid.error");
|
|
9
|
-
class Uuid extends abstract_value_object_1.AbstractValueObject {
|
|
1
|
+
import { isUUID } from 'class-validator';
|
|
2
|
+
import { Err, Ok } from 'oxide.ts';
|
|
3
|
+
import { v7 } from 'ui7';
|
|
4
|
+
import { AbstractValueObject } from '../abstract-value-object';
|
|
5
|
+
import { NotAnUuidError } from '../value-objects/errors/not-an-uuid.error';
|
|
6
|
+
export class Uuid extends AbstractValueObject {
|
|
10
7
|
get value() {
|
|
11
8
|
return this.props.value;
|
|
12
9
|
}
|
|
13
10
|
static create(uuid) {
|
|
14
|
-
if (!
|
|
15
|
-
return
|
|
11
|
+
if (!isUUID(uuid)) {
|
|
12
|
+
return Err(new NotAnUuidError(uuid));
|
|
16
13
|
}
|
|
17
|
-
return
|
|
14
|
+
return Ok(new Uuid({ value: uuid }));
|
|
18
15
|
}
|
|
19
16
|
static generate() {
|
|
20
|
-
return new Uuid({ value:
|
|
17
|
+
return new Uuid({ value: v7() });
|
|
21
18
|
}
|
|
22
19
|
}
|
|
23
|
-
exports.Uuid = Uuid;
|
package/package.json
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@libs-for-dev/nestjs-ddd-library",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "NestJS DDD library",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ddd-library",
|
|
7
7
|
"nestjs"
|
|
8
8
|
],
|
|
9
|
+
"bugs": "https://gitlab.com/libs-for-dev/nestjs/nestjs-ddd-library/-/issues",
|
|
9
10
|
"repository": "https://gitlab.com/libs-for-dev/nestjs/nestjs-ddd-library",
|
|
10
11
|
"license": "MIT",
|
|
11
12
|
"author": {
|
|
12
13
|
"name": "libs-for-dev"
|
|
13
14
|
},
|
|
15
|
+
"type": "module",
|
|
14
16
|
"main": "dist/index.js",
|
|
15
17
|
"types": "dist/index.d.ts",
|
|
16
18
|
"files": [
|
|
@@ -22,7 +24,7 @@
|
|
|
22
24
|
"dev:test:watch": "vitest",
|
|
23
25
|
"lint": "yarn concurrently --timings 'yarn:lint:*'",
|
|
24
26
|
"lint:audit": "yarn npm audit",
|
|
25
|
-
"lint:depcheck": "
|
|
27
|
+
"lint:depcheck": "knip",
|
|
26
28
|
"lint:eslint": "eslint --cache --cache-location /tmp .",
|
|
27
29
|
"lint:scriptlint": "scriptlint",
|
|
28
30
|
"lint:tsc": "tsc --noEmit --pretty --project .",
|
|
@@ -31,28 +33,28 @@
|
|
|
31
33
|
"test:unit": "vitest run"
|
|
32
34
|
},
|
|
33
35
|
"devDependencies": {
|
|
34
|
-
"@libs-for-dev/eslint-rules": "2.
|
|
35
|
-
"@nestjs/common": "^11.1.
|
|
36
|
-
"@nestjs/core": "^11.1.
|
|
36
|
+
"@libs-for-dev/eslint-rules": "2.4.0",
|
|
37
|
+
"@nestjs/common": "^11.1.11",
|
|
38
|
+
"@nestjs/core": "^11.1.11",
|
|
37
39
|
"@nestjs/cqrs": "^11.0.3",
|
|
38
|
-
"@stryker-mutator/core": "^
|
|
39
|
-
"@stryker-mutator/vitest-runner": "^
|
|
40
|
-
"@types/node": "^
|
|
41
|
-
"@vitest/coverage-v8": "^
|
|
42
|
-
"class-validator": "^0.14.
|
|
43
|
-
"concurrently": "^9.1
|
|
40
|
+
"@stryker-mutator/core": "^9.4.0",
|
|
41
|
+
"@stryker-mutator/vitest-runner": "^9.4.0",
|
|
42
|
+
"@types/node": "^25.0.3",
|
|
43
|
+
"@vitest/coverage-v8": "^4.0.16",
|
|
44
|
+
"class-validator": "^0.14.3",
|
|
45
|
+
"concurrently": "^9.2.1",
|
|
44
46
|
"create-ts-index": "^1.14.0",
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"
|
|
47
|
+
"eslint": "9.39.2",
|
|
48
|
+
"fast-equals": "^6.0.0",
|
|
49
|
+
"jiti": "^2.6.1",
|
|
50
|
+
"knip": "^5.78.0",
|
|
49
51
|
"oxide.ts": "^1.1.0",
|
|
50
52
|
"reflect-metadata": "^0.2.2",
|
|
51
53
|
"scriptlint": "^3.0.0",
|
|
52
|
-
"tsc-alias": "^1.8.
|
|
53
|
-
"typescript": "^5.
|
|
54
|
+
"tsc-alias": "^1.8.16",
|
|
55
|
+
"typescript": "^5.9.3",
|
|
54
56
|
"ui7": "^0.2.3",
|
|
55
|
-
"vitest": "^
|
|
57
|
+
"vitest": "^4.0.16",
|
|
56
58
|
"yarn-audit-fix": "^10.1.1"
|
|
57
59
|
},
|
|
58
60
|
"peerDependencies": {
|
|
@@ -62,7 +64,8 @@
|
|
|
62
64
|
"oxide.ts": ">=1.0.0 <2.0.0",
|
|
63
65
|
"ui7": ">=0.2.0 <0.3.0"
|
|
64
66
|
},
|
|
65
|
-
"
|
|
67
|
+
"bundleDependencies": false,
|
|
68
|
+
"packageManager": "yarn@4.9.4",
|
|
66
69
|
"engines": {
|
|
67
70
|
"node": "^23.11.0"
|
|
68
71
|
}
|