feat(caprover): allow human to mark an app as successfully updated
This commit is contained in:
		
							parent
							
								
									5c599842f6
								
							
						
					
					
						commit
						204efe8a8b
					
				
							
								
								
									
										15
									
								
								domain/events/ApplicationUpdateFinished.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								domain/events/ApplicationUpdateFinished.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,15 @@
 | 
				
			|||||||
 | 
					import DomainEvent from "../DomainEvent";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type ApplicationUpdateFinishedPayload = {
 | 
				
			||||||
 | 
					  id: string;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class ApplicationUpdateFinished
 | 
				
			||||||
 | 
					  implements DomainEvent<ApplicationUpdateFinishedPayload>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  readonly type = "ApplicationUpdateFinished" as const;
 | 
				
			||||||
 | 
					  constructor(
 | 
				
			||||||
 | 
					    public readonly payload: ApplicationUpdateFinishedPayload,
 | 
				
			||||||
 | 
					    public readonly createdAt: Date
 | 
				
			||||||
 | 
					  ) {}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										99
									
								
								domain/projections/ApplicationUpdates.test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								domain/projections/ApplicationUpdates.test.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,99 @@
 | 
				
			|||||||
 | 
					import { beforeEach, describe, expect, it } from "bun:test";
 | 
				
			||||||
 | 
					import { TestApp } from "../testing/TestApp";
 | 
				
			||||||
 | 
					import { ApplicationUpdateStarted } from "../events/ApplicationUpdateStarted";
 | 
				
			||||||
 | 
					import { ApplicationUpdateFinished } from "../events/ApplicationUpdateFinished";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe("ApplicationUpdates", () => {
 | 
				
			||||||
 | 
					  describe("getPendingUpdates", () => {
 | 
				
			||||||
 | 
					    it("should return an empty array when there are no pending updates", () => {
 | 
				
			||||||
 | 
					      const app = new TestApp();
 | 
				
			||||||
 | 
					      expect(app.projections.ApplicationUpdates.getPendingUpdates()).toEqual(
 | 
				
			||||||
 | 
					        []
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it("should return all pending updates when several started", () => {
 | 
				
			||||||
 | 
					      const app = new TestApp([
 | 
				
			||||||
 | 
					        new ApplicationUpdateStarted(
 | 
				
			||||||
 | 
					          { id: "mail", newVersion: "1.0.0" },
 | 
				
			||||||
 | 
					          new Date()
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        new ApplicationUpdateStarted(
 | 
				
			||||||
 | 
					          { id: "blog", newVersion: "10.0.1" },
 | 
				
			||||||
 | 
					          new Date()
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					      ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const updates = app.projections.ApplicationUpdates.getPendingUpdates();
 | 
				
			||||||
 | 
					      expect(updates).toEqual([
 | 
				
			||||||
 | 
					        { id: "mail", newVersion: "1.0.0" },
 | 
				
			||||||
 | 
					        { id: "blog", newVersion: "10.0.1" },
 | 
				
			||||||
 | 
					      ]);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it("should return all pending updates for an application while it isn't successfull", () => {
 | 
				
			||||||
 | 
					      const app = new TestApp([
 | 
				
			||||||
 | 
					        new ApplicationUpdateStarted(
 | 
				
			||||||
 | 
					          { id: "mail", newVersion: "1.0.0" },
 | 
				
			||||||
 | 
					          new Date()
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        new ApplicationUpdateStarted(
 | 
				
			||||||
 | 
					          { id: "blog", newVersion: "10.0.1" },
 | 
				
			||||||
 | 
					          new Date()
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        new ApplicationUpdateStarted(
 | 
				
			||||||
 | 
					          { id: "mail", newVersion: "1.0.1" },
 | 
				
			||||||
 | 
					          new Date()
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					      ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const updates = app.projections.ApplicationUpdates.getPendingUpdates();
 | 
				
			||||||
 | 
					      expect(updates).toEqual([
 | 
				
			||||||
 | 
					        { id: "mail", newVersion: "1.0.0" },
 | 
				
			||||||
 | 
					        { id: "blog", newVersion: "10.0.1" },
 | 
				
			||||||
 | 
					        { id: "mail", newVersion: "1.0.1" },
 | 
				
			||||||
 | 
					      ]);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it("should not return updates for applications after they were marked as successful", () => {
 | 
				
			||||||
 | 
					      const app = new TestApp([
 | 
				
			||||||
 | 
					        new ApplicationUpdateStarted(
 | 
				
			||||||
 | 
					          { id: "mail", newVersion: "1.0.0" },
 | 
				
			||||||
 | 
					          new Date()
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        new ApplicationUpdateStarted(
 | 
				
			||||||
 | 
					          { id: "blog", newVersion: "10.0.1" },
 | 
				
			||||||
 | 
					          new Date()
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        new ApplicationUpdateFinished({ id: "mail" }, new Date()),
 | 
				
			||||||
 | 
					      ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const updates = app.projections.ApplicationUpdates.getPendingUpdates();
 | 
				
			||||||
 | 
					      expect(updates).toEqual([{ id: "blog", newVersion: "10.0.1" }]);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it("should consider the latest update for an application", () => {
 | 
				
			||||||
 | 
					      const app = new TestApp([
 | 
				
			||||||
 | 
					        new ApplicationUpdateStarted(
 | 
				
			||||||
 | 
					          { id: "mail", newVersion: "1.0.0" },
 | 
				
			||||||
 | 
					          new Date()
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        new ApplicationUpdateStarted(
 | 
				
			||||||
 | 
					          { id: "blog", newVersion: "4.2.0" },
 | 
				
			||||||
 | 
					          new Date()
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        new ApplicationUpdateFinished({ id: "mail" }, new Date()),
 | 
				
			||||||
 | 
					        new ApplicationUpdateStarted(
 | 
				
			||||||
 | 
					          { id: "mail", newVersion: "1.0.1" },
 | 
				
			||||||
 | 
					          new Date()
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					      ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const updates = app.projections.ApplicationUpdates.getPendingUpdates();
 | 
				
			||||||
 | 
					      expect(updates).toEqual([
 | 
				
			||||||
 | 
					        { id: "blog", newVersion: "4.2.0" },
 | 
				
			||||||
 | 
					        { id: "mail", newVersion: "1.0.1" },
 | 
				
			||||||
 | 
					      ]);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
@ -1,5 +1,7 @@
 | 
				
			|||||||
import DomainEvent from "../DomainEvent";
 | 
					import DomainEvent from "../DomainEvent";
 | 
				
			||||||
import DomainProjection from "../DomainProjection";
 | 
					import DomainProjection from "../DomainProjection";
 | 
				
			||||||
 | 
					import { ApplicationUpdateFinished } from "../events/ApplicationUpdateFinished";
 | 
				
			||||||
 | 
					import { ApplicationUpdateStarted } from "../events/ApplicationUpdateStarted";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type UpdateDefinition = {
 | 
					export type UpdateDefinition = {
 | 
				
			||||||
  id: string;
 | 
					  id: string;
 | 
				
			||||||
@ -7,10 +9,14 @@ export type UpdateDefinition = {
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default class ApplicationUpdates implements DomainProjection {
 | 
					export default class ApplicationUpdates implements DomainProjection {
 | 
				
			||||||
  private readonly pendingUpdates: UpdateDefinition[] = [];
 | 
					  private pendingUpdates: UpdateDefinition[] = [];
 | 
				
			||||||
  handle(event: DomainEvent<any>): void {
 | 
					  handle(event: DomainEvent<any>): void {
 | 
				
			||||||
    if (event.type === "ApplicationUpdateStarted") {
 | 
					    if (event.type === "ApplicationUpdateStarted") {
 | 
				
			||||||
      this.pendingUpdates.push(event.payload);
 | 
					      this.pendingUpdates.push(event.payload);
 | 
				
			||||||
 | 
					    } else if (event.type === "ApplicationUpdateFinished") {
 | 
				
			||||||
 | 
					      this.pendingUpdates = this.pendingUpdates.filter((pendingUpdate) => {
 | 
				
			||||||
 | 
					        return pendingUpdate.id !== event.payload.id;
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -3,6 +3,7 @@ import EventStore from "../domain/EventStore";
 | 
				
			|||||||
import { ApplicationUpdateStarted } from "../domain/events/ApplicationUpdateStarted";
 | 
					import { ApplicationUpdateStarted } from "../domain/events/ApplicationUpdateStarted";
 | 
				
			||||||
import DomainEvent from "../domain/DomainEvent";
 | 
					import DomainEvent from "../domain/DomainEvent";
 | 
				
			||||||
import DomainProjection from "../domain/DomainProjection";
 | 
					import DomainProjection from "../domain/DomainProjection";
 | 
				
			||||||
 | 
					import { ApplicationUpdateFinished } from "../domain/events/ApplicationUpdateFinished";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default class FileEventStore implements EventStore {
 | 
					export default class FileEventStore implements EventStore {
 | 
				
			||||||
  private handlers: DomainProjection[] = [];
 | 
					  private handlers: DomainProjection[] = [];
 | 
				
			||||||
@ -55,8 +56,15 @@ export default class FileEventStore implements EventStore {
 | 
				
			|||||||
          },
 | 
					          },
 | 
				
			||||||
          new Date(event.createdAt)
 | 
					          new Date(event.createdAt)
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
 | 
					      case "ApplicationUpdateFinished":
 | 
				
			||||||
 | 
					        return new ApplicationUpdateFinished(
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            id: event.payload.id,
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          new Date(event.createdAt)
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
      default:
 | 
					      default:
 | 
				
			||||||
        throw new Error("Unknown event type" + event.type);
 | 
					        throw new Error("Unknown event type: " + event.type);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										4
									
								
								joe.ts
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								joe.ts
									
									
									
									
									
								
							@ -3,6 +3,7 @@ import applications from "./routes/applications";
 | 
				
			|||||||
import applicationsUpdate from "./routes/applications.update";
 | 
					import applicationsUpdate from "./routes/applications.update";
 | 
				
			||||||
import Layout, { html } from "./ui/Layout";
 | 
					import Layout, { html } from "./ui/Layout";
 | 
				
			||||||
import DockerHub from "./services/DockerHub";
 | 
					import DockerHub from "./services/DockerHub";
 | 
				
			||||||
 | 
					import Human from "./services/Human";
 | 
				
			||||||
import FileEventStore from "./infrastructure/FileEventStore";
 | 
					import FileEventStore from "./infrastructure/FileEventStore";
 | 
				
			||||||
import AppQueries from "./domain/AppQueries";
 | 
					import AppQueries from "./domain/AppQueries";
 | 
				
			||||||
import AppProjections from "./domain/AppProjections";
 | 
					import AppProjections from "./domain/AppProjections";
 | 
				
			||||||
@ -25,6 +26,7 @@ const caprover = new Caprover(
 | 
				
			|||||||
  process.env.CAPTAIN_PASSWORD
 | 
					  process.env.CAPTAIN_PASSWORD
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
const dockerHub = new DockerHub();
 | 
					const dockerHub = new DockerHub();
 | 
				
			||||||
 | 
					const human = new Human(eventStore);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const server = Bun.serve({
 | 
					const server = Bun.serve({
 | 
				
			||||||
  port: 3000,
 | 
					  port: 3000,
 | 
				
			||||||
@ -42,7 +44,7 @@ const server = Bun.serve({
 | 
				
			|||||||
      case "/applications":
 | 
					      case "/applications":
 | 
				
			||||||
        return applications(req, caprover, queries);
 | 
					        return applications(req, caprover, queries);
 | 
				
			||||||
      case "/applications/update":
 | 
					      case "/applications/update":
 | 
				
			||||||
        return applicationsUpdate(req, caprover, dockerHub, queries);
 | 
					        return applicationsUpdate(req, caprover, dockerHub, human, queries);
 | 
				
			||||||
      default:
 | 
					      default:
 | 
				
			||||||
        return new Response("Not Found", { status: 404 });
 | 
					        return new Response("Not Found", { status: 404 });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -2,6 +2,7 @@ import AppQueries from "../domain/AppQueries";
 | 
				
			|||||||
import { UpdateDefinition } from "../domain/projections/ApplicationUpdates";
 | 
					import { UpdateDefinition } from "../domain/projections/ApplicationUpdates";
 | 
				
			||||||
import Caprover, { Application } from "../services/Caprover";
 | 
					import Caprover, { Application } from "../services/Caprover";
 | 
				
			||||||
import DockerHub from "../services/DockerHub";
 | 
					import DockerHub from "../services/DockerHub";
 | 
				
			||||||
 | 
					import Human from "../services/Human";
 | 
				
			||||||
import Layout, { html } from "../ui/Layout";
 | 
					import Layout, { html } from "../ui/Layout";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const UpdateForm = (application: Application, latestVersions: string[]) => {
 | 
					const UpdateForm = (application: Application, latestVersions: string[]) => {
 | 
				
			||||||
@ -17,7 +18,7 @@ const UpdateForm = (application: Application, latestVersions: string[]) => {
 | 
				
			|||||||
        return html`<option value="${version}">${version}</option>`;
 | 
					        return html`<option value="${version}">${version}</option>`;
 | 
				
			||||||
      })}
 | 
					      })}
 | 
				
			||||||
    </select>
 | 
					    </select>
 | 
				
			||||||
    <button type="submit">Update</button>
 | 
					    <button type="submit" name="action" value="update">Update</button>
 | 
				
			||||||
  </form>`;
 | 
					  </form>`;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -28,11 +29,15 @@ const FreeUpdateForm = (application: Application) => {
 | 
				
			|||||||
      Manual version
 | 
					      Manual version
 | 
				
			||||||
      <input name="version" placeholder="hello-world:latest" type="text" />
 | 
					      <input name="version" placeholder="hello-world:latest" type="text" />
 | 
				
			||||||
    </label>
 | 
					    </label>
 | 
				
			||||||
    <button type="submit">Update</button>
 | 
					    <button type="submit" name="action" value="update">Update</button>
 | 
				
			||||||
  </form>`;
 | 
					  </form>`;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const PendingUpdates = (pendingUpdates: UpdateDefinition[], logs: string) => {
 | 
					const PendingUpdates = (
 | 
				
			||||||
 | 
					  application: Application,
 | 
				
			||||||
 | 
					  pendingUpdates: UpdateDefinition[],
 | 
				
			||||||
 | 
					  logs: string
 | 
				
			||||||
 | 
					) => {
 | 
				
			||||||
  if (pendingUpdates.length === 0) {
 | 
					  if (pendingUpdates.length === 0) {
 | 
				
			||||||
    return "";
 | 
					    return "";
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@ -47,13 +52,19 @@ const PendingUpdates = (pendingUpdates: UpdateDefinition[], logs: string) => {
 | 
				
			|||||||
        .join("")}
 | 
					        .join("")}
 | 
				
			||||||
    </ul>
 | 
					    </ul>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <pre>${logs}</pre>
 | 
					    <pre class="logs">${logs}</pre>
 | 
				
			||||||
    <button
 | 
					    <button
 | 
				
			||||||
      id="refresh"
 | 
					      id="refresh"
 | 
				
			||||||
      onclick="window.location.replace('#refresh'); window.location.reload();"
 | 
					      onclick="window.location.replace('#refresh'); window.location.reload();"
 | 
				
			||||||
    >
 | 
					    >
 | 
				
			||||||
      Refresh
 | 
					      Refresh
 | 
				
			||||||
    </button>
 | 
					    </button>
 | 
				
			||||||
 | 
					    <form method="POST">
 | 
				
			||||||
 | 
					      <input type="hidden" name="appName" value="${application.name}" />
 | 
				
			||||||
 | 
					      <button type="submit" name="action" value="mark-as-updated">
 | 
				
			||||||
 | 
					        Mark as successfully updated!
 | 
				
			||||||
 | 
					      </button>
 | 
				
			||||||
 | 
					    </form>
 | 
				
			||||||
  </section>`;
 | 
					  </section>`;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -66,7 +77,7 @@ const Page = (
 | 
				
			|||||||
  return html`<div>
 | 
					  return html`<div>
 | 
				
			||||||
    <h1>Updating ${application.name}</h1>
 | 
					    <h1>Updating ${application.name}</h1>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ${PendingUpdates(pendingUpdates, logs)}
 | 
					    ${PendingUpdates(application, pendingUpdates, logs)}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <section>
 | 
					    <section>
 | 
				
			||||||
      <dl>
 | 
					      <dl>
 | 
				
			||||||
@ -99,14 +110,34 @@ export default async (
 | 
				
			|||||||
  req: Request,
 | 
					  req: Request,
 | 
				
			||||||
  caprover: Caprover,
 | 
					  caprover: Caprover,
 | 
				
			||||||
  dockerHub: DockerHub,
 | 
					  dockerHub: DockerHub,
 | 
				
			||||||
 | 
					  human: Human,
 | 
				
			||||||
  queries: AppQueries
 | 
					  queries: AppQueries
 | 
				
			||||||
): Promise<Response> => {
 | 
					): Promise<Response> => {
 | 
				
			||||||
  if (req.method === "POST") {
 | 
					  if (req.method === "POST") {
 | 
				
			||||||
    const body = await req.formData();
 | 
					    const body = await req.formData();
 | 
				
			||||||
 | 
					    switch (body.get("action")) {
 | 
				
			||||||
 | 
					      case "update":
 | 
				
			||||||
 | 
					        const appName = body.get("appName") as string;
 | 
				
			||||||
        await caprover.updateApplication(
 | 
					        await caprover.updateApplication(
 | 
				
			||||||
      body.get("appName") as string,
 | 
					          appName,
 | 
				
			||||||
          body.get("version") as string
 | 
					          body.get("version") as string
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const url = new URL(req.url);
 | 
				
			||||||
 | 
					        url.searchParams.set("name", appName);
 | 
				
			||||||
 | 
					        return new Response(null, {
 | 
				
			||||||
 | 
					          status: 301,
 | 
				
			||||||
 | 
					          headers: { Location: url.toString() },
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      case "mark-as-updated":
 | 
				
			||||||
 | 
					        human.markApplicationAsUpdated(body.get("appName") as string);
 | 
				
			||||||
 | 
					        return new Response(null, {
 | 
				
			||||||
 | 
					          status: 301,
 | 
				
			||||||
 | 
					          headers: { Location: "/applications" },
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      default:
 | 
				
			||||||
 | 
					        throw new Error(`Unsupported action ${body.get("action")}`);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const applications = await caprover.getApps();
 | 
					  const applications = await caprover.getApps();
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										20
									
								
								services/Human.test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								services/Human.test.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,20 @@
 | 
				
			|||||||
 | 
					import { describe, expect, it } from "bun:test";
 | 
				
			||||||
 | 
					import { TestApp } from "../domain/testing/TestApp";
 | 
				
			||||||
 | 
					import Human from "./Human";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe("Human", () => {
 | 
				
			||||||
 | 
					  it("should mark the application as updated", () => {
 | 
				
			||||||
 | 
					    const eventStore = new TestApp().eventStore;
 | 
				
			||||||
 | 
					    const human = new Human(eventStore);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const appName = "MyApp";
 | 
				
			||||||
 | 
					    human.markApplicationAsUpdated(appName);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const events = eventStore.getAllEvents();
 | 
				
			||||||
 | 
					    expect(events).toBeArrayOfSize(1);
 | 
				
			||||||
 | 
					    expect(events[0]).toMatchObject({
 | 
				
			||||||
 | 
					      type: "ApplicationUpdateFinished",
 | 
				
			||||||
 | 
					      payload: { id: "MyApp" },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										17
									
								
								services/Human.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								services/Human.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,17 @@
 | 
				
			|||||||
 | 
					import EventStore from "../domain/EventStore";
 | 
				
			||||||
 | 
					import { ApplicationUpdateFinished } from "../domain/events/ApplicationUpdateFinished";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Actions done by a Human in the application
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					class Human {
 | 
				
			||||||
 | 
					  constructor(private eventStore: EventStore) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  markApplicationAsUpdated(appName: string) {
 | 
				
			||||||
 | 
					    this.eventStore.append(
 | 
				
			||||||
 | 
					      new ApplicationUpdateFinished({ id: appName }, new Date())
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default Human;
 | 
				
			||||||
@ -6,6 +6,11 @@ const Layout = (content: string) => {
 | 
				
			|||||||
      <title>Joe</title>
 | 
					      <title>Joe</title>
 | 
				
			||||||
      <link rel="stylesheet" href="https://cdn.simplecss.org/simple.min.css" />
 | 
					      <link rel="stylesheet" href="https://cdn.simplecss.org/simple.min.css" />
 | 
				
			||||||
      <meta charset="utf-8" />
 | 
					      <meta charset="utf-8" />
 | 
				
			||||||
 | 
					      <style>
 | 
				
			||||||
 | 
					        .logs {
 | 
				
			||||||
 | 
					          font-size: 0.6em;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      </style>
 | 
				
			||||||
    </head>
 | 
					    </head>
 | 
				
			||||||
    <body>
 | 
					    <body>
 | 
				
			||||||
      <header>
 | 
					      <header>
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user