diff --git a/app/Controllers/Http/AssetsController.ts b/app/Controllers/Http/AssetsController.ts index 93cb073..e23f6a9 100644 --- a/app/Controllers/Http/AssetsController.ts +++ b/app/Controllers/Http/AssetsController.ts @@ -4,6 +4,7 @@ import AssetService from 'App/Services/AssetService'; import CacheService from 'App/Services/CacheService'; import Database from '@ioc:Adonis/Lucid/Database'; import Drive from '@ioc:Adonis/Core/Drive' +import Application from '@ioc:Adonis/Core/Application'; export default class AssetsController { public async index({ }: HttpContextContract) { @@ -124,7 +125,10 @@ export default class AssetsController { await asset.related('taxonomies').query().update({ assetId: null }) await asset.delete() - await Drive.delete(asset.filename) + // don't actually delete the file when not in production + if (Application.inProduction) { + await Drive.delete(asset.filename) + } return response.status(200).json({ status: 200, diff --git a/app/Controllers/Http/InvoicesController.ts b/app/Controllers/Http/InvoicesController.ts new file mode 100644 index 0000000..6349753 --- /dev/null +++ b/app/Controllers/Http/InvoicesController.ts @@ -0,0 +1,32 @@ +import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext' +import User from 'App/Models/User' +import InvoiceStoreValidator from 'App/Validators/InvoiceStoreValidator' +import { DateTime } from 'luxon' + +export default class InvoicesController { + public async index({}: HttpContextContract) {} + + public async create({}: HttpContextContract) {} + + public async store({ request, response, params }: HttpContextContract) { + const { periodStartAt, periodEndAt, ...data } = await request.validate(InvoiceStoreValidator) + const user = await User.findOrFail(params.id) + + await user.related('invoices').create({ + ...data, + paid: true, + periodStartAt: periodStartAt ? DateTime.fromSeconds(periodStartAt) : undefined, + periodEndAt: periodEndAt ? DateTime.fromSeconds(periodEndAt) : undefined + }) + + return response.redirect().back() + } + + public async show({}: HttpContextContract) {} + + public async edit({}: HttpContextContract) {} + + public async update({}: HttpContextContract) {} + + public async destroy({}: HttpContextContract) {} +} diff --git a/app/Controllers/Http/PostsController.ts b/app/Controllers/Http/PostsController.ts index 6b42f29..6129b09 100644 --- a/app/Controllers/Http/PostsController.ts +++ b/app/Controllers/Http/PostsController.ts @@ -110,7 +110,7 @@ export default class PostsController { const postTypes = PostType const taxonomies = await TaxonomyService.getAllForTree() - + return view.render('studio/posts/createOrEdit', { post, assets, taxonomies, postTypes }) } diff --git a/app/Controllers/Http/UsersController.ts b/app/Controllers/Http/UsersController.ts index 7771e15..4c5ce76 100644 --- a/app/Controllers/Http/UsersController.ts +++ b/app/Controllers/Http/UsersController.ts @@ -30,8 +30,9 @@ export default class UsersController { const roles = await Role.all() const user = await User.findOrFail(params.id) const profile = await user.related('profile').query().firstOrFail() + const invoices = await user.related('invoices').query().orderBy('updatedAt', 'desc') - return view.render('studio/users/show', { roles, user, profile }) + return view.render('studio/users/show', { roles, user, profile, invoices }) } public async edit({}: HttpContextContract) {} diff --git a/app/Models/Invoice.ts b/app/Models/Invoice.ts new file mode 100644 index 0000000..9e4c5b2 --- /dev/null +++ b/app/Models/Invoice.ts @@ -0,0 +1,50 @@ +import { DateTime } from 'luxon' +import { BaseModel, BelongsTo, belongsTo, column } from '@ioc:Adonis/Lucid/Orm' +import User from './User' + +export default class Invoice extends BaseModel { + @column({ isPrimary: true }) + public id: number + + @column() + public userId: number + + @column() + public invoiceId: string + + @column() + public invoiceNumber: string + + @column() + public chargeId: string + + @column() + public amountDue: number + + @column() + public amountPaid: number + + @column() + public amountRemaining: number + + @column() + public status: string + + @column() + public paid: boolean + + @column.dateTime() + public periodStartAt: DateTime + + @column.dateTime() + public periodEndAt: DateTime + + @column.dateTime({ autoCreate: true }) + public createdAt: DateTime + + @column.dateTime({ autoCreate: true, autoUpdate: true }) + public updatedAt: DateTime + + @belongsTo(() => User) + public user: BelongsTo +} diff --git a/app/Models/User.ts b/app/Models/User.ts index 90bb900..7edf994 100644 --- a/app/Models/User.ts +++ b/app/Models/User.ts @@ -21,6 +21,7 @@ import RequestVote from './RequestVote' import HistoryTypes from 'App/Enums/HistoryTypes' import Plan from './Plan' import Plans from 'App/Enums/Plans' +import Invoice from './Invoice' class User extends AppBaseModel { @column({ isPrimary: true }) @@ -205,6 +206,9 @@ class User extends AppBaseModel { onQuery: query => query.whereNotNull('lessonRequestId') }) public lessonRequestVotes: HasMany + + @hasMany(() => Invoice) + public invoices: HasMany } User['findForAuth'] = function (uids: string[], uidValue: string) { diff --git a/app/Validators/InvoiceStoreValidator.ts b/app/Validators/InvoiceStoreValidator.ts new file mode 100644 index 0000000..056ae55 --- /dev/null +++ b/app/Validators/InvoiceStoreValidator.ts @@ -0,0 +1,50 @@ +import { schema, rules } from '@ioc:Adonis/Core/Validator' +import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext' + +export default class InvoiceStoreValidator { + constructor(protected ctx: HttpContextContract) {} + + /* + * Define schema to validate the "shape", "type", "formatting" and "integrity" of data. + * + * For example: + * 1. The username must be of data type string. But then also, it should + * not contain special characters or numbers. + * ``` + * schema.string({}, [ rules.alpha() ]) + * ``` + * + * 2. The email must be of data type string, formatted as a valid + * email. But also, not used by any other user. + * ``` + * schema.string({}, [ + * rules.email(), + * rules.unique({ table: 'users', column: 'email' }), + * ]) + * ``` + */ + public schema = schema.create({ + invoiceId: schema.string([rules.unique({ table: 'invoices', column: 'invoice_id' })]), + invoiceNumber: schema.string(), + chargeId: schema.string.optional(), + amountDue: schema.number(), + amountPaid: schema.number(), + amountRemaining: schema.number(), + status: schema.string.optional(), + periodStartAt: schema.number.optional(), + periodEndAt: schema.number.optional(), + }) + + /** + * Custom messages for validation failures. You can make use of dot notation `(.)` + * for targeting nested fields and array expressions `(*)` for targeting all + * children of an array. For example: + * + * { + * 'profile.username.required': 'Username is required', + * 'scores.*.number': 'Define scores as valid numbers' + * } + * + */ + public messages = {} +} diff --git a/public/assets/entrypoints.json b/public/assets/entrypoints.json index 437f833..391cb1f 100644 --- a/public/assets/entrypoints.json +++ b/public/assets/entrypoints.json @@ -2,40 +2,40 @@ "entrypoints": { "app": { "css": [ - "http://localhost:64876/assets/app.css" + "http://localhost:63685/assets/app.css" ], "js": [ - "http://localhost:64876/assets/app.js" + "http://localhost:63685/assets/app.js" ] }, "post": { "js": [ - "http://localhost:64876/assets/post.js" + "http://localhost:63685/assets/post.js" ] }, "stream": { "js": [ - "http://localhost:64876/assets/stream.js" + "http://localhost:63685/assets/stream.js" ] }, "file_manager": { "js": [ - "http://localhost:64876/assets/file_manager.js" + "http://localhost:63685/assets/file_manager.js" ] }, "tiptap_basic": { "js": [ - "http://localhost:64876/assets/tiptap_basic.js" + "http://localhost:63685/assets/tiptap_basic.js" ] }, "studio.posts.editor": { "js": [ - "http://localhost:64876/assets/studio.posts.editor.js" + "http://localhost:63685/assets/studio.posts.editor.js" ] }, "studio.collections": { "js": [ - "http://localhost:64876/assets/studio.collections.js" + "http://localhost:63685/assets/studio.collections.js" ] } } diff --git a/public/assets/manifest.json b/public/assets/manifest.json index 93085e1..d7ea7fa 100644 --- a/public/assets/manifest.json +++ b/public/assets/manifest.json @@ -1,10 +1,10 @@ { - "assets/app.css": "http://localhost:64876/assets/app.css", - "assets/app.js": "http://localhost:64876/assets/app.js", - "assets/post.js": "http://localhost:64876/assets/post.js", - "assets/stream.js": "http://localhost:64876/assets/stream.js", - "assets/file_manager.js": "http://localhost:64876/assets/file_manager.js", - "assets/tiptap_basic.js": "http://localhost:64876/assets/tiptap_basic.js", - "assets/studio.posts.editor.js": "http://localhost:64876/assets/studio.posts.editor.js", - "assets/studio.collections.js": "http://localhost:64876/assets/studio.collections.js" + "assets/app.css": "http://localhost:63685/assets/app.css", + "assets/app.js": "http://localhost:63685/assets/app.js", + "assets/post.js": "http://localhost:63685/assets/post.js", + "assets/stream.js": "http://localhost:63685/assets/stream.js", + "assets/file_manager.js": "http://localhost:63685/assets/file_manager.js", + "assets/tiptap_basic.js": "http://localhost:63685/assets/tiptap_basic.js", + "assets/studio.posts.editor.js": "http://localhost:63685/assets/studio.posts.editor.js", + "assets/studio.collections.js": "http://localhost:63685/assets/studio.collections.js" } \ No newline at end of file diff --git a/resources/views/studio/users/show.edge b/resources/views/studio/users/show.edge index ba3097e..70757b9 100644 --- a/resources/views/studio/users/show.edge +++ b/resources/views/studio/users/show.edge @@ -43,4 +43,69 @@ @end @end +
+ + + + + + + + + + + @each (invoice in invoices) + + + + + + + @endeach + +
DateNumberAmountStatus
{{ invoice.periodStartAt.toFormat('MMM dd, yyyy') }}{{ invoice.invoiceNumber }}{{ invoice.amountDue }}{{ invoice.status }}
+ +
+ {{ csrfField() }} + + + + + + + + + + + +
+
+ @endsection \ No newline at end of file diff --git a/start/routes.ts b/start/routes.ts index 0268709..495f6c0 100644 --- a/start/routes.ts +++ b/start/routes.ts @@ -88,6 +88,7 @@ Route.group(() => { Route.get('/', 'UsersController.index').as('index') Route.get('/:id', 'UsersController.show').as('show').where('id', Route.matchers.number()) + Route.post('/:id/invoice', 'InvoicesController.store').as('invoices.store').where('id', Route.matchers.number()) Route.patch('/:id/role', 'UsersController.role').as('role').where('id', Route.matchers.number()) Route.group(() => {