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 { ThemedText } from '@/components/ThemedText'
|
||||||
import { ThemedView } from '@/components/ThemedView';
|
import { ThemedView } from '@/components/ThemedView'
|
||||||
|
|
||||||
export default function ChallengesScreen() {
|
export default function ChallengesScreen() {
|
||||||
return (
|
return (
|
||||||
@ -10,5 +10,5 @@ export default function ChallengesScreen() {
|
|||||||
<ThemedText>Ici on aura la gestion des challenges</ThemedText>
|
<ThemedText>Ici on aura la gestion des challenges</ThemedText>
|
||||||
</ThemedView>
|
</ThemedView>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { ScrollView } from 'react-native';
|
import { ScrollView } from 'react-native'
|
||||||
|
import { ThemedText } from '@/components/ThemedText'
|
||||||
import { ThemedText } from '@/components/ThemedText';
|
import { ThemedView } from '@/components/ThemedView'
|
||||||
import { ThemedView } from '@/components/ThemedView';
|
|
||||||
|
|
||||||
export default function HistoryScreen() {
|
export default function HistoryScreen() {
|
||||||
return (
|
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>
|
<ThemedText>Ici on aura la gestion de l'historique des trains empruntés et des challenges effectués</ThemedText>
|
||||||
</ThemedView>
|
</ThemedView>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
@ -27,4 +27,4 @@ const styles = StyleSheet.create({
|
|||||||
flex: 1,
|
flex: 1,
|
||||||
alignSelf: 'stretch',
|
alignSelf: 'stretch',
|
||||||
},
|
},
|
||||||
});
|
})
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { ScrollView } from 'react-native';
|
import { ScrollView } from 'react-native'
|
||||||
|
|
||||||
import { ThemedText } from '@/components/ThemedText';
|
import { ThemedText } from '@/components/ThemedText'
|
||||||
import { ThemedView } from '@/components/ThemedView';
|
import { ThemedView } from '@/components/ThemedView'
|
||||||
|
|
||||||
export default function TrainScreen() {
|
export default function TrainScreen() {
|
||||||
return (
|
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>
|
<ThemedText>Ici on aura la page pour ajouter un trajet en train depuis Rail Planner</ThemedText>
|
||||||
</ThemedView>
|
</ThemedView>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
import { Text, type TextProps, StyleSheet } from 'react-native';
|
import { Text, type TextProps, StyleSheet } from 'react-native'
|
||||||
|
import { useThemeColor } from '@/hooks/useThemeColor'
|
||||||
import { useThemeColor } from '@/hooks/useThemeColor';
|
|
||||||
|
|
||||||
export type ThemedTextProps = TextProps & {
|
export type ThemedTextProps = TextProps & {
|
||||||
lightColor?: string;
|
lightColor?: string
|
||||||
darkColor?: string;
|
darkColor?: string
|
||||||
type?: 'default' | 'title' | 'defaultSemiBold' | 'subtitle' | 'link';
|
type?: 'default' | 'title' | 'defaultSemiBold' | 'subtitle' | 'link'
|
||||||
};
|
}
|
||||||
|
|
||||||
export function ThemedText({
|
export function ThemedText({
|
||||||
style,
|
style,
|
||||||
@ -15,7 +14,7 @@ export function ThemedText({
|
|||||||
type = 'default',
|
type = 'default',
|
||||||
...rest
|
...rest
|
||||||
}: ThemedTextProps) {
|
}: ThemedTextProps) {
|
||||||
const color = useThemeColor({ light: lightColor, dark: darkColor }, 'text');
|
const color = useThemeColor({ light: lightColor, dark: darkColor }, 'text')
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Text
|
<Text
|
||||||
@ -30,7 +29,7 @@ export function ThemedText({
|
|||||||
]}
|
]}
|
||||||
{...rest}
|
{...rest}
|
||||||
/>
|
/>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
@ -57,4 +56,4 @@ const styles = StyleSheet.create({
|
|||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
color: '#0a7ea4',
|
color: '#0a7ea4',
|
||||||
},
|
},
|
||||||
});
|
})
|
||||||
|
@ -1,14 +1,13 @@
|
|||||||
import { View, type ViewProps } from 'react-native';
|
import { View, type ViewProps } from 'react-native'
|
||||||
|
import { useThemeColor } from '@/hooks/useThemeColor'
|
||||||
import { useThemeColor } from '@/hooks/useThemeColor';
|
|
||||||
|
|
||||||
export type ThemedViewProps = ViewProps & {
|
export type ThemedViewProps = ViewProps & {
|
||||||
lightColor?: string;
|
lightColor?: string
|
||||||
darkColor?: string;
|
darkColor?: string
|
||||||
};
|
}
|
||||||
|
|
||||||
export function ThemedView({ style, lightColor, darkColor, ...otherProps }: ThemedViewProps) {
|
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 * as React from 'react'
|
||||||
import renderer from 'react-test-renderer';
|
import renderer from 'react-test-renderer'
|
||||||
|
|
||||||
import { ThemedText } from '../ThemedText';
|
import { ThemedText } from '../ThemedText'
|
||||||
|
|
||||||
it(`renders correctly`, () => {
|
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!
|
Snapshot test!
|
||||||
</Text>
|
</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.
|
* 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 tintColorLight = '#0a7ea4'
|
||||||
const tintColorDark = '#fff';
|
const tintColorDark = '#fff'
|
||||||
|
|
||||||
export const Colors = {
|
export const Colors = {
|
||||||
light: {
|
light: {
|
||||||
@ -23,4 +23,4 @@ export const Colors = {
|
|||||||
tabIconDefault: '#9BA1A6',
|
tabIconDefault: '#9BA1A6',
|
||||||
tabIconSelected: tintColorDark,
|
tabIconSelected: tintColorDark,
|
||||||
},
|
},
|
||||||
};
|
}
|
||||||
|
@ -1 +1 @@
|
|||||||
export { useColorScheme } from 'react-native';
|
export { useColorScheme } from 'react-native'
|
||||||
|
@ -1,21 +1,21 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react'
|
||||||
import { useColorScheme as useRNColorScheme } from 'react-native';
|
import { useColorScheme as useRNColorScheme } from 'react-native'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* To support static rendering, this value needs to be re-calculated on the client side for web
|
* To support static rendering, this value needs to be re-calculated on the client side for web
|
||||||
*/
|
*/
|
||||||
export function useColorScheme() {
|
export function useColorScheme() {
|
||||||
const [hasHydrated, setHasHydrated] = useState(false);
|
const [hasHydrated, setHasHydrated] = useState(false)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setHasHydrated(true);
|
setHasHydrated(true)
|
||||||
}, []);
|
}, [])
|
||||||
|
|
||||||
const colorScheme = useRNColorScheme();
|
const colorScheme = useRNColorScheme()
|
||||||
|
|
||||||
if (hasHydrated) {
|
if (hasHydrated) {
|
||||||
return colorScheme;
|
return colorScheme
|
||||||
}
|
}
|
||||||
|
|
||||||
return 'light';
|
return 'light'
|
||||||
}
|
}
|
||||||
|
@ -3,19 +3,19 @@
|
|||||||
* https://docs.expo.dev/guides/color-schemes/
|
* https://docs.expo.dev/guides/color-schemes/
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Colors } from '@/constants/Colors';
|
import { Colors } from '@/constants/Colors'
|
||||||
import { useColorScheme } from '@/hooks/useColorScheme';
|
import { useColorScheme } from '@/hooks/useColorScheme'
|
||||||
|
|
||||||
export function useThemeColor(
|
export function useThemeColor(
|
||||||
props: { light?: string; dark?: string },
|
props: { light?: string; dark?: string },
|
||||||
colorName: keyof typeof Colors.light & keyof typeof Colors.dark
|
colorName: keyof typeof Colors.light & keyof typeof Colors.dark
|
||||||
) {
|
) {
|
||||||
const theme = useColorScheme() ?? 'light';
|
const theme = useColorScheme() ?? 'light'
|
||||||
const colorFromProps = props[theme];
|
const colorFromProps = props[theme]
|
||||||
|
|
||||||
if (colorFromProps) {
|
if (colorFromProps) {
|
||||||
return colorFromProps;
|
return colorFromProps
|
||||||
} else {
|
} 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-function-return-type': 'off',
|
||||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||||
'@typescript-eslint/no-explicit-any': '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
|
/node_modules
|
||||||
/build
|
/build
|
||||||
|
|
||||||
|
# Databases
|
||||||
|
*.sqlite3
|
||||||
|
*.sqlite3-journal
|
||||||
|
|
||||||
# Logs
|
# Logs
|
||||||
logs
|
logs
|
||||||
*.log
|
*.log
|
||||||
@ -37,10 +41,7 @@ lerna-debug.log*
|
|||||||
|
|
||||||
# dotenv environment variable files
|
# dotenv environment variable files
|
||||||
.env
|
.env
|
||||||
.env.development.local
|
.env*.local
|
||||||
.env.test.local
|
|
||||||
.env.production.local
|
|
||||||
.env.local
|
|
||||||
|
|
||||||
# temp directory
|
# temp directory
|
||||||
.temp
|
.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": {
|
"dependencies": {
|
||||||
"@nestjs/common": "^10.0.0",
|
"@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/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",
|
"reflect-metadata": "^0.2.0",
|
||||||
"rxjs": "^7.8.1"
|
"rxjs": "^7.8.1",
|
||||||
|
"swagger-ui-express": "^5.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@nestjs/cli": "^10.0.0",
|
"@nestjs/cli": "^10.0.0",
|
||||||
"@nestjs/schematics": "^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/express": "^5.0.0",
|
||||||
"@types/jest": "^29.5.2",
|
"@types/jest": "^29.5.2",
|
||||||
"@types/node": "^20.3.1",
|
"@types/node": "^20.3.1",
|
||||||
|
"@types/passport-jwt": "^4.0.1",
|
||||||
"@types/supertest": "^6.0.0",
|
"@types/supertest": "^6.0.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
||||||
"@typescript-eslint/parser": "^8.0.0",
|
"@typescript-eslint/parser": "^8.0.0",
|
||||||
@ -65,5 +77,8 @@
|
|||||||
],
|
],
|
||||||
"coverageDirectory": "../coverage",
|
"coverageDirectory": "../coverage",
|
||||||
"testEnvironment": "node"
|
"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 { Module } from '@nestjs/common'
|
||||||
import { AppController } from './app.controller';
|
import { PrismaService } from './prisma/prisma.service'
|
||||||
import { AppService } from './app.service';
|
import { PrismaModule } from './prisma/prisma.module'
|
||||||
|
import { UsersModule } from './users/users.module'
|
||||||
|
import { AuthModule } from './auth/auth.module'
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [],
|
imports: [PrismaModule, UsersModule, AuthModule],
|
||||||
controllers: [AppController],
|
providers: [PrismaService],
|
||||||
providers: [AppService],
|
|
||||||
})
|
})
|
||||||
export class AppModule {}
|
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 { NestFactory, Reflector } from '@nestjs/core'
|
||||||
import { AppModule } from './app.module';
|
import { AppModule } from './app.module'
|
||||||
|
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'
|
||||||
|
import { ClassSerializerInterceptor, ValidationPipe } from '@nestjs/common'
|
||||||
|
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
const app = await NestFactory.create(AppModule);
|
const app = await NestFactory.create(AppModule)
|
||||||
await app.listen(process.env.PORT ?? 3000);
|
|
||||||
|
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 { Test, TestingModule } from '@nestjs/testing'
|
||||||
import { INestApplication } from '@nestjs/common';
|
import { INestApplication } from '@nestjs/common'
|
||||||
import * as request from 'supertest';
|
import * as request from 'supertest'
|
||||||
import { AppModule } from './../src/app.module';
|
import { AppModule } from './../src/app.module'
|
||||||
|
|
||||||
describe('AppController (e2e)', () => {
|
describe('AppController (e2e)', () => {
|
||||||
let app: INestApplication;
|
let app: INestApplication
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const moduleFixture: TestingModule = await Test.createTestingModule({
|
const moduleFixture: TestingModule = await Test.createTestingModule({
|
||||||
imports: [AppModule],
|
imports: [AppModule],
|
||||||
}).compile();
|
}).compile()
|
||||||
|
|
||||||
app = moduleFixture.createNestApplication();
|
app = moduleFixture.createNestApplication()
|
||||||
await app.init();
|
await app.init()
|
||||||
});
|
})
|
||||||
|
|
||||||
it('/ (GET)', () => {
|
it('/ (GET)', () => {
|
||||||
return request(app.getHttpServer())
|
return request(app.getHttpServer())
|
||||||
.get('/')
|
.get('/')
|
||||||
.expect(200)
|
.expect(200)
|
||||||
.expect('Hello World!');
|
.expect('Hello World!')
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
Loading…
x
Reference in New Issue
Block a user