Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.DS_Store
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
2 changes: 1 addition & 1 deletion fullstack/task/packages/client/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"start": {
"executor": "nx:run-commands",
"options": {
"command": "yarn vite",
"command": "yarn vite --host 0.0.0.0",
"cwd": "packages/client"
}
},
Expand Down
46 changes: 45 additions & 1 deletion fullstack/task/packages/client/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,49 @@
import React from 'react';
import { useQuery } from '@apollo/client';
import { GET_EXCHANGE_RATES } from './graphql/queries';
import { ExchangeRate } from './types/ExchangeRate';

interface ExchangeRateResponse {
exchangeRatesList: ExchangeRate[];
}

function App() {
return <p>TODO</p>;
const { data, loading, error } = useQuery<ExchangeRateResponse>(GET_EXCHANGE_RATES);

if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;

const exchangeRates = data?.exchangeRatesList || [];

return (
<div>
<h1>Exchange Rates</h1>
<table border="1">
<thead>
<tr>
<th>Country</th>
<th>Currency</th>
<th>Amount</th>
<th>Code</th>
<th>Rate</th>
<th>Last updated at</th>
</tr>
</thead>
<tbody>
{exchangeRates.map((rate) => (
<tr key={rate.currencyCode}>
<td>{rate.country}</td>
<td>{rate.currency}</td>
<td>{rate.amount}</td>
<td>{rate.currencyCode}</td>
<td>{rate.rate}</td>
<td>{rate.updatedAtUtc}</td>
</tr>
))}
</tbody>
</table>
</div>
);
}

export default App;
14 changes: 14 additions & 0 deletions fullstack/task/packages/client/src/graphql/queries.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { gql } from '@apollo/client';

export const GET_EXCHANGE_RATES = gql`
query GetExchangeRates {
exchangeRatesList {
country
currency
currencyCode
amount
rate
updatedAtUtc
}
}
`;
6 changes: 4 additions & 2 deletions fullstack/task/packages/client/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import { ApolloClient, ApolloProvider, InMemoryCache } from '@apollo/client';
import App from './App';

const client = new ApolloClient({
uri: 'http://localhost:4000/graphql',
cache: new InMemoryCache(),
uri: 'http://localhost:4001/graphql',
cache: new InMemoryCache({
addTypename: false
}),
});

ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
Expand Down
8 changes: 8 additions & 0 deletions fullstack/task/packages/client/src/types/ExchangeRate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export interface ExchangeRate {
country: string;
currency: string;
amount: number;
currencyCode: string;
rate: number;
updatedAtUtc?: string;
}
8 changes: 4 additions & 4 deletions fullstack/task/packages/server/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,16 +42,16 @@
},
"configurations": {
"run": {
"command": "yarn typeorm-ts-node-commonjs migration:run -d ./src/config/typeorm/data-source.ts"
"command": "yarn typeorm-ts-node-commonjs migration:run --dataSource ./src/config/typeorm/data-source.ts"
},
"revert": {
"command": "yarn typeorm-ts-node-commonjs migration:revert -d ./src/config/typeorm/data-source.ts"
"command": "yarn typeorm-ts-node-commonjs migration:revert --dataSource ./src/config/typeorm/data-source.ts"
},
"generate": {
"command": "yarn typeorm-ts-node-commonjs migration:generate -d ./src/config/typeorm/data-source.ts ./src/migrations/{args.name}"
"command": "yarn typeorm-ts-node-commonjs migration:generate --dataSource ./src/config/typeorm/data-source.ts ./src/migrations/{args.name}"
},
"create": {
"command": "yarn typeorm-ts-node-commonjs migration:create -d ./src/config/typeorm/data-source.ts ./src/migrations/{args.name}"
"command": "yarn typeorm-ts-node-commonjs migration:create ./src/migrations/{args.name}"
}
}
}
Expand Down
15 changes: 14 additions & 1 deletion fullstack/task/packages/server/schema.gql
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,21 @@ A date-time string at UTC, such as 2019-12-03T09:54:33Z, compliant with the date
"""
scalar DateTime

type ExchangeRate {
id: ID!
createdAtUtc: DateTime!
updatedAtUtc: DateTime
deleteDateUtc: DateTime
version: Int!
country: String!
currency: String!
amount: Float!
currencyCode: String!
rate: Float!
}

type Query {
exchangeRates: String!
exchangeRatesList: [ExchangeRate!]!
exampleByName(name: String!): Example
}

Expand Down
2 changes: 2 additions & 0 deletions fullstack/task/packages/server/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { TypeOrmModule } from '@nestjs/typeorm';
import { graphqlConfig, typeormConfig } from './config';
import { modules } from './entity-modules';
import { ExchangeRateModule } from './services/exchange-rate/exchange-rate.module';
import { CurrencyFetchModule } from './services/currency-fetch/currency-fetch.module';

@Module({
imports: [
Expand All @@ -14,6 +15,7 @@ import { ExchangeRateModule } from './services/exchange-rate/exchange-rate.modul
TypeOrmModule.forRoot(typeormConfig),
GraphQLModule.forRoot(graphqlConfig),
ExchangeRateModule,
CurrencyFetchModule,
...modules,
],
controllers: [],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Field, ObjectType } from '@nestjs/graphql';
import { IsString } from 'class-validator';
import { Column, Entity, PrimaryColumn } from 'typeorm';

@ObjectType()
@Entity('cache_metadata')
export class CacheMetadata {
@IsString()
@Field(() => String)
@PrimaryColumn({ type: 'varchar', length: 50 })
public cacheKey!: string;

@Field(() => Date)
@Column({ type: 'timestamptz' })
public lastFetchedAt!: Date;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Field, Float, ObjectType } from '@nestjs/graphql';
import { IsDecimal, IsString, Length } from 'class-validator';
import { Column, Entity, Index } from 'typeorm';
import { EntityWithMeta } from '../common';
import { VAR_CHAR } from './constants';

@ObjectType()
@Entity('exchange_rates')
@Index('IDX_exchange_rates_currency_code', ['currencyCode'], { unique: true })
export class ExchangeRate extends EntityWithMeta {
@IsString()
@Field(() => String)
@Column({ ...VAR_CHAR })
public country!: string;

@IsString()
@Field(() => String)
@Column({ ...VAR_CHAR })
public currency!: string;

@IsDecimal()
@Field(() => Float)
@Column({ type: 'decimal', precision: 10, scale: 0 })
public amount!: number;

@IsString()
@Length(3, 3)
@Field(() => String)
@Column({ type: 'varchar', length: 3 })
public currencyCode!: string;

@IsDecimal()
@Field(() => Float)
@Column({ type: 'decimal', precision: 10, scale: 6 })
public rate!: number;
}
2 changes: 2 additions & 0 deletions fullstack/task/packages/server/src/entities/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
export * from './example.entity';
export * from './exchange-rate.entity';
export * from './cache-metadata.entity';
1 change: 1 addition & 0 deletions fullstack/task/packages/server/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create<NestApplication>(AppModule);

app.enableCors();
app.useGlobalPipes(new ValidationPipe({ whitelist: false, transform: false }));

useContainer(app.select(AppModule), { fallbackOnErrors: true });
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { MigrationInterface, QueryRunner } from "typeorm"

export class createTableExchangeRates1758100643630 implements MigrationInterface {

public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
CREATE TABLE "exchange_rates"
(
"id" uuid NOT NULL DEFAULT uuid_generate_v4(),
"createdAtUtc" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
"updatedAtUtc" TIMESTAMP WITH TIME ZONE DEFAULT now(),
"deleteDateUtc" TIMESTAMP WITH TIME ZONE,
"version" integer NOT NULL,
"country" character varying(255) NOT NULL,
"currency" character varying(255) NOT NULL,
"amount" numeric(10, 2) NOT NULL,
"currencyCode" character varying(255) NOT NULL,
"rate" numeric(10, 2) NOT NULL,
CONSTRAINT "PK_876duygd68as6d8a6d8as768" PRIMARY KEY ("id")
)`);

await queryRunner.query(`
CREATE UNIQUE INDEX "IDX_exchange_rates_currency_code" ON "exchange_rates" ("currencyCode")
`);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP INDEX "IDX_exchange_rates_currency_code"`);
await queryRunner.query(`DROP TABLE "exchange_rates"`);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { MigrationInterface, QueryRunner } from 'typeorm';

export class CreateCacheMetadataTable1758102061344 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
CREATE TABLE cache_metadata (
"cacheKey" VARCHAR(50) PRIMARY KEY,
"lastFetchedAt" TIMESTAMPTZ NOT NULL
)
`);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP TABLE IF EXISTS cache_metadata`);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { CurrencyFetchService } from './currency-fetch.service';
import { ExchangeRate, CacheMetadata } from '../../entities';

@Module({
imports: [TypeOrmModule.forFeature([ExchangeRate, CacheMetadata])],
providers: [CurrencyFetchService],
exports: [CurrencyFetchService],
})
export class CurrencyFetchModule {}
Loading