Recent Activity
Here's what @aaron-ford has been up to this past year
-
Completed lesson Updating A Course's Difficulty, Status, or Access Level
-
Anniversary Thanks for being an Acocasts member for 1 year
-
Completed lesson Getting A Course's Details, Modules, & Lessons
-
Completed lesson Paginating our Course List
-
Commented on post Creating, Updating, and Deleting Courses
Rather than have someone have to know the ids relations, how would it be best to implement using a string? Like for status ID. I can give the user a list of valid statuses. They want to make an API call with a status of 'Planned'. I can have my validator verify that planned exists in the statues table, that's fine, but I want to call the parameter in the API call status, not statusId, since it isn't an ID. That means the validator needs to validate the status 'Planned' exists in the statuses table, and then when we pass the results of the validation to the store course method, it expects a type of courseValidator, which now has status instead of statusId, which doesn't exist on the table. So I'm not sure if I should transform API parameter names before passing the data to the validator, so when the data gets to the validator and the store action the parameters match the DB, or if I should validate and then transform? Transforming before validating kind of seems to reduce the usefulness of the validator, but transforming after validation means we're changing the type expected by the store action to some new transformed object. What would be the best way to do something like this? I hope that makes sense. Essentially I want to use human readable data in the API which requires a parameter name different than the validator/table, but validate it against DB values so the action can use the validated data to create the data in the DB.
-
Completed lesson Creating, Updating, and Deleting Courses
-
Completed lesson Listing Courses
-
Commented on post API Authorization Checks
Can we clean up the error responses and not have it return the frames array? Just message, name, status?
-
Completed lesson API Authorization Checks
-
Completed lesson Status API CRUD
-
Completed lesson Access Level API CRUD
-
Completed lesson Deleting A Difficulty
-
Completed lesson Updating A Difficulty
-
Completed lesson Getting A Specific Difficulty
-
Completed lesson Creating Organization Difficulties
-
Completed lesson Listing Organization Difficulties
-
Completed lesson Simple API Versioning
-
Completed lesson Setting Up Our REST Client
-
Completed lesson Our First API Endpoint to Get Our Organization's Details
-
Completed lesson The Goal of our REST API
-
Completed lesson Deleting/Revoking Access Tokens
-
Commented on post Displaying & Copying A Newly Created Access Token
How do you prevent hydration errors on the dates? The server is rendering a different date then the front end, which seems to make sense if we are formatting it to the locale of the user, but it doesn't seem to like that.
-
Completed lesson Displaying & Copying A Newly Created Access Token
-
Completed lesson Listing an Organization's Access Tokens
-
Completed lesson Opaque Access Tokens (OAT) vs JSON Web Tokens (JWT)
-
Replied to Yep, spot on Aaron! The access tokens are scoped by their type...
The reason it could use the same table is because a course is linked to an organization, right? If I wanted to have API keys for something not linked to an organization, I'd have to have a separate table, since the tokenable_id in the table would have to reference a different table? Or if say you wanted someone to be able to make changes to a course but not the overall organization? You'd make a token table that references the courses model, or whatever model you want to grant rights to?
table .integer('tokenable_id') .notNullable() .unsigned() .references('id') .inTable('otherModelName') .onDelete('CASCADE')Copied! -
Completed lesson Creating Access Tokens Part 2: Inertia/Vue
-
Completed lesson Creating Access Tokens Part 1: AdonisJS
-
Commented on post Configuring Access Token Auth on top of Session Auth
So if we need multiple access tokens, like say we wanted to make it so courses could have tokens to modify their data within an organization, we could just add the DbAccessTokensProvider.forModel(Courses, {
type: 'course_ api_token',
prefix: 'api'
}
to the courses model, but would we want to copy the migration and make a second course_api_access_tokens table to use for the table parameter in the DbAccessTokensProvider? Or is there a way to use the one api_access_tokens table? Can both use the same type of api_token? Since they are both for api calls? Or do they have to have different types? I assume it would also require adding something like
courseApi: tokensGuard({ provider: tokensUserProvider({ tokens: 'accessTokens', model: () => import('#models/courses'), }),Copied!to the auth.ts config file. I assume If everything set up in this video is duplicated for a second api, the rest of the videos in this series just be applicable to either guard.
-
Completed lesson Defining Access Token Abilities & DTO
-
Completed lesson Separation of API & Web Auth Guard Concerns
-
Completed lesson Configuring Access Token Auth on top of Session Auth
-
Completed lesson Overview of our Database Schema
-
Completed lesson Getting Familiar with our Web Project
-
Completed lesson Getting the Web Project Up & Running
-
Completed lesson Goal of this Series
-
Commented on post Completing Our AppLayout & Navigation Bar
I have been going through trying to clean up my console errors. I have an error for 'Extraneous non-props attributes (user, messages) were passed to component but could not be automatically inherited because component renders fragment or text root nodes'. I ran your code and see the error exists there too. How do we fix this? I also get Invalid prop: type check failed for prop "href". Expected String with value "null", got Null for the pagination links. I tried modifying the components to allow null for the href, but that didn't work. Does the pagination dto npm package need to be modified?
Update: I wrapped the nav and sheet into a parent div and that seems to fix the hydration issues. Either that, or listing user and messages in the props, but since the nav doesn't directly use them, I went with wrapping it in a div.
-
Started discussion Dynamic Dropdown
-
Replied to Glad to hear that sped things up for ya! There shouldn't be ...
The onSubmmit transform did what I needed, thanks! It's been a while since I've done some of the lessons, so I forgot we'd done that. As always, thanks for the help!
-
Replied to Hey Aaron!Yeah, I don't think it should have any issue with ...
Yeah, that sped it up. I was using numbers since I thought it would need to be a number for the vine validation, but it seems to work. The one thing I can't get the validation to work on when using a string is enums. I read that using v-model.number can force it to be a number, but I haven't been able to get that to work. Maybe because FormInput is a custom component that doesn't support the model modifier? Is there a way to to get a FormInput to transform a string to a number for the backend?
-
Started discussion Component Delay
-
Replied to Yeah, these were changes made for production after the Building...
I should have checked the component to see if it was modified. Thanks, I have it working now. If you make a video on the filtering, let me know. An omni search would be great, like being able to search by a string and get a list of items where the id/name/description or similar fields match. There's a lot of pages I'll want to have some kind of search/filter on. Hopefully I can figure it out going through your code. I think you've mentioned you already have plans to do a series on testing in AdonisJS, which will be nice. Thanks again for the help!
-
Replied to Awesome, glad to hear you were able to get it working!Yes, Shadcn's...
Are there more lessons for PlotMyCourse other than the building with AdonisJS 6 and Inertia? Or are these changes you made outside the course for your personal production repo? Looks like there are a lot of interesting and useful things I can reference for my own project. The numbered links work for me, but using the arrows to try and move from page to page or jump to the start or end don't work. I'm using the fromPaginator as shown above:
const props = defineProps<{ users: SimplePaginatorDtoContract<UserDto> }>() const users = ref(props.users.data) const paginationInfo = props.users.metaCopied!And I copied the pagination from the production PlotMyCourse repo, replacing the lessons.meta with paginationInfo. The UI updates and highlights the right numbers, but there must not be a link being generated to actually change them. Hovering over them shows no link, but hovering over the numbers does. The controller is adding the start and end as directed:
async index({ request, inertia, auth }: HttpContext) { const page = request.input('page', 1) const paginator = await User.query().orderBy('username', 'asc').paginate(page, 10) paginator.baseUrl(router.makeUrl('users.index')) //paginator.queryString(request.qs()) return inertia.render('admin/users/index', { users: UserDto.fromPaginator(paginator, { start: paginator.firstPage, end: paginator.lastPage, }), }) }Copied!I've been going through my code and comparing it to yours. Other than the fact you use an action to get your pagination based off supplied filters, it seems similar from what I can tell. Any idea why the links wouldn't generate?
-
Replied to Hey Aaron!Yeah, adding a fromPaginator would be one way to do...
Thanks! I am using your dto package, and had just stumbled upon the knowledge you had built the fromPaginator function into it, but this helped me get it working. I have the initial results showing up, and the links on the bottom (though I have to get them working now). It looks like Shadcn has a pagination component, so I think I'll try setting that up.
When is AdonisJS 7 coming out?
Thanks for your help!
-
Started discussion How to paginate with DTOs?
-
Completed lesson Tapping into Model Factory States
-
Completed lesson Stubbing Fake Data with Model Factories
-
Completed lesson Clearing Login Attempt Rate Limits on Password Reset
-
-
Commented on post Restricting Login Attempts with Rate Limiting
I am getting an error from the SettingsShell.vue, I am guessing it is from changing the tailwind config. I have gone back over the last lesson and this one, and I haven't found any differences in the change I made vs what you made.
[postcss]
/plotmycourse/inertia/components/SettingsShell.vue?vue&type=style&index=0&scoped=9c23e674&lang.css:3:5: The
text-primaryclass does not exist. Iftext-primaryis a custom class, make sure it is defined within a@layerdirective. -
Completed lesson Restricting Login Attempts with Rate Limiting
-
Completed lesson Applying Our Authorization UI Checks
-
Completed lesson Applying Our Server-Side Authorization Checks
-
Completed lesson Rolling Our Own Authorization Access Controls
-
Completed lesson Refreshing Partial Page Data
-
Completed lesson Removing an Organization User
-
Completed lesson Canceling an Organization Invite
-
Completed lesson Adding the Organization Invite User Interface
-
Completed lesson Accepting an Organization Invitation
-
Completed lesson Sending an Invitation to Join Our Organization
-
Completed lesson Cascading and Deleting Model Relationships
-
Completed lesson Listing Current Organization Members
-
Commented on post Updating & Deleting an Organization
Everything works except the delete. I get
delete from
organizationswhereid= 3 - Cannot delete or update a parent row: a foreign key constraint failsI feel like maybe I had something like this in a previous lesson, but can't find it. Is this something with how AdonisJS handles relationships in MySQL? Do I have to alter the code slightly to properly delete related modules?
-
Completed lesson Updating & Deleting an Organization
-
Completed lesson Account Deletion & Cleaning Dangling Organizations
-
Completed lesson Alerting Users When Their Account Email Is Changed
-
Completed lesson Allowing Users to Safely Update Their Account Email
-
Completed lesson User Profile Settings
-
Completed lesson Creating the Settings Shell
-
Completed lesson Storing Lesson Order Changes & Handling Cross-Module Drag & Drops
-
Completed lesson Storing Module Order Changes from Vue Draggable
-
Completed lesson Patching Tag Changes for our Modules & Lessons
-
-
Commented on post Editing & Deleting Course Lessons
Sorry for all the questions. I am getting a lock timeout error when I try to delete. I copied your destroy_lesson code from the repo to make sure our files matched and still get it. I get it trying to delete any lesson. I built my DB fresh and reseeded, just in case. If I remove the await decrement lesson I can delete lessons just fine, just for some reason the lessons are locked when trying to increment:
ERROR (5078): updatelessonssetorder=order- 1 where (`module_id` = 1 andorder> 1) and (`organization_id` = 1) - Lock wait timeout exceeded; try restarting transaction.The query looks right, it seems to be trying to update the right data. Any ideas?
-
Completed lesson Editing & Deleting Course Lessons
-
Replied to Hi Aaron!It sounds like your organization is undefined within...
Thanks! I found it. A typo… was missing the in organization. That's why you don't code late at night.
-
Commented on post Creating & Listing Sortable Course Lessons
The line
statusId: props.organization.statuses.at(0)?.id
works fine in my SortableModules, but in the SortableLessons, it is breaking the page due to 'Cannot read properties of undefined (reading 'statuses').' I have looked through all the files I thought could be related, but haven't found any differences. Any suggestions on what could be causing it?
-
Completed lesson Creating & Listing Sortable Course Lessons
-
Completed lesson Creating, Editing, & Deleting Course Modules
-
Completed lesson Querying & Listing Sortable Course Modules
-
Completed lesson The Tag Selector
-
Completed lesson Showing A Course's Details
-
Completed lesson Deleting Courses
-
Completed lesson Editing & Updating Courses
-
Completed lesson Creating A New Course
-
Replied to Yep! I'm working on updating lesson 1.4 of this series this ...
Thanks. Just a note, I tried npx shadcn-vue@radix add table and it failed with an error 'Failed to fetch base color from registry.' I had to run it with a version (npx shadcn-vue@0.11.4 add table) to get it to install, despite the many warnings.
-
Replied to It looks like shadcn updated recently to use Reka instead of...
I found npx shadcn-vue@0.11.4 add table, that seems to install it, though it does give a bunch of warnings:
npm WARN EBADENGINE Unsupported engine {
npm WARN EBADENGINE package: 'undici@7.4.0',
npm WARN EBADENGINE required: { node: '>=20.18.1' },
npm WARN EBADENGINE current: { node: 'v20.11.0', npm: '10.5.1' }
npm WARN EBADENGINE }
npm WARN deprecated inflight@1.0.6: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.
npm WARN deprecated source-map-resolve@0.6.0: See https://github.com/lydell/source-map-resolve#deprecated
npm WARN deprecated glob@7.2.3: Glob versions prior to v9 are no longer supported
I'll try and continue with this, but if there's a better way, please let me know. Thanks!
-
Commented on post Querying & Listing An Organization's Courses
It looks like shadcn updated recently to use Reka instead of Radix. I tried following the upgrade instructions at https://www.shadcn-vue.com/docs/changelog#update-your-project, by adding
"ui": "~/components/ui", "lib": "~/lib", "composables": "~/composables"Copied!to the components.json file, and then trying to reinstall the components, but I still get the same message as before:
An invalid components.json file was found at /plotmycourse.
Before you can add components, you must create a valid components.json file by running the init command.
Learn more at https://shadcn-vue.com/docs/components-json.
Should I just delete the components.json file and run the init? Or what is the best route forward for continuity with the course?
-
Completed lesson Querying & Listing An Organization's Courses
-
Completed lesson Replicating Behaviors for Access Levels & Statuses
-
Completed lesson Creating A Reusable Sorting Vue Component
-
Completed lesson How To Create A Custom VineJS Validation Rule
-
Commented on post Replacing A Course's Deleted Difficulty
Is AdonisJS set up to handle soft deletes? If you didn't want to replace the difficulty and maintain the original relationships? Or if you wanted to delete a course or organization for some reason, and not lose any records you might have to things like users grades?
Also, I feel like I've occasionally seen warnings in the code that I think are due to FormInput type group (remember me on the login page) not having a label or a v-model attribute. Should a group type have those attributes? Or should they be made optional in the FormInput's defined props?
-
Completed lesson Sorting Difficulties with Drag & Drop
-
Completed lesson Reusable VineJS Exists In Organization Validation
-
Completed lesson Replacing A Course's Deleted Difficulty
-
Completed lesson Confirming & Deleting Difficulties
-
Completed lesson Updating Difficulties
-
Replied to Yeah, if I'm following correctly, you could probably achieve...
Thanks! I simplified it by adding an is_default column to the organization_users table.
-
Commented on post Setting & Loading the User's Active Organization
Is there a simple way to query on a relation's relation? For example, if you wanted to have a setting called 'default organization' defined in a settings table, and you assign that setting to a user in a user_settings table, can you query the related organization where the user's user_settings.settings_id = settings.id where settings.name = 'default organization?' Or would you just use joins on anything more complicated than one relation away? Hopefully that makes sense. The purpose is to set it up where if there's an active organization Id and the user is assigned to that organization, use it. Otherwise, see if they have a default organization Id set in their settings and use that. If those both fail, grab the first organization from their list.
-
Completed lesson Listing & Creating Difficulties
-
Completed lesson The Confirm Delete Dialog & Deleting the Active Organization
-
Completed lesson Editing the Active Organization
-
Completed lesson Creating A UseResourceActions Composable
-
Completed lesson Switching Between Organizations
-
Completed lesson The Form Dialog Component & Adding Organizations
-
Commented on post Listing the User's Organizations
From a UI perspective, if someone has a lot of organizations, would you just let the list grow long, or is there a good way to limit the dropdown length and have the menu scroll after a certain point?
-
Completed lesson Creating A Layout
-
Completed lesson Listing the User's Organizations
-
Completed lesson Setting & Loading the User's Active Organization
-
-
Replied to That's odd - do the tailwind classes work okay? Maybe your colors...
I think the button color is coming from inertia_layout.edge. I might have installed dark mode or something in my initial setup, but if I change it to match yours, I get a bunch of errors. I might just ignore it for now. I don't know if that affects the red color of the error. My app.css matches. Thanks for the help!
-
Completed lesson Form Validation & Displaying Errors
-
Completed lesson Inertia Form Basics
-
Completed lesson What Code Can & Can't Be Shared Between AdonisJS & Inertia
-
Completed lesson Partial and Lazy Data Loading and Evaluation
-
Completed lesson Global Components and Hydration Mismatch in Action
-
Completed lesson The Link Component and Programmatic Linking
-
Completed lesson Linking Between Pages & Page State Flow
-
Completed lesson Sharing Data from AdonisJS to Vue via Inertia
-
Completed lesson Setting Up TailwindCSS, Shadcn-Vue, and Automatic Component Imports
-
Completed lesson Creating Our AdonisJS App With InertiaJS
-
Completed lesson Server-Side Rendering (SSR) vs Client-Side Rendering (CSR)
-
Completed lesson The Flow of Pages and Page Props
-
Completed lesson Specifying Page Titles & Meta Tags
-
Completed lesson Default Layouts & Overwriting the Default Layout
-
Commented on post Forgot Password & Password Reset
When you say we might want to add a referrer policy to this page, how do we do that?
-
Completed lesson Forgot Password & Password Reset
-
Commented on post Logging In Users & Displaying Exceptions
I have
<Alert v-if="exceptions.E_INVALID_CREDENTIALS" variant="destructive" class="mb-6">but the destructive doesn't seem to change my error to be red. Any idea why? Also, my buttons have always been purple instead of black for some reason, maybe that is related?
-
Completed lesson Adding the Remember Me Token
-
Completed lesson Logging In Users & Displaying Exceptions
-
Completed lesson Onboarding Newly Registered Users
-
Completed lesson Logging Out Users
-
Completed lesson Splitting Our Routes Between Auth & Web
-
Completed lesson User Registration with InertiaJS
-
Completed lesson Creating A Toast Message Manager
-
Commented on post Typing Lucid Models in Inertia with DTOs
The import for the UserDto works fine in my inertia.js file, but it can't be found in the AppLayout.vue file. Did I miss something? I tried going back over it and I think I followed all the steps.
-
Completed lesson Completing Our AppLayout & Navigation Bar
-
Completed lesson Typing Lucid Models in Inertia with DTOs
-
Completed lesson Seeding Our Initial Database Data
-
Completed lesson Creating A Lucid Model Mixin for our Organization Relationship
-
Commented on post Defining Our Lucid Models & Relationships
If we want to define single or multi column indexes for our tables, do we do that in the migrations files?
-
Completed lesson Defining Our Lucid Models & Relationships
-
Completed lesson Defining Our Migrations & Foreign Keys
-
Completed lesson Understanding Our Database Schema
-
Completed lesson What Are Some of Inertia's Limitations
-
Completed lesson Cross-Site Request Forgery (CSRF) Protection in InertiaJS
-
Completed lesson Creating A FormInput Vue Component
-
Completed lesson Common useForm Methods & Options
-
Completed lesson The useForm Helper