Browse Source

the start of the broadcast show service, controller, and route

develop
Rob Colbert 3 months ago
parent
commit
27f8f3a8c5
  1. 108
      src/app/controllers/broadcast-show.ts
  2. 28
      src/app/controllers/lib/populators.ts
  3. 30
      src/app/services/broadcast-show.ts
  4. 34
      src/app/views/broadcast-show/editor.pug
  5. 13
      src/app/views/broadcast-show/home.pug

108
src/app/controllers/broadcast-show.ts

@ -0,0 +1,108 @@
// app/controllers/broadcast-show.ts
// Copyright (C) 2025 DTP Technologies, LLC
// All Rights Reserved
import { NextFunction, Request, Response } from "express";
import { WebController, WebServer } from "../../lib/dtplib.js";
import BroadcastShowService from "../services/broadcast-show.js";
import { populateBroadcastShow } from "./lib/populators.js";
export default class BroadcastShowController extends WebController {
static get name(): string {
return "BroadcastShowController";
}
static get slug(): string {
return "broadcastShow";
}
constructor(server: WebServer) {
super(server, BroadcastShowController);
}
get route(): string {
return "/show";
}
async start(): Promise<void> {
this.router.param("showId", populateBroadcastShow(this));
this.router.post("/:showId", this.postUpdate.bind(this));
this.router.post("/", this.postCreate.bind(this));
this.router.get("/create", this.getEditor.bind(this));
this.router.get("/:showId/edit", this.getEditor.bind(this));
this.router.get("/:showId", this.getShowView.bind(this));
this.router.get("/", this.getHome.bind(this));
}
async postCreate(
req: Request,
res: Response,
next: NextFunction
): Promise<void> {
const showService = this.getService<BroadcastShowService>("broadcastShow");
try {
res.locals.show = await showService.create(req.body);
res.redirect(`/show/${res.locals.show._id}/edit`);
} catch (error) {
this.log.error("failed to process show create request", { error });
return next(error);
}
}
async postUpdate(
req: Request,
res: Response,
next: NextFunction
): Promise<void> {
const showService = this.getService<BroadcastShowService>("broadcastShow");
try {
res.locals.show = await showService.update(res.locals.show, req.body);
res.redirect(`/show/${res.locals.show._id}/edit`);
} catch (error) {
this.log.error("failed to process show update request", { error });
return next(error);
}
}
async getEditor(_req: Request, res: Response): Promise<void> {
res.render("broadcast-show/editor");
}
async getShowView(
_req: Request,
res: Response,
next: NextFunction
): Promise<void> {
const showService = this.getService<BroadcastShowService>("broadcastShow");
try {
res.locals.recommendedShows = await showService.getRecommended(
3,
res.locals.show
);
res.render("broadcast-show/view");
} catch (error) {
this.log.error("failed to present the Show Home view", { error });
return next(error);
}
}
async getHome(
req: Request,
res: Response,
next: NextFunction
): Promise<void> {
const showService = this.getService<BroadcastShowService>("broadcastShow");
try {
res.locals.pagination = this.getPaginationParameters(req, 12);
res.locals.showLibrary = await showService.getLive(res.locals.pagination);
res.render("broadcast-show/home");
} catch (error) {
this.log.error("failed to present the Show Home view", { error });
return next(error);
}
}
}

28
src/app/controllers/lib/populators.ts

@ -11,6 +11,7 @@ import { WebController, WebError } from "../../../lib/dtplib.js";
import { ImageService } from "../../services/image.js";
import { UserService } from "../../services/user.js";
import { BroadcastShowService } from "app/services/broadcast-show.js";
export function populateAccountByEmail(
controller: WebController
@ -66,6 +67,33 @@ export function populateAccountById(controller: WebController): RequestHandler {
};
}
export function populateBroadcastShow(
controller: WebController
): RequestHandler {
const showService =
controller.getService<BroadcastShowService>("broadcastShow");
return async function (
_req: Request,
res: Response,
next: NextFunction,
showId?: string
): Promise<void> {
assert(showId, "Show ID is required");
try {
res.locals.show = await showService.getById(
Types.ObjectId.createFromHexString(showId)
);
if (!res.locals.show) {
throw new WebError(404, "Broadcast show not found");
}
return next();
} catch (error) {
controller.log.error("failed to populate show ID", { showId, error });
return next(error);
}
};
}
export function populateImageId(controller: WebController): RequestHandler {
const imageService =
controller.platform.components.getService<ImageService>("image");

30
src/app/services/broadcast-show.ts

@ -119,6 +119,36 @@ export class BroadcastShowService extends DtpService {
const totalShowCount = await BroadcastShow.estimatedDocumentCount();
return { shows, totalShowCount };
}
/**
* For now just fetches a sample of the live list minus any shows specified
* for exclusion (such as the one(s) on the page right now).
* @param maxCount number The maximum number of shows to return.
* @param exclude Array<IBroadcastShow|mongoose.Types.ObjectId> Array of shows to exclude from the result set.
* @returns An array of recommended shows for display.
*/
async getRecommended(
maxCount: number,
exclude?: Array<IBroadcastShow | Types.ObjectId>
): Promise<Array<IBroadcastShow>> {
const pipeline: Array<mongoose.PipelineStage> = [];
if (exclude) {
if (!Array.isArray(exclude)) {
exclude = [exclude];
}
pipeline.push({
$match: { $nin: exclude.map((show) => show._id) },
});
}
pipeline.push({
$sample: { size: maxCount },
});
const shows = await BroadcastShow.aggregate(pipeline);
return await BroadcastShow.populate(shows, this.populateBroadcastShow);
}
}
export default BroadcastShowService;

34
src/app/views/broadcast-show/editor.pug

@ -0,0 +1,34 @@
extends ../layouts/main-sidebar
block content
- var actionUrl = show ? `/show/${show._id}` : "/show";
form(method="POST", action= actionUrl).uk-form
.uk-card.uk-card-default.uk-card-small
.uk-card-header
h1.uk-card-title #{show ? "Edit" : "Create New"} Broadcast Show
.uk-card-body
.uk-margin
label(for="title").uk-form-label Title
input(id="title", name="title", type="text", placeholder="Enter show title", required).uk-input
.uk-margin
label(for="description").uk-form-label Description
textarea(
id="description",
name="description",
rows="4",
placeholder="Enter show description",
required,
).uk-textarea.uk-resize-vertical
.uk-card-footer
div(uk-grid).uk-grid-small
.uk-width-expand
+renderBackButton()
.uk-width-auto
button(type="submit").uk-button.uk-button-primary.uk-border-rounded
span
if show
i.fa-solid.fa-save
else
i.fa-solid.fa-plus
span.uk-margin-small-left #{show ? "Update" : "Create"} Show

13
src/app/views/broadcast-show/home.pug

@ -0,0 +1,13 @@
extends ../layouts/main-sidebar
block content
div(uk-grid).uk-grid-small.uk-flex-middle
.uk-width-expand
h1 Show Directory
.uk-width-auto
a(href="/show/create").uk-button.uk-button-default.uk-button-small.uk-border-rounded
span
i.fa-solid.fa-plus
span.uk-margin-small-left Create Show
pre= JSON.stringify(showLibrary, null, 2)
Loading…
Cancel
Save