Rob Colbert
2 years ago
9 changed files with 326 additions and 0 deletions
@ -0,0 +1,113 @@ |
|||
// admin/deck.js
|
|||
// Copyright (C) 2022 DTP Technologies, LLC
|
|||
// License: Apache-2.0
|
|||
|
|||
'use strict'; |
|||
|
|||
const DTP_COMPONENT_NAME = 'admin:deck'; |
|||
|
|||
const express = require('express'); |
|||
|
|||
// const mongoose = require('mongoose');
|
|||
|
|||
const { SiteController, SiteError } = require('../../../lib/site-lib'); |
|||
|
|||
class DeckController extends SiteController { |
|||
|
|||
constructor (dtp) { |
|||
super(dtp, DTP_COMPONENT_NAME); |
|||
} |
|||
|
|||
async start ( ) { |
|||
const router = express.Router(); |
|||
|
|||
router.use(async (req, res, next) => { |
|||
res.locals.currentView = 'admin'; |
|||
res.locals.adminView = 'deck'; |
|||
return next(); |
|||
}); |
|||
|
|||
router.param('deckId', this.populateDeckId.bind(this)); |
|||
|
|||
router.post('/:deckId', this.postDeckUpdate.bind(this)); |
|||
router.post('/', this.postDeckCreate.bind(this)); |
|||
|
|||
router.get('/create', this.getDeckCreateForm.bind(this)); |
|||
|
|||
router.get('/:deckId', this.getDeckView.bind(this)); |
|||
|
|||
router.get('/', this.getHomeView.bind(this)); |
|||
|
|||
return router; |
|||
} |
|||
|
|||
async populateDeckId (req, res, next, deckId) { |
|||
const { deck: deckService } = this.dtp.services; |
|||
try { |
|||
res.locals.deck = await deckService.getById(deckId); |
|||
if (!res.locals.deck) { |
|||
throw new SiteError(404, 'Deck not found'); |
|||
} |
|||
return next(); |
|||
} catch (error) { |
|||
this.log.error('failed to populate deck', { deckId, error }); |
|||
return next(error); |
|||
} |
|||
} |
|||
|
|||
async postDeckUpdate (req, res, next) { |
|||
const { deck: deckService } = this.dtp.services; |
|||
try { |
|||
res.locals.deck = await deckService.update(res.locals.deck, req.body); |
|||
res.redirect(`/admin/deck/${res.locals.deck._id}`); |
|||
} catch (error) { |
|||
this.log.error('failed to update deck', { |
|||
deckId: res.locals.deck._id, |
|||
error, |
|||
}); |
|||
return next(error); |
|||
} |
|||
} |
|||
|
|||
async postDeckCreate (req, res, next) { |
|||
const { deck: deckService } = this.dtp.services; |
|||
try { |
|||
res.locals.deck = await deckService.create(req.user, req.body); |
|||
res.redirect(`/admin/deck/${res.locals.deck._id}`); |
|||
} catch (error) { |
|||
this.log.error('failed to create deck', { |
|||
deckId: res.locals.deck._id, |
|||
error, |
|||
}); |
|||
return next(error); |
|||
} |
|||
} |
|||
|
|||
async getDeckView (req, res) { |
|||
res.render('admin/deck/view'); |
|||
} |
|||
|
|||
async getDeckCreateForm (req, res) { |
|||
res.render('admin/deck/editor'); |
|||
} |
|||
|
|||
async getHomeView (req, res, next) { |
|||
const { deck: deckService } = this.dtp.services; |
|||
try { |
|||
res.locals.pagination = this.getPaginationParameters(req, 20); |
|||
res.locals.decks = await deckService.getDecks(res.locals.pagination); |
|||
res.render('admin/deck/index'); |
|||
} catch (error) { |
|||
this.log.error('failed to create deck', { |
|||
deckId: res.locals.deck._id, |
|||
error, |
|||
}); |
|||
return next(error); |
|||
} |
|||
} |
|||
} |
|||
|
|||
module.exports = async (dtp) => { |
|||
let controller = new DeckController(dtp); |
|||
return controller; |
|||
}; |
@ -0,0 +1,23 @@ |
|||
// deck.js
|
|||
// Copyright (C) 2022 DTP Technologies, LLC
|
|||
// License: Apache-2.0
|
|||
|
|||
'use strict'; |
|||
|
|||
const mongoose = require('mongoose'); |
|||
|
|||
const Schema = mongoose.Schema; |
|||
|
|||
const DeckSchema = new Schema({ |
|||
created: { type: Date, required: true, index: -1 }, |
|||
owner: { type: Schema.ObjectId, required: true, index: 1, ref: 'User' }, |
|||
name: { type: String, required: true }, |
|||
name_lc: { type: String, required: true, lowercase: true, index: 1 }, |
|||
description: { type: String, required: true }, |
|||
tags: { type: [String] }, |
|||
flags: { |
|||
isOfficial: { type: Boolean, default: true, required: true, index: 1 }, |
|||
}, |
|||
}); |
|||
|
|||
module.exports = mongoose.model('Deck', DeckSchema); |
@ -0,0 +1,45 @@ |
|||
// playing-card.js
|
|||
// Copyright (C) 2022 DTP Technologies, LLC
|
|||
// License: Apache-2.0
|
|||
|
|||
'use strict'; |
|||
|
|||
const mongoose = require('mongoose'); |
|||
|
|||
const Schema = mongoose.Schema; |
|||
|
|||
const CARD_TYPE_LIST = ['question', 'answer']; |
|||
|
|||
const MediaMetadataSchema = new Schema({ |
|||
type: { type: String }, |
|||
size: { type: Number }, |
|||
bitRate: { type: Number }, |
|||
duration: { type: Number }, |
|||
video: { |
|||
width: { type: Number }, |
|||
height: { type: Number }, |
|||
fps: { type: Number }, |
|||
}, |
|||
}); |
|||
|
|||
const PlayingCardSchema = new Schema({ |
|||
deck: { type: Schema.ObjectId, required: true, index: 1 }, |
|||
type: { type: String, enum: CARD_TYPE_LIST, required: true, index: 1 }, |
|||
content: { |
|||
text: { type: String }, |
|||
image: { type: Schema.ObjectId, ref: 'Image' }, |
|||
video: { |
|||
bucket: { type: String }, |
|||
key: { type: String }, |
|||
metadata: { type: MediaMetadataSchema }, |
|||
}, |
|||
}, |
|||
options: { |
|||
blankCount: { type: Number, required: true, min: 0 }, |
|||
}, |
|||
flags: { |
|||
isOfficial: { type: Boolean, default: true, required: true, index: 1 }, |
|||
}, |
|||
}); |
|||
|
|||
module.exports = mongoose.model('PlayingCard', PlayingCardSchema); |
@ -0,0 +1,99 @@ |
|||
// cache.js
|
|||
// Copyright (C) 2022 DTP Technologies, LLC
|
|||
// License: Apache-2.0
|
|||
|
|||
'use strict'; |
|||
|
|||
const mongoose = require('mongoose'); |
|||
|
|||
// const PlayingCard = mongoose.model('PlayingCard');
|
|||
const Deck = mongoose.model('Deck'); |
|||
|
|||
const striptags = require('striptags'); |
|||
|
|||
const { SiteService } = require('../../lib/site-lib'); |
|||
|
|||
class DeckService extends SiteService { |
|||
|
|||
constructor (dtp) { |
|||
super(dtp, module.exports); |
|||
|
|||
this.populateDeck = [ |
|||
{ |
|||
path: 'owner', |
|||
select: 'created username username_lc displayName picture', |
|||
}, |
|||
]; |
|||
} |
|||
|
|||
async create (owner, deckDefinition) { |
|||
const NOW = new Date(); |
|||
const deck = new Deck(); |
|||
|
|||
deck.created = NOW; |
|||
deck.owner = owner._id; |
|||
deck.name = striptags(deckDefinition.name.trim()); |
|||
deck.name_lc = deck.name.toLowerCase(); |
|||
deck.description = striptags(deckDefinition.description.trim()); |
|||
|
|||
if (deckDefinition.tags) { |
|||
deck.tags = deckDefinition.tags.split(',').map((tag) => tag.trim()); |
|||
} |
|||
|
|||
deck.flags = { |
|||
isOfficial: deckDefinition.isOfficial === 'on', |
|||
}; |
|||
|
|||
await deck.save(); |
|||
|
|||
return deck.toObject(); |
|||
} |
|||
|
|||
async update (deck, deckDefinition) { |
|||
|
|||
deckDefinition.name = striptags(deckDefinition.name.trim()); |
|||
deckDefinition.name_lc = deckDefinition.name.toLowerCase(); |
|||
deckDefinition.description = striptags(deckDefinition.description.trim()); |
|||
if (deckDefinition.tags) { |
|||
deckDefinition.tags = deckDefinition.tags.split(',').map((tag) => tag.trim()); |
|||
} |
|||
|
|||
await Deck.updateOne( |
|||
{ _id: deck._id }, |
|||
{ |
|||
$set: { |
|||
name: deckDefinition.name, |
|||
name_lc: deckDefinition.name_lc, |
|||
description: deckDefinition.description, |
|||
tags: deckDefinition.tags, |
|||
'flags.isOfficial': deckDefinition.isOfficial === 'on', |
|||
}, |
|||
}, |
|||
); |
|||
} |
|||
|
|||
async getDecks (pagination) { |
|||
const decks = await Deck |
|||
.find() |
|||
.sort({ created: -1 }) |
|||
.skip(pagination.skip) |
|||
.limit(pagination.cpp) |
|||
.populate(this.populateDeck) |
|||
.lean(); |
|||
return decks; |
|||
} |
|||
|
|||
async getById (deckId) { |
|||
const deck = await Deck |
|||
.findOne({ _id: deckId }) |
|||
.populate(this.populateDeck) |
|||
.lean(); |
|||
return deck; |
|||
} |
|||
} |
|||
|
|||
module.exports = { |
|||
slug: 'deck', |
|||
name: 'deck', |
|||
create: (dtp) => { return new DeckService(dtp); }, |
|||
}; |
@ -0,0 +1,24 @@ |
|||
extends ../layouts/main |
|||
block content |
|||
|
|||
- var actionUrl = deck ? `/admin/deck/${deck._id}` : `/admin/deck`; |
|||
|
|||
form(method="POST", action= actionUrl).uk-form |
|||
.uk-card.uk-card-default.uk-card-small |
|||
.uk-card-header |
|||
h1.uk-card-title Create New Deck |
|||
|
|||
.uk-card-body |
|||
.uk-margin |
|||
label(for="name").uk-form-label Deck name |
|||
input(id="name", name="name", type="text", placeholder="Enter deck name").uk-input |
|||
.uk-margin |
|||
label(for="description").uk-form-label Description |
|||
textarea(id="description", name="description", rows="3", placeholder="Enter deck description").uk-textarea |
|||
.uk-margin |
|||
label(for="tags").uk-form-label Tags |
|||
input(id="tags", name="tags", type="text", placeholder="Enter search tags").uk-input |
|||
.uk-text-small.uk-text-muted Separate multiple tags with a comma |
|||
|
|||
.uk-card-footer |
|||
button(type="submit").uk-button.uk-button-primary= deck ? 'Update Deck' : 'Create Deck' |
@ -0,0 +1,6 @@ |
|||
extends ../layouts/main |
|||
block content |
|||
|
|||
h1 Deck Index |
|||
|
|||
pre= JSON.stringify(decks, null, 2) |
@ -0,0 +1,4 @@ |
|||
extends ../layouts/main |
|||
block content |
|||
|
|||
h1 Deck View |
Loading…
Reference in new issue