Skip to content

Commit 549fcc7

Browse files
committed
feat(vsa): change folder structure to add vertical slice architecture
closes #413
1 parent 815c831 commit 549fcc7

File tree

156 files changed

+598
-600
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

156 files changed

+598
-600
lines changed

Diff for: jest.config.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ module.exports = {
3030

3131
/* Bootstrap settings */
3232
// Set initial config and enable jest-extended features
33-
setupFilesAfterEnv: ['<rootDir>/test/jest.setup.ts', 'jest-extended/all'],
33+
setupFilesAfterEnv: ['<rootDir>/test/jest.setup.ts'],
3434

3535
/* Global test settings */
3636
// Automatically clear mock calls and instances between every test

Diff for: package-lock.json

+337-337
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: package.json

+21-21
Original file line numberDiff line numberDiff line change
@@ -64,20 +64,20 @@
6464
"dependencies": {
6565
"@ngneat/falso": "^7.2.0",
6666
"@prisma/client": "^5.10.2",
67-
"@tsed/ajv": "^7.62.2",
68-
"@tsed/common": "^7.62.2",
69-
"@tsed/components-scan": "^7.62.2",
70-
"@tsed/core": "^7.62.2",
71-
"@tsed/di": "^7.62.2",
72-
"@tsed/exceptions": "^7.62.2",
73-
"@tsed/ioredis": "^7.62.2",
74-
"@tsed/json-mapper": "^7.62.2",
67+
"@tsed/ajv": "^7.62.3",
68+
"@tsed/common": "^7.62.3",
69+
"@tsed/components-scan": "^7.62.3",
70+
"@tsed/core": "^7.62.3",
71+
"@tsed/di": "^7.62.3",
72+
"@tsed/exceptions": "^7.62.3",
73+
"@tsed/ioredis": "^7.62.3",
74+
"@tsed/json-mapper": "^7.62.3",
7575
"@tsed/logger": "^6.6.3",
76-
"@tsed/openspec": "^7.62.2",
77-
"@tsed/platform-express": "^7.62.2",
78-
"@tsed/prisma": "^7.62.2",
79-
"@tsed/schema": "^7.62.2",
80-
"@tsed/swagger": "^7.62.2",
76+
"@tsed/openspec": "^7.62.3",
77+
"@tsed/platform-express": "^7.62.3",
78+
"@tsed/prisma": "^7.62.3",
79+
"@tsed/schema": "^7.62.3",
80+
"@tsed/swagger": "^7.62.3",
8181
"ajv": "^8.12.0",
8282
"argon2": "^0.40.1",
8383
"body-parser": "^1.20.2",
@@ -134,18 +134,18 @@
134134
"@types/luxon": "^3.4.2",
135135
"@types/method-override": "^0.0.35",
136136
"@types/multer": "^1.4.11",
137-
"@types/node": "^20.11.24",
137+
"@types/node": "^20.11.25",
138138
"@types/node-emoji": "^1.8.2",
139139
"@types/source-map-support": "^0.5.10",
140140
"@types/supertest": "^6.0.2",
141141
"@types/swagger-schema-official": "^2.0.25",
142142
"@types/uuid": "^9.0.8",
143-
"@typescript-eslint/eslint-plugin": "^7.1.0",
144-
"@typescript-eslint/parser": "^7.1.0",
143+
"@typescript-eslint/eslint-plugin": "^7.1.1",
144+
"@typescript-eslint/parser": "^7.1.1",
145145
"commitizen": "^4.3.0",
146146
"copyfiles": "^2.4.1",
147147
"cross-env": "^7.0.3",
148-
"cspell": "^8.5.0",
148+
"cspell": "^8.6.0",
149149
"cz-conventional-changelog": "^3.3.0",
150150
"eslint": "^8.57.0",
151151
"eslint-config-airbnb-base": "^15.0.0",
@@ -159,7 +159,7 @@
159159
"eslint-plugin-jest": "^27.9.0",
160160
"eslint-plugin-jest-extended": "^2.0.0",
161161
"eslint-plugin-jsonc": "^2.13.0",
162-
"eslint-plugin-markdown": "^3.0.1",
162+
"eslint-plugin-markdown": "^4.0.1",
163163
"eslint-plugin-n": "^16.6.2",
164164
"eslint-plugin-optimize-regex": "^1.2.1",
165165
"eslint-plugin-prefer-arrow": "^1.2.3",
@@ -198,15 +198,15 @@
198198
"tsc-alias": "^1.8.8",
199199
"tsconfig-paths": "^4.2.0",
200200
"tsx": "^4.7.1",
201-
"typescript": "^5.3.3",
201+
"typescript": "^5.4.2",
202202
"yaml-eslint-parser": "^1.2.2"
203203
},
204204
"engines": {
205205
"node": ">=20.9.0",
206206
"npm": ">=6.7.0"
207207
},
208208
"prisma": {
209-
"schema": "src/infrastructure/shared/persistence/prisma/schema.prisma",
210-
"seed": "tsx src/infrastructure/shared/persistence/prisma/seed.ts"
209+
"schema": "src/shared/infrastructure/persistence/prisma/schema.prisma",
210+
"seed": "tsx src/shared/infrastructure/persistence/prisma/seed.ts"
211211
}
212212
}

Diff for: src/index.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ import 'source-map-support/register';
44
import { PlatformExpress } from '@tsed/platform-express';
55
import * as emoji from 'node-emoji';
66

7-
import { Logger } from '@domain/shared';
8-
import { bootstrap } from '@infrastructure/shared';
97
import { Server } from '@presentation/rest/server';
8+
import { Logger } from '@shared/domain';
9+
import { bootstrap } from '@shared/infrastructure';
1010

1111
const start = async (): Promise<void> => {
1212
await bootstrap();

Diff for: src/application/health/check-health-status.request.ts renamed to src/modules/health/application/check-health-status.request.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { UseCaseRequest } from '@application/shared';
2-
import { TriggeredBy } from '@domain/shared/entities/triggered-by';
1+
import { UseCaseRequest } from '@shared/application';
2+
import { TriggeredBy } from '@shared/domain/entities/triggered-by';
33

44
class CheckHealthStatusRequest extends UseCaseRequest {
55
readonly appVersion: string;

Diff for: src/application/health/check-health-status.usecase.ts renamed to src/modules/health/application/check-health-status.usecase.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { BaseUseCase, UseCase } from '@application/shared';
2-
import { HealthStatus } from '@domain/health';
1+
import { HealthStatus } from '@modules/health/domain';
2+
import { BaseUseCase, UseCase } from '@shared/application';
33

44
import { CheckHealthStatusRequest } from './check-health-status.request';
55
import { HealthStatusResponse } from './health-status.response';

Diff for: src/application/health/health-status.response.ts renamed to src/modules/health/application/health-status.response.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { HealthStatus } from '@domain/health';
1+
import { HealthStatus } from '@modules/health/domain';
22

33
class HealthStatusResponse {
44
readonly status: string;
File renamed without changes.

Diff for: src/domain/health/health-status.ts renamed to src/modules/health/domain/health-status.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { DomainEntity } from '@domain/shared/entities';
1+
import { DomainEntity } from '@shared/domain/entities';
22

33
class HealthStatus extends DomainEntity {
44
readonly status: string;
File renamed without changes.

Diff for: src/modules/health/infrastructure/.gitkeep

Whitespace-only changes.

Diff for: src/application/sessions/end/end-session.request.ts renamed to src/modules/sessions/application/end/end-session.request.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { UseCaseRequest } from '@application/shared';
2-
import { Nullable } from '@domain/shared';
3-
import { TriggeredBy } from '@domain/shared/entities/triggered-by';
1+
import { UseCaseRequest } from '@shared/application';
2+
import { Nullable } from '@shared/domain';
3+
import { TriggeredBy } from '@shared/domain/entities/triggered-by';
44

55
class EndSessionRequest extends UseCaseRequest {
66
readonly accessToken: Nullable<string>;

Diff for: src/application/sessions/end/end-session.usecase.ts renamed to src/modules/sessions/application/end/end-session.usecase.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
import { BaseUseCase, UseCase } from '@application/shared';
21
import {
32
InvalidSessionException,
43
Session,
54
SessionRepository,
65
SessionRevocationReason,
76
SessionRevokedBy
8-
} from '@domain/sessions';
9-
import { TokenProviderDomainService } from '@domain/sessions/tokens';
10-
import { Nullable } from '@domain/shared';
7+
} from '@modules/sessions/domain';
8+
import { TokenProviderDomainService } from '@modules/sessions/domain/tokens';
9+
import { BaseUseCase, UseCase } from '@shared/application';
10+
import { Nullable } from '@shared/domain';
1111

1212
import { EndSessionRequest } from './end-session.request';
1313

File renamed without changes.

Diff for: src/application/sessions/refresh/refresh-session.request.ts renamed to src/modules/sessions/application/refresh/refresh-session.request.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { UseCaseRequest } from '@application/shared';
2-
import { TriggeredBy } from '@domain/shared/entities/triggered-by';
3-
import { InvalidParameterException } from '@domain/shared/exceptions';
1+
import { UseCaseRequest } from '@shared/application';
2+
import { TriggeredBy } from '@shared/domain/entities/triggered-by';
3+
import { InvalidParameterException } from '@shared/domain/exceptions';
44

55
class RefreshSessionRequest extends UseCaseRequest {
66
readonly refreshToken: string;

Diff for: src/application/sessions/refresh/refresh-session.usecase.ts renamed to src/modules/sessions/application/refresh/refresh-session.usecase.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1-
import { SessionResponse } from '@application/sessions';
2-
import { BaseUseCase, UseCase } from '@application/shared';
1+
import { SessionResponse } from '@modules/sessions/application';
32
import {
43
InvalidSessionException,
54
Session,
65
SessionExpiresAt,
76
SessionRefreshTokenHash,
87
SessionRepository,
98
SessionUserData
10-
} from '@domain/sessions';
11-
import { RefreshToken, TokenProviderDomainService } from '@domain/sessions/tokens';
12-
import { User, UserRepository, UserUuid } from '@domain/users';
9+
} from '@modules/sessions/domain';
10+
import { RefreshToken, TokenProviderDomainService } from '@modules/sessions/domain/tokens';
11+
import { User, UserRepository, UserUuid } from '@modules/users/domain';
12+
import { BaseUseCase, UseCase } from '@shared/application';
1313

1414
import { RefreshSessionRequest } from './refresh-session.request';
1515

Diff for: src/application/sessions/session.response.ts renamed to src/modules/sessions/application/session.response.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { Session } from '@domain/sessions/session';
2-
import { AccessToken, RefreshToken } from '@domain/sessions/tokens';
1+
import { Session } from '@modules/sessions/domain/session';
2+
import { AccessToken, RefreshToken } from '@modules/sessions/domain/tokens';
33

44
class SessionResponse {
55
readonly session: Session;

Diff for: src/application/sessions/start/start-session.request.ts renamed to src/modules/sessions/application/start/start-session.request.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { UseCaseRequest } from '@application/shared';
2-
import { TriggeredBy } from '@domain/shared/entities/triggered-by';
3-
import { InvalidParameterException } from '@domain/shared/exceptions';
1+
import { UseCaseRequest } from '@shared/application';
2+
import { TriggeredBy } from '@shared/domain/entities/triggered-by';
3+
import { InvalidParameterException } from '@shared/domain/exceptions';
44

55
class StartSessionRequest extends UseCaseRequest {
66
readonly userUuid: string;

Diff for: src/application/sessions/start/start-session.usecase.ts renamed to src/modules/sessions/application/start/start-session.usecase.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1-
import { SessionResponse } from '@application/sessions';
2-
import { BaseUseCase, UseCase } from '@application/shared';
1+
import { SessionResponse } from '@modules/sessions/application';
32
import {
43
Session,
54
SessionExpiresAt,
65
SessionRefreshTokenHash,
76
SessionRepository,
87
SessionUserData
9-
} from '@domain/sessions';
10-
import { TokenProviderDomainService } from '@domain/sessions/tokens';
11-
import { Uuid } from '@domain/shared/value-object';
12-
import { User, UserNotExistsException, UserRepository, UserUuid } from '@domain/users';
8+
} from '@modules/sessions/domain';
9+
import { TokenProviderDomainService } from '@modules/sessions/domain/tokens';
10+
import { User, UserNotExistsException, UserRepository, UserUuid } from '@modules/users/domain';
11+
import { BaseUseCase, UseCase } from '@shared/application';
12+
import { Uuid } from '@shared/domain/value-object';
1313

1414
import { StartSessionRequest } from './start-session.request';
1515

Diff for: src/application/sessions/validate/validate-session.request.ts renamed to src/modules/sessions/application/validate/validate-session.request.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { UseCaseRequest } from '@application/shared';
2-
import { Nullable } from '@domain/shared';
3-
import { TriggeredBy } from '@domain/shared/entities/triggered-by';
1+
import { UseCaseRequest } from '@shared/application';
2+
import { Nullable } from '@shared/domain';
3+
import { TriggeredBy } from '@shared/domain/entities/triggered-by';
44

55
class ValidateSessionRequest extends UseCaseRequest {
66
readonly accessToken: Nullable<string>;

Diff for: src/application/sessions/validate/validate-session.usecase.ts renamed to src/modules/sessions/application/validate/validate-session.usecase.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
import { BaseUseCase, UseCase } from '@application/shared';
21
import {
32
InvalidSessionException,
43
SessionExpiresAt,
54
SessionRefreshTokenHash,
65
SessionRepository,
76
SessionUserData
8-
} from '@domain/sessions';
9-
import { TokenProviderDomainService } from '@domain/sessions/tokens';
10-
import { UserRepository } from '@domain/users';
7+
} from '@modules/sessions/domain';
8+
import { TokenProviderDomainService } from '@modules/sessions/domain/tokens';
9+
import { UserRepository } from '@modules/users/domain';
10+
import { BaseUseCase, UseCase } from '@shared/application';
1111

1212
import { ValidateSessionRequest } from './validate-session.request';
1313
import { ValidatedSessionResponse } from './validated-session.response';

Diff for: src/application/sessions/validate/validated-session.response.ts renamed to src/modules/sessions/application/validate/validated-session.response.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { Session } from '@domain/sessions/session';
2-
import { AccessToken, RefreshToken } from '@domain/sessions/tokens';
3-
import { Nullable } from '@domain/shared';
1+
import { Session } from '@modules/sessions/domain/session';
2+
import { AccessToken, RefreshToken } from '@modules/sessions/domain/tokens';
3+
import { Nullable } from '@shared/domain';
44

55
class ValidatedSessionResponse {
66
public session: Nullable<Session>;
File renamed without changes.

Diff for: src/domain/sessions/invalid-session.exception.ts renamed to src/modules/sessions/domain/invalid-session.exception.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { DomainException } from '@domain/shared/exceptions';
1+
import { DomainException } from '@shared/domain/exceptions';
22

33
class InvalidSessionException extends DomainException {
44
constructor() {

Diff for: src/domain/sessions/session-expires-at.ts renamed to src/modules/sessions/domain/session-expires-at.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { DateValueObject } from '@domain/shared/value-object';
1+
import { DateValueObject } from '@shared/domain/value-object';
22

33
class SessionExpiresAt extends DateValueObject {}
44

Diff for: src/domain/sessions/session-id.ts renamed to src/modules/sessions/domain/session-id.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { NumberValueObject } from '@domain/shared/value-object';
1+
import { NumberValueObject } from '@shared/domain/value-object';
22

33
class SessionId extends NumberValueObject {}
44

Diff for: src/domain/sessions/session-refresh-token-hash.ts renamed to src/modules/sessions/domain/session-refresh-token-hash.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { HasherDomainService } from '@domain/shared/services';
2-
import { StringValueObject } from '@domain/shared/value-object';
1+
import { HasherDomainService } from '@shared/domain/services';
2+
import { StringValueObject } from '@shared/domain/value-object';
33

44
class SessionRefreshTokenHash extends StringValueObject {
55
public static async createFromPlainRefreshToken(refreshToken: string): Promise<SessionRefreshTokenHash> {

Diff for: src/domain/sessions/session-revoked-at.ts renamed to src/modules/sessions/domain/session-revoked-at.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { DateValueObject } from '@domain/shared/value-object';
1+
import { DateValueObject } from '@shared/domain/value-object';
22

33
class SessionRevokedAt extends DateValueObject {}
44

Diff for: src/domain/sessions/session-revoked-by.ts renamed to src/modules/sessions/domain/session-revoked-by.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { StringValueObject } from '@domain/shared/value-object';
1+
import { StringValueObject } from '@shared/domain/value-object';
22

33
class SessionRevokedBy extends StringValueObject {}
44

Diff for: src/domain/sessions/session-revoked-reason.ts renamed to src/modules/sessions/domain/session-revoked-reason.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { StringValueObject } from '@domain/shared/value-object';
1+
import { StringValueObject } from '@shared/domain/value-object';
22

33
class SessionRevocationReason extends StringValueObject {}
44

Diff for: src/domain/sessions/session-user-data.ts renamed to src/modules/sessions/domain/session-user-data.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { CompositeValueObject } from '@domain/shared/value-object';
1+
import { CompositeValueObject } from '@shared/domain/value-object';
22

33
class SessionUserData extends CompositeValueObject {
44
readonly username: string;

Diff for: src/domain/sessions/session-user-uuid.ts renamed to src/modules/sessions/domain/session-user-uuid.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Uuid } from '@domain/shared/value-object';
1+
import { Uuid } from '@shared/domain/value-object';
22

33
class SessionUserUuid extends Uuid {}
44

Diff for: src/domain/sessions/session-uuid.ts renamed to src/modules/sessions/domain/session-uuid.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Uuid } from '@domain/shared/value-object';
1+
import { Uuid } from '@shared/domain/value-object';
22

33
class SessionUuid extends Uuid {}
44

Diff for: src/domain/sessions/session.repository.ts renamed to src/modules/sessions/domain/session.repository.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Nullable } from '@domain/shared';
1+
import { Nullable } from '@shared/domain';
22

33
import { Session } from './session';
44
import { SessionUuid } from './session-uuid';

Diff for: src/domain/sessions/session.ts renamed to src/modules/sessions/domain/session.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { DateTime } from 'luxon';
22

3-
import { Nullable } from '@domain/shared';
4-
import { DomainEntity } from '@domain/shared/entities/domain-entity';
3+
import { Nullable } from '@shared/domain';
4+
import { DomainEntity } from '@shared/domain/entities/domain-entity';
55

66
import { SessionExpiresAt } from './session-expires-at';
77
import { SessionId } from './session-id';

Diff for: src/domain/sessions/tokens/access-token.ts renamed to src/modules/sessions/domain/tokens/access-token.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { SessionUuid } from '@domain/sessions/session-uuid';
2-
import { UserEmail, UserRole, UserUsername, UserUuid } from '@domain/users';
1+
import { SessionUuid } from '@modules/sessions/domain/session-uuid';
2+
import { UserEmail, UserRole, UserUsername, UserUuid } from '@modules/users/domain';
33

44
import { Token, TokenType } from './token';
55
import { TokenExpiresAt } from './token-expires-at';
File renamed without changes.

Diff for: src/domain/sessions/tokens/refresh-token.ts renamed to src/modules/sessions/domain/tokens/refresh-token.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { SessionUuid } from '@domain/sessions/session-uuid';
2-
import { UserEmail, UserRole, UserUsername, UserUuid } from '@domain/users';
1+
import { SessionUuid } from '@modules/sessions/domain/session-uuid';
2+
import { UserEmail, UserRole, UserUsername, UserUuid } from '@modules/users/domain';
33

44
import { Token, TokenType } from './token';
55
import { TokenExpiresAt } from './token-expires-at';

Diff for: src/domain/sessions/tokens/token-expires-at.ts renamed to src/modules/sessions/domain/tokens/token-expires-at.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { DateValueObject } from '@domain/shared/value-object';
1+
import { DateValueObject } from '@shared/domain/value-object';
22

33
class TokenExpiresAt extends DateValueObject {}
44

Diff for: src/domain/sessions/tokens/token-provider.domain-service.ts renamed to src/modules/sessions/domain/tokens/token-provider.domain-service.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { SessionUuid } from '@domain/sessions/session-uuid';
2-
import { Nullable } from '@domain/shared';
3-
import { UserEmail, UserRole, UserUsername, UserUuid } from '@domain/users';
1+
import { SessionUuid } from '@modules/sessions/domain/session-uuid';
2+
import { UserEmail, UserRole, UserUsername, UserUuid } from '@modules/users/domain';
3+
import { Nullable } from '@shared/domain';
44

55
import { AccessToken } from './access-token';
66
import { RefreshToken } from './refresh-token';

Diff for: src/domain/sessions/tokens/token.ts renamed to src/modules/sessions/domain/tokens/token.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { DateTime } from 'luxon';
22

3-
import { SessionUuid } from '@domain/sessions/session-uuid';
4-
import { UserEmail, UserRole, UserUsername, UserUuid } from '@domain/users';
3+
import { SessionUuid } from '@modules/sessions/domain/session-uuid';
4+
import { UserEmail, UserRole, UserUsername, UserUuid } from '@modules/users/domain';
55

66
import { TokenExpiresAt } from './token-expires-at';
77

File renamed without changes.

0 commit comments

Comments
 (0)