complete badges

This commit is contained in:
Holly Stubbs 2024-10-08 11:04:46 +01:00
parent d83eef15c0
commit da80eb2d78
Signed by: tgpholly
GPG key ID: B8583C4B7D18119E
16 changed files with 73 additions and 21 deletions

View file

@ -120,11 +120,13 @@ export default class AdminController_Auth$Admin extends Controller {
adminBadgeViewModel.description = badge.Description;
adminBadgeViewModel.imageUrl = badge.ImageUrl;
adminBadgeViewModel.forUrl = badge.ForUrl;
adminBadgeViewModel.isSecret = badge.IsSecret;
} else {
adminBadgeViewModel.name = "";
adminBadgeViewModel.description = "";
adminBadgeViewModel.imageUrl = "";
adminBadgeViewModel.forUrl = "";
adminBadgeViewModel.isSecret = false;
}
return this.view(adminBadgeViewModel);
@ -135,7 +137,7 @@ export default class AdminController_Auth$Admin extends Controller {
return this.badRequest();
}
await BadgeService.SaveBadge(this.session.userId, parseInt(adminBadgeViewModel.id), adminBadgeViewModel.name ?? "", adminBadgeViewModel.description ?? "", adminBadgeViewModel.imageUrl ?? "", adminBadgeViewModel.forUrl ?? "");
await BadgeService.SaveBadge(this.session.userId, parseInt(adminBadgeViewModel.id), adminBadgeViewModel.name ?? "", adminBadgeViewModel.description ?? "", adminBadgeViewModel.imageUrl ?? "", adminBadgeViewModel.forUrl ?? "", (adminBadgeViewModel.isSecret?.toString() ?? "") === "on");
return this.redirectToAction("badges");
}

View file

@ -1,7 +1,11 @@
import Controller from "./Controller";
import FunkyArray from "funky-array";
import UserService from "../services/UserService";
import HomeViewModel from "../models/home/HomeViewModel";
import PartyService from "../services/PartyService";
import BadgeService from "../services/BadgeService";
import Badge from "../entities/Badge";
import UserBadge from "../entities/UserBadge";
export default class HomeController extends Controller {
public async Index_Get_AllowAnonymous() {
@ -14,12 +18,23 @@ export default class HomeController extends Controller {
const parties = await PartyService.GetUserParties(this.session.userId);
const activeUserParty = await UserService.GetActiveParty(this.session.userId);
const unlockedBadges = await UserService.LoadUserBadges(this.session.userId);
const unlockedBadgesById = new FunkyArray<number, UserBadge>();
for (const unlockedBadge of unlockedBadges) {
unlockedBadgesById.set(unlockedBadge.BadgeId, unlockedBadge);
}
const badges = await BadgeService.LoadAll();
const badgeById = new FunkyArray<number, Badge>();
for (const badge of badges) {
badgeById.set(badge.Id, badge);
}
const homeViewModel: HomeViewModel = {
user,
parties,
activeUserParty,
unlockedBadges
unlockedBadges,
unlockedBadgesById,
badgeById
};
return this.view("home", homeViewModel);

View file

@ -4,6 +4,7 @@ export default class Badge {
public Description: string;
public ImageUrl: string;
public ForUrl: string;
public IsSecret: boolean;
public CreatedByUserId: number;
public CreatedDatetime: Date;
public LastModifiedByUserId?: number;
@ -18,6 +19,7 @@ export default class Badge {
this.Description = "";
this.ImageUrl = "";
this.ForUrl = "";
this.IsSecret = false;
this.CreatedByUserId = Number.MIN_VALUE;
this.CreatedDatetime = new Date(0);
this.IsDeleted = false;

View file

@ -3,5 +3,6 @@ export default interface AdminBadgeViewModel {
name?: string,
description?: string,
imageUrl?: string,
forUrl?: string
forUrl?: string,
isSecret?: boolean
}

View file

@ -1,3 +1,5 @@
import Badge from "../../entities/Badge";
import FunkyArray from "funky-array";
import Party from "../../entities/Party";
import User from "../../entities/User";
import UserBadge from "../../entities/UserBadge";
@ -7,5 +9,7 @@ export default interface HomeViewModel {
user: User,
parties: Array<Party>,
activeUserParty: UserParty | null,
unlockedBadges: Array<UserBadge>
unlockedBadges: Array<UserBadge>,
unlockedBadgesById: FunkyArray<number, UserBadge>,
badgeById: FunkyArray<number, Badge>
}

View file

@ -35,12 +35,12 @@ export default abstract class BadgeRepo {
public static async insertUpdate(badge:Badge) {
if (badge.Id === Number.MIN_VALUE) {
badge.Id = (await Database.Instance.query("INSERT Badge (Name, Description, ImageUrl, ForUrl, CreatedByUserId, CreatedDatetime, LastModifiedByUserId, LastModifiedDatetime, DeletedByUserId, DeletedDatetime, IsDeleted) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING Id;", [
badge.Name, badge.Description, badge.ImageUrl, badge.ForUrl, badge.CreatedByUserId, badge.CreatedDatetime.getTime(), badge.LastModifiedByUserId ?? null, badge.LastModifiedDatetime?.getTime() ?? null, badge.DeletedByUserId ?? null, badge.DeletedDatetime?.getTime() ?? null, Number(badge.IsDeleted)
badge.Id = (await Database.Instance.query("INSERT Badge (Name, Description, ImageUrl, ForUrl, IsSecret, CreatedByUserId, CreatedDatetime, LastModifiedByUserId, LastModifiedDatetime, DeletedByUserId, DeletedDatetime, IsDeleted) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING Id;", [
badge.Name, badge.Description, badge.ImageUrl, badge.ForUrl, Number(badge.IsSecret), badge.CreatedByUserId, badge.CreatedDatetime.getTime(), badge.LastModifiedByUserId ?? null, badge.LastModifiedDatetime?.getTime() ?? null, badge.DeletedByUserId ?? null, badge.DeletedDatetime?.getTime() ?? null, Number(badge.IsDeleted)
]))[0]["Id"];
} else {
await Database.Instance.query(`UPDATE Badge SET Name = ?, Description = ?, ImageUrl = ?, ForUrl = ?, CreatedByUserId = ?, CreatedDatetime = ?, LastModifiedByUserId = ?, LastModifiedDatetime = ?, DeletedByUserId = ?, DeletedDatetime = ?, IsDeleted = ? WHERE Id = ?`, [
badge.Name, badge.Description, badge.ImageUrl, badge.ForUrl, badge.CreatedByUserId, badge.CreatedDatetime.getTime(), badge.LastModifiedByUserId ?? null, badge.LastModifiedDatetime?.getTime() ?? null, badge.DeletedByUserId ?? null, badge.DeletedDatetime?.getTime() ?? null, Number(badge.IsDeleted), badge.Id
await Database.Instance.query(`UPDATE Badge SET Name = ?, Description = ?, ImageUrl = ?, ForUrl = ?, IsSecret = ?, CreatedByUserId = ?, CreatedDatetime = ?, LastModifiedByUserId = ?, LastModifiedDatetime = ?, DeletedByUserId = ?, DeletedDatetime = ?, IsDeleted = ? WHERE Id = ?`, [
badge.Name, badge.Description, badge.ImageUrl, badge.ForUrl, Number(badge.IsSecret), badge.CreatedByUserId, badge.CreatedDatetime.getTime(), badge.LastModifiedByUserId ?? null, badge.LastModifiedDatetime?.getTime() ?? null, badge.DeletedByUserId ?? null, badge.DeletedDatetime?.getTime() ?? null, Number(badge.IsDeleted), badge.Id
]);
}
@ -54,11 +54,12 @@ function populateBadgeFromDB(badge:Badge, dbBadge:any) {
badge.Description = dbBadge.Description;
badge.ImageUrl = dbBadge.ImageUrl;
badge.ForUrl = dbBadge.ForUrl;
badge.IsSecret = dbBadge.IsSecret[0] === 1;
badge.CreatedByUserId = dbBadge.CreatedByUserId;
badge.CreatedDatetime = new Date(dbBadge.CreatedDatetime);
badge.LastModifiedByUserId = dbBadge.LastModifiedByUserId;
badge.LastModifiedDatetime = RepoBase.convertNullableDatetimeIntToDate(dbBadge.LastModifiedDatetime);
badge.DeletedByUserId = dbBadge.DeletedByUserId;
badge.DeletedDatetime = RepoBase.convertNullableDatetimeIntToDate(dbBadge.DeletedDatetime);
badge.IsDeleted = dbBadge.IsDeleted === 1;
badge.IsDeleted = dbBadge.IsDeleted[0] === 1;
}

View file

@ -86,5 +86,5 @@ function populatePartyFromDB(party:Party, dbParty:any) {
party.LastModifiedDatetime = RepoBase.convertNullableDatetimeIntToDate(dbParty.LastModifiedDatetime);
party.DeletedByUserId = dbParty.DeletedByUserId;
party.DeletedDatetime = RepoBase.convertNullableDatetimeIntToDate(dbParty.DeletedDatetime);
party.IsDeleted = dbParty.IsDeleted === 1;
party.IsDeleted = dbParty.IsDeleted[0] === 1;
}

View file

@ -15,7 +15,7 @@ export default abstract class UserBadgeRepo {
}
public static async selectByUserId(userId:number) {
const dbUserBadge = await Database.Instance.query("SELECT * FROM UserBadge WHERE UserId = ? AND IsDeleted = 0 LIMIT 1", [ userId ]);
const dbUserBadge = await Database.Instance.query("SELECT * FROM UserBadge WHERE UserId = ? AND IsDeleted = 0", [ userId ]);
const userBadges = new Array<UserBadge>();
for (const row of dbUserBadge) {
@ -63,5 +63,5 @@ function populateUserBadgeFromDB(userBadge:UserBadge, dbUser:any) {
userBadge.LastModifiedDatetime = RepoBase.convertNullableDatetimeIntToDate(dbUser.LastModifiedDatetime);
userBadge.DeletedByUserId = dbUser.DeletedByUserId;
userBadge.DeletedDatetime = RepoBase.convertNullableDatetimeIntToDate(dbUser.DeletedDatetime);
userBadge.IsDeleted = dbUser.IsDeleted === 1;
userBadge.IsDeleted = dbUser.IsDeleted[0] === 1;
}

View file

@ -84,5 +84,5 @@ function populateUserPartyFromDB(userParty:UserParty, dbUserParty:any) {
userParty.LastModifiedDatetime = RepoBase.convertNullableDatetimeIntToDate(dbUserParty.LastModifiedDatetime);
userParty.DeletedByUserId = dbUserParty.DeletedByUserId;
userParty.DeletedDatetime = RepoBase.convertNullableDatetimeIntToDate(dbUserParty.DeletedDatetime);
userParty.IsDeleted = dbUserParty.IsDeleted === 1;
userParty.IsDeleted = dbUserParty.IsDeleted[0] === 1;
}

View file

@ -77,12 +77,12 @@ function populateUserFromDB(user:User, dbUser:any) {
user.PasswordHash = dbUser.PasswordHash;
user.PasswordSalt = dbUser.PasswordSalt;
user.APIKey = dbUser.APIKey;
user.HasUsedClient = dbUser.HasUsedClient === 1;
user.HasUsedClient = dbUser.HasUsedClient[0] === 1;
user.CreatedByUserId = dbUser.CreatedByUserId;
user.CreatedDatetime = new Date(dbUser.CreatedDatetime);
user.LastModifiedByUserId = dbUser.LastModifiedByUserId;
user.LastModifiedDatetime = RepoBase.convertNullableDatetimeIntToDate(dbUser.LastModifiedDatetime);
user.DeletedByUserId = dbUser.DeletedByUserId;
user.DeletedDatetime = RepoBase.convertNullableDatetimeIntToDate(dbUser.DeletedDatetime);
user.IsDeleted = dbUser.IsDeleted === 1;
user.IsDeleted = dbUser.IsDeleted[0] === 1;
}

View file

@ -4,7 +4,7 @@ import BadgeRepo from "../repos/BadgeRepo";
import BadgeCache from "../objects/BadgeCache";
export default abstract class BadgeService {
public static async SaveBadge(currentUserId:number, id:number | undefined, name:string, description:string, imageUrl: string, forUrl:string) {
public static async SaveBadge(currentUserId:number, id:number | undefined, name:string, description:string, imageUrl: string, forUrl:string, isSecret: boolean) {
try {
let badge = id ? await BadgeRepo.selectById(id) : null;
if (badge === null) {
@ -16,10 +16,13 @@ export default abstract class BadgeService {
badge.LastModifiedDatetime = new Date();
}
console.log(isSecret);
badge.Name = name;
badge.Description = description;
badge.ImageUrl = imageUrl;
badge.ForUrl = forUrl;
badge.IsSecret = isSecret;
badge = await BadgeRepo.insertUpdate(badge);

View file

@ -42,12 +42,20 @@
<img id="imageImg" style="image-rendering:pixelated;margin-top: 36px" class="mb-3" src="<%= imageUrl.trim().length === 0 ? "/img/missing.png" : imageUrl %>" onerror="if (this.src != '/img/missing.png') this.src = '/img/missing.png';" width="32" height="32" />
</div>
</div>
<div class="row mb-5">
<div class="row mb-3">
<div class="col">
<label for="forUrl" class="form-label">For URL</label>
<input class="form-control" id="forUrl" name="forUrl" value="<%= typeof(forUrl) === "undefined" ? "" : forUrl %>" required />
</div>
</div>
<div class="row mb-5">
<div class="col">
<div class="form-check form-switch">
<input type="checkbox" class="form-check-input" id="isSecret" name="isSecret" <%= isSecret ? "checked" : "" %> />
<label for="isSecret" class="form-check-label">Is Secret</label>
</div>
</div>
</div>
<div class="row mb-3">
<div class="col text-center">
<button type="submit" class="btn btn-primary">Save</button>

View file

@ -27,6 +27,7 @@
<th>&nbsp;</th>
<th>Name</th>
<th>For URL</th>
<th style="white-space:nowrap">Secret</th>
<th>&nbsp;</th>
</thead>
<tbody>
@ -35,6 +36,7 @@
<td class="align-middle"><img style="image-rendering:pixelated" src="<%= badge.ImageUrl.trim().length === 0 ? "/img/missing.png" : badge.ImageUrl %>" width="32" height="32" /></td>
<td class="align-middle"><%= badge.Name %></td>
<td class="align-middle"><a href="<%= badge.ForUrl %>"><%= badge.ForUrl %></a></td>
<td class="align-middle text-center"><%= badge.IsSecret ? "Yes" : "No" %></td>
<td class="text-end text-nowrap align-middle">
<a class="btn btn-sm btn-primary" href="/admin/badge?id=<%= badge.Id %>">Edit</a>
<a class="btn btn-sm btn-danger" href="/admin/deletebadge?id=<%= badge.Id %>" onclick="return confirm(`Are you sure you want to delete '<%= badge.Name %>'?`)">Delete</a>

View file

@ -1,5 +1,4 @@
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/js/bootstrap.bundle.min.js" integrity="sha512-7Pi/otdlbbCR+LnW+F7PwFcSDJOuUJB3OxtEHbg4vSMvzvJjde4Po1v4BR9Gdc9aXNUNFVUY+SK51wWT8WF0Gg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/cookieconsent/3.1.1/cookieconsent.min.js" integrity="sha512-yXXqOFjdjHNH1GND+1EO0jbvvebABpzGKD66djnUfiKlYME5HGMUJHoCaeE4D5PTG2YsSJf6dwqyUUvQvS0vaA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script>
(() => {

View file

@ -18,6 +18,7 @@
</style>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js" integrity="sha512-v2CJ7UaYy4JwqLDIrZUI/4hqeoQieOmAZNXBeQyjo21dadnwR+8ZaIJVT8EE2iyI61OV8e6M8PP2/4hpQINQ/g==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/js/bootstrap.bundle.min.js" integrity="sha512-7Pi/otdlbbCR+LnW+F7PwFcSDJOuUJB3OxtEHbg4vSMvzvJjde4Po1v4BR9Gdc9aXNUNFVUY+SK51wWT8WF0Gg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
</head>
<body>
<nav class="navbar navbar-expand bg-body-tertiary">

View file

@ -24,7 +24,7 @@
<% } %>
</div>
</div>
<div class="col">
<div class="col pb-5">
<h3>Your Parties</h3>
<% if (parties.length > 0) { %>
<table class="table table-striped">
@ -66,11 +66,25 @@
<h3>Badges</h3>
<div class="row mt-4">
<div class="col">
<% for (let i = 0; i < 71; i++) { %>
<img class="mb-3" src="https://eusv.net/Mu6LedXkxrZDUB">
<% for (const badgeKey of badgeById.keys) { %>
<% const badge = badgeById.get(badgeKey); const unlockedBadge = unlockedBadgesById.get(badgeKey); %>
<% if (unlockedBadge) { %>
<span class="d-inline-block mb-3" tabindex="0" data-bs-toggle="popover" data-bs-trigger="hover focus" data-bs-placement="top" data-bs-title="<%= badge.Name %>" data-bs-content="<%= badge.Description %><br><small>Unlocked on <%= badge.CreatedDatetime.toString().split(" ").slice(1, 4).join(" ") %></small>">
<img width="32" height="32" src="<%= badge.ImageUrl %>">
</span>
<% } else if (!badge.IsSecret) { %>
<span class="d-inline-block mb-3" tabindex="0" data-bs-toggle="popover" data-bs-trigger="hover focus" data-bs-placement="top" data-bs-title="<%= badge.Name %>" data-bs-content="<small>Not yet unlocked</small>">
<img src="https://eusv.net/Mu6LedXkxrZDUB">
</span>
<% } %>
<% } %>
</div>
</div>
</div>
</div>
<script>
const popoverTriggerList = document.querySelectorAll('[data-bs-toggle="popover"]');
const popoverList = [...popoverTriggerList].map(popoverTriggerEl => new bootstrap.Popover(popoverTriggerEl, { html: true }));
</script>
<%- include("../base/footer") %>