Skip to content

Commit

Permalink
fix(backend): Fixed type declaration and expiration check in Invitati…
Browse files Browse the repository at this point in the history
…onsVerifyAPI

In the `InvitationsVerifyAPI` class, the type declaration for the `invitation` variable was fixed. Additionally, the expiration check was modified to compare the current UTC time with the expiration time to determine if the invitation has expired.

feat(frontend): Added copying invite link functionality

A button was added to the `InvitationItem.vue` component to allow users to copy the invite link to their clipboard. When clicked, the invite link is copied and a toast message is displayed notifying the user that the link has been copied.

feat(frontend): Modified dateLess filter for invitation expiration

A new filter named `dateLess` was introduced to compare two dates and return `true` if the first date is less than the second date. This filter is used in the `InvitationItem.vue` component to apply different text colors based on the proximity of the expiration date.

fix(frontend): Modified validation for username input in JellyfinSetupView

The validation for the username input in the `JellyfinSetupView.vue` component was modified to require alphanumeric characters and lowercase letters only.
  • Loading branch information
realashleybailey committed Sep 10, 2023
1 parent 788d220 commit 4df5189
Show file tree
Hide file tree
Showing 7 changed files with 70 additions and 11 deletions.
5 changes: 3 additions & 2 deletions backend/api/routes/invitations_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,15 +82,16 @@ class InvitationsVerifyAPI(Resource):
@api.response(500, "Internal server error")
def get(self, invite_code):
# Select the invite from the database
invitation = Invitations.get_or_none(Invitations.code == invite_code)
invitation: Invitations = Invitations.get_or_none(Invitations.code == invite_code)

if not invitation:
return {"message": "Invitation not found"}, 404

if invitation.used is True and invitation.unlimited is not True:
return {"message": "Invitation has already been used"}, 400

if invitation.expires and invitation.expires <= datetime.now():
print(invitation.expires, datetime.utcnow())
if invitation.expires and invitation.expires <= datetime.utcnow():
return {"message": "Invitation has expired"}, 400

return {"message": "Invitation is valid"}, 200
8 changes: 6 additions & 2 deletions frontend/src/components/Forms/InviteForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ import DefaultInput from "@/components/Inputs/DefaultInput.vue";
import SelectInput from "@/components/Inputs/SelectInput.vue";
import DateInput from "@/components/Inputs/DateInput.vue";
import DefaultButton from "../Buttons/DefaultButton.vue";
import type { ToastID } from "vue-toastification/dist/types/types";
export default defineComponent({
Expand All @@ -91,8 +92,9 @@ export default defineComponent({
clipboard: useClipboard(),
created: false,
failed: false,
inviteCode: "",
inviteData: {
inviteCode: customAlphabet("ABCDEFGHIJKLMNOPQRSTUVWXYZ")(6),
inviteCode: "",
expiration: 1440 as number | null | "custom",
customExpiration: "" as string,
options: [] as string[],
Expand Down Expand Up @@ -220,6 +222,7 @@ export default defineComponent({
// Show the invite
this.created = true;
this.inviteCode = this.inviteData.inviteCode;
this.$formkit.reset("inviteForm");
this.inviteData.inviteCode = customAlphabet("ABCDEFGHIJKLMNOPQRSTUVWXYZ")(6);
},
Expand Down Expand Up @@ -247,7 +250,7 @@ export default defineComponent({
});
},
inviteLink() {
return `${window.location.origin}/j/${this.inviteData.inviteCode}`;
return `${window.location.origin}/j/${this.inviteCode}`;
},
},
watch: {
Expand Down Expand Up @@ -276,6 +279,7 @@ export default defineComponent({
},
async mounted() {
this.getLibraries();
this.inviteData.inviteCode = customAlphabet("ABCDEFGHIJKLMNOPQRSTUVWXYZ")(6);
this.inviteData.libraries = this.libraries.map((library) => library.id);
},
});
Expand Down
34 changes: 31 additions & 3 deletions frontend/src/components/InvitationList/InvitationItem.vue
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<template>
<ListItem icon="fa-envelope">
<template #title>
<span class="text-lg">{{ invite.code }}</span>
<p class="text-xs truncate text-gray-500 dark:text-gray-400 w-full">{{ expired }}</p>
<button @click="copyLink()" class="text-lg">{{ invite.code }}</button>
<p class="text-xs truncate w-full" :class="color">{{ expired }}</p>
</template>
<template #buttons>
<div class="flex flex-row space-x-2">
Expand All @@ -21,6 +21,7 @@
import { defineComponent } from "vue";
import { useInvitationStore } from "@/stores/invitations";
import { mapActions } from "pinia";
import { useClipboard } from "@vueuse/core";
import type { Invitation } from "@/types/api/invitations";
Expand All @@ -37,15 +38,42 @@ export default defineComponent({
required: true,
},
},
data() {
return {
clipboard: useClipboard(),
};
},
methods: {
async copyLink() {
await this.clipboard.copy(`${location.origin}/j/${this.invite.code}`);
this.$toast.info(this.__("Copied to clipboard"));
},
async deleteLocalInvitation() {
await this.deleteInvitation(this.invite.id);
},
...mapActions(useInvitationStore, ["deleteInvitation"]),
},
computed: {
expired(): string {
return (this.$filter("isPast", this.invite.expires) ? this.__("Expires") : this.__("Expired")) + " " + this.$filter("timeAgo", this.invite.expires);
if (this.$filter("isPast", this.invite.expires)) {
return this.__("Expired %{s}", { s: this.$filter("timeAgo", this.invite.expires) });
} else {
return this.__("Expires %{s}", { s: this.$filter("timeAgo", this.invite.expires) });
}
},
color() {
const inHalfDay = new Date();
inHalfDay.setHours(inHalfDay.getHours() + 12);
if (this.$filter("isPast", this.invite.expires)) {
return "text-red-600 dark:text-red-500";
}
if (this.$filter("dateLess", this.invite.expires, inHalfDay)) {
return "text-yellow-500 dark:text-yellow-400";
}
return "text-gray-500 dark:text-gray-400";
},
},
});
Expand Down
23 changes: 23 additions & 0 deletions frontend/src/ts/filters/dateLess.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import moment from "moment";

/**
* dateLess - Filter that returns true if the first date is less than the second date
*
* @param {string} value - The first date to compare
* @param {string} other - The second date to compare
* @param {boolean} utc - Whether input date is in UTC
* @returns The boolean result of the calculation
*/
function dateLess(value: string | Date, other: string | Date, utc: boolean = true): boolean {
if (utc) {
value = moment.utc(value).local();
other = moment.utc(other).local();
} else {
value = moment(value).local();
other = moment(other).local();
}

return value.isBefore(other);
}

export default dateLess;
2 changes: 2 additions & 0 deletions frontend/src/ts/filters/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import removeVersion from "./removeVersion";
import timeAgo from "./timeAgo";
import toMinutes from "./toMinutes";
import isPast from "./isPast";
import dateLess from "./dateLess";

const filters = {
titleCase,
Expand All @@ -14,6 +15,7 @@ const filters = {
timeAgo,
toMinutes,
isPast,
dateLess,
};

export default filters;
7 changes: 4 additions & 3 deletions frontend/src/ts/filters/isPast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ import moment from "moment";
* @returns The boolean result of the calculation
*/
function isPast(value: string | Date, utc: boolean = true): boolean {
if (utc) value = moment.utc(value).local().toDate();
else value = moment(value).local().toDate();
return moment().isBefore(value);
if (utc) value = moment.utc(value).local();
else value = moment(value).local();

return value.isBefore(moment());
}

export default isPast;
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<template>
<div ref="jellyForm">
<FormKit type="form" id="jellyfinForm" v-model="form" @submit="submit()" submit-label="Create Account" :submit-attrs="{ wrapperClass: 'flex justify-end' }">
<FormKit type="text" label="Username" name="username" placeholder="marvin" validation="required:trim|alphanumeric" required autocomplete="off" />
<FormKit type="text" label="Username" name="username" placeholder="marvin" validation="required:trim|alphanumeric|lowercase" required autocomplete="off" />
<FormKit type="email" label="Email" name="email" placeholder="marvin@wizarr.dev" validation="required:trim|email" required autocomplete="email" />
<FormKit type="password" label="Password" name="password" placeholder="••••••••" validation="required:trim" required autocomplete="new-password" :classes="{ outer: form.password ? '!mb-0' : '' }" />
<PasswordMeter :password="form.password" class="mb-[23px] mt-1 px-[2px]" v-if="form.password" />
Expand Down

0 comments on commit 4df5189

Please sign in to comment.