- added working typescript support to vue

- added bouncer support
- added UserPolicies
- added first user index call
This commit is contained in:
Sockenklaus
2023-07-08 08:16:08 +02:00
parent 6d28aaecb7
commit 864da02de5
23 changed files with 619 additions and 582 deletions

View File

@@ -5,7 +5,8 @@
"@adonisjs/core/build/commands/index.js",
"@adonisjs/repl/build/commands",
"@eidellev/inertia-adonisjs/build/commands",
"@adonisjs/lucid/build/commands"
"@adonisjs/lucid/build/commands",
"@adonisjs/bouncer/build/commands"
],
"exceptionHandlerNamespace": "App/Exceptions/Handler",
"aliases": {
@@ -22,7 +23,8 @@
"environment": [
"web"
]
}
},
"./start/bouncer"
],
"providers": [
"./providers/AppProvider",
@@ -32,7 +34,8 @@
"@adonisjs/shield",
"@eidellev/inertia-adonisjs",
"@adonisjs/lucid",
"@adonisjs/auth"
"@adonisjs/auth",
"@adonisjs/bouncer"
],
"metaFiles": [
{

3
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"typescript.tsdk": "node_modules/typescript/lib"
}

View File

@@ -591,6 +591,42 @@
"description": "Disable locks acquired to run migrations safely"
}
]
},
"make:policy": {
"settings": {},
"commandPath": "@adonisjs/bouncer/build/commands/MakePolicy",
"commandName": "make:policy",
"description": "Make a new bouncer policy",
"args": [
{
"type": "string",
"propertyName": "name",
"name": "name",
"required": true,
"description": "Name of the policy to create"
}
],
"aliases": [],
"flags": [
{
"name": "resource-model",
"propertyName": "resourceModel",
"type": "string",
"description": "Name of the resource model to authorize"
},
{
"name": "user-model",
"propertyName": "userModel",
"type": "string",
"description": "Name of the user model to be authorized"
},
{
"name": "actions",
"propertyName": "actions",
"type": "array",
"description": "Actions to implement"
}
]
}
},
"aliases": {}

View File

@@ -2,7 +2,7 @@ import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import { schema } from '@ioc:Adonis/Core/Validator'
export default class AuthController {
public async login({ auth, request, response, session }: HttpContextContract){
public async login({ auth, request, response }: HttpContextContract){
const loginSchema = schema.create({
@@ -18,27 +18,13 @@ export default class AuthController {
}
})
session.flash({
login: {
warning: 'test'
}
})
await auth.attempt(username, password)
response.redirect().toRoute('events.index')
}
public async logout({ auth, response, session }: HttpContextContract) {
public async logout({ auth, response }: HttpContextContract) {
await auth.logout()
session.flash('gfd', {
warning: 'test'
})
session.flash('login', {
warning: "noch eine warning"
})
response.redirect('/login')
}
}

View File

@@ -1,14 +1,17 @@
import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import User from 'App/Models/User'
import Logger from '@ioc:Adonis/Core/Logger'
import Database from '@ioc:Adonis/Lucid/Database'
export default class UsersController {
public async index({ auth, response, inertia }: HttpContextContract) {
public async index({ inertia, bouncer }: HttpContextContract) {
if(auth.user?.isAdmin) {
return inertia.render('Users/Index')
}
else response.redirect().toRoute('events.index')
await bouncer.with('UserPolicy').authorize('index')
const users = await Database
.from('users')
.select('id', 'username', 'is_admin')
return inertia.render('Users/Index', { users })
}
public async create({ auth, inertia }: HttpContextContract) {

View File

@@ -38,6 +38,10 @@ export default class ExceptionHandler extends HttpExceptionHandler {
session.flash('login', { error: error.message });
return response.redirect().back();
}
if(['E_AUTHORIZATION_FAILURE'].includes(error.code)) {
session.flash('auth', { error: error.message })
return response.redirect().back()
}
/**
* Forward rest of the exceptions to the parent class

View File

@@ -12,7 +12,10 @@ export default class User extends BaseModel {
@column({ serializeAs: null })
public password: string
@column()
@column({
consume: Boolean,
serialize: Boolean
})
public isAdmin: boolean
@column()

View File

@@ -0,0 +1,25 @@
import User from 'App/Models/User'
import { BasePolicy } from '@ioc:Adonis/Addons/Bouncer'
export default class UserPolicy extends BasePolicy {
public async index(user: User) {
return user.isAdmin
}
public async show(user: User, query: User) {
return user.isAdmin || user.id === query.id
}
public async update(user: User, query: User) {
return user.isAdmin || user.id === query.id
}
public async destroy(user: User) {
return user.isAdmin
}
public async store(user: User) {
return user.isAdmin
}
}

16
contracts/bouncer.ts Normal file
View File

@@ -0,0 +1,16 @@
/**
* Contract source: https://git.io/Jte3v
*
* Feel free to let us know via PR, if you find something broken in this config
* file.
*/
import { actions, policies } from '../start/bouncer'
declare module '@ioc:Adonis/Addons/Bouncer' {
type ApplicationActions = ExtractActionsTypes<typeof actions>
type ApplicationPolicies = ExtractPoliciesTypes<typeof policies>
interface ActionsList extends ApplicationActions {}
interface PoliciesList extends ApplicationPolicies {}
}

883
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -67,6 +67,7 @@
},
"dependencies": {
"@adonisjs/auth": "^8.2.3",
"@adonisjs/bouncer": "^2.3.0",
"@adonisjs/core": "^5.9.0",
"@adonisjs/lucid": "^18.4.0",
"@adonisjs/repl": "^3.1.11",
@@ -76,11 +77,13 @@
"@eidellev/inertia-adonisjs": "^8.0.1",
"@inertiajs/vue3": "^1.0.9",
"@vue/compiler-sfc": "^3.3.4",
"@vue/tsconfig": "^0.1.3",
"luxon": "^3.3.0",
"proxy-addr": "^2.0.7",
"reflect-metadata": "^0.1.13",
"source-map-support": "^0.5.21",
"sqlite3": "^5.1.6",
"ts-loader": "^9.4.4",
"vue": "^3.3.4"
}
}

View File

@@ -5,3 +5,6 @@
</div>
</main>
</template>
<script setup lang="ts">
</script>

View File

@@ -1,8 +1,8 @@
<template>
</template>
<script setup>
<script setup lang="ts">
import { useMessage } from 'naive-ui'
import { watch, defineProps, onUpdated, onMounted } from 'vue'
import { onUpdated, onMounted } from 'vue'
const message = useMessage()
const props = defineProps(['messages'])
@@ -15,16 +15,13 @@
displayNewMessages(props.messages)
})
function displayNewMessages(messages) {
function displayNewMessages(messages: any) {
console.log(messages)
let output = []
let output: Array<Object> = []
output = flattenObject(removeValidationErrors(messages))
console.log(output)
output?.forEach((item) => {
output?.forEach((item: any) => {
for (let key in item) {
switch (key){
case 'error':
@@ -55,33 +52,32 @@
})
}
function removeValidationErrors(input) {
if(input === null || !Object.hasOwn(input, "errors")) return input
function removeValidationErrors(input: any) {
if(input === null || !input.hasOwnProperty("errors")) return input
const { errors: _, ...output } = input
const { errors: _, ...output } = (input as any)
return output
}
function flattenObject(input) {
function flattenObject(input: Object) {
if (input === null) return input
return Object.values(input).map((value) => Object.entries(value)).flat().reduce((acc, [key, value]) => {
return Object.values(input).map((value) => Object.entries(value)).flat().reduce((acc: Array<Object>, [key, value]) => {
acc.push({[key]: value});
return acc;
}, []);
}
function translateError(errorMsg) {
function translateError(errorMsg: string) {
switch(errorMsg.split(":")[0]) {
case 'E_INVALID_AUTH_PASSWORD':
return "Falsches Passwort eingegeben."
case 'E_INVALID_AUTH_UID':
return "Benutzername nicht gefunden"
case 'E_AUTHORIZATION_FAILURE':
return 'Rechte unzureichend um diese Aktion auszuführen.'
}
return errorMsg
}
function isEmpty(obj) {
return Object.keys(obj).length === 0;
}
</script>

View File

@@ -1,15 +1,25 @@
<template>
<n-message-provider>
<FlashMessages
:messages="props.flashMessages"
/>
</n-message-provider>
<div>
Bin in Events
</div>
</template>
<script setup>
<script setup lang="ts">
import BELayout from '@/layouts/BELayout.vue'
import FlashMessages from '@/components/FlashMessages.vue'
defineOptions({
layout: BELayout
})
const props = defineProps({
flashMessages: Object
})
</script>

View File

@@ -1,13 +1,26 @@
<template>
<n-message-provider>
<FlashMessages
:messages="props.flashMessages"
/>
</n-message-provider>
<div>
Willkommen auf der Hauptseite
</div>
<div>
{{ helloWorld }}
</div>
</template>
<script setup>
<script setup lang="ts">
import { ref } from 'vue'
import FlashMessages from '@/components/FlashMessages.vue'
defineProps({
const props = defineProps({
test: String,
flashMessages: Object
})
const helloWorld = ref<String>("helloWorld")
</script>

View File

@@ -46,11 +46,12 @@
</n-form>
</template>
<script setup>
import { ref, reactive, watch } from 'vue'
<script setup lang="ts">
import { ref } from 'vue'
import { router } from '@inertiajs/vue3'
import type { FormInst } from 'naive-ui'
import LoginLayout from '@/layouts/LoginLayout.vue'
import FlashMessages from '@/components/FlashMessages'
import FlashMessages from '@/components/FlashMessages.vue'
defineOptions({ layout: LoginLayout })
const props = defineProps(['flashMessages'])
@@ -60,7 +61,7 @@
password: '',
})
const formRef = ref(null)
const formRef = ref<FormInst | null>(null)
const rules = ref({
username: {
@@ -75,15 +76,6 @@
}
})
function translateLoginError(errorMsg) {
switch(errorMsg.split(":")[0]) {
case 'E_INVALID_AUTH_PASSWORD':
return "Falsches Passwort eingegeben."
case 'E_INVALID_AUTH_UID':
return "Benutzername nicht gefunden"
}
}
function onClickLogin(){
formRef.value?.validate((errors) => {
if(!errors) router.post('login', form.value)

View File

@@ -1,14 +1,27 @@
<template>
<n-message-provider>
<FlashMessages
:messages="props.flashMessages"
/>
</n-message-provider>
<div>
Bin in Users
</div>
<div>
{{ users }}
</div>
</template>
<script setup>
<script setup lang="ts">
import BELayout from '@/layouts/BELayout.vue'
import MainNav from '@/components/MainNav.vue'
import FlashMessages from '@/components/FlashMessages.vue'
defineOptions({ layout: BELayout })
const props = defineProps({
users: Object,
flashMessages: Object
})
</script>

5
resources/js/vue-shim.d.ts vendored Normal file
View File

@@ -0,0 +1,5 @@
declare module '*.vue' {
import type { DefineComponent } from "vue"
const component: DefineComponent<{}, {}, any>
export default component
}

59
start/bouncer.ts Normal file
View File

@@ -0,0 +1,59 @@
/**
* Contract source: https://git.io/Jte3T
*
* Feel free to let us know via PR, if you find something broken in this config
* file.
*/
import Bouncer from '@ioc:Adonis/Addons/Bouncer'
/*
|--------------------------------------------------------------------------
| Bouncer Actions
|--------------------------------------------------------------------------
|
| Actions allows you to separate your application business logic from the
| authorization logic. Feel free to make use of policies when you find
| yourself creating too many actions
|
| You can define an action using the `.define` method on the Bouncer object
| as shown in the following example
|
| ```
| Bouncer.define('deletePost', (user: User, post: Post) => {
| return post.user_id === user.id
| })
| ```
|
|****************************************************************
| NOTE: Always export the "actions" const from this file
|****************************************************************
*/
export const { actions } = Bouncer
/*
|--------------------------------------------------------------------------
| Bouncer Policies
|--------------------------------------------------------------------------
|
| Policies are self contained actions for a given resource. For example: You
| can create a policy for a "User" resource, one policy for a "Post" resource
| and so on.
|
| The "registerPolicies" accepts a unique policy name and a function to lazy
| import the policy
|
| ```
| Bouncer.registerPolicies({
| UserPolicy: () => import('App/Policies/User'),
| PostPolicy: () => import('App/Policies/Post')
| })
| ```
|
|****************************************************************
| NOTE: Always export the "policies" const from this file
|****************************************************************
*/
export const { policies } = Bouncer.registerPolicies({
UserPolicy: () => import('App/Policies/UserPolicy')
})

View File

@@ -8,7 +8,6 @@
"build"
],
"compilerOptions": {
"allowJs": true,
"outDir": "build",
"rootDir": "./",
"sourceMap": true,
@@ -35,7 +34,8 @@
"@japa/preset-adonis/build/adonis-typings",
"@eidellev/inertia-adonisjs",
"@adonisjs/lucid",
"@adonisjs/auth"
"@adonisjs/auth",
"@adonisjs/bouncer"
]
}
}

13
tsconfig.vue.json Normal file
View File

@@ -0,0 +1,13 @@
{
// tsconfig.vue.json
"extends": "@vue/tsconfig/tsconfig.json",
"include": [
"./resources/js/**/*"
],
"compilerOptions": {
"paths": {
"@/*": ["./resources/js/"]
}
}
}

View File

@@ -47,7 +47,7 @@ Encore.setPublicPath('/assets')
| entrypoints.
|
*/
Encore.addEntry('app', './resources/js/app.js')
Encore.addEntry('app', './resources/js/app.ts')
/*
|--------------------------------------------------------------------------
@@ -187,6 +187,8 @@ Encore.enableVueLoader(() => {}, {
version: 3,
runtimeCompilerBuild: false,
useJsx: false
}).enableTypeScriptLoader(config => {
config.configFile = 'tsconfig.vue.json'
}).addAliases({
'@': join(__dirname, 'resources/js')
}).configureDefinePlugin(options => {