Compare commits
7 Commits
d08dcb9720
...
45a1cebcf9
Author | SHA1 | Date | |
---|---|---|---|
45a1cebcf9 | |||
376c297eda | |||
4486e99225 | |||
9b3fe93f4f | |||
15e0263559 | |||
2ad2063339 | |||
ab180a12ce |
15
.vscode/settings.json
vendored
Normal file
15
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"sqltools.connections": [
|
||||
{
|
||||
"previewLimit": 50,
|
||||
"server": "localhost",
|
||||
"port": 5432,
|
||||
"driver": "PostgreSQL",
|
||||
"name": "Traintrape-moi",
|
||||
"database": "traintrape-moi",
|
||||
"username": "traintrapemoi",
|
||||
"socketPath": "/run/postgresql/.s.PGSQL.5432"
|
||||
}
|
||||
],
|
||||
"sqltools.useNodeRuntime": true
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
import { ScrollView } from 'react-native';
|
||||
import { ScrollView } from 'react-native'
|
||||
|
||||
import { ThemedText } from '@/components/ThemedText';
|
||||
import { ThemedView } from '@/components/ThemedView';
|
||||
import { ThemedText } from '@/components/ThemedText'
|
||||
import { ThemedView } from '@/components/ThemedView'
|
||||
|
||||
export default function ChallengesScreen() {
|
||||
return (
|
||||
@ -10,5 +10,5 @@ export default function ChallengesScreen() {
|
||||
<ThemedText>Ici on aura la gestion des challenges</ThemedText>
|
||||
</ThemedView>
|
||||
</ScrollView>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { ScrollView } from 'react-native';
|
||||
|
||||
import { ThemedText } from '@/components/ThemedText';
|
||||
import { ThemedView } from '@/components/ThemedView';
|
||||
import { ScrollView } from 'react-native'
|
||||
import { ThemedText } from '@/components/ThemedText'
|
||||
import { ThemedView } from '@/components/ThemedView'
|
||||
|
||||
export default function HistoryScreen() {
|
||||
return (
|
||||
@ -10,5 +9,5 @@ export default function HistoryScreen() {
|
||||
<ThemedText>Ici on aura la gestion de l'historique des trains empruntés et des challenges effectués</ThemedText>
|
||||
</ThemedView>
|
||||
</ScrollView>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
@ -27,4 +27,4 @@ const styles = StyleSheet.create({
|
||||
flex: 1,
|
||||
alignSelf: 'stretch',
|
||||
},
|
||||
});
|
||||
})
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { ScrollView } from 'react-native';
|
||||
import { ScrollView } from 'react-native'
|
||||
|
||||
import { ThemedText } from '@/components/ThemedText';
|
||||
import { ThemedView } from '@/components/ThemedView';
|
||||
import { ThemedText } from '@/components/ThemedText'
|
||||
import { ThemedView } from '@/components/ThemedView'
|
||||
|
||||
export default function TrainScreen() {
|
||||
return (
|
||||
@ -10,5 +10,5 @@ export default function TrainScreen() {
|
||||
<ThemedText>Ici on aura la page pour ajouter un trajet en train depuis Rail Planner</ThemedText>
|
||||
</ThemedView>
|
||||
</ScrollView>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
@ -1,12 +1,11 @@
|
||||
import { Text, type TextProps, StyleSheet } from 'react-native';
|
||||
|
||||
import { useThemeColor } from '@/hooks/useThemeColor';
|
||||
import { Text, type TextProps, StyleSheet } from 'react-native'
|
||||
import { useThemeColor } from '@/hooks/useThemeColor'
|
||||
|
||||
export type ThemedTextProps = TextProps & {
|
||||
lightColor?: string;
|
||||
darkColor?: string;
|
||||
type?: 'default' | 'title' | 'defaultSemiBold' | 'subtitle' | 'link';
|
||||
};
|
||||
lightColor?: string
|
||||
darkColor?: string
|
||||
type?: 'default' | 'title' | 'defaultSemiBold' | 'subtitle' | 'link'
|
||||
}
|
||||
|
||||
export function ThemedText({
|
||||
style,
|
||||
@ -15,7 +14,7 @@ export function ThemedText({
|
||||
type = 'default',
|
||||
...rest
|
||||
}: ThemedTextProps) {
|
||||
const color = useThemeColor({ light: lightColor, dark: darkColor }, 'text');
|
||||
const color = useThemeColor({ light: lightColor, dark: darkColor }, 'text')
|
||||
|
||||
return (
|
||||
<Text
|
||||
@ -30,7 +29,7 @@ export function ThemedText({
|
||||
]}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
@ -57,4 +56,4 @@ const styles = StyleSheet.create({
|
||||
fontSize: 16,
|
||||
color: '#0a7ea4',
|
||||
},
|
||||
});
|
||||
})
|
||||
|
@ -1,14 +1,13 @@
|
||||
import { View, type ViewProps } from 'react-native';
|
||||
|
||||
import { useThemeColor } from '@/hooks/useThemeColor';
|
||||
import { View, type ViewProps } from 'react-native'
|
||||
import { useThemeColor } from '@/hooks/useThemeColor'
|
||||
|
||||
export type ThemedViewProps = ViewProps & {
|
||||
lightColor?: string;
|
||||
darkColor?: string;
|
||||
};
|
||||
lightColor?: string
|
||||
darkColor?: string
|
||||
}
|
||||
|
||||
export function ThemedView({ style, lightColor, darkColor, ...otherProps }: ThemedViewProps) {
|
||||
const backgroundColor = useThemeColor({ light: lightColor, dark: darkColor }, 'background');
|
||||
const backgroundColor = useThemeColor({ light: lightColor, dark: darkColor }, 'background')
|
||||
|
||||
return <View style={[{ backgroundColor }, style]} {...otherProps} />;
|
||||
return <View style={[{ backgroundColor }, style]} {...otherProps} />
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
import * as React from 'react';
|
||||
import renderer from 'react-test-renderer';
|
||||
import * as React from 'react'
|
||||
import renderer from 'react-test-renderer'
|
||||
|
||||
import { ThemedText } from '../ThemedText';
|
||||
import { ThemedText } from '../ThemedText'
|
||||
|
||||
it(`renders correctly`, () => {
|
||||
const tree = renderer.create(<ThemedText>Snapshot test!</ThemedText>).toJSON();
|
||||
const tree = renderer.create(<ThemedText>Snapshot test!</ThemedText>).toJSON()
|
||||
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
expect(tree).toMatchSnapshot()
|
||||
})
|
||||
|
@ -21,4 +21,4 @@ exports[`renders correctly 1`] = `
|
||||
>
|
||||
Snapshot test!
|
||||
</Text>
|
||||
`;
|
||||
`
|
||||
|
@ -3,8 +3,8 @@
|
||||
* There are many other ways to style your app. For example, [Nativewind](https://www.nativewind.dev/), [Tamagui](https://tamagui.dev/), [unistyles](https://reactnativeunistyles.vercel.app), etc.
|
||||
*/
|
||||
|
||||
const tintColorLight = '#0a7ea4';
|
||||
const tintColorDark = '#fff';
|
||||
const tintColorLight = '#0a7ea4'
|
||||
const tintColorDark = '#fff'
|
||||
|
||||
export const Colors = {
|
||||
light: {
|
||||
@ -23,4 +23,4 @@ export const Colors = {
|
||||
tabIconDefault: '#9BA1A6',
|
||||
tabIconSelected: tintColorDark,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
@ -1 +1 @@
|
||||
export { useColorScheme } from 'react-native';
|
||||
export { useColorScheme } from 'react-native'
|
||||
|
@ -1,21 +1,21 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useColorScheme as useRNColorScheme } from 'react-native';
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useColorScheme as useRNColorScheme } from 'react-native'
|
||||
|
||||
/**
|
||||
* To support static rendering, this value needs to be re-calculated on the client side for web
|
||||
*/
|
||||
export function useColorScheme() {
|
||||
const [hasHydrated, setHasHydrated] = useState(false);
|
||||
const [hasHydrated, setHasHydrated] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
setHasHydrated(true);
|
||||
}, []);
|
||||
setHasHydrated(true)
|
||||
}, [])
|
||||
|
||||
const colorScheme = useRNColorScheme();
|
||||
const colorScheme = useRNColorScheme()
|
||||
|
||||
if (hasHydrated) {
|
||||
return colorScheme;
|
||||
return colorScheme
|
||||
}
|
||||
|
||||
return 'light';
|
||||
return 'light'
|
||||
}
|
||||
|
@ -3,19 +3,19 @@
|
||||
* https://docs.expo.dev/guides/color-schemes/
|
||||
*/
|
||||
|
||||
import { Colors } from '@/constants/Colors';
|
||||
import { useColorScheme } from '@/hooks/useColorScheme';
|
||||
import { Colors } from '@/constants/Colors'
|
||||
import { useColorScheme } from '@/hooks/useColorScheme'
|
||||
|
||||
export function useThemeColor(
|
||||
props: { light?: string; dark?: string },
|
||||
colorName: keyof typeof Colors.light & keyof typeof Colors.dark
|
||||
) {
|
||||
const theme = useColorScheme() ?? 'light';
|
||||
const colorFromProps = props[theme];
|
||||
const theme = useColorScheme() ?? 'light'
|
||||
const colorFromProps = props[theme]
|
||||
|
||||
if (colorFromProps) {
|
||||
return colorFromProps;
|
||||
return colorFromProps
|
||||
} else {
|
||||
return Colors[theme][colorName];
|
||||
return Colors[theme][colorName]
|
||||
}
|
||||
}
|
||||
|
2
server/.env.example
Normal file
2
server/.env.example
Normal file
@ -0,0 +1,2 @@
|
||||
DATABASE_URL="postgres://username:password@localhost:5432/traintrape-moi"
|
||||
JWT_SECRET="CHANGE_ME"
|
@ -21,5 +21,6 @@ module.exports = {
|
||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'@typescript-eslint/semi': 'never',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
9
server/.gitignore
vendored
9
server/.gitignore
vendored
@ -3,6 +3,10 @@
|
||||
/node_modules
|
||||
/build
|
||||
|
||||
# Databases
|
||||
*.sqlite3
|
||||
*.sqlite3-journal
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
@ -37,10 +41,7 @@ lerna-debug.log*
|
||||
|
||||
# dotenv environment variable files
|
||||
.env
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
.env*.local
|
||||
|
||||
# temp directory
|
||||
.temp
|
||||
|
836
server/package-lock.json
generated
836
server/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -21,18 +21,30 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@nestjs/common": "^10.0.0",
|
||||
"@nestjs/core": "^10.0.0",
|
||||
"@nestjs/core": "^10.4.13",
|
||||
"@nestjs/jwt": "^10.2.0",
|
||||
"@nestjs/passport": "^10.0.3",
|
||||
"@nestjs/platform-express": "^10.0.0",
|
||||
"@nestjs/swagger": "^8.1.0",
|
||||
"@prisma/client": "^6.0.1",
|
||||
"bcrypt": "^5.1.1",
|
||||
"class-validator": "^0.14.1",
|
||||
"passport": "^0.7.0",
|
||||
"passport-jwt": "^4.0.1",
|
||||
"prisma": "^6.0.1",
|
||||
"reflect-metadata": "^0.2.0",
|
||||
"rxjs": "^7.8.1"
|
||||
"rxjs": "^7.8.1",
|
||||
"swagger-ui-express": "^5.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nestjs/cli": "^10.0.0",
|
||||
"@nestjs/schematics": "^10.0.0",
|
||||
"@nestjs/testing": "^10.0.0",
|
||||
"@nestjs/testing": "^10.4.13",
|
||||
"@types/bcrypt": "^5.0.2",
|
||||
"@types/express": "^5.0.0",
|
||||
"@types/jest": "^29.5.2",
|
||||
"@types/node": "^20.3.1",
|
||||
"@types/passport-jwt": "^4.0.1",
|
||||
"@types/supertest": "^6.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
||||
"@typescript-eslint/parser": "^8.0.0",
|
||||
@ -65,5 +77,8 @@
|
||||
],
|
||||
"coverageDirectory": "../coverage",
|
||||
"testEnvironment": "node"
|
||||
},
|
||||
"prisma": {
|
||||
"seed": "ts-node prisma/seed.ts"
|
||||
}
|
||||
}
|
||||
|
109
server/prisma/migrations/20241207114558_init/migration.sql
Normal file
109
server/prisma/migrations/20241207114558_init/migration.sql
Normal file
@ -0,0 +1,109 @@
|
||||
-- CreateEnum
|
||||
CREATE TYPE "MoneyUpdateType" AS ENUM ('START', 'WIN_CHALLENGE', 'BUY_TRAIN');
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "User" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"password" TEXT NOT NULL,
|
||||
"money" INTEGER NOT NULL DEFAULT 0,
|
||||
"currentRunner" BOOLEAN NOT NULL DEFAULT false,
|
||||
|
||||
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Geolocation" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"userId" INTEGER NOT NULL,
|
||||
"longitude" DOUBLE PRECISION NOT NULL,
|
||||
"latitude" DOUBLE PRECISION NOT NULL,
|
||||
"timestamp" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Geolocation_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Challenge" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"title" TEXT NOT NULL,
|
||||
"description" TEXT NOT NULL,
|
||||
"reward" INTEGER NOT NULL,
|
||||
|
||||
CONSTRAINT "Challenge_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "ChallengeAction" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"userId" INTEGER NOT NULL,
|
||||
"challengeId" INTEGER NOT NULL,
|
||||
"active" BOOLEAN NOT NULL DEFAULT false,
|
||||
"success" BOOLEAN NOT NULL DEFAULT false,
|
||||
|
||||
CONSTRAINT "ChallengeAction_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "TrainTrip" (
|
||||
"id" TEXT NOT NULL,
|
||||
"userId" INTEGER NOT NULL,
|
||||
"distance" DOUBLE PRECISION NOT NULL,
|
||||
"from" TEXT NOT NULL,
|
||||
"to" TEXT NOT NULL,
|
||||
"departureTime" TIMESTAMP(3) NOT NULL,
|
||||
"arrivalTime" TIMESTAMP(3) NOT NULL,
|
||||
"infoJson" JSONB NOT NULL,
|
||||
"geometry" TEXT NOT NULL,
|
||||
|
||||
CONSTRAINT "TrainTrip_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "MoneyUpdate" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"userId" INTEGER NOT NULL,
|
||||
"before" INTEGER NOT NULL,
|
||||
"after" INTEGER NOT NULL,
|
||||
"reason" "MoneyUpdateType" NOT NULL,
|
||||
"actionId" INTEGER,
|
||||
"tripId" TEXT,
|
||||
|
||||
CONSTRAINT "MoneyUpdate_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "User_name_key" ON "User"("name");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Challenge_title_key" ON "Challenge"("title");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "ChallengeAction_challengeId_key" ON "ChallengeAction"("challengeId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "MoneyUpdate_actionId_key" ON "MoneyUpdate"("actionId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "MoneyUpdate_tripId_key" ON "MoneyUpdate"("tripId");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Geolocation" ADD CONSTRAINT "Geolocation_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "ChallengeAction" ADD CONSTRAINT "ChallengeAction_challengeId_fkey" FOREIGN KEY ("challengeId") REFERENCES "Challenge"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "ChallengeAction" ADD CONSTRAINT "ChallengeAction_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "TrainTrip" ADD CONSTRAINT "TrainTrip_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "MoneyUpdate" ADD CONSTRAINT "MoneyUpdate_actionId_fkey" FOREIGN KEY ("actionId") REFERENCES "ChallengeAction"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "MoneyUpdate" ADD CONSTRAINT "MoneyUpdate_tripId_fkey" FOREIGN KEY ("tripId") REFERENCES "TrainTrip"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "MoneyUpdate" ADD CONSTRAINT "MoneyUpdate_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
3
server/prisma/migrations/migration_lock.toml
Normal file
3
server/prisma/migrations/migration_lock.toml
Normal file
@ -0,0 +1,3 @@
|
||||
# Please do not edit this file manually
|
||||
# It should be added in your version-control system (i.e. Git)
|
||||
provider = "postgresql"
|
81
server/prisma/schema.prisma
Normal file
81
server/prisma/schema.prisma
Normal file
@ -0,0 +1,81 @@
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
model User {
|
||||
id Int @id @default(autoincrement())
|
||||
name String @unique
|
||||
password String
|
||||
money Int @default(0)
|
||||
currentRunner Boolean @default(false)
|
||||
actions ChallengeAction[]
|
||||
geolocations Geolocation[]
|
||||
moneyUpdates MoneyUpdate[]
|
||||
trips TrainTrip[]
|
||||
}
|
||||
|
||||
model Geolocation {
|
||||
id Int @id @default(autoincrement())
|
||||
userId Int
|
||||
longitude Float
|
||||
latitude Float
|
||||
timestamp DateTime
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
}
|
||||
|
||||
model Challenge {
|
||||
id Int @id @default(autoincrement())
|
||||
title String @unique
|
||||
description String
|
||||
reward Int
|
||||
action ChallengeAction?
|
||||
}
|
||||
|
||||
model ChallengeAction {
|
||||
id Int @id @default(autoincrement())
|
||||
userId Int
|
||||
challengeId Int @unique
|
||||
active Boolean @default(false)
|
||||
success Boolean @default(false)
|
||||
challenge Challenge @relation(fields: [challengeId], references: [id])
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
moneyUpdate MoneyUpdate?
|
||||
}
|
||||
|
||||
model TrainTrip {
|
||||
id String @id
|
||||
userId Int
|
||||
distance Float
|
||||
from String
|
||||
to String
|
||||
departureTime DateTime
|
||||
arrivalTime DateTime
|
||||
infoJson Json
|
||||
geometry String
|
||||
moneyUpdate MoneyUpdate?
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
}
|
||||
|
||||
model MoneyUpdate {
|
||||
id Int @id @default(autoincrement())
|
||||
userId Int
|
||||
before Int
|
||||
after Int
|
||||
reason MoneyUpdateType
|
||||
actionId Int? @unique
|
||||
tripId String? @unique
|
||||
action ChallengeAction? @relation(fields: [actionId], references: [id])
|
||||
trip TrainTrip? @relation(fields: [tripId], references: [id])
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
}
|
||||
|
||||
enum MoneyUpdateType {
|
||||
START
|
||||
WIN_CHALLENGE
|
||||
BUY_TRAIN
|
||||
}
|
30
server/prisma/seed.ts
Normal file
30
server/prisma/seed.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import { PrismaClient } from '@prisma/client'
|
||||
import * as bcrypt from 'bcrypt'
|
||||
|
||||
const prisma = new PrismaClient()
|
||||
|
||||
async function main() {
|
||||
const emmyPassword = await bcrypt.hash("Emmy", 10)
|
||||
const emmy = await prisma.user.upsert({
|
||||
where: { id: 1 },
|
||||
update: { name: 'Emmy' },
|
||||
create: { name: 'Emmy', password: emmyPassword },
|
||||
})
|
||||
|
||||
const taminaPassword = await bcrypt.hash("Tamina", 10)
|
||||
const tamina = await prisma.user.upsert({
|
||||
where: { id: 2 },
|
||||
update: { name: 'Tamina' },
|
||||
create: { name: 'Tamina', password: taminaPassword },
|
||||
})
|
||||
console.log({ emmy, tamina })
|
||||
}
|
||||
|
||||
main()
|
||||
.catch((e) => {
|
||||
console.error(e)
|
||||
process.exit(1)
|
||||
})
|
||||
.finally(async () => {
|
||||
await prisma.$disconnect()
|
||||
})
|
@ -1,22 +0,0 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { AppController } from './app.controller';
|
||||
import { AppService } from './app.service';
|
||||
|
||||
describe('AppController', () => {
|
||||
let appController: AppController;
|
||||
|
||||
beforeEach(async () => {
|
||||
const app: TestingModule = await Test.createTestingModule({
|
||||
controllers: [AppController],
|
||||
providers: [AppService],
|
||||
}).compile();
|
||||
|
||||
appController = app.get<AppController>(AppController);
|
||||
});
|
||||
|
||||
describe('root', () => {
|
||||
it('should return "Hello World!"', () => {
|
||||
expect(appController.getHello()).toBe('Hello World!');
|
||||
});
|
||||
});
|
||||
});
|
@ -1,12 +0,0 @@
|
||||
import { Controller, Get } from '@nestjs/common';
|
||||
import { AppService } from './app.service';
|
||||
|
||||
@Controller()
|
||||
export class AppController {
|
||||
constructor(private readonly appService: AppService) {}
|
||||
|
||||
@Get()
|
||||
getHello(): string {
|
||||
return this.appService.getHello();
|
||||
}
|
||||
}
|
@ -1,10 +1,11 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { AppController } from './app.controller';
|
||||
import { AppService } from './app.service';
|
||||
import { Module } from '@nestjs/common'
|
||||
import { PrismaService } from './prisma/prisma.service'
|
||||
import { PrismaModule } from './prisma/prisma.module'
|
||||
import { UsersModule } from './users/users.module'
|
||||
import { AuthModule } from './auth/auth.module'
|
||||
|
||||
@Module({
|
||||
imports: [],
|
||||
controllers: [AppController],
|
||||
providers: [AppService],
|
||||
imports: [PrismaModule, UsersModule, AuthModule],
|
||||
providers: [PrismaService],
|
||||
})
|
||||
export class AppModule {}
|
||||
|
@ -1,8 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
@Injectable()
|
||||
export class AppService {
|
||||
getHello(): string {
|
||||
return 'Hello World!';
|
||||
}
|
||||
}
|
20
server/src/auth/auth.controller.spec.ts
Normal file
20
server/src/auth/auth.controller.spec.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing'
|
||||
import { AuthController } from './auth.controller'
|
||||
import { AuthService } from './auth.service'
|
||||
|
||||
describe('AuthController', () => {
|
||||
let controller: AuthController
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
controllers: [AuthController],
|
||||
providers: [AuthService],
|
||||
}).compile()
|
||||
|
||||
controller = module.get<AuthController>(AuthController)
|
||||
})
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(controller).toBeDefined()
|
||||
})
|
||||
})
|
17
server/src/auth/auth.controller.ts
Normal file
17
server/src/auth/auth.controller.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { Body, Controller, Post } from '@nestjs/common'
|
||||
import { AuthService } from './auth.service'
|
||||
import { ApiOkResponse, ApiTags } from '@nestjs/swagger'
|
||||
import { AuthEntity } from './entity/auth.entity'
|
||||
import { LoginDto } from './dto/login.dto'
|
||||
|
||||
@Controller('auth')
|
||||
@ApiTags('auth')
|
||||
export class AuthController {
|
||||
constructor(private readonly authService: AuthService) {}
|
||||
|
||||
@Post('login')
|
||||
@ApiOkResponse({ type: AuthEntity })
|
||||
login(@Body() { name, password }: LoginDto) {
|
||||
return this.authService.login(name, password)
|
||||
}
|
||||
}
|
26
server/src/auth/auth.module.ts
Normal file
26
server/src/auth/auth.module.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import { Module } from '@nestjs/common'
|
||||
import { AuthService } from './auth.service'
|
||||
import { AuthController } from './auth.controller'
|
||||
import { PrismaModule } from 'src/prisma/prisma.module'
|
||||
import { PassportModule } from '@nestjs/passport'
|
||||
import { JwtModule } from '@nestjs/jwt'
|
||||
import { env } from 'process'
|
||||
import { UsersModule } from 'src/users/users.module'
|
||||
import { JwtStrategy } from './jwt.strategy'
|
||||
|
||||
export const JWT_SECRET = env.JWT_SECRET
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
PrismaModule,
|
||||
PassportModule,
|
||||
JwtModule.register({
|
||||
secret: JWT_SECRET,
|
||||
signOptions: { expiresIn: '5m' },
|
||||
}),
|
||||
UsersModule,
|
||||
],
|
||||
controllers: [AuthController],
|
||||
providers: [AuthService, JwtStrategy],
|
||||
})
|
||||
export class AuthModule {}
|
18
server/src/auth/auth.service.spec.ts
Normal file
18
server/src/auth/auth.service.spec.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing'
|
||||
import { AuthService } from './auth.service'
|
||||
|
||||
describe('AuthService', () => {
|
||||
let service: AuthService
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [AuthService],
|
||||
}).compile()
|
||||
|
||||
service = module.get<AuthService>(AuthService)
|
||||
})
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined()
|
||||
})
|
||||
})
|
27
server/src/auth/auth.service.ts
Normal file
27
server/src/auth/auth.service.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { Injectable, NotFoundException, UnauthorizedException } from '@nestjs/common'
|
||||
import { PrismaService } from './../prisma/prisma.service'
|
||||
import { JwtService } from '@nestjs/jwt'
|
||||
import { AuthEntity } from './entity/auth.entity'
|
||||
import * as bcrypt from 'bcrypt'
|
||||
|
||||
@Injectable()
|
||||
export class AuthService {
|
||||
constructor(private prisma: PrismaService, private jwtService: JwtService) {}
|
||||
|
||||
async login(name: string, password: string): Promise<AuthEntity> {
|
||||
const user = await this.prisma.user.findUnique({ where: { name: name } })
|
||||
if (!user) {
|
||||
throw new NotFoundException(`Aucun⋅e utilisateur⋅rice avec pour nom ${name}`)
|
||||
}
|
||||
|
||||
const isPasswordValid = await bcrypt.compare(password, user.password)
|
||||
|
||||
if (!isPasswordValid) {
|
||||
throw new UnauthorizedException('Mot de passe incorrect')
|
||||
}
|
||||
|
||||
return {
|
||||
accessToken: this.jwtService.sign({ userId: user.id }),
|
||||
}
|
||||
}
|
||||
}
|
14
server/src/auth/dto/login.dto.ts
Normal file
14
server/src/auth/dto/login.dto.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { ApiProperty } from '@nestjs/swagger'
|
||||
import { IsNotEmpty, IsString, MinLength } from 'class-validator'
|
||||
|
||||
export class LoginDto {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@ApiProperty()
|
||||
name: string
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@ApiProperty()
|
||||
password: string
|
||||
}
|
6
server/src/auth/entity/auth.entity.ts
Normal file
6
server/src/auth/entity/auth.entity.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { ApiProperty } from '@nestjs/swagger'
|
||||
|
||||
export class AuthEntity {
|
||||
@ApiProperty()
|
||||
accessToken: string
|
||||
}
|
5
server/src/auth/jwt-auth.guard.ts
Normal file
5
server/src/auth/jwt-auth.guard.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { Injectable } from '@nestjs/common'
|
||||
import { AuthGuard } from '@nestjs/passport'
|
||||
|
||||
@Injectable()
|
||||
export class JwtAuthGuard extends AuthGuard('jwt') {}
|
23
server/src/auth/jwt.strategy.ts
Normal file
23
server/src/auth/jwt.strategy.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { Injectable, UnauthorizedException } from '@nestjs/common'
|
||||
import { PassportStrategy } from '@nestjs/passport'
|
||||
import { ExtractJwt, Strategy } from 'passport-jwt'
|
||||
import { JWT_SECRET } from './auth.module'
|
||||
import { UsersService } from 'src/users/users.service'
|
||||
|
||||
@Injectable()
|
||||
export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
|
||||
constructor(private usersService: UsersService) {
|
||||
super({
|
||||
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
|
||||
secretOrKey: JWT_SECRET,
|
||||
})
|
||||
}
|
||||
|
||||
async validate(payload: { userId: number }) {
|
||||
const user = await this.usersService.findOne(payload.userId)
|
||||
if (!user) {
|
||||
throw new UnauthorizedException()
|
||||
}
|
||||
return user
|
||||
}
|
||||
}
|
@ -1,8 +1,23 @@
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
import { AppModule } from './app.module';
|
||||
import { NestFactory, Reflector } from '@nestjs/core'
|
||||
import { AppModule } from './app.module'
|
||||
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'
|
||||
import { ClassSerializerInterceptor, ValidationPipe } from '@nestjs/common'
|
||||
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create(AppModule);
|
||||
await app.listen(process.env.PORT ?? 3000);
|
||||
const app = await NestFactory.create(AppModule)
|
||||
|
||||
app.useGlobalPipes(new ValidationPipe())
|
||||
app.useGlobalInterceptors(new ClassSerializerInterceptor(app.get(Reflector)))
|
||||
|
||||
const config = new DocumentBuilder()
|
||||
.setTitle('Traintrape-moi')
|
||||
.setDescription('API permettant de stocker les données de Traintrape-moi')
|
||||
.setVersion('1.0')
|
||||
.addBearerAuth()
|
||||
.build()
|
||||
const document = SwaggerModule.createDocument(app, config)
|
||||
|
||||
SwaggerModule.setup('', app, document)
|
||||
await app.listen(process.env.PORT ?? 3000)
|
||||
}
|
||||
bootstrap();
|
||||
bootstrap()
|
||||
|
8
server/src/prisma/prisma.module.ts
Normal file
8
server/src/prisma/prisma.module.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { Module } from '@nestjs/common'
|
||||
import { PrismaService } from './prisma.service'
|
||||
|
||||
@Module({
|
||||
providers: [PrismaService],
|
||||
exports: [PrismaService],
|
||||
})
|
||||
export class PrismaModule {}
|
18
server/src/prisma/prisma.service.spec.ts
Normal file
18
server/src/prisma/prisma.service.spec.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing'
|
||||
import { PrismaService } from './prisma.service'
|
||||
|
||||
describe('PrismaService', () => {
|
||||
let service: PrismaService
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [PrismaService],
|
||||
}).compile()
|
||||
|
||||
service = module.get<PrismaService>(PrismaService)
|
||||
})
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined()
|
||||
})
|
||||
})
|
5
server/src/prisma/prisma.service.ts
Normal file
5
server/src/prisma/prisma.service.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { Injectable } from '@nestjs/common'
|
||||
import { PrismaClient } from '@prisma/client'
|
||||
|
||||
@Injectable()
|
||||
export class PrismaService extends PrismaClient {}
|
24
server/src/users/entities/user.entity.ts
Normal file
24
server/src/users/entities/user.entity.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { ApiProperty } from "@nestjs/swagger"
|
||||
import { User } from "@prisma/client"
|
||||
import { Exclude } from 'class-transformer'
|
||||
|
||||
export class UserEntity implements User {
|
||||
constructor(partial: Partial<UserEntity>) {
|
||||
Object.assign(this, partial)
|
||||
}
|
||||
|
||||
@ApiProperty({description: "Identifiant unique"})
|
||||
id: number
|
||||
|
||||
@ApiProperty({description: "Nom de læ joueur⋅se"})
|
||||
name: string
|
||||
|
||||
@Exclude()
|
||||
password: string
|
||||
|
||||
@ApiProperty({description: "Nombre de jetons dont dispose actuellement læ joueur⋅se"})
|
||||
money: number
|
||||
|
||||
@ApiProperty({description: "Est-ce que cet⋅te joueur⋅se est cellui actuellement en course"})
|
||||
currentRunner: boolean
|
||||
}
|
20
server/src/users/users.controller.spec.ts
Normal file
20
server/src/users/users.controller.spec.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing'
|
||||
import { UsersController } from './users.controller'
|
||||
import { UsersService } from './users.service'
|
||||
|
||||
describe('UsersController', () => {
|
||||
let controller: UsersController
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
controllers: [UsersController],
|
||||
providers: [UsersService],
|
||||
}).compile()
|
||||
|
||||
controller = module.get<UsersController>(UsersController)
|
||||
})
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(controller).toBeDefined()
|
||||
})
|
||||
})
|
31
server/src/users/users.controller.ts
Normal file
31
server/src/users/users.controller.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import { Controller, Get, NotFoundException, Param, ParseIntPipe, UseGuards } from '@nestjs/common'
|
||||
import { UsersService } from './users.service'
|
||||
import { ApiBearerAuth, ApiNotFoundResponse, ApiOkResponse } from '@nestjs/swagger'
|
||||
import { UserEntity } from './entities/user.entity'
|
||||
import { JwtAuthGuard } from 'src/auth/jwt-auth.guard'
|
||||
|
||||
@Controller('users')
|
||||
export class UsersController {
|
||||
constructor(private readonly usersService: UsersService) {}
|
||||
|
||||
@Get()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@ApiBearerAuth()
|
||||
@ApiOkResponse({ type: UserEntity, isArray: true })
|
||||
async findAll() {
|
||||
const users = await this.usersService.findAll()
|
||||
return users.map(user => new UserEntity(user))
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@ApiBearerAuth()
|
||||
@ApiOkResponse({ type: UserEntity })
|
||||
@ApiNotFoundResponse({ description: "Utilisateur⋅rice non trouvé⋅e" })
|
||||
async findOne(@Param('id', ParseIntPipe) id: number) {
|
||||
const user = await this.usersService.findOne(id)
|
||||
if (!user)
|
||||
throw new NotFoundException(`L'utilisateur⋅rice avec l'identifiant ${id} n'existe pas`)
|
||||
return new UserEntity(user)
|
||||
}
|
||||
}
|
12
server/src/users/users.module.ts
Normal file
12
server/src/users/users.module.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { Module } from '@nestjs/common'
|
||||
import { UsersService } from './users.service'
|
||||
import { UsersController } from './users.controller'
|
||||
import { PrismaModule } from 'src/prisma/prisma.module'
|
||||
|
||||
@Module({
|
||||
controllers: [UsersController],
|
||||
providers: [UsersService],
|
||||
imports: [PrismaModule],
|
||||
exports: [UsersService],
|
||||
})
|
||||
export class UsersModule {}
|
18
server/src/users/users.service.spec.ts
Normal file
18
server/src/users/users.service.spec.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing'
|
||||
import { UsersService } from './users.service'
|
||||
|
||||
describe('UsersService', () => {
|
||||
let service: UsersService
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [UsersService],
|
||||
}).compile()
|
||||
|
||||
service = module.get<UsersService>(UsersService)
|
||||
})
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined()
|
||||
})
|
||||
})
|
15
server/src/users/users.service.ts
Normal file
15
server/src/users/users.service.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { Injectable } from '@nestjs/common'
|
||||
import { PrismaService } from 'src/prisma/prisma.service'
|
||||
|
||||
@Injectable()
|
||||
export class UsersService {
|
||||
constructor(private prisma: PrismaService) {}
|
||||
|
||||
async findAll() {
|
||||
return await this.prisma.user.findMany()
|
||||
}
|
||||
|
||||
async findOne(id: number) {
|
||||
return await this.prisma.user.findUnique({ where: { id } })
|
||||
}
|
||||
}
|
@ -1,24 +1,24 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { INestApplication } from '@nestjs/common';
|
||||
import * as request from 'supertest';
|
||||
import { AppModule } from './../src/app.module';
|
||||
import { Test, TestingModule } from '@nestjs/testing'
|
||||
import { INestApplication } from '@nestjs/common'
|
||||
import * as request from 'supertest'
|
||||
import { AppModule } from './../src/app.module'
|
||||
|
||||
describe('AppController (e2e)', () => {
|
||||
let app: INestApplication;
|
||||
let app: INestApplication
|
||||
|
||||
beforeEach(async () => {
|
||||
const moduleFixture: TestingModule = await Test.createTestingModule({
|
||||
imports: [AppModule],
|
||||
}).compile();
|
||||
}).compile()
|
||||
|
||||
app = moduleFixture.createNestApplication();
|
||||
await app.init();
|
||||
});
|
||||
app = moduleFixture.createNestApplication()
|
||||
await app.init()
|
||||
})
|
||||
|
||||
it('/ (GET)', () => {
|
||||
return request(app.getHttpServer())
|
||||
.get('/')
|
||||
.expect(200)
|
||||
.expect('Hello World!');
|
||||
});
|
||||
});
|
||||
.expect('Hello World!')
|
||||
})
|
||||
})
|
||||
|
Loading…
x
Reference in New Issue
Block a user