Skip to content
This repository has been archived by the owner on Sep 7, 2023. It is now read-only.

Commit

Permalink
✨ Bootstrap location models, validators, manager and routes
Browse files Browse the repository at this point in the history
  • Loading branch information
matthiasrohmer committed Jul 9, 2021
1 parent b264571 commit f15125d
Show file tree
Hide file tree
Showing 13 changed files with 603 additions and 91 deletions.
80 changes: 80 additions & 0 deletions app/Controllers/Http/v1/LocationController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
import { ApiDocument } from 'App/Helpers/Api/Document';
import Location, { LocationStatus } from 'App/Models/Location';
import LocationManager from 'App/Helpers/Managers/LocationManager';

// TODO(matthiasrohmer): Add permissions
export default class LocationController {
public async index(ctx: HttpContextContract) {
const manager: LocationManager = new LocationManager(ctx);
await manager.all();

return new ApiDocument(ctx, manager.toResources(), {
paginator: manager.paginator,
});
}

public async store(ctx: HttpContextContract) {
const manager: LocationManager = new LocationManager(ctx);
await manager.create();

return new ApiDocument(ctx, manager.toResources());
}

public async show(ctx: HttpContextContract) {
const manager: LocationManager = new LocationManager(ctx);

manager.include = 'address,types,subjects';
await manager.byId();

const location: Location = manager.instance;
const publishable = await location.publishable();

return new ApiDocument(ctx, manager.toResources(), { publishable });
}

public async update(ctx: HttpContextContract) {
const manager: LocationManager = new LocationManager(ctx);
await manager.update();

manager.include = 'address,types,subjects';
const location: Location = await manager.byId();
const publishable = await location.publishable();

if (publishable !== true) {
location.status = LocationStatus.DRAFT;
if (location.$isDirty) {
await location.save();
}
}

return new ApiDocument(ctx, manager.toResources(), {
publishable,
});
}

public async translate(ctx: HttpContextContract) {
const manager: LocationManager = new LocationManager(ctx);
await manager.byId();
await manager.translate();

return new ApiDocument(ctx, manager.toResources());
}

// public async destroy(ctx: HttpContextContract) {
// const { params, auth } = ctx;
// if (!auth.user) {
// throw new UnauthorizedException();
// }

// const location = await Location.query()
// .preload('address')
// .where('cid', params.id)
// .firstOrFail();
// const address = location.address;

// await Promise.all([location.delete(), address.delete()]);

// return new ApiDocument(ctx, {}, 'Location deleted successfully');
// }
}
70 changes: 45 additions & 25 deletions app/Helpers/Managers/BaseManager.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
import { LucidModel } from '@ioc:Adonis/Lucid/Model';
import Resource from 'App/Helpers/Api/Resource';
import { LucidRow, ModelPaginatorContract } from '@ioc:Adonis/Lucid/Model';
import {
LucidModel,
LucidRow,
ModelPaginatorContract,
} from '@ioc:Adonis/Lucid/Model';
import { RawBuilderContract } from '@ioc:Adonis/Lucid/DatabaseQueryBuilder';
import { ModelQueryBuilderContract } from '@ioc:Adonis/Lucid/Orm';

interface OrderableInstruction {
name: string;
Expand Down Expand Up @@ -31,14 +35,10 @@ interface ManagerSettings {
filters?: Filter[];
}

type Model<T extends LucidModel> = T;
export class BaseManager<ManagedModel extends LucidModel> {
public ManagedModel: ManagedModel;

export class BaseManager {
public ModelClass: Model<LucidModel>;

public RessourceClass;

public instances: Array<LucidRow> = [];
public instances: Array<InstanceType<ManagedModel>> = [];

public paginator: ModelPaginatorContract<LucidRow>;

Expand All @@ -60,13 +60,8 @@ export class BaseManager {
queryId: 'id',
};

constructor(
ctx: HttpContextContract,
ModelClass: LucidModel,
ResourceClass = Resource
) {
this.ModelClass = ModelClass;
this.RessourceClass = ResourceClass;
constructor(ctx: HttpContextContract, ManagedModel: ManagedModel) {
this.ManagedModel = ManagedModel;

this.ctx = ctx;
this.language = ctx.language as string;
Expand All @@ -75,9 +70,9 @@ export class BaseManager {

public query(
options: { sort?: string; includes?: string; filter?: string } = {}
) {
let query = this.ModelClass.query();
if (this.ModelClass.$hasRelation('translations')) {
): ModelQueryBuilderContract<ManagedModel> {
let query = this.ManagedModel.query() as any;
if (this.ManagedModel.$hasRelation('translations')) {
query = query.preload('translations');
}

Expand Down Expand Up @@ -196,7 +191,7 @@ export class BaseManager {
this.paginator = result;
this.paginator.baseUrl(this.ctx.request.completeUrl());

this.instances = result.rows;
this.instances = result.all() as InstanceType<ManagedModel>[];

return result;
}
Expand Down Expand Up @@ -230,16 +225,16 @@ export class BaseManager {
return this.instances[0];
}

public set instance(instance) {
public set instance(instance: InstanceType<ManagedModel>) {
this.instances = [instance];
}

public async create() {
return [new this.ModelClass()];
return new this.ManagedModel();
}

public async update() {
return this.instances;
return this.instance;
}

public async $validateTranslation() {
Expand All @@ -255,12 +250,13 @@ export class BaseManager {
}

public async $saveTranslation(attributes) {
const translation = this.instance.translations.find((translation) => {
const instance = this.instance as any;
const translation = instance.translations.find((translation) => {
return translation.language === attributes.language;
});

if (!translation) {
await this.instance.related('translations').create(attributes);
await instance.related('translations').create(attributes);
} else {
translation.merge(attributes);
await translation.save();
Expand All @@ -274,6 +270,30 @@ export class BaseManager {
return this.byId();
}

public async $updateLinks(instance, links) {
if (links) {
await instance.load('links');

let index = 0;
while (instance.links[index] || links[index]) {
const link = instance.links[index];
const url = links[index];

if (link && url) {
const link = instance.links[index];
link.url = links[index];
await link.save();
} else if (!link && url) {
await instance.related('links').create({ url });
} else if (link && !url) {
await link.delete();
}

index++;
}
}
}

private $toResource(instance): Resource {
const resource = new Resource(instance);
resource.boot();
Expand Down
132 changes: 132 additions & 0 deletions app/Helpers/Managers/LocationManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
import BaseManager from 'App/Helpers/Managers/BaseManager';
import Location from 'App/Models/Location';
import {
CreateLocationValidator,
UpdateLocationValidator,
} from 'App/Validators/v1/LocationValidator';
import { LocationTranslationValidator } from 'App/Validators/v1/LocationTranslationValidator';
import Database from '@ioc:Adonis/Lucid/Database';
import Address from 'App/Models/Address';

export default class LocationManager extends BaseManager<typeof Location> {
public ManagedModel = Location;

public settings = {
queryId: 'public_id',
orderableBy: [
{
name: 'name',
query: Database.raw(
`(SELECT name FROM organizer_translations WHERE organizer_translations.organizer_id = organizers.id AND organizer_translations.language = '${this.language}')`
),
},
{
name: 'createdAt',
attribute: 'created_at',
},
{
name: 'updatedAt',
attribute: 'updated_at',
},
],
};

public validators = {
translate: LocationTranslationValidator,
};

constructor(ctx: HttpContextContract) {
super(ctx, Location);
}

public query() {
return super.query().preload('address');
}

private async $createAddress(location, attributes, trx) {
const address = new Address();
await location.related();
address.fill(attributes);

address.useTransaction(trx);
await address.save();
await location.related('address').associate(address);

return address;
}

public async create() {
const { attributes, relations } = await this.ctx.request.validate(
new CreateLocationValidator(this.ctx)
);

const location = new Location();
await Database.transaction(async (trx) => {
location.useTransaction(trx);
await location.save();

await location.related('translations').create({
name: attributes.name,
description: attributes.description,
language: this.language,
});

if (relations?.address) {
await this.$createAddress(location, relations.address.attributes, trx);
}

await this.$updateLinks(location, relations?.links);
});

return await await this.byId(location.publicId);
}

public async update() {
const { attributes, relations } = await this.ctx.request.validate(
new UpdateLocationValidator(this.ctx)
);

const location = await this.byId();
await Database.transaction(async (trx) => {
location.useTransaction(trx);
if (location.$isDirty) {
await location.save();
}

if (relations?.address) {
if (location.address) {
location.address.merge(relations.address.attributes);
await location.address.save();
} else {
await this.$createAddress(
location,
relations.address.attributes,
trx
);
}
}

await this.$updateLinks(location, relations?.links);
});

return await this.byId(location.publicId);
}

public async translate() {
const attributes = await this.$validateTranslation();

// Creating an organizer translation without a name is forbidden,
// but initially creating one without a name is impossible. Hence fallback
// to the initial name
if (!attributes.name) {
attributes.name = this.instance?.translations?.find((translation) => {
return translation.name;
})?.name;
}

await this.$saveTranslation(attributes);

return this.byId();
}
}
Loading

0 comments on commit f15125d

Please sign in to comment.