Many to Many Joins in TypeORM - nestjs

We are currently working on implementing NestJS against our DB. We decided to use TypeORM to give us a strong ORM to use for most of our basic to intermediate queries. However, I am having issues setting up one particular relationship in our entities.
Our table structure comes from an existing system that cannot be modified. It is as follows:
Employee Table
+-----------+-----------+---------------+
| HRCO (PK) | HRRef(PK) | udDisplayName |
+-----------+-----------+---------------+
| 2 | 323 | John |
| 2 | 500 | Larry |
| 1 | 29 | Jim |
+-----------+-----------+---------------+
**Skill Table**
+----------+----------+----------------+
| HRCo(PK) | Code(PK) | Description |
+----------+----------+----------------+
| 2 | CODE1 | Test Class |
| 2 | CODE2 | Crane Training |
| 1 | CODE1 | Truck Training |
+----------+----------+----------------+
**Join Table - EmployeeSkills**
+-----------+---------------+-----------+------------+
| HRCO (FK) | Employee (FK) | Code (FK) | Expires |
+-----------+---------------+-----------+------------+
| 2 | 323 | CODE1 | 2019-01-01 |
| 2 | 323 | CODE2 | 2020-01-01 |
| 1 | 29 | CODE1 | 2020-01-01 |
+-----------+---------------+-----------+------------+
I recognize this is a many-to-many relationship that has a composite foreign key. Most of the NestJS docs lead you down using the ManyToMany and OneToMany x 2 pattern of establishing the relationship. However, those seem to only work if the joiner table has one key for each table rather than a composite.
Right now my classes look like the following:
Skills
import { Entity, Column, PrimaryColumn, ManyToOne, OneToMany } from "typeorm";
import { EmployeeToSkillEntity } from "../employee-to-skill/employee-skill.entity";
#Entity({name:"skills"})
export class SkillEntity {
#PrimaryColumn({ name: "HRCo" })
company: number;
#PrimaryColumn({ name: "Code" })
code: number;
#Column({name:"Description"})
description: Date;
#OneToMany(type => EmployeeToSkillEntity, employeeToSkill => employeeToSkill.code)
employeeToSkills: EmployeeToSkillEntity[]
}
Employee
import { Entity, Column, PrimaryColumn, OneToMany } from "typeorm";
import { EmployeeToSkillEntity } from "../employee-to-skill/employee-skill.entity";
/**
* #ignore
*/
#Entity({name:"employee"})
export class EmployeeEntity {
#PrimaryColumn({ name: "HRCo" })
company: number;
#PrimaryColumn({ name: "HRRef" })
employeeNumber: number;
#Column({name: "udDisplayName"})
displayName: string;
#OneToMany(type => EmployeeToSkillEntity, employeeToSkill => employeeToSkill.employee)
employeeToSkills: EmployeeToSkillEntity[]
}
import { Entity, Column, PrimaryColumn, ManyToOne, PrimaryGeneratedColumn } from "typeorm";
import { EmployeeEntity } from "../employee/employee.entity";
import { SkillEntity } from "../skill/skill.entity";
/**
* #ignore
*/
#Entity({ name: "employeeskills" })
export class EmployeeToSkillEntity {
#PrimaryColumn({ name: "HRCo" })
companyNumber: number;
#PrimaryColumn({ name: "HRRef" })
employeeNumber: number;
#PrimaryColumn({ name: "Code" })
code: string;
#Column({ name: "CertDate" })
certDate: Date;
#Column({ name: "ExpireDate" })
expireDate: Date;
#Column({ name: "SkillTester" })
skillTester: string;
#Column({ name: "HistSeq" })
histSeq: string;
#Column({ name: "Notes" })
notes: string;
#Column({ name: "UniqueAttchID" })
attachmentID: number;
#Column({ name: "Type" })
type: string;
#Column({ name: "KeyID" })
keyID: number;
#Column({ name: "udLastModDate" })
lastModifiedDate: Date;
#Column({ name: "udLicense" })
license: number;
#ManyToOne(type => EmployeeEntity, (employee) => employee.employeeToSkills)
#JoinColumn([{ name: "HRCo", referencedColumnName: "companyNumber" }, { name: "HRRef", referencedColumnName: "employeeNumber" }])
employee: EmployeeEntity;
#ManyToOne(type => SkillEntity, (skill) => skill.employeeToSkills)
#JoinColumn([{ name: "HRCo", referencedColumnName: "companyNumber" }, { name: "Code", referencedColumnName: "code" }])
skill: SkillEntity;
}
However, I am getting an error on query generation because I am getting columns for the relationship + the property name like "skillCode".
Any help would be appreciated.
Thanks

In order to properly 'configure' TypeORM used by Nest.js, consider #JoinTable which supports composite keys as well. Please give it a try (hopefully, judging from your questions, it is the issue with the query itself and properly getting through join table - and the #JoinTable may already help you with that) and it still getting issues, please update the code with exact query and error you are getting. Any logs from TypeORM's debug mode would be nice as well.
Cheers!

Adding JoinColumn() to the ManyToOne relationships was the key.
You need to make the column of the current class, to the property of the associated entity. You must do this for each FK you have in the relationship.

Related

TypeError: string indices must be integers in the chess.com api

SO i was watching this tutorial (https://www.youtube.com/watch?v=KYNbHGs-qG4)minute:14.50 to 15.14 by "tech with Tim" about the api and he put the username and ranks of the players but at me just give me the error "TypeError: string indices must be integers"
but for him works
from chessdotcom import get_leaderboards
import pprint
printer = pprint.PrettyPrinter()
def print_leaderboards():
data = get_leaderboards().json
categories = data.keys()
for category in categories:
print('Category:', category)
# idx = index
for idx, entry in enumerate(data[category]):
print(f'Rank: {idx + 1} | Username: {entry["username"]} | Rating: {entry["score"]}')
print_leaderboards()
Because there are no such keys inside "data[category]".
You need to add a subcategory from keys:
['daily', 'daily960', 'live_rapid', 'live_blitz', 'live_bullet', 'live_bughouse', 'live_blitz960', 'live_threecheck', 'live_crazyhouse', 'live_kingofthehill', 'tactics', 'rush', ' battle']
And line with FOR loop will be like:
for idx, entry in enumerate(data[category]['daily']):
And you will see:
Rank: 1 | Username: igorkovalenko | Rating: 2715
Rank: 2 | Username: RWHaines | Rating: 2604
Rank: 3 | Username: Zgorl | Rating: 2601
Rank: 4 | Username: francisbegbie | Rating: 2513
Rank: 5 | Username: Ryzeaf | Rating: 2509
Rank: 6 | Username: if_name_main_a | Rating: 2508
Rank: 7 | Username: JolinTsai | Rating: 2506

How do I format [future, non-current] dates in Eleventy + Nunjucks?

I'm building a site with a CMS (Netlify) for a local band, and they have future gig dates they will put on the site. So far the dates show up as very long non-formatted strings that include the time and time zone. I'm trying to figure out how to format the dates to be simpler (day, date, time for example).
I've tried plugins like nunjucks-date but I'm a little confused about how to use a plugin (and filters) in this case.
My repo: https://github.com/mollycarroll/serapis-eleventy-2
Example gig entry:
---
layout: gig
venue: Cedar Lake Cellars
date: 2022-05-28
time: 6pm
city: Wright City, MO
---
Gig template:
<h2>{{ venue }}</h2>
<h4>{{ city }} {{ date }} {{ time }}</h4>
config.yml for the CMS:
- name: 'gigs'
label: 'Shows'
folder: 'src/gigs'
create: true
slug: '{{month}}-{{day}}-{{venue}}'
fields:
- { label: 'Layout', name: 'layout', widget: 'hidden', default: '_includes/gig.njk' }
- { label: 'Date', name: 'date', widget: 'date', default: '' }
- { label: 'Time', name: 'time', widget: 'string', default: '' }
- { label: 'Venue', name: 'venue', widget: 'string', default: '' }
- { label: 'City', name: 'city', widget: 'string', default: '' }
Thanks for any help.
First, you should create a filter, let's say src/filters/date.js with the following content:
const { DateTime } = require("luxon");
// Add a friendly date filter to nunjucks.
// Defaults to format of LLLL d, y unless an
// alternate is passed as a parameter.
// {{ date | friendlyDate('OPTIONAL FORMAT STRING') }}
// List of supported tokens: https://moment.github.io/luxon/docs/manual/formatting.html#table-of-tokens
module.exports = (dateObj, format = 'LLLL d, y') => {
return DateTime.fromISO(dateObj, { zone: "Europe/Amsterdam", locale: "en" }).toFormat(format);
};
Make sure you check Luxon documentation for details. Then add the filter in .eleventy.js:
module.exports = function(eleventyConfig) {
...
eleventyConfig.addFilter("date", require("./src/filters/date.js"));
...
};
Now you can use it in Nunjacks with a default value {{ date }}, in this example 'LLLL d, y', or any value you need at a certain position on your website {{ date | date('dd. LLLL yyyy.') }}. This can be very useful if you need at some point just month and year or just day and month.
You can even create multiple language filters, like dateEn.js and dateDe.js, and format each to its own language if you have a multilingual site.
Hope this helps.
EDIT: In order for this filter to work the dateObj should be in ISO 8601 format.

textarea on vue not accepting null

I use:
- "vue": "3.2.26",
- "vee-validate": "4.5.6",
- "typescript": "4.5.4"
While creating a textarea field on vue3 I ran into a problem
i have
example with vee-validate
import { Field, useForm } from 'vee-validate'
<Field v-slot="{ field, errors }" name="name" type="text">
<VControl icon="feather:edit-2" :has-error="Boolean(formErrors.name)">
<input
v-bind="field"
class="input is-primary-focus"
type="text"
placeholder="Placeholder"
autocomplete="name"
/>
<p v-if="errors" class="help is-danger">{{ formErrors.name}}</p>
</VControl>
</Field>
simple example
<textarea
v-model="fieldValues.description"
class="textarea is-success-focus"
rows="3"
placeholder="Description"
></textarea>
for model
export interface iCat {
id: number
name: string
description: string | null
}
but textarea return error
Type 'null' is not assignable to type 'string | number | string[] | undefined'.
for vee-validate
const {
values: fieldValues,
errors: formErrors,
handleSubmit,
} = useForm({
initialValues: {
id: 0,
name: '',
description: ''
},
validationSchema: object({
id: number().required().integer(),
name: string().required(),
description: string().notRequired().default(null).nullable()
}),
})
if check #vue/runtime-dom/dist/runtime-dom.d.ts
export interface TextareaHTMLAttributes extends HTMLAttributes {
....
value?: string | string[] | number
...
}
If I look in node-moduls, I see that the textarea does not accept null as a value - how can I properly solve this problem then?
Unfortunately, you can't change the existing type of value for TextareaHTMLAttributes (at least not in TypeScript 4.5.5). Type augmentation only allows extension (adding properties to the type, or creating a new type that extends the original TextareaHTMLAttributes interface with a new type for value).
A workaround is to use a new type that extends iCat, changing its description type to the expected type of TextareaHTMLAttributes's value:
Declare a new type (named "iFieldValues"), using Omit to exclude the original description property from iCat, and an intersection with a new description property that has a type of TextareaHTMLAttributes['value'].
Use type assertion (as iFieldValues) on the values returned from useForm().
// MyForm.vue
<script setup lang="ts">
import { toRefs } from 'vue'
import type { TextareaHTMLAttributes } from '#vue/runtime-dom'
import { useForm } from 'vee-validate'
import { object, number, string } from 'yup'
export interface iCat {
id: number
name: string
description: string | null
}
1️⃣
type iFieldValues = Omit<iCat, 'description'> & {
description: TextareaHTMLAttributes['value']
}
const {
values,
errors: formErrors,
handleSubmit,
} = useForm({
initialValues: {
id: 0,
name: '',
description: ''
},
validationSchema: object({
id: number().required().integer(),
name: string().required(),
description: string().notRequired().default(null).nullable()
}),
})
2️⃣
const fieldValues = values as iFieldValues
</script>

AWS CDK error creating an ARecord for CloudFront distro

Given:
export class MyARecord extends cdk.Stack {
public readonly arecord: ARecord
constructor(scope: cdk.Construct, id: string, props: IcARecordARecordProps) {
super(scope, id, props);
const hostedZone = HostedZone.fromHostedZoneAttributes(this, 'hostedZone', {
hostedZoneId: props.hostedZoneId,
zoneName: props.apexDomainName
})
console.log(hostedZone.hostedZoneId);
console.log(hostedZone.zoneName);
const distribution = Distribution.fromDistributionAttributes(this, 'distribution', {
domainName: props.apexDomainName,
distributionId: props.distributionId
});
this.arecord = new ARecord(this, `${props.projectName}ARecord`, {
zone: hostedZone,
target: RecordTarget.fromAlias(
new CloudFrontTarget(distribution)
)
})
}
}
Deploying I get the following logging and error:
hostedZoneId: 'Z1016966FIW9RVHP46ST',
zoneName: 'testmyroute53domain.com',
08:21:59 | CREATE_FAILED | AWS::Route53::RecordSet | testmyroute53domaincomARecordB9B947F2
[Tried to create an alias that targets testmyroute53domain.com., type A in zone Z2FDTNDATAQYW2, but the alias target name does not lie within the target zone]
Anybody have any idea why it asks about a zone 'Z2FDTNDATAQYW2' when the zone id is clearly 'Z1016966FIW9RVHP46ST'?
Manually I made the A record and it works.... Just AWS CDK does not want to create it for me...

Add Icons to project Material Table error

I`m try to do this https://github.com/mbrn/material-table but I get error. I think there is a problem because I use TSX not JSX? do you know how to fix?
No overload matches this call.
Overload 1 of 2, '(props: { component: ElementType<any>; } & { children?: ReactNode; color?: "primary" | "secondary" | "disabled" | "error" | "inherit" | "action" | undefined; fontSize?: "inherit" | ... 3 more ... | undefined; htmlColor?: string | undefined; shapeRendering?: string | undefined; titleAccess?: string | undefined; viewBox?: string | undefined; } & CommonProps<...> & Pick<...>): Element', gave the following error.
Property 'component' is missing in type '{ ref: ((instance: unknown) => void) | MutableRefObject<unknown> | null; children?: ReactNode; }' but required in type '{ component: ElementType<any>; }'.
Overload 2 of 2, '(props: DefaultComponentProps<SvgIconTypeMap<{}, "svg">>): Element', gave the following error.
Type '((instance: unknown) => void) | MutableRefObject<unknown> | null' is not assignable to type '((instance: SVGSVGElement | null) => void) | RefObject<SVGSVGElement> | null | undefined'.
Type 'MutableRefObject<unknown>' is not assignable to type '((instance: SVGSVGElement | null) => void) | RefObject<SVGSVGElement> | null | undefined'.
Type 'MutableRefObject<unknown>' is not assignable to type 'RefObject<SVGSVGElement>'.ts(2769)
OverridableComponent.d.ts(17, 7): 'component' is declared here.
and here
No overload matches this call.
Overload 1 of 2, '(props: Readonly<MaterialTableProps<{ name: string; surname: string; birthYear: number; birthCity: number; }>>): MaterialTable<{ name: string; surname: string; birthYear: number; birthCity: number; }>', gave the following error.
Type '{ Add: React.ForwardRefExoticComponent<React.RefAttributes<unknown>>; Check: React.ForwardRefExoticComponent<React.RefAttributes<unknown>>; Clear: React.ForwardRefExoticComponent<React.RefAttributes<unknown>>; ... 13 more ...; ViewColumn: React.ForwardRefExoticComponent<...>; }' is not assignable to type 'Icons'.
The types of 'Add.defaultProps' are incompatible between these types.
Type 'Partial<RefAttributes<unknown>> | undefined' is not assignable to type 'Partial<RefAttributes<SVGSVGElement>> | undefined'.
Type 'Partial<RefAttributes<unknown>>' is not assignable to type 'Partial<RefAttributes<SVGSVGElement>>'.
Types of property 'ref' are incompatible.
Type '((instance: unknown) => void) | RefObject<unknown> | null | undefined' is not assignable to type '((instance: SVGSVGElement | null) => void) | RefObject<SVGSVGElement> | null | undefined'.
try this
forwardRef<SVGSVGElement>
in the icons
or
import Icons like this:
import MaterialTable, { Icons } from 'material-table'
const tableIcons: Icons = {...}

Resources