Compare commits
47 Commits
Author | SHA1 | Date | |
---|---|---|---|
19a3d168a1 | |||
81ce9eb546 | |||
d40af87c46 | |||
7eb548e138 | |||
6c2ebe7d7a | |||
1dd7952976 | |||
922a11b23b | |||
5a2a2db5e2 | |||
61449caef1 | |||
c9b8246218 | |||
c89dfa6786 | |||
8f3e442077 | |||
0430710522 | |||
c1805643c8 | |||
f1c296c0d3 | |||
2041c8998a | |||
cff010f2b4 | |||
986aca3931 | |||
a57a059c2a | |||
b7dc6af2e4 | |||
4fc58aedd7 | |||
bf08eefe57 | |||
6d6afc6274 | |||
82c90c1ceb | |||
4fe4808b3c | |||
b6b9b989c7 | |||
13b223ed5f | |||
04833277e1 | |||
31179d7292 | |||
1c97262ee3 | |||
bf791db47f | |||
fa25015472 | |||
4d02572d0c | |||
d418a53144 | |||
9b850ba88f | |||
41f52e67d3 | |||
2d06e0fb12 | |||
114ab257b2 | |||
f382150da3 | |||
630da41536 | |||
674ee65c29 | |||
e8a0449ad2 | |||
a25ce29154 | |||
6cf744bbc5 | |||
7c25158c85 | |||
05d0ff7134 | |||
6497a05e3c |
32
.gitea/workflows/build.yaml
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
name: Build DeguApp backend
|
||||||
|
|
||||||
|
on: pull_request
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout Repository
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Set up Node.js and TypeScript
|
||||||
|
uses: actions/setup-node@v1
|
||||||
|
with:
|
||||||
|
node-version: "20.14.0"
|
||||||
|
|
||||||
|
- name: npm install
|
||||||
|
working-directory: api/
|
||||||
|
run: |
|
||||||
|
npm install
|
||||||
|
|
||||||
|
- name: npm run build
|
||||||
|
working-directory: api/
|
||||||
|
run: |
|
||||||
|
npm run build --if-present
|
||||||
|
|
||||||
|
- name: npm run test
|
||||||
|
working-directory: api/
|
||||||
|
run: |
|
||||||
|
npm run test
|
||||||
|
|
46
README.md
@ -35,7 +35,12 @@ To get started with DeguApp, follow these steps:
|
|||||||
2. Install dependencies:
|
2. Install dependencies:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd deguapp
|
# frontend
|
||||||
|
cd deguapp/frontend
|
||||||
|
npm install
|
||||||
|
|
||||||
|
# backend
|
||||||
|
cd deguapp/api
|
||||||
npm install
|
npm install
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -43,6 +48,45 @@ To get started with DeguApp, follow these steps:
|
|||||||
|
|
||||||
5. Open the app in your browser or Android emulator and start exploring!
|
5. Open the app in your browser or Android emulator and start exploring!
|
||||||
|
|
||||||
|
## Local builds
|
||||||
|
### Android
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd frontend/
|
||||||
|
npm i
|
||||||
|
|
||||||
|
export ANDROID_HOME=$HOME/.Android/Sdk/
|
||||||
|
export PATH=$PATH:$ANDROID_HOME/emulator
|
||||||
|
export PATH=$PATH:$ANDROID_HOME/platform-tools
|
||||||
|
|
||||||
|
echo "EXPO_PUBLIC_API_URL=https://degu.filiprojek.cz/api/v1" > .env
|
||||||
|
|
||||||
|
npx expo prebuild
|
||||||
|
|
||||||
|
# edit gradle.properties and add info about signing key
|
||||||
|
# copy signing key to android/app/[keyname].keystore
|
||||||
|
# edit android/app/build.gradle
|
||||||
|
|
||||||
|
npx react-native build-android --mode=release
|
||||||
|
|
||||||
|
bundletool build-apks --bundle=./frontend/android/app/build/outputs/bundle/release/app-release.aab --output ./deguapp.apks --ks <upload-key.keystore> --ks-key-alias <upload-key-alias>
|
||||||
|
|
||||||
|
bundletool install-apks --apks=./deguapp.apks
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Resources:
|
||||||
|
|
||||||
|
- https://github.com/expo/eas-cli/issues/1300
|
||||||
|
- https://reactnative.dev/docs/signed-apk-android#generating-the-release-aab
|
||||||
|
|
||||||
|
### Server
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd api/
|
||||||
|
npm i
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
Contributions are welcome! If you'd like to contribute to DeguApp, please fork the repository and submit a pull request with your changes.
|
Contributions are welcome! If you'd like to contribute to DeguApp, please fork the repository and submit a pull request with your changes.
|
||||||
|
2
api/.gitignore
vendored
@ -2,3 +2,5 @@ node_modules/
|
|||||||
dist/
|
dist/
|
||||||
package-lock.json
|
package-lock.json
|
||||||
.env
|
.env
|
||||||
|
test-report.html
|
||||||
|
uploads/
|
36
api/biome.json
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://biomejs.dev/schemas/1.7.3/schema.json",
|
||||||
|
"files": {
|
||||||
|
"ignore": [".expo/", ".vscode/", "node_modules/", "dist/", "*.json"]
|
||||||
|
},
|
||||||
|
"organizeImports": {
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"linter": {
|
||||||
|
"enabled": false,
|
||||||
|
"rules": {
|
||||||
|
"recommended": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"formatter": {
|
||||||
|
"enabled": true,
|
||||||
|
"formatWithErrors": false,
|
||||||
|
"ignore": [],
|
||||||
|
"attributePosition": "auto",
|
||||||
|
"indentStyle": "tab",
|
||||||
|
"indentWidth": 2,
|
||||||
|
"lineEnding": "lf",
|
||||||
|
"lineWidth": 120
|
||||||
|
},
|
||||||
|
"javascript": {
|
||||||
|
"formatter": {
|
||||||
|
"arrowParentheses": "always",
|
||||||
|
"bracketSameLine": true,
|
||||||
|
"bracketSpacing": true,
|
||||||
|
"jsxQuoteStyle": "double",
|
||||||
|
"quoteProperties": "asNeeded",
|
||||||
|
"semicolons": "always",
|
||||||
|
"trailingComma": "all"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -59,6 +59,7 @@
|
|||||||
"eslint": "^8.3.0",
|
"eslint": "^8.3.0",
|
||||||
"http": "^0.0.1-security",
|
"http": "^0.0.1-security",
|
||||||
"jest": "^29.7.0",
|
"jest": "^29.7.0",
|
||||||
|
"jest-html-reporter": "^3.10.2",
|
||||||
"mocha": "^9.1.3",
|
"mocha": "^9.1.3",
|
||||||
"mongodb-memory-server": "^9.2.0",
|
"mongodb-memory-server": "^9.2.0",
|
||||||
"npm-run-all": "^4.1.5",
|
"npm-run-all": "^4.1.5",
|
||||||
@ -76,6 +77,12 @@
|
|||||||
"testEnvironment": "node",
|
"testEnvironment": "node",
|
||||||
"setupFilesAfterEnv": [
|
"setupFilesAfterEnv": [
|
||||||
"./tests/_setupFile.ts"
|
"./tests/_setupFile.ts"
|
||||||
|
],
|
||||||
|
"reporters": [
|
||||||
|
"default",
|
||||||
|
["./node_modules/jest-html-reporter", {
|
||||||
|
"pageTitle": "Test Report"
|
||||||
|
}]
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,29 +1,29 @@
|
|||||||
import express from "express";
|
import express from "express";
|
||||||
import morgan from "morgan";
|
import morgan from "morgan";
|
||||||
import path from 'path'
|
import path from "path";
|
||||||
import cors from 'cors'
|
import cors from "cors";
|
||||||
import cookieParser from 'cookie-parser'
|
import cookieParser from "cookie-parser";
|
||||||
import { router as routes } from "./routes";
|
import { router as routes } from "./routes";
|
||||||
//import { router as middlewares } from './middlewares'
|
//import { router as middlewares } from './middlewares'
|
||||||
import env from './config/environment'
|
import env from "./config/environment";
|
||||||
|
|
||||||
export let corsWhitelist: Array<string>
|
export let corsWhitelist: Array<string>;
|
||||||
if (env.CORS_WHITELIST != 'undefined') {
|
if (env.CORS_WHITELIST != "undefined") {
|
||||||
corsWhitelist = [...['http://localhost:8080', 'http://localhost:6040'], ...env.CORS_WHITELIST.split(';')]
|
corsWhitelist = [...["http://localhost:8080", "http://localhost:6040"], ...env.CORS_WHITELIST.split(";")];
|
||||||
} else {
|
} else {
|
||||||
corsWhitelist = ['http://localhost:8080', 'http://localhost:6040']
|
corsWhitelist = ["http://localhost:8080", "http://localhost:6040"];
|
||||||
}
|
}
|
||||||
const corsOptions = {
|
const corsOptions = {
|
||||||
origin: function (origin: any, callback: any) {
|
origin: function (origin: any, callback: any) {
|
||||||
if (!origin || corsWhitelist.indexOf(origin) !== -1) {
|
if (!origin || corsWhitelist.indexOf(origin) !== -1) {
|
||||||
callback(null, true)
|
callback(null, true);
|
||||||
} else {
|
} else {
|
||||||
callback(new Error('Not allowed by CORS'))
|
callback(new Error("Not allowed by CORS"));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
optionsSuccessStatus: 200,
|
optionsSuccessStatus: 200,
|
||||||
credentials: true
|
credentials: true,
|
||||||
}
|
};
|
||||||
|
|
||||||
export const app = express();
|
export const app = express();
|
||||||
|
|
||||||
@ -31,12 +31,13 @@ export const app = express();
|
|||||||
//app.use(middlewares)
|
//app.use(middlewares)
|
||||||
//app.set('view engine', 'ejs')
|
//app.set('view engine', 'ejs')
|
||||||
//app.set('views', path.join(__dirname, 'views'))
|
//app.set('views', path.join(__dirname, 'views'))
|
||||||
app.use(cors(corsOptions))
|
app.use(cors(corsOptions));
|
||||||
app.use(morgan("dev"));
|
app.use(morgan("dev"));
|
||||||
app.use(express.urlencoded({ extended: true }));
|
app.use(express.urlencoded({ extended: true }));
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
app.use(express.static(path.join(__dirname, 'public')))
|
app.use(express.static(path.join(__dirname, "public")));
|
||||||
app.use(cookieParser())
|
app.use('/public/uploads', express.static(path.join(__dirname, '../uploads')));
|
||||||
|
app.use(cookieParser());
|
||||||
|
|
||||||
// Routes
|
// Routes
|
||||||
app.use(routes);
|
app.use(routes);
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import { Request, Response } from 'express';
|
import { Request, Response } from "express";
|
||||||
import bcrypt from 'bcrypt';
|
import bcrypt from "bcrypt";
|
||||||
import jwt from 'jsonwebtoken';
|
import jwt from "jsonwebtoken";
|
||||||
import env from '../config/environment';
|
import env from "../config/environment";
|
||||||
import Docs from '../services/docsService';
|
import Docs from "../services/docsService";
|
||||||
import User from '../models/User';
|
import User from "../models/User";
|
||||||
import {Log} from 'nork'
|
import { Log } from "nork";
|
||||||
import { IUser, signupExam, ISignin, signinExam } from '../validators/authValidator';
|
import { IUser, signupExam, ISignin, signinExam } from "../validators/authValidator";
|
||||||
|
|
||||||
new Docs('user', 'signup', '/api/v1/auth/signup', 'POST', 'user signup api', undefined, signupExam, 'status object');
|
new Docs("user", "signup", "/api/v1/auth/signup", "POST", "user signup api", undefined, signupExam, "status object");
|
||||||
export async function signup_post(req: Request, res: Response) {
|
export async function signup_post(req: Request, res: Response) {
|
||||||
try {
|
try {
|
||||||
const payload: IUser = req.body;
|
const payload: IUser = req.body;
|
||||||
@ -16,27 +16,27 @@ export async function signup_post(req: Request, res: Response) {
|
|||||||
const user = new User(payload);
|
const user = new User(payload);
|
||||||
await user.save();
|
await user.save();
|
||||||
|
|
||||||
res.status(201).json(Log.info(201, 'user was successfully signed up'));
|
res.status(201).json(Log.info(201, "user was successfully signed up"));
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
if (err.code == 11000) {
|
if (err.code == 11000) {
|
||||||
res.status(400).json(Log.error(400, 'this user already exists'));
|
res.status(400).json(Log.error(400, "this user already exists"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Log.error(500, err);
|
Log.error(500, err);
|
||||||
res.status(500).json(Log.error(500, 'something went wrong'));
|
res.status(500).json(Log.error(500, "something went wrong"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
new Docs('user', 'signin', '/api/v1/auth/signin', 'POST', 'user signin api', undefined, signinExam, 'status object');
|
new Docs("user", "signin", "/api/v1/auth/signin", "POST", "user signin api", undefined, signinExam, "status object");
|
||||||
export async function signin_post(req: Request, res: Response) {
|
export async function signin_post(req: Request, res: Response) {
|
||||||
try {
|
try {
|
||||||
const payload: ISignin = req.body;
|
const payload: ISignin = req.body;
|
||||||
|
|
||||||
const user = await User.findOne({ email: payload.email });
|
const user = await User.findOne({ email: payload.email });
|
||||||
if (!user) {
|
if (!user) {
|
||||||
res.cookie('jwt', '', { httpOnly: true, maxAge: 0 });
|
res.cookie("jwt", "", { httpOnly: true, maxAge: 0 });
|
||||||
res.cookie('auth', false, { httpOnly: false, maxAge: 0 });
|
res.cookie("auth", false, { httpOnly: false, maxAge: 0 });
|
||||||
res.status(401).json(Log.error(401, 'email or password is wrong'));
|
res.status(401).json(Log.error(401, "email or password is wrong"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,38 +44,47 @@ export async function signin_post(req: Request, res: Response) {
|
|||||||
const maxAge = 3 * 24 * 60 * 60; // 3 days in seconds
|
const maxAge = 3 * 24 * 60 * 60; // 3 days in seconds
|
||||||
const createToken = (id: any) => {
|
const createToken = (id: any) => {
|
||||||
return jwt.sign({ id }, env.JWT_SECRET, {
|
return jwt.sign({ id }, env.JWT_SECRET, {
|
||||||
expiresIn: maxAge
|
expiresIn: maxAge,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const token = createToken(user._id);
|
const token = createToken(user._id);
|
||||||
res.cookie('jwt', token, { httpOnly: true, maxAge: maxAge * 1000 });
|
res.cookie("jwt", token, { httpOnly: true, maxAge: maxAge * 1000 });
|
||||||
res.cookie('auth', true, { httpOnly: false, maxAge: maxAge * 1000 });
|
res.cookie("auth", true, { httpOnly: false, maxAge: maxAge * 1000 });
|
||||||
|
|
||||||
res.json(Log.info(200, 'user is logged in', {jwt: token}));
|
res.json(Log.info(200, "user is logged in", { jwt: token }));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
res.cookie('jwt', '', { httpOnly: true, maxAge: 0 });
|
res.cookie("jwt", "", { httpOnly: true, maxAge: 0 });
|
||||||
res.cookie('auth', false, { httpOnly: false, maxAge: 0 });
|
res.cookie("auth", false, { httpOnly: false, maxAge: 0 });
|
||||||
res.status(401).json(Log.error(401, 'email or password is wrong'));
|
res.status(401).json(Log.error(401, "email or password is wrong"));
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
Log.error(500, err);
|
Log.error(500, err);
|
||||||
res.status(500).json(Log.error(500, 'something went wrong'));
|
res.status(500).json(Log.error(500, "something went wrong"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
new Docs('user', 'logout', '/api/v1/auth/logout', 'POST', 'user logout api', undefined, {}, 'status object');
|
new Docs("user", "logout", "/api/v1/auth/logout", "POST", "user logout api", undefined, {}, "status object");
|
||||||
export function logout_post(req: Request, res: Response) {
|
export function logout_post(req: Request, res: Response) {
|
||||||
res.cookie('jwt', '', { httpOnly: true, maxAge: 0 });
|
res.cookie("jwt", "", { httpOnly: true, maxAge: 0 });
|
||||||
res.cookie('auth', false, { httpOnly: false, maxAge: 0 });
|
res.cookie("auth", false, { httpOnly: false, maxAge: 0 });
|
||||||
res.json(Log.info(200, 'user was logged out'));
|
res.json(Log.info(200, "user was logged out"));
|
||||||
}
|
}
|
||||||
|
|
||||||
new Docs('user', 'status', '/api/v1/auth/status', 'GET', 'user login status api', undefined, undefined, 'status code | user object');
|
new Docs(
|
||||||
|
"user",
|
||||||
|
"status",
|
||||||
|
"/api/v1/auth/status",
|
||||||
|
"GET",
|
||||||
|
"user login status api",
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
"status code | user object",
|
||||||
|
);
|
||||||
export function status_get(req: Request, res: Response) {
|
export function status_get(req: Request, res: Response) {
|
||||||
let userObject = res.locals.user;
|
let userObject = res.locals.user;
|
||||||
userObject.password = undefined;
|
userObject.password = undefined;
|
||||||
userObject.__v = undefined;
|
userObject.__v = undefined;
|
||||||
res.status(200).json(Log.info(200, 'user is logged in', userObject));
|
res.status(200).json(Log.info(200, "user is logged in", userObject));
|
||||||
}
|
}
|
@ -1,13 +1,22 @@
|
|||||||
import { Request, Response } from 'express';
|
import { Request, Response } from "express";
|
||||||
import Beer from '../models/Beer';
|
import Beer from "../models/Beer";
|
||||||
import { isValidObjectId, Types } from 'mongoose';
|
import { isValidObjectId, Types } from "mongoose";
|
||||||
import fs from 'fs';
|
import fs from "fs";
|
||||||
import path from 'path';
|
import path from "path";
|
||||||
import {Log} from 'nork'
|
import { Log } from "nork";
|
||||||
import Docs from '../services/docsService';
|
import Docs from "../services/docsService";
|
||||||
import { addExam, delExam, editExam, IBeer } from '../validators/beerValidator';
|
import { addExam, delExam, editExam, IBeer } from "../validators/beerValidator";
|
||||||
|
|
||||||
new Docs('beer', 'add', '/api/v1/beer/add', 'POST', 'beer add api', undefined, { ...addExam, photos: 'optional field | max 4 images | formData' }, 'status object | beer object');
|
new Docs(
|
||||||
|
"beer",
|
||||||
|
"add",
|
||||||
|
"/api/v1/beer/add",
|
||||||
|
"POST",
|
||||||
|
"beer add api",
|
||||||
|
undefined,
|
||||||
|
{ ...addExam, photos: "optional field | max 4 images | formData" },
|
||||||
|
"status object | beer object",
|
||||||
|
);
|
||||||
export async function add_post(req: Request, res: Response) {
|
export async function add_post(req: Request, res: Response) {
|
||||||
try {
|
try {
|
||||||
if (req.files) {
|
if (req.files) {
|
||||||
@ -19,28 +28,37 @@ export async function add_post(req: Request, res: Response) {
|
|||||||
}
|
}
|
||||||
const beer = new Beer(req.body);
|
const beer = new Beer(req.body);
|
||||||
await beer.save();
|
await beer.save();
|
||||||
res.status(201).json(Log.info(201, 'beer was created', beer));
|
res.status(201).json(Log.info(201, "beer was created", beer));
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
Log.error(500, 'error in add_post', err);
|
Log.error(500, "error in add_post", err);
|
||||||
res.status(500).json(Log.error(500, 'something went wrong'));
|
res.status(500).json(Log.error(500, "something went wrong"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
new Docs('beer', 'get', '/api/v1/beer/get', 'GET', 'beer get api', undefined, undefined, 'status object | array of beer objects');
|
new Docs(
|
||||||
|
"beer",
|
||||||
|
"get",
|
||||||
|
"/api/v1/beer/get",
|
||||||
|
"GET",
|
||||||
|
"beer get api",
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
"status object | array of beer objects",
|
||||||
|
);
|
||||||
export async function get_get(req: Request, res: Response) {
|
export async function get_get(req: Request, res: Response) {
|
||||||
try {
|
try {
|
||||||
const beer = await Beer.find({}, '-__v');
|
const beer = await Beer.find({}, "-__v");
|
||||||
res.status(200).json(Log.info(200, 'beers fetched', beer));
|
res.status(200).json(Log.info(200, "beers fetched", beer));
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
Log.error(500, 'error in get_get', err);
|
Log.error(500, "error in get_get", err);
|
||||||
res.status(500).json(Log.error(500, 'something went wrong'));
|
res.status(500).json(Log.error(500, "something went wrong"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
new Docs('beer', 'del', '/api/v1/beer/del', 'POST', 'beer del api', undefined, delExam, 'status object');
|
new Docs("beer", "del", "/api/v1/beer/del", "POST", "beer del api", undefined, delExam, "status object");
|
||||||
export async function del_post(req: Request, res: Response) {
|
export async function del_post(req: Request, res: Response) {
|
||||||
try {
|
try {
|
||||||
if (!isValidObjectId(req.body._id)) throw Log.error(400, 'this is not valid _id');
|
if (!isValidObjectId(req.body._id)) throw Log.error(400, "this is not valid _id");
|
||||||
|
|
||||||
const beer = await Beer.deleteOne(new Types.ObjectId(req.body._id));
|
const beer = await Beer.deleteOne(new Types.ObjectId(req.body._id));
|
||||||
|
|
||||||
@ -54,30 +72,39 @@ export async function del_post(req: Request, res: Response) {
|
|||||||
res.status(err.code).json(err);
|
res.status(err.code).json(err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Log.error(500, 'error in del_post', err);
|
Log.error(500, "error in del_post", err);
|
||||||
res.status(500).json(Log.error(500, 'something went wrong'));
|
res.status(500).json(Log.error(500, "something went wrong"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
new Docs('beer', 'edit', '/api/v1/beer/edit', 'POST', 'beer edit api', undefined, { ...editExam, photos: 'optional field | max 4 images | formData' }, 'status object | beer data');
|
new Docs(
|
||||||
|
"beer",
|
||||||
|
"edit",
|
||||||
|
"/api/v1/beer/edit",
|
||||||
|
"POST",
|
||||||
|
"beer edit api",
|
||||||
|
undefined,
|
||||||
|
{ ...editExam, photos: "optional field | max 4 images | formData" },
|
||||||
|
"status object | beer data",
|
||||||
|
);
|
||||||
export async function edit_post(req: Request, res: Response) {
|
export async function edit_post(req: Request, res: Response) {
|
||||||
try {
|
try {
|
||||||
if (!isValidObjectId(req.body._id)) throw Log.error(400, 'this is not valid _id');
|
if (!isValidObjectId(req.body._id)) throw Log.error(400, "this is not valid _id");
|
||||||
|
|
||||||
if (req.files) {
|
if (req.files) {
|
||||||
if (!req.body.imgs) {
|
if (!req.body.imgs) {
|
||||||
req.body.imgs = [];
|
req.body.imgs = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof req.body.imgs == 'string') {
|
if (typeof req.body.imgs == "string") {
|
||||||
req.body.imgs = [req.body.imgs];
|
req.body.imgs = [req.body.imgs];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req.body.imgs.length + req.files.length > 4) {
|
if (req.body.imgs.length + req.files.length > 4) {
|
||||||
req.body.imgs.forEach((el: string[]) => {
|
req.body.imgs.forEach((el: string[]) => {
|
||||||
fs.rmSync(path.join(__dirname, '../../uploads/' + el));
|
fs.rmSync(path.join(__dirname, "../../uploads/" + el));
|
||||||
});
|
});
|
||||||
throw Log.error(400, 'exceeds the 4 image limit');
|
throw Log.error(400, "exceeds the 4 image limit");
|
||||||
}
|
}
|
||||||
|
|
||||||
const files: any = req.files;
|
const files: any = req.files;
|
||||||
@ -90,11 +117,11 @@ export async function edit_post(req: Request, res: Response) {
|
|||||||
const beer = await Beer.findOneAndUpdate(new Types.ObjectId(req.body._id), payload, { new: true });
|
const beer = await Beer.findOneAndUpdate(new Types.ObjectId(req.body._id), payload, { new: true });
|
||||||
res.json(Log.info(200, `beer ${req.body._id} edited`, beer));
|
res.json(Log.info(200, `beer ${req.body._id} edited`, beer));
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
if (err.code && typeof err.code == 'number') {
|
if (err.code && typeof err.code == "number") {
|
||||||
res.status(err.code).json(err);
|
res.status(err.code).json(err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Log.error(500, 'error in del_post', err);
|
Log.error(500, "error in del_post", err);
|
||||||
res.status(500).json(Log.error(500, 'something went wrong'));
|
res.status(500).json(Log.error(500, "something went wrong"));
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,14 +1,14 @@
|
|||||||
import { Request, Response } from 'express';
|
import { Request, Response } from "express";
|
||||||
import path from 'path';
|
import path from "path";
|
||||||
import fs from 'fs';
|
import fs from "fs";
|
||||||
import {Log} from 'nork'
|
import { Log } from "nork";
|
||||||
import { Docs } from '../services/docsService';
|
import { Docs } from "../services/docsService";
|
||||||
|
|
||||||
new Docs('docs', 'get_all', '/api/v1', 'GET', 'Get docs json', undefined, undefined, 'docs json');
|
new Docs("docs", "get_all", "/api/v1", "GET", "Get docs json", undefined, undefined, "docs json");
|
||||||
export function docs_get(req: Request, res: Response) {
|
export function docs_get(req: Request, res: Response) {
|
||||||
try {
|
try {
|
||||||
res.json(JSON.parse(fs.readFileSync(path.join(__dirname, '../public/api.json')).toString()));
|
res.json(JSON.parse(fs.readFileSync(path.join(__dirname, "../public/api.json")).toString()));
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
res.status(500).json(Log.error(500, 'api.json docs file does not exists under public folder'));
|
res.status(500).json(Log.error(500, "api.json docs file does not exists under public folder"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
71
api/src/controllers/reviewController.ts
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
import { Request, Response } from "express";
|
||||||
|
import Review from "../models/Review";
|
||||||
|
import { isValidObjectId, Types } from "mongoose";
|
||||||
|
import { Log } from "nork";
|
||||||
|
import Docs from "../services/docsService";
|
||||||
|
import { addExam, delExam, IReview } from "../validators/reviewValidator";
|
||||||
|
|
||||||
|
new Docs(
|
||||||
|
"review",
|
||||||
|
"add",
|
||||||
|
"/api/v1/review/add",
|
||||||
|
"POST",
|
||||||
|
"review add api",
|
||||||
|
undefined,
|
||||||
|
addExam,
|
||||||
|
"status object | review object",
|
||||||
|
);
|
||||||
|
export async function add_post(req: Request, res: Response) {
|
||||||
|
try {
|
||||||
|
const data: IReview = req.body;
|
||||||
|
data.user_id = res.locals.user._id
|
||||||
|
const review = new Review(data);
|
||||||
|
await review.save();
|
||||||
|
res.status(201).json(Log.info(201, "review was added", review));
|
||||||
|
} catch (err) {
|
||||||
|
Log.error(500, "error while adding review", err);
|
||||||
|
res.status(500).json(Log.error(500, "something went wrong"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
new Docs(
|
||||||
|
"review",
|
||||||
|
"get",
|
||||||
|
"/api/v1/review/get",
|
||||||
|
"GET",
|
||||||
|
"review get api",
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
"status object | array of review objects",
|
||||||
|
);
|
||||||
|
export async function get_get(req: Request, res: Response) {
|
||||||
|
try {
|
||||||
|
const review = await Review.find({}, "-__v");
|
||||||
|
res.status(200).json(Log.info(200, "reviews fetched", review));
|
||||||
|
} catch (err) {
|
||||||
|
Log.error(500, "error while geting reviews", err);
|
||||||
|
res.status(500).json(Log.error(500, "something went wrong"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
new Docs("review", "del", "/api/v1/review/del", "POST", "review del api", undefined, delExam, "status object");
|
||||||
|
export async function del_post(req: Request, res: Response) {
|
||||||
|
try {
|
||||||
|
if (!isValidObjectId(req.body._id)) throw Log.error(400, "this is not valid _id");
|
||||||
|
|
||||||
|
const review = await Review.deleteOne(new Types.ObjectId(req.body._id));
|
||||||
|
|
||||||
|
if (review.deletedCount > 0) {
|
||||||
|
res.status(200).json(Log.info(200, `review ${req.body._id} deleted`));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw Log.error(400, `review ${req.body._id} does not exist`);
|
||||||
|
} catch (err: any) {
|
||||||
|
if (err.code) {
|
||||||
|
res.status(err.code).json(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Log.error(500, "error in del_post", err);
|
||||||
|
res.status(500).json(Log.error(500, "something went wrong"));
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +1,9 @@
|
|||||||
import { Request, Response, NextFunction } from 'express';
|
import { Request, Response, NextFunction } from "express";
|
||||||
import jwt from 'jsonwebtoken';
|
import jwt from "jsonwebtoken";
|
||||||
import env from '../config/environment';
|
import env from "../config/environment";
|
||||||
import { Log } from 'nork';
|
import { Log } from "nork";
|
||||||
import User from '../models/User';
|
import User from "../models/User";
|
||||||
import { isValidObjectId } from 'mongoose';
|
import { isValidObjectId } from "mongoose";
|
||||||
|
|
||||||
export function requireAuth(req: Request, res: Response, next: NextFunction) {
|
export function requireAuth(req: Request, res: Response, next: NextFunction) {
|
||||||
const token = req.cookies?.jwt;
|
const token = req.cookies?.jwt;
|
||||||
@ -12,40 +12,40 @@ export function requireAuth(req: Request, res: Response, next: NextFunction) {
|
|||||||
jwt.verify(token, env.JWT_SECRET, async (err: any, decodedToken: any) => {
|
jwt.verify(token, env.JWT_SECRET, async (err: any, decodedToken: any) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
// console.error(err.message)
|
// console.error(err.message)
|
||||||
res.status(401).send(Log.error(401, 'user is not authenticated'));
|
res.status(401).send(Log.error(401, "user is not authenticated"));
|
||||||
}
|
}
|
||||||
if (!err) {
|
if (!err) {
|
||||||
const user = await User.findById(decodedToken.id);
|
const user = await User.findById(decodedToken.id);
|
||||||
if (user === null) {
|
if (user === null) {
|
||||||
res.status(401).send(Log.error(401, 'user is not authenticated'));
|
res.status(401).send(Log.error(401, "user is not authenticated"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
res.locals.user = user;
|
res.locals.user = user;
|
||||||
Log.info(100, 'user is authenticated');
|
Log.info(100, "user is authenticated");
|
||||||
next();
|
next();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!token) {
|
if (!token) {
|
||||||
res.status(401).send(Log.error(401, 'user is not authenticated'));
|
res.status(401).send(Log.error(401, "user is not authenticated"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function requireVerified(req: Request, res: Response, next: NextFunction) {
|
export function requireVerified(req: Request, res: Response, next: NextFunction) {
|
||||||
if (res.locals.user._id) {
|
if (res.locals.user._id) {
|
||||||
if (res.locals.user.verified) {
|
if (res.locals.user.verified) {
|
||||||
Log.info(100, 'user is verified');
|
Log.info(100, "user is verified");
|
||||||
next();
|
next();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
res.status(403).json(Log.error(403, 'user is not verified'));
|
res.status(403).json(Log.error(403, "user is not verified"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!res.locals.user._id) {
|
if (!res.locals.user._id) {
|
||||||
res.status(401).send(Log.error(401, 'user is not authenticated'));
|
res.status(401).send(Log.error(401, "user is not authenticated"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -53,33 +53,33 @@ export function requireVerified(req: Request, res: Response, next: NextFunction)
|
|||||||
export class requireRole {
|
export class requireRole {
|
||||||
static Admin(req: Request, res: Response, next: NextFunction) {
|
static Admin(req: Request, res: Response, next: NextFunction) {
|
||||||
if (res.locals.user.admin) {
|
if (res.locals.user.admin) {
|
||||||
Log.info(100, 'user is admin');
|
Log.info(100, "user is admin");
|
||||||
next();
|
next();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
res.status(403).json(Log.error(403, 'insufficient permissions'));
|
res.status(403).json(Log.error(403, "insufficient permissions"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
static Owner(req: Request, res: Response, next: NextFunction) {
|
static Owner(req: Request, res: Response, next: NextFunction) {
|
||||||
try {
|
try {
|
||||||
if (!isValidObjectId(req.body.domain_id)) {
|
if (!isValidObjectId(req.body.domain_id)) {
|
||||||
throw Log.error(400, 'neznámé domain_id');
|
throw Log.error(400, "neznámé domain_id");
|
||||||
}
|
}
|
||||||
|
|
||||||
const domain = res.locals.user.domains.filter((domain: any) => domain.domain_id == req.body.domain_id);
|
const domain = res.locals.user.domains.filter((domain: any) => domain.domain_id == req.body.domain_id);
|
||||||
console.log(domain);
|
console.log(domain);
|
||||||
|
|
||||||
if (domain.length < 1) {
|
if (domain.length < 1) {
|
||||||
throw Log.error(400, 'neznámé domain_id');
|
throw Log.error(400, "neznámé domain_id");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (domain[0].role == 1) {
|
if (domain[0].role == 1) {
|
||||||
Log.info(100, 'user is owner');
|
Log.info(100, "user is owner");
|
||||||
next();
|
next();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
res.status(403).json(Log.error(403, 'insufficient permissions'));
|
res.status(403).json(Log.error(403, "insufficient permissions"));
|
||||||
return;
|
return;
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
res.status(400).json(err);
|
res.status(400).json(err);
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import multer from 'multer';
|
import multer from "multer";
|
||||||
import {Log} from 'nork'
|
import { Log } from "nork";
|
||||||
import { Request, Response, NextFunction } from 'express';
|
import { Request, Response, NextFunction } from "express";
|
||||||
|
|
||||||
const validateMulterRequestMiddleware = async (err: any, req: Request, res: Response, next: NextFunction) => {
|
const validateMulterRequestMiddleware = async (err: any, req: Request, res: Response, next: NextFunction) => {
|
||||||
if (err instanceof multer.MulterError) {
|
if (err instanceof multer.MulterError) {
|
||||||
Log.error(500, 'error while processing uploaded files', JSON.stringify(err));
|
Log.error(500, "error while processing uploaded files", JSON.stringify(err));
|
||||||
res.status(400).json(Log.error(400, 'error while processing uploaded files'));
|
res.status(400).json(Log.error(400, "error while processing uploaded files"));
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
next();
|
next();
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Request, Response, NextFunction } from 'express';
|
import { Request, Response, NextFunction } from "express";
|
||||||
import { object, AnySchema } from 'yup';
|
import { object, AnySchema } from "yup";
|
||||||
import { Log } from 'nork';
|
import { Log } from "nork";
|
||||||
|
|
||||||
const validate = (schema: AnySchema) => async (req: Request, res: Response, next: NextFunction) => {
|
const validate = (schema: AnySchema) => async (req: Request, res: Response, next: NextFunction) => {
|
||||||
try {
|
try {
|
||||||
@ -8,7 +8,7 @@ const validate = (schema: AnySchema) => async (req: Request, res: Response, next
|
|||||||
|
|
||||||
next();
|
next();
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
return res.status(400).json(Log.error(400, 'validation error', err));
|
return res.status(400).json(Log.error(400, "validation error", err));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,34 +1,34 @@
|
|||||||
import path from 'path';
|
import path from "path";
|
||||||
import { Schema, model } from 'mongoose';
|
import { Schema, model } from "mongoose";
|
||||||
import { IBeer } from '../validators/beerValidator';
|
import { IBeer } from "../validators/beerValidator";
|
||||||
|
|
||||||
const schema = new Schema<IBeer | any>(
|
const schema = new Schema<IBeer | any>(
|
||||||
{
|
{
|
||||||
name: {
|
name: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true
|
required: true,
|
||||||
},
|
},
|
||||||
degree: {
|
degree: {
|
||||||
type: Number,
|
type: Number,
|
||||||
required: true
|
required: true,
|
||||||
},
|
},
|
||||||
packaging: {
|
packaging: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true
|
required: true,
|
||||||
},
|
},
|
||||||
brand: {
|
brand: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true
|
required: true,
|
||||||
},
|
},
|
||||||
imgs: {
|
imgs: {
|
||||||
type: Array,
|
type: Array,
|
||||||
required: false,
|
required: false,
|
||||||
default: []
|
default: [],
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
timestamps: true
|
timestamps: true,
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
export default model(path.basename(__filename).split('.')[0], schema);
|
export default model(path.basename(__filename).split(".")[0], schema);
|
||||||
|
45
api/src/models/Review.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import path from "path";
|
||||||
|
import { Schema, model } from "mongoose";
|
||||||
|
import { IReview } from "../validators/reviewValidator";
|
||||||
|
|
||||||
|
const schema = new Schema<IReview | any>(
|
||||||
|
{
|
||||||
|
foam: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
bitter_sweetness: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
taste: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
packaging: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
sourness: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
would_again: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
beer_id: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
user_id: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
timestamps: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
export default model(path.basename(__filename).split(".")[0], schema);
|
@ -1,26 +1,26 @@
|
|||||||
import path from 'path';
|
import path from "path";
|
||||||
import { Schema, model } from 'mongoose';
|
import { Schema, model } from "mongoose";
|
||||||
import { IUser } from '../validators/authValidator';
|
import { IUser } from "../validators/authValidator";
|
||||||
|
|
||||||
const schema = new Schema<IUser>(
|
const schema = new Schema<IUser>(
|
||||||
{
|
{
|
||||||
username: {
|
username: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true
|
required: true,
|
||||||
},
|
},
|
||||||
email: {
|
email: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
unique: true
|
unique: true,
|
||||||
},
|
},
|
||||||
password: {
|
password: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true
|
required: true,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
timestamps: true
|
timestamps: true,
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
export default model(path.basename(__filename).split('.')[0], schema);
|
export default model(path.basename(__filename).split(".")[0], schema);
|
||||||
|
@ -1 +1 @@
|
|||||||
{"version":"2.0.0","endpoints":{"user":{"signup":{"name":"user","operation":"signup","route":"/api/v1/auth/signup","method":"POST","description":"user signup api","body":{"username":"testuser","email":"text@example.com","password":"Test1234"},"response":"status object"},"signin":{"name":"user","operation":"signin","route":"/api/v1/auth/signin","method":"POST","description":"user signin api","body":{"email":"text@example.com","password":"Test1234"},"response":"status object"},"logout":{"name":"user","operation":"logout","route":"/api/v1/auth/logout","method":"POST","description":"user logout api","body":{},"response":"status object"},"status":{"name":"user","operation":"status","route":"/api/v1/auth/status","method":"GET","description":"user login status api","response":"status code | user object"}},"beer":{"add":{"name":"beer","operation":"add","route":"/api/v1/beer/add","method":"POST","description":"beer add api","body":{"brand":"Pilsner Urqell","name":"Kozel","degree":11,"packaging":"can","photos":"optional field | max 4 images | formData"},"response":"status object | beer object"},"get":{"name":"beer","operation":"get","route":"/api/v1/beer/get","method":"GET","description":"beer get api","response":"status object | array of beer objects"},"del":{"name":"beer","operation":"del","route":"/api/v1/beer/del","method":"POST","description":"beer del api","body":{"_id":"6352b303b71cb62222f39895"},"response":"status object"},"edit":{"name":"beer","operation":"edit","route":"/api/v1/beer/edit","method":"POST","description":"beer edit api","body":{"_id":"6355b95dc03fad77bc380146","brand":"Pilsner Urqell","name":"Radegast","degree":12,"packaging":"bottle","imgs":[],"photos":"optional field | max 4 images | formData"},"response":"status object | beer data"}},"docs":{"get_all":{"name":"docs","operation":"get_all","route":"/api/v1","method":"GET","description":"Get docs json","response":"docs json"}}}}
|
{"version":"2.0.0","endpoints":{"user":{"signup":{"name":"user","operation":"signup","route":"/api/v1/auth/signup","method":"POST","description":"user signup api","body":{"username":"testuser","email":"text@example.com","password":"Test1234"},"response":"status object"},"signin":{"name":"user","operation":"signin","route":"/api/v1/auth/signin","method":"POST","description":"user signin api","body":{"email":"text@example.com","password":"Test1234"},"response":"status object"},"logout":{"name":"user","operation":"logout","route":"/api/v1/auth/logout","method":"POST","description":"user logout api","body":{},"response":"status object"},"status":{"name":"user","operation":"status","route":"/api/v1/auth/status","method":"GET","description":"user login status api","response":"status code | user object"}},"beer":{"add":{"name":"beer","operation":"add","route":"/api/v1/beer/add","method":"POST","description":"beer add api","body":{"brand":"Pilsner Urqell","name":"Kozel","degree":11,"packaging":"can","photos":"optional field | max 4 images | formData"},"response":"status object | beer object"},"get":{"name":"beer","operation":"get","route":"/api/v1/beer/get","method":"GET","description":"beer get api","response":"status object | array of beer objects"},"del":{"name":"beer","operation":"del","route":"/api/v1/beer/del","method":"POST","description":"beer del api","body":{"_id":"6352b303b71cb62222f39895"},"response":"status object"},"edit":{"name":"beer","operation":"edit","route":"/api/v1/beer/edit","method":"POST","description":"beer edit api","body":{"_id":"6355b95dc03fad77bc380146","brand":"Pilsner Urqell","name":"Radegast","degree":12,"packaging":"bottle","imgs":[],"photos":"optional field | max 4 images | formData"},"response":"status object | beer data"}},"docs":{"get_all":{"name":"docs","operation":"get_all","route":"/api/v1","method":"GET","description":"Get docs json","response":"docs json"}},"review":{"add":{"name":"review","operation":"add","route":"/api/v1/review/add","method":"POST","description":"review add api","body":{"beer_id":"6352b303b71cb62222f39895","foam":3,"bitter_sweetness":2,"taste":5,"packaging":3,"sourness":false,"would_again":true},"response":"status object | review object"},"get":{"name":"review","operation":"get","route":"/api/v1/review/get","method":"GET","description":"review get api","response":"status object | array of review objects"},"del":{"name":"review","operation":"del","route":"/api/v1/review/del","method":"POST","description":"review del api","body":{"_id":"6352b303b71cb62222f39895"},"response":"status object"}}}}
|
@ -1,29 +1,42 @@
|
|||||||
import { Router } from "express";
|
import { Router } from "express";
|
||||||
import multer from 'multer';
|
import multer from "multer";
|
||||||
import path from 'path'
|
import path from "path";
|
||||||
import * as authController from "../controllers/authController";
|
import * as authController from "../controllers/authController";
|
||||||
import * as beerController from "../controllers/beerController"
|
import * as beerController from "../controllers/beerController";
|
||||||
import * as docsController from "../controllers/docsController"
|
import * as docsController from "../controllers/docsController";
|
||||||
|
import * as reviewController from "../controllers/reviewController";
|
||||||
import { requireAuth } from "../middlewares/authMiddleware";
|
import { requireAuth } from "../middlewares/authMiddleware";
|
||||||
import validate from '../middlewares/validateRequest'
|
import validate from "../middlewares/validateRequest";
|
||||||
import valMulter from '../middlewares/validateMulterRequest';
|
import valMulter from "../middlewares/validateMulterRequest";
|
||||||
import * as AuthVal from '../validators/authValidator'
|
import * as AuthVal from "../validators/authValidator";
|
||||||
import * as BVal from '../validators/beerValidator';
|
import * as BVal from "../validators/beerValidator";
|
||||||
|
import * as RVal from "../validators/reviewValidator";
|
||||||
|
|
||||||
const upload = multer({ dest: path.resolve(__dirname, '../../uploads') });
|
const upload = multer({ dest: path.resolve(__dirname, "../../uploads") });
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.get('/', docsController.docs_get);
|
router.get("/", docsController.docs_get);
|
||||||
|
|
||||||
router.post("/auth/signup",validate(AuthVal.signup) , authController.signup_post);
|
router.post("/auth/signup", validate(AuthVal.signup), authController.signup_post);
|
||||||
router.post("/auth/signin",validate(AuthVal.signin) , authController.signin_post);
|
router.post("/auth/signin", validate(AuthVal.signin), authController.signin_post);
|
||||||
router.post("/auth/logout", requireAuth, authController.logout_post);
|
router.post("/auth/logout", requireAuth, authController.logout_post);
|
||||||
router.get("/auth/status", requireAuth, authController.status_get);
|
router.get("/auth/status", requireAuth, authController.status_get);
|
||||||
|
|
||||||
router.post('/beer/add', [requireAuth, upload.array('photos', 4), valMulter, validate(BVal.add)], beerController.add_post);
|
router.post(
|
||||||
router.get('/beer/get', [requireAuth], beerController.get_get);
|
"/beer/add",
|
||||||
router.post('/beer/del', [requireAuth, validate(BVal.del)], beerController.del_post);
|
[requireAuth, upload.array("photos", 4), valMulter, validate(BVal.add)],
|
||||||
router.post('/beer/edit', [requireAuth, upload.array('photos', 4), valMulter, validate(BVal.edit)], beerController.edit_post);
|
beerController.add_post,
|
||||||
|
);
|
||||||
|
router.get("/beer/get", [requireAuth], beerController.get_get);
|
||||||
|
router.post("/beer/del", [requireAuth, validate(BVal.del)], beerController.del_post);
|
||||||
|
router.post(
|
||||||
|
"/beer/edit",
|
||||||
|
[requireAuth, upload.array("photos", 4), valMulter, validate(BVal.edit)],
|
||||||
|
beerController.edit_post,
|
||||||
|
);
|
||||||
|
|
||||||
|
router.post("/review/add", [requireAuth, validate(RVal.add)], reviewController.add_post);
|
||||||
|
router.get("/review/get", requireAuth, reviewController.get_get);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
@ -5,11 +5,11 @@ export const router = Router();
|
|||||||
|
|
||||||
router.use("/api/v1", api_v1);
|
router.use("/api/v1", api_v1);
|
||||||
|
|
||||||
//router.get("*", (req: Request, res: Response) => {
|
router.get("*", (req: Request, res: Response) => {
|
||||||
// res.sendFile(path.join(__dirname, "../views/index.html"));
|
res.sendFile(path.join(__dirname, "../public/index.html"));
|
||||||
//});
|
});
|
||||||
|
|
||||||
// 404
|
// 404
|
||||||
router.use((req: Request, res: Response) => {
|
router.use((req: Request, res: Response) => {
|
||||||
res.status(404).send("Error 404\n");
|
res.status(404).send("Error 404\n");
|
||||||
});
|
});
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import http from "http";
|
import http from "http";
|
||||||
import { app } from "./app";
|
import { app } from "./app";
|
||||||
import env from "./config/environment";
|
import env from "./config/environment";
|
||||||
import mongoose from 'mongoose' // TODO: dopsat nork module pro db
|
import mongoose from "mongoose"; // TODO: dopsat nork module pro db
|
||||||
import { Log } from "nork";
|
import { Log } from "nork";
|
||||||
|
|
||||||
const port: number = env.APP_PORT || 8080;
|
const port: number = env.APP_PORT || 8080;
|
||||||
@ -23,13 +23,11 @@ export const server = http.createServer(app);
|
|||||||
// runServer()
|
// runServer()
|
||||||
//}
|
//}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
if (!process.env.DOCS_GEN) {
|
if (!process.env.DOCS_GEN) {
|
||||||
try {
|
try {
|
||||||
await mongoose.connect(env.DB_URI);
|
await mongoose.connect(env.DB_URI);
|
||||||
Log.info(200, 'connected to db');
|
Log.info(200, "connected to db");
|
||||||
server.listen(port, () => {
|
server.listen(port, () => {
|
||||||
Log.info(200, `Server is listening on http://localhost:${port}`);
|
Log.info(200, `Server is listening on http://localhost:${port}`);
|
||||||
});
|
});
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import fs from 'fs';
|
import fs from "fs";
|
||||||
import path from 'path';
|
import path from "path";
|
||||||
|
|
||||||
export type HttpMethods = 'POST' | 'GET' | 'PUT' | 'DELETE';
|
export type HttpMethods = "POST" | "GET" | "PUT" | "DELETE";
|
||||||
export type ApiResponse = 'status object' | Object | string;
|
export type ApiResponse = "status object" | Object | string;
|
||||||
|
|
||||||
export class Docs {
|
export class Docs {
|
||||||
private name: string;
|
private name: string;
|
||||||
@ -14,7 +14,16 @@ export class Docs {
|
|||||||
private body?: Object;
|
private body?: Object;
|
||||||
private response?: ApiResponse;
|
private response?: ApiResponse;
|
||||||
|
|
||||||
public constructor(name: string, operation: string, route: string, method: HttpMethods, description?: string, parameters?: Object[], body?: Object, response?: ApiResponse) {
|
public constructor(
|
||||||
|
name: string,
|
||||||
|
operation: string,
|
||||||
|
route: string,
|
||||||
|
method: HttpMethods,
|
||||||
|
description?: string,
|
||||||
|
parameters?: Object[],
|
||||||
|
body?: Object,
|
||||||
|
response?: ApiResponse,
|
||||||
|
) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.operation = operation;
|
this.operation = operation;
|
||||||
this.route = route;
|
this.route = route;
|
||||||
@ -32,14 +41,14 @@ export class Docs {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const jsonPath = path.join(__dirname, '../public/api.json');
|
const jsonPath = path.join(__dirname, "../public/api.json");
|
||||||
const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '../../package.json')).toString());
|
const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, "../../package.json")).toString());
|
||||||
|
|
||||||
const jsonAPI = () => JSON.parse(fs.readFileSync(jsonPath).toString());
|
const jsonAPI = () => JSON.parse(fs.readFileSync(jsonPath).toString());
|
||||||
const genJsonAPI = () => fs.writeFileSync(jsonPath, JSON.stringify({ version: pkg.version, endpoints: {} }));
|
const genJsonAPI = () => fs.writeFileSync(jsonPath, JSON.stringify({ version: pkg.version, endpoints: {} }));
|
||||||
|
|
||||||
if (!fs.existsSync(path.join(__dirname, '../public'))) {
|
if (!fs.existsSync(path.join(__dirname, "../public"))) {
|
||||||
fs.mkdirSync(path.join(__dirname, '../public'));
|
fs.mkdirSync(path.join(__dirname, "../public"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!fs.existsSync(jsonPath)) {
|
if (!fs.existsSync(jsonPath)) {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import * as shell from 'shelljs';
|
import * as shell from "shelljs";
|
||||||
|
|
||||||
// Copy all the view templates
|
// Copy all the view templates
|
||||||
//shell.cp('-R', 'src/views', 'dist/')
|
//shell.cp('-R', 'src/views', 'dist/')
|
||||||
shell.cp('-R', 'src/public', 'dist/');
|
shell.cp("-R", "src/public", "dist/");
|
||||||
shell.cp('-u', 'src/.env', 'dist/');
|
shell.cp("-u", "src/.env", "dist/");
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
const mongoose = require('mongoose');
|
const mongoose = require("mongoose");
|
||||||
const { MongoMemoryServer } = require('mongodb-memory-server');
|
const { MongoMemoryServer } = require("mongodb-memory-server");
|
||||||
|
|
||||||
let mongo: any = null;
|
let mongo: any = null;
|
||||||
|
|
||||||
const connectDB = async () => {
|
const connectDB = async () => {
|
||||||
mongo = await MongoMemoryServer.create({ binary: { os: { os: 'linux', dist: 'ubuntu', release: '18.04' } } }); // TODO: check that host OS is Void Linux, else remove the argument
|
mongo = await MongoMemoryServer.create({ binary: { os: { os: "linux", dist: "ubuntu", release: "18.04" } } }); // TODO: check that host OS is Void Linux, else remove the argument
|
||||||
const uri = mongo.getUri();
|
const uri = mongo.getUri();
|
||||||
|
|
||||||
await mongoose.connect(uri);
|
await mongoose.connect(uri);
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import * as yup from 'yup';
|
import * as yup from "yup";
|
||||||
import YupPassword from 'yup-password';
|
import YupPassword from "yup-password";
|
||||||
YupPassword(yup);
|
YupPassword(yup);
|
||||||
import { Schema } from 'mongoose';
|
import { Schema } from "mongoose";
|
||||||
|
|
||||||
interface mongooseAddition {
|
interface mongooseAddition {
|
||||||
_id?: Schema.Types.ObjectId;
|
_id?: Schema.Types.ObjectId;
|
||||||
@ -13,22 +13,22 @@ interface mongooseAddition {
|
|||||||
export const signup = yup.object({
|
export const signup = yup.object({
|
||||||
username: yup.string().required(),
|
username: yup.string().required(),
|
||||||
email: yup.string().email().required(),
|
email: yup.string().email().required(),
|
||||||
password: yup.string().min(8).minLowercase(1).minUppercase(1).minNumbers(1).required()
|
password: yup.string().min(8).minLowercase(1).minUppercase(1).minNumbers(1).required(),
|
||||||
});
|
});
|
||||||
export interface IUser extends yup.InferType<typeof signup>, mongooseAddition {}
|
export interface IUser extends yup.InferType<typeof signup>, mongooseAddition {}
|
||||||
export const signupExam: IUser = {
|
export const signupExam: IUser = {
|
||||||
username: 'testuser',
|
username: "testuser",
|
||||||
email: 'text@example.com',
|
email: "text@example.com",
|
||||||
password: 'Test1234'
|
password: "Test1234",
|
||||||
};
|
};
|
||||||
|
|
||||||
// SignIn
|
// SignIn
|
||||||
export const signin = yup.object({
|
export const signin = yup.object({
|
||||||
email: yup.string().email().required(),
|
email: yup.string().email().required(),
|
||||||
password: yup.string().min(8).minLowercase(1).minUppercase(1).minNumbers(1).required()
|
password: yup.string().min(8).minLowercase(1).minUppercase(1).minNumbers(1).required(),
|
||||||
});
|
});
|
||||||
export interface ISignin extends yup.InferType<typeof signin>, mongooseAddition {}
|
export interface ISignin extends yup.InferType<typeof signin>, mongooseAddition {}
|
||||||
export const signinExam: ISignin = {
|
export const signinExam: ISignin = {
|
||||||
email: 'text@example.com',
|
email: "text@example.com",
|
||||||
password: 'Test1234'
|
password: "Test1234",
|
||||||
};
|
};
|
@ -1,7 +1,7 @@
|
|||||||
import * as yup from 'yup';
|
import * as yup from "yup";
|
||||||
import YupPassword from 'yup-password';
|
import YupPassword from "yup-password";
|
||||||
YupPassword(yup);
|
YupPassword(yup);
|
||||||
import { Schema } from 'mongoose';
|
import { Schema } from "mongoose";
|
||||||
|
|
||||||
interface mongooseAddition {
|
interface mongooseAddition {
|
||||||
_id?: Schema.Types.ObjectId;
|
_id?: Schema.Types.ObjectId;
|
||||||
@ -14,23 +14,23 @@ export const add = yup.object({
|
|||||||
brand: yup.string().required(),
|
brand: yup.string().required(),
|
||||||
name: yup.string().required(),
|
name: yup.string().required(),
|
||||||
degree: yup.number().required(),
|
degree: yup.number().required(),
|
||||||
packaging: yup.string().required()
|
packaging: yup.string().required(),
|
||||||
});
|
});
|
||||||
export interface IBeer extends yup.InferType<typeof add>, mongooseAddition {}
|
export interface IBeer extends yup.InferType<typeof add>, mongooseAddition {}
|
||||||
export const addExam: IBeer = {
|
export const addExam: IBeer = {
|
||||||
brand: 'Pilsner Urqell',
|
brand: "Pilsner Urqell",
|
||||||
name: 'Kozel',
|
name: "Kozel",
|
||||||
degree: 11,
|
degree: 11,
|
||||||
packaging: 'can'
|
packaging: "can",
|
||||||
};
|
};
|
||||||
|
|
||||||
// Remove
|
// Remove
|
||||||
export const del = yup.object({
|
export const del = yup.object({
|
||||||
_id: yup.string().required()
|
_id: yup.string().required(),
|
||||||
});
|
});
|
||||||
export interface IDel extends yup.InferType<typeof del> {}
|
export interface IDel extends yup.InferType<typeof del> {}
|
||||||
export const delExam: IDel = {
|
export const delExam: IDel = {
|
||||||
_id: '6352b303b71cb62222f39895'
|
_id: "6352b303b71cb62222f39895",
|
||||||
};
|
};
|
||||||
|
|
||||||
// Update
|
// Update
|
||||||
@ -41,12 +41,17 @@ export const edit = yup.object({
|
|||||||
degree: yup.number(),
|
degree: yup.number(),
|
||||||
packaging: yup.string(),
|
packaging: yup.string(),
|
||||||
|
|
||||||
//imgs: yup.mixed().when('$imgs', (imgs, schema) =>
|
//imgs: yup.mixed().when('$imgs', (imgs, schema) =>
|
||||||
// Array.isArray(imgs) ? schema.array() : schema.string()
|
// Array.isArray(imgs) ? schema.array() : schema.string()
|
||||||
//)
|
//)
|
||||||
|
|
||||||
imgs: yup.mixed().test('is-array-or-string', 'imgs must be either an array or a string', value =>
|
imgs: yup
|
||||||
Array.isArray(value) || typeof value === 'string')
|
.mixed()
|
||||||
|
.test(
|
||||||
|
"is-array-or-string",
|
||||||
|
"imgs must be either an array or a string",
|
||||||
|
(value) => Array.isArray(value) || typeof value === "string",
|
||||||
|
),
|
||||||
|
|
||||||
//imgs: yup.mixed().when('isArray', {
|
//imgs: yup.mixed().when('isArray', {
|
||||||
// is: Array.isArray,
|
// is: Array.isArray,
|
||||||
@ -56,10 +61,10 @@ export const edit = yup.object({
|
|||||||
});
|
});
|
||||||
export interface IEdit extends yup.InferType<typeof edit> {}
|
export interface IEdit extends yup.InferType<typeof edit> {}
|
||||||
export const editExam: IEdit = {
|
export const editExam: IEdit = {
|
||||||
_id: '6355b95dc03fad77bc380146',
|
_id: "6355b95dc03fad77bc380146",
|
||||||
brand: 'Pilsner Urqell',
|
brand: "Pilsner Urqell",
|
||||||
name: 'Radegast',
|
name: "Radegast",
|
||||||
degree: 12,
|
degree: 12,
|
||||||
packaging: 'bottle',
|
packaging: "bottle",
|
||||||
imgs: []
|
imgs: [],
|
||||||
};
|
};
|
||||||
|
43
api/src/validators/reviewValidator.ts
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import * as yup from "yup";
|
||||||
|
import mongoose, { Schema } from "mongoose";
|
||||||
|
|
||||||
|
interface mongooseAddition {
|
||||||
|
_id?: Schema.Types.ObjectId;
|
||||||
|
createdAt?: Schema.Types.Date;
|
||||||
|
updatedAt?: Schema.Types.Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
let objectIdSchema = yup
|
||||||
|
.string()
|
||||||
|
.test("is-objectId", "Invalid ObjectId", (value: any) => mongoose.Types.ObjectId.isValid(value));
|
||||||
|
|
||||||
|
// Add
|
||||||
|
export const add = yup.object({
|
||||||
|
beer_id: objectIdSchema,
|
||||||
|
foam: yup.number().min(1).max(3).required(),
|
||||||
|
bitter_sweetness: yup.number().min(1).max(5).required(),
|
||||||
|
taste: yup.number().min(1).max(5).required(),
|
||||||
|
packaging: yup.number().min(1).max(5).required(),
|
||||||
|
sourness: yup.boolean().required(),
|
||||||
|
would_again: yup.boolean().required(),
|
||||||
|
user_id: yup.string().notRequired()
|
||||||
|
});
|
||||||
|
export interface IReview extends yup.InferType<typeof add>, mongooseAddition {}
|
||||||
|
export const addExam: IReview = {
|
||||||
|
beer_id: "6352b303b71cb62222f39895",
|
||||||
|
foam: 3,
|
||||||
|
bitter_sweetness: 2,
|
||||||
|
taste: 5,
|
||||||
|
packaging: 3,
|
||||||
|
sourness: false,
|
||||||
|
would_again: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Remove
|
||||||
|
export const del = yup.object({
|
||||||
|
_id: objectIdSchema.required(),
|
||||||
|
});
|
||||||
|
export interface IDel extends yup.InferType<typeof del> {}
|
||||||
|
export const delExam: IDel = {
|
||||||
|
_id: "6352b303b71cb62222f39895",
|
||||||
|
};
|
@ -1,4 +1,4 @@
|
|||||||
import { connectDB, dropDB, dropCollections } from '../src/utils/test_mongodb';
|
import { connectDB, dropDB, dropCollections } from "../src/utils/test_mongodb";
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
await connectDB();
|
await connectDB();
|
||||||
|
@ -1,22 +1,22 @@
|
|||||||
import supertest from 'supertest';
|
import supertest from "supertest";
|
||||||
import { app } from '../src/app';
|
import { app } from "../src/app";
|
||||||
import { connectDB, dropDB, dropCollections } from '../src/utils/test_mongodb';
|
import { connectDB, dropDB, dropCollections } from "../src/utils/test_mongodb";
|
||||||
|
|
||||||
const request = supertest(app);
|
const request = supertest(app);
|
||||||
|
|
||||||
export const getJWT = async () => {
|
export const getJWT = async () => {
|
||||||
try {
|
try {
|
||||||
const resReg: any = await request.post('/api/v1/auth/signup').send({
|
const resReg: any = await request.post("/api/v1/auth/signup").send({
|
||||||
email: 'test@example.local',
|
email: "test@example.local",
|
||||||
password: 'admin1234',
|
password: "admin1234",
|
||||||
username: 'Test Test'
|
username: "Test Test",
|
||||||
});
|
});
|
||||||
|
|
||||||
const resLog: any = await request.post('/api/auth/login').send({
|
const resLog: any = await request.post("/api/auth/login").send({
|
||||||
email: 'test@example.local',
|
email: "test@example.local",
|
||||||
password: 'admin1234'
|
password: "admin1234",
|
||||||
});
|
});
|
||||||
if (resLog.statusCode != 200) throw 'error while logging in';
|
if (resLog.statusCode != 200) throw "error while logging in";
|
||||||
|
|
||||||
const body = JSON.parse(resLog.text);
|
const body = JSON.parse(resLog.text);
|
||||||
return Promise.resolve(body.data.jwt);
|
return Promise.resolve(body.data.jwt);
|
||||||
@ -31,194 +31,194 @@ export const getJWT = async () => {
|
|||||||
* @returns JWT cookie
|
* @returns JWT cookie
|
||||||
*/
|
*/
|
||||||
export async function login(): Promise<string> {
|
export async function login(): Promise<string> {
|
||||||
const res = await request.post('/api/v1/auth/signin').send({
|
const res = await request.post("/api/v1/auth/signin").send({
|
||||||
email: 'thisistest@host.local',
|
email: "thisistest@host.local",
|
||||||
password: 'Admin1234'
|
password: "Admin1234",
|
||||||
});
|
});
|
||||||
return res.headers['set-cookie'];
|
return res.headers["set-cookie"];
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function signup(): Promise<boolean> {
|
export async function signup(): Promise<boolean> {
|
||||||
const res = await request.post('/api/v1/auth/signup').send({
|
const res = await request.post("/api/v1/auth/signup").send({
|
||||||
email: 'thisistest@host.local',
|
email: "thisistest@host.local",
|
||||||
password: 'Admin1234',
|
password: "Admin1234",
|
||||||
username: 'Test Test'
|
username: "Test Test",
|
||||||
});
|
});
|
||||||
|
|
||||||
if (res.statusCode == 201) return true;
|
if (res.statusCode == 201) return true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('POST /api/v1/auth/signup', () => {
|
describe("POST /api/v1/auth/signup", () => {
|
||||||
describe('should drop validation error', () => {
|
describe("should drop validation error", () => {
|
||||||
it('should drop 400 (empty request))', async () => {
|
it("should drop 400 (empty request))", async () => {
|
||||||
const res: any = await request.post('/api/v1/auth/signup').send({});
|
const res: any = await request.post("/api/v1/auth/signup").send({});
|
||||||
expect(res.statusCode).toBe(400);
|
expect(res.statusCode).toBe(400);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should drop 400 (email))', async () => {
|
it("should drop 400 (email))", async () => {
|
||||||
const res: any = await request.post('/api/v1/auth/signup').send({
|
const res: any = await request.post("/api/v1/auth/signup").send({
|
||||||
email: '',
|
email: "",
|
||||||
username: 'User Admin',
|
username: "User Admin",
|
||||||
password: 'Admin1234'
|
password: "Admin1234",
|
||||||
});
|
});
|
||||||
console.log(res)
|
console.log(res);
|
||||||
const body = JSON.parse(res.text);
|
const body = JSON.parse(res.text);
|
||||||
expect(res.statusCode).toBe(400);
|
expect(res.statusCode).toBe(400);
|
||||||
expect(body.data.path).toBe('email');
|
expect(body.data.path).toBe("email");
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should drop 400 (username))', async () => {
|
it("should drop 400 (username))", async () => {
|
||||||
const res: any = await request.post('/api/v1/auth/signup').send({
|
const res: any = await request.post("/api/v1/auth/signup").send({
|
||||||
email: 'admin@localhost.local',
|
email: "admin@localhost.local",
|
||||||
username: '',
|
username: "",
|
||||||
password: 'Admin1234'
|
password: "Admin1234",
|
||||||
});
|
});
|
||||||
const body = JSON.parse(res.text);
|
const body = JSON.parse(res.text);
|
||||||
|
|
||||||
expect(res.statusCode).toBe(400);
|
expect(res.statusCode).toBe(400);
|
||||||
expect(body.data.path).toBe('username');
|
expect(body.data.path).toBe("username");
|
||||||
});
|
});
|
||||||
it('should drop 400 (password))', async () => {
|
it("should drop 400 (password))", async () => {
|
||||||
const res: any = await request.post('/api/v1/auth/signup').send({
|
const res: any = await request.post("/api/v1/auth/signup").send({
|
||||||
email: 'admin@localhost.local',
|
email: "admin@localhost.local",
|
||||||
username: 'User Admin',
|
username: "User Admin",
|
||||||
password: ''
|
password: "",
|
||||||
});
|
});
|
||||||
const body = JSON.parse(res.text);
|
const body = JSON.parse(res.text);
|
||||||
|
|
||||||
expect(res.statusCode).toBe(400);
|
expect(res.statusCode).toBe(400);
|
||||||
expect(body.data.path).toBe('password');
|
expect(body.data.path).toBe("password");
|
||||||
});
|
});
|
||||||
it('should drop 400 (password - min 8 chars', async () => {
|
it("should drop 400 (password - min 8 chars", async () => {
|
||||||
const res = await request.post('/api/v1/auth/signup').send({
|
const res = await request.post("/api/v1/auth/signup").send({
|
||||||
email: 'admin@localhost.local',
|
email: "admin@localhost.local",
|
||||||
username: 'User Admin',
|
username: "User Admin",
|
||||||
password: 'Admin12'
|
password: "Admin12",
|
||||||
});
|
});
|
||||||
const body = JSON.parse(res.text);
|
const body = JSON.parse(res.text);
|
||||||
|
|
||||||
expect(res.statusCode).toBe(400);
|
expect(res.statusCode).toBe(400);
|
||||||
expect(body.data.path).toBe('password');
|
expect(body.data.path).toBe("password");
|
||||||
});
|
});
|
||||||
it('should drop 400 (password - min 1 number', async () => {
|
it("should drop 400 (password - min 1 number", async () => {
|
||||||
const res = await request.post('/api/v1/auth/signup').send({
|
const res = await request.post("/api/v1/auth/signup").send({
|
||||||
email: 'admin@localhost.local',
|
email: "admin@localhost.local",
|
||||||
username: 'User Admin',
|
username: "User Admin",
|
||||||
password: 'Adminadmin'
|
password: "Adminadmin",
|
||||||
});
|
});
|
||||||
const body = JSON.parse(res.text);
|
const body = JSON.parse(res.text);
|
||||||
|
|
||||||
expect(res.statusCode).toBe(400);
|
expect(res.statusCode).toBe(400);
|
||||||
expect(body.data.path).toBe('password');
|
expect(body.data.path).toBe("password");
|
||||||
});
|
});
|
||||||
it('should drop 400 (password - min 1 uppercase', async () => {
|
it("should drop 400 (password - min 1 uppercase", async () => {
|
||||||
const res = await request.post('/api/v1/auth/signup').send({
|
const res = await request.post("/api/v1/auth/signup").send({
|
||||||
email: 'admin@localhost.local',
|
email: "admin@localhost.local",
|
||||||
username: 'User Admin',
|
username: "User Admin",
|
||||||
password: 'admin1234'
|
password: "admin1234",
|
||||||
});
|
});
|
||||||
const body = JSON.parse(res.text);
|
const body = JSON.parse(res.text);
|
||||||
|
|
||||||
expect(res.statusCode).toBe(400);
|
expect(res.statusCode).toBe(400);
|
||||||
expect(body.data.path).toBe('password');
|
expect(body.data.path).toBe("password");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should register an user', async () => {
|
test("should register an user", async () => {
|
||||||
const res: any = await request.post('/api/v1/auth/signup').send({
|
const res: any = await request.post("/api/v1/auth/signup").send({
|
||||||
email: 'thisistest@host.local',
|
email: "thisistest@host.local",
|
||||||
password: 'Admin1234',
|
password: "Admin1234",
|
||||||
username: 'Test Test'
|
username: "Test Test",
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(res.statusCode).toBe(201);
|
expect(res.statusCode).toBe(201);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('POST /api/v1/auth/signin', () => {
|
describe("POST /api/v1/auth/signin", () => {
|
||||||
const url = '/api/v1/auth/signin';
|
const url = "/api/v1/auth/signin";
|
||||||
|
|
||||||
describe('should drop an validation error', () => {
|
describe("should drop an validation error", () => {
|
||||||
it('should drop 400 (empty)', async () => {
|
it("should drop 400 (empty)", async () => {
|
||||||
const res = await request.post(url).send();
|
const res = await request.post(url).send();
|
||||||
|
|
||||||
expect(res.statusCode).toBe(400);
|
expect(res.statusCode).toBe(400);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should drop 400 (email)', async () => {
|
it("should drop 400 (email)", async () => {
|
||||||
const res = await request.post(url).send({
|
const res = await request.post(url).send({
|
||||||
password: 'Admin1234'
|
password: "Admin1234",
|
||||||
});
|
});
|
||||||
const body = JSON.parse(res.text);
|
const body = JSON.parse(res.text);
|
||||||
|
|
||||||
expect(res.statusCode).toBe(400);
|
expect(res.statusCode).toBe(400);
|
||||||
expect(body.data.path).toBe('email');
|
expect(body.data.path).toBe("email");
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should drop 400 (password)', async () => {
|
it("should drop 400 (password)", async () => {
|
||||||
const res = await request.post(url).send({
|
const res = await request.post(url).send({
|
||||||
email: 'thisistest@host.local'
|
email: "thisistest@host.local",
|
||||||
});
|
});
|
||||||
const body = JSON.parse(res.text);
|
const body = JSON.parse(res.text);
|
||||||
|
|
||||||
expect(res.statusCode).toBe(400);
|
expect(res.statusCode).toBe(400);
|
||||||
expect(body.data.path).toBe('password');
|
expect(body.data.path).toBe("password");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should drop 401', async () => {
|
test("should drop 401", async () => {
|
||||||
const res = await request.post(url).send({
|
const res = await request.post(url).send({
|
||||||
email: 'thisistest@host.local',
|
email: "thisistest@host.local",
|
||||||
password: 'Test12365465132'
|
password: "Test12365465132",
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(res.statusCode).toBe(401);
|
expect(res.statusCode).toBe(401);
|
||||||
expect(res.header['set-cookie'][0]).toContain("jwt=; Max-Age=0")
|
expect(res.header["set-cookie"][0]).toContain("jwt=; Max-Age=0");
|
||||||
expect(res.header['set-cookie'][1]).toContain("auth=false")
|
expect(res.header["set-cookie"][1]).toContain("auth=false");
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should login an user', async () => {
|
test("should login an user", async () => {
|
||||||
const res: any = await request.post(url).send({
|
const res: any = await request.post(url).send({
|
||||||
email: 'thisistest@host.local',
|
email: "thisistest@host.local",
|
||||||
password: 'Admin1234'
|
password: "Admin1234",
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(res.statusCode).toBe(200);
|
expect(res.statusCode).toBe(200);
|
||||||
expect(res.header['set-cookie'][0]).toContain("jwt=")
|
expect(res.header["set-cookie"][0]).toContain("jwt=");
|
||||||
expect(res.header['set-cookie'][1]).toContain("auth=true")
|
expect(res.header["set-cookie"][1]).toContain("auth=true");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('POST /api/v1/auth/logout', () => {
|
describe("POST /api/v1/auth/logout", () => {
|
||||||
const url = '/api/v1/auth/logout';
|
const url = "/api/v1/auth/logout";
|
||||||
test('should drop 401 error', async () => {
|
test("should drop 401 error", async () => {
|
||||||
const res = await request.post(url).send({});
|
const res = await request.post(url).send({});
|
||||||
expect(res.statusCode).toBe(401);
|
expect(res.statusCode).toBe(401);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should logout an user', async () => {
|
test("should logout an user", async () => {
|
||||||
const jwt = await login();
|
const jwt = await login();
|
||||||
const res = await request.post(url).set('Cookie', jwt).send();
|
const res = await request.post(url).set("Cookie", jwt).send();
|
||||||
|
|
||||||
expect(res.statusCode).toBe(200);
|
expect(res.statusCode).toBe(200);
|
||||||
expect(res.header['set-cookie'][0]).toContain("jwt=; Max-Age=0")
|
expect(res.header["set-cookie"][0]).toContain("jwt=; Max-Age=0");
|
||||||
expect(res.header['set-cookie'][1]).toContain("auth=false")
|
expect(res.header["set-cookie"][1]).toContain("auth=false");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('GET /api/v1/auth/status', () => {
|
describe("GET /api/v1/auth/status", () => {
|
||||||
const url = '/api/v1/auth/status';
|
const url = "/api/v1/auth/status";
|
||||||
test('should return login status 401', async () => {
|
test("should return login status 401", async () => {
|
||||||
const res = await request.get(url).send();
|
const res = await request.get(url).send();
|
||||||
expect(res.statusCode).toBe(401);
|
expect(res.statusCode).toBe(401);
|
||||||
});
|
});
|
||||||
test('should return login status 200', async () => {
|
test("should return login status 200", async () => {
|
||||||
const jwt = await login();
|
const jwt = await login();
|
||||||
const res = await request.get(url).set('Cookie', jwt).send();
|
const res = await request.get(url).set("Cookie", jwt).send();
|
||||||
expect(res.statusCode).toBe(200);
|
expect(res.statusCode).toBe(200);
|
||||||
expect(res.body.data.username).toBe("Test Test")
|
expect(res.body.data.username).toBe("Test Test");
|
||||||
expect(res.body.data.email).toBe("thisistest@host.local")
|
expect(res.body.data.email).toBe("thisistest@host.local");
|
||||||
expect(res.body.data.password).toBeUndefined()
|
expect(res.body.data.password).toBeUndefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
@ -1,166 +1,193 @@
|
|||||||
import supertest from 'supertest';
|
import supertest from "supertest";
|
||||||
import { app } from '../src/app';
|
import { app } from "../src/app";
|
||||||
import { login } from './auth.test';
|
import { login } from "./auth.test";
|
||||||
import { addExam, delExam, editExam } from '../src/validators/beerValidator';
|
import { addExam, delExam, editExam } from "../src/validators/beerValidator";
|
||||||
|
|
||||||
const request = supertest(app);
|
const request = supertest(app);
|
||||||
|
|
||||||
describe('POST /api/v1/beer/add', () => {
|
describe("POST /api/v1/beer/add", () => {
|
||||||
const url = '/api/v1/beer/add';
|
const url = "/api/v1/beer/add";
|
||||||
test('should drop 401 error', async () => {
|
test("should drop 401 error", async () => {
|
||||||
const res = await request.post(url).send({});
|
const res = await request.post(url).send({});
|
||||||
expect(res.statusCode).toBe(401);
|
expect(res.statusCode).toBe(401);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should drop 400 ()', async () => {
|
test("should drop 400 ()", async () => {
|
||||||
const jwt = await login();
|
const jwt = await login();
|
||||||
const res = await request.post(url).set('Cookie', jwt).send({});
|
const res = await request.post(url).set("Cookie", jwt).send({});
|
||||||
|
|
||||||
expect(res.statusCode).toBe(400);
|
expect(res.statusCode).toBe(400);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should drop 400 (brand)', async () => {
|
test("should drop 400 (name)", async () => {
|
||||||
|
const jwt = await login();
|
||||||
|
const body: any = { ...addExam };
|
||||||
|
delete body.name;
|
||||||
|
const res = await request.post(url).set("Cookie", jwt).send(body);
|
||||||
|
|
||||||
|
expect(res.statusCode).toBe(400);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should drop 400 (degree)", async () => {
|
||||||
|
const jwt = await login();
|
||||||
|
const body: any = { ...addExam };
|
||||||
|
delete body.degree;
|
||||||
|
const res = await request.post(url).set("Cookie", jwt).send(body);
|
||||||
|
|
||||||
|
expect(res.statusCode).toBe(400);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should drop 400 (packaging)", async () => {
|
||||||
|
const jwt = await login();
|
||||||
|
const body: any = { ...addExam };
|
||||||
|
delete body.packaging;
|
||||||
|
const res = await request.post(url).set("Cookie", jwt).send(body);
|
||||||
|
|
||||||
|
expect(res.statusCode).toBe(400);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should drop 400 (brand)", async () => {
|
||||||
const jwt = await login();
|
const jwt = await login();
|
||||||
const body: any = { ...addExam };
|
const body: any = { ...addExam };
|
||||||
delete body.brand;
|
delete body.brand;
|
||||||
const res = await request.post(url).set('Cookie', jwt).send(body);
|
const res = await request.post(url).set("Cookie", jwt).send(body);
|
||||||
|
|
||||||
expect(res.statusCode).toBe(400);
|
expect(res.statusCode).toBe(400);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should drop 201', async () => {
|
test("should drop 201", async () => {
|
||||||
const jwt = await login();
|
const jwt = await login();
|
||||||
const res = await request.post(url).set('Cookie', jwt).send(addExam);
|
const res = await request.post(url).set("Cookie", jwt).send(addExam);
|
||||||
|
|
||||||
expect(res.statusCode).toBe(201);
|
expect(res.statusCode).toBe(201);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('GET /api/v1/beer/get', () => {
|
describe("GET /api/v1/beer/get", () => {
|
||||||
const url = '/api/v1/beer/get';
|
const url = "/api/v1/beer/get";
|
||||||
|
|
||||||
test('should drop 401', async () => {
|
test("should drop 401", async () => {
|
||||||
const res = await request.get(url).send();
|
const res = await request.get(url).send();
|
||||||
|
|
||||||
expect(res.statusCode).toBe(401);
|
expect(res.statusCode).toBe(401);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should drop 200', async () => {
|
test("should drop 200", async () => {
|
||||||
const jwt = await login();
|
const jwt = await login();
|
||||||
const res = await request.get(url).set('Cookie', jwt).send();
|
const res = await request.get(url).set("Cookie", jwt).send();
|
||||||
|
|
||||||
expect(res.statusCode).toBe(200);
|
expect(res.statusCode).toBe(200);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('POST /api/v1/beer/del', () => {
|
describe("POST /api/v1/beer/del", () => {
|
||||||
const url = '/api/v1/beer/del';
|
const url = "/api/v1/beer/del";
|
||||||
|
|
||||||
test('should drop 401', async () => {
|
test("should drop 401", async () => {
|
||||||
const res = await request.post(url).send();
|
const res = await request.post(url).send();
|
||||||
|
|
||||||
expect(res.statusCode).toBe(401);
|
expect(res.statusCode).toBe(401);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should drop 400', async () => {
|
test("should drop 400", async () => {
|
||||||
const jwt = await login();
|
const jwt = await login();
|
||||||
const res = await request.post(url).set('Cookie', jwt).send(delExam);
|
const res = await request.post(url).set("Cookie", jwt).send(delExam);
|
||||||
|
|
||||||
expect(res.statusCode).toBe(400);
|
expect(res.statusCode).toBe(400);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should drop 400', async () => {
|
test("should drop 400", async () => {
|
||||||
const jwt = await login();
|
const jwt = await login();
|
||||||
const res = await request.post(url).set('Cookie', jwt).send({
|
const res = await request.post(url).set("Cookie", jwt).send({
|
||||||
_id: 'thisWillNotWork'
|
_id: "thisWillNotWork",
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(res.statusCode).toBe(400);
|
expect(res.statusCode).toBe(400);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should drop 200', async () => {
|
test("should drop 200", async () => {
|
||||||
const jwt = await login();
|
const jwt = await login();
|
||||||
const req = await request.post('/api/v1/beer/add').set('Cookie', jwt).send(addExam);
|
const req = await request.post("/api/v1/beer/add").set("Cookie", jwt).send(addExam);
|
||||||
const id = req.body.data._id;
|
const id = req.body.data._id;
|
||||||
const res = await request.post(url).set('Cookie', jwt).send({
|
const res = await request.post(url).set("Cookie", jwt).send({
|
||||||
_id: id
|
_id: id,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(res.statusCode).toBe(200);
|
expect(res.statusCode).toBe(200);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('POST /api/v1/beer/edit', () => {
|
describe("POST /api/v1/beer/edit", () => {
|
||||||
const url = '/api/v1/beer/edit';
|
const url = "/api/v1/beer/edit";
|
||||||
|
|
||||||
test('should drop 401', async () => {
|
test("should drop 401", async () => {
|
||||||
const res = await request.post(url).send();
|
const res = await request.post(url).send();
|
||||||
|
|
||||||
expect(res.statusCode).toBe(401);
|
expect(res.statusCode).toBe(401);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should drop 400', async () => {
|
test("should drop 400", async () => {
|
||||||
const jwt = await login();
|
const jwt = await login();
|
||||||
|
|
||||||
const payload: any = { ...editExam };
|
const payload: any = { ...editExam };
|
||||||
delete payload._id;
|
delete payload._id;
|
||||||
|
|
||||||
const res = await request.post(url).set('Cookie', jwt).send(payload);
|
const res = await request.post(url).set("Cookie", jwt).send(payload);
|
||||||
|
|
||||||
expect(res.statusCode).toBe(400);
|
expect(res.statusCode).toBe(400);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should drop 200', async () => {
|
test("should drop 200", async () => {
|
||||||
const jwt = await login();
|
const jwt = await login();
|
||||||
|
|
||||||
const payload: any = { ...editExam };
|
const payload: any = { ...editExam };
|
||||||
delete payload.name;
|
delete payload.name;
|
||||||
|
|
||||||
const res = await request.post(url).set('Cookie', jwt).send(payload);
|
const res = await request.post(url).set("Cookie", jwt).send(payload);
|
||||||
|
|
||||||
expect(res.statusCode).toBe(200);
|
expect(res.statusCode).toBe(200);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should drop 200', async () => {
|
test("should drop 200", async () => {
|
||||||
const jwt = await login();
|
const jwt = await login();
|
||||||
|
|
||||||
const payload: any = { ...editExam };
|
const payload: any = { ...editExam };
|
||||||
delete payload.degree;
|
delete payload.degree;
|
||||||
|
|
||||||
const res = await request.post(url).set('Cookie', jwt).send(payload);
|
const res = await request.post(url).set("Cookie", jwt).send(payload);
|
||||||
|
|
||||||
expect(res.statusCode).toBe(200);
|
expect(res.statusCode).toBe(200);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should drop 200', async () => {
|
test("should drop 200", async () => {
|
||||||
const jwt = await login();
|
const jwt = await login();
|
||||||
|
|
||||||
const payload: any = { ...editExam };
|
const payload: any = { ...editExam };
|
||||||
delete payload.packaging;
|
delete payload.packaging;
|
||||||
|
|
||||||
const res = await request.post(url).set('Cookie', jwt).send(payload);
|
const res = await request.post(url).set("Cookie", jwt).send(payload);
|
||||||
|
|
||||||
expect(res.statusCode).toBe(200);
|
expect(res.statusCode).toBe(200);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should drop 200', async () => {
|
test("should drop 200", async () => {
|
||||||
const jwt = await login();
|
const jwt = await login();
|
||||||
|
|
||||||
const payload: any = { ...editExam };
|
const payload: any = { ...editExam };
|
||||||
delete payload.brand;
|
delete payload.brand;
|
||||||
|
|
||||||
const res = await request.post(url).set('Cookie', jwt).send(payload);
|
const res = await request.post(url).set("Cookie", jwt).send(payload);
|
||||||
|
|
||||||
expect(res.statusCode).toBe(200);
|
expect(res.statusCode).toBe(200);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should drop 200', async () => {
|
test("should drop 200", async () => {
|
||||||
const jwt = await login();
|
const jwt = await login();
|
||||||
const req = await request.post('/api/v1/beer/add').set('Cookie', jwt).send(addExam);
|
const req = await request.post("/api/v1/beer/add").set("Cookie", jwt).send(addExam);
|
||||||
const _id = req.body.data._id;
|
const _id = req.body.data._id;
|
||||||
const payload = { ...editExam, _id: _id };
|
const payload = { ...editExam, _id: _id };
|
||||||
|
|
||||||
let res = await request.post(url).set('Cookie', jwt).send(payload);
|
let res = await request.post(url).set("Cookie", jwt).send(payload);
|
||||||
|
|
||||||
delete res.body.data._id;
|
delete res.body.data._id;
|
||||||
delete res.body.data.__v;
|
delete res.body.data.__v;
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import request from 'supertest';
|
import request from "supertest";
|
||||||
import { app } from '../src/app';
|
import { app } from "../src/app";
|
||||||
|
|
||||||
describe('GET /api/v1', () => {
|
describe("GET /api/v1", () => {
|
||||||
describe('should return json with docs', () => {
|
describe("should return json with docs", () => {
|
||||||
test('should respond with a 200 status code', async () => {
|
test("should respond with a 200 status code", async () => {
|
||||||
const response = await request(app).get('/api/v1').send({});
|
const response = await request(app).get("/api/v1").send({});
|
||||||
expect(response.headers['content-type']).toMatch(/json/);
|
expect(response.headers["content-type"]).toMatch(/json/);
|
||||||
expect(response.statusCode).toBe(200);
|
expect(response.statusCode).toBe(200);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
99
api/tests/review.test.ts
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
import supertest from "supertest";
|
||||||
|
import { app } from "../src/app";
|
||||||
|
import { login } from "./auth.test";
|
||||||
|
import { addExam, delExam } from "../src/validators/reviewValidator";
|
||||||
|
|
||||||
|
const request = supertest(app);
|
||||||
|
|
||||||
|
describe("POST /api/v1/review/add", () => {
|
||||||
|
const url = "/api/v1/review/add";
|
||||||
|
test("should drop 401 error", async () => {
|
||||||
|
const res = await request.post(url).send({});
|
||||||
|
expect(res.statusCode).toBe(401);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should drop 400 ()", async () => {
|
||||||
|
const jwt = await login();
|
||||||
|
const res = await request.post(url).set("Cookie", jwt).send({});
|
||||||
|
|
||||||
|
expect(res.statusCode).toBe(400);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should drop 400 (foam)", async () => {
|
||||||
|
const jwt = await login();
|
||||||
|
const body: any = { ...addExam };
|
||||||
|
delete body.foam;
|
||||||
|
const res = await request.post(url).set("Cookie", jwt).send(body);
|
||||||
|
|
||||||
|
expect(res.statusCode).toBe(400);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should drop 400 (bitter_sweetness)", async () => {
|
||||||
|
const jwt = await login();
|
||||||
|
const body: any = { ...addExam };
|
||||||
|
delete body.bitter_sweetness;
|
||||||
|
const res = await request.post(url).set("Cookie", jwt).send(body);
|
||||||
|
|
||||||
|
expect(res.statusCode).toBe(400);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should drop 400 (taste)", async () => {
|
||||||
|
const jwt = await login();
|
||||||
|
const body: any = { ...addExam };
|
||||||
|
delete body.taste;
|
||||||
|
const res = await request.post(url).set("Cookie", jwt).send(body);
|
||||||
|
|
||||||
|
expect(res.statusCode).toBe(400);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should drop 400 (packaging)", async () => {
|
||||||
|
const jwt = await login();
|
||||||
|
const body: any = { ...addExam };
|
||||||
|
delete body.packaging;
|
||||||
|
const res = await request.post(url).set("Cookie", jwt).send(body);
|
||||||
|
|
||||||
|
expect(res.statusCode).toBe(400);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should drop 400 (sourness)", async () => {
|
||||||
|
const jwt = await login();
|
||||||
|
const body: any = { ...addExam };
|
||||||
|
delete body.sourness;
|
||||||
|
const res = await request.post(url).set("Cookie", jwt).send(body);
|
||||||
|
|
||||||
|
expect(res.statusCode).toBe(400);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should drop 400 (would_again)", async () => {
|
||||||
|
const jwt = await login();
|
||||||
|
const body: any = { ...addExam };
|
||||||
|
delete body.would_again;
|
||||||
|
const res = await request.post(url).set("Cookie", jwt).send(body);
|
||||||
|
|
||||||
|
expect(res.statusCode).toBe(400);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should drop 201", async () => {
|
||||||
|
const jwt = await login();
|
||||||
|
const res = await request.post(url).set("Cookie", jwt).send(addExam);
|
||||||
|
|
||||||
|
expect(res.statusCode).toBe(201);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("GET /api/v1/review/get", () => {
|
||||||
|
const url = "/api/v1/review/get";
|
||||||
|
|
||||||
|
test("should drop 401", async () => {
|
||||||
|
const res = await request.get(url).send();
|
||||||
|
|
||||||
|
expect(res.statusCode).toBe(401);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should drop 200", async () => {
|
||||||
|
const jwt = await login();
|
||||||
|
const res = await request.get(url).set("Cookie", jwt).send();
|
||||||
|
|
||||||
|
expect(res.statusCode).toBe(200);
|
||||||
|
});
|
||||||
|
});
|
1
frontend/.gitignore
vendored
@ -37,3 +37,4 @@ yarn-error.*
|
|||||||
.env
|
.env
|
||||||
|
|
||||||
.vscode/
|
.vscode/
|
||||||
|
*.swp
|
||||||
|
@ -25,6 +25,17 @@
|
|||||||
"web": {
|
"web": {
|
||||||
"favicon": "./assets/favicon.png"
|
"favicon": "./assets/favicon.png"
|
||||||
},
|
},
|
||||||
"plugins": ["expo-router"]
|
"plugins": [
|
||||||
|
"expo-router",
|
||||||
|
[
|
||||||
|
"expo-image-picker",
|
||||||
|
{
|
||||||
|
"photosPermission": "The app accesses your photos to let you share them with your friends.",
|
||||||
|
"cameraPermission": "The app accesses your camera to let you take photos of your beer and share them with your friends.",
|
||||||
|
"microphonePermission": "The app accesses your microphone to let you record audio and share it with your friends."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"expo-secure-store"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@ export default function TabLayout() {
|
|||||||
backgroundColor: colors.darkSecondary,
|
backgroundColor: colors.darkSecondary,
|
||||||
},
|
},
|
||||||
tabBarActiveTintColor: colors.gold,
|
tabBarActiveTintColor: colors.gold,
|
||||||
headerShown: false,
|
headerShown: true,
|
||||||
}}
|
}}
|
||||||
sceneContainerStyle={{ backgroundColor: colors.dark }}
|
sceneContainerStyle={{ backgroundColor: colors.dark }}
|
||||||
>
|
>
|
||||||
@ -43,7 +43,7 @@ export default function TabLayout() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Tabs.Screen
|
<Tabs.Screen
|
||||||
name="review"
|
name="review/index"
|
||||||
options={{
|
options={{
|
||||||
title: "Reviews",
|
title: "Reviews",
|
||||||
tabBarIcon: ({ color }) => (
|
tabBarIcon: ({ color }) => (
|
||||||
@ -62,7 +62,14 @@ export default function TabLayout() {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Hide following routes from bottom bar */}
|
{/* Hide following routes from bottom bar */}
|
||||||
<Tabs.Screen name="beer/add" options={{ href: null }} />
|
<Tabs.Screen
|
||||||
|
name="beer/add"
|
||||||
|
options={{ href: null, title: "Add beer" }}
|
||||||
|
/>
|
||||||
|
<Tabs.Screen
|
||||||
|
name="review/add/[beer_id]"
|
||||||
|
options={{ href: null, title: "Add review" }}
|
||||||
|
/>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
@ -1,10 +1,263 @@
|
|||||||
import { View } from "react-native";
|
import { StyleSheet, TextInput, View, Image } from "react-native";
|
||||||
|
import { useState } from "react";
|
||||||
|
import Button from "@components/Button";
|
||||||
import Text from "@components/Text";
|
import Text from "@components/Text";
|
||||||
|
import { colors } from "@components/style";
|
||||||
|
import * as ImagePicker from "expo-image-picker";
|
||||||
|
import DropDownPicker from "react-native-dropdown-picker";
|
||||||
|
const DropdownTheme = require("@components/DropdownTheme");
|
||||||
|
import { Platform } from "react-native";
|
||||||
|
|
||||||
export default function BeerAdd() {
|
export default function BeerAdd() {
|
||||||
|
const [b_name, setBName] = useState("");
|
||||||
|
const [b_degree, setBDegree] = useState("");
|
||||||
|
const [b_packaging, setBPackaging] = useState(null);
|
||||||
|
const [b_brand, setBBrand] = useState("");
|
||||||
|
const [image, setImage] = useState(null);
|
||||||
|
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const [items, setItems] = useState([
|
||||||
|
{ label: "Tank beer", value: "tank" },
|
||||||
|
{ label: "Keg beer", value: "keg" },
|
||||||
|
{ label: "Glass bottle", value: "glass" },
|
||||||
|
{ label: "Can", value: "can" },
|
||||||
|
{ label: "PET bottle", value: "pet" },
|
||||||
|
]);
|
||||||
|
|
||||||
|
DropDownPicker.addTheme("DropdownTheme", DropdownTheme);
|
||||||
|
DropDownPicker.setTheme("DropdownTheme");
|
||||||
|
|
||||||
|
ImagePicker.getCameraPermissionsAsync(); //check if the user has granted permission to access the camera
|
||||||
|
const pickImage = async () => {
|
||||||
|
const permissionResult =
|
||||||
|
await ImagePicker.requestMediaLibraryPermissionsAsync();
|
||||||
|
|
||||||
|
if (permissionResult.granted === false) {
|
||||||
|
alert("You've refused to allow this appp to access your photos!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// No permissions request is necessary for launching the image library
|
||||||
|
const result = await ImagePicker.launchImageLibraryAsync({
|
||||||
|
mediaTypes: ImagePicker.MediaTypeOptions.Images,
|
||||||
|
allowsEditing: true,
|
||||||
|
aspect: [3, 4],
|
||||||
|
// quality: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Explore the result
|
||||||
|
console.log(result);
|
||||||
|
|
||||||
|
if (!result.canceled) {
|
||||||
|
setImage(result.assets[0]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const openCamera = async () => {
|
||||||
|
// Ask the user for the permission to access the camera
|
||||||
|
const permissionResult = await ImagePicker.requestCameraPermissionsAsync();
|
||||||
|
|
||||||
|
if (permissionResult.granted === false) {
|
||||||
|
alert("You've refused to allow this app to access your camera!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await ImagePicker.launchCameraAsync();
|
||||||
|
|
||||||
|
// Explore the result
|
||||||
|
console.log(result);
|
||||||
|
|
||||||
|
if (!result.canceled) {
|
||||||
|
setImage(result.assets[0]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function validateDegreeInput(text) {
|
||||||
|
let newText = "";
|
||||||
|
let numbers = "0123456789.";
|
||||||
|
|
||||||
|
for (var i = 0; i < text.length; i++) {
|
||||||
|
if (numbers.indexOf(text[i]) > -1) {
|
||||||
|
newText = newText + text[i];
|
||||||
|
setBDegree(newText);
|
||||||
|
} else {
|
||||||
|
// your call back function
|
||||||
|
alert("Please enter numbers only.");
|
||||||
|
setBDegree("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function dataURItoBlob(dataURI) {
|
||||||
|
// convert base64/URLEncoded data component to raw binary data held in a string
|
||||||
|
var byteString;
|
||||||
|
if (dataURI.split(",")[0].indexOf("base64") >= 0)
|
||||||
|
byteString = atob(dataURI.split(",")[1]);
|
||||||
|
else byteString = unescape(dataURI.split(",")[1]);
|
||||||
|
|
||||||
|
// separate out the mime component
|
||||||
|
var mimeString = dataURI.split(",")[0].split(":")[1].split(";")[0];
|
||||||
|
|
||||||
|
// write the bytes of the string to a typed array
|
||||||
|
var ia = new Uint8Array(byteString.length);
|
||||||
|
for (var i = 0; i < byteString.length; i++) {
|
||||||
|
ia[i] = byteString.charCodeAt(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Blob([ia], { type: mimeString });
|
||||||
|
}
|
||||||
|
|
||||||
|
async function addBeer() {
|
||||||
|
// TODO: after the request - redirect to /beer/{new_beer_id}?; plus some modal about successful state
|
||||||
|
|
||||||
|
const data = new FormData();
|
||||||
|
if (Platform.OS == "web") {
|
||||||
|
// TODO: On phone its imposibble to upload an image
|
||||||
|
data.append("photos", dataURItoBlob(image.uri));
|
||||||
|
}
|
||||||
|
data.append("brand", b_brand);
|
||||||
|
data.append("name", b_name);
|
||||||
|
data.append("degree", b_degree);
|
||||||
|
data.append("packaging", "can");
|
||||||
|
|
||||||
|
try {
|
||||||
|
const req = await fetch(`${process.env.EXPO_PUBLIC_API_URL}/beer/add`, {
|
||||||
|
method: "POST",
|
||||||
|
credentials: "include",
|
||||||
|
body: data,
|
||||||
|
});
|
||||||
|
const res = await req.json();
|
||||||
|
|
||||||
|
if (res.code == 201 && res.data._id) {
|
||||||
|
// TODO: reditect using expo router
|
||||||
|
// window.location.href = `/beer/${res.data._id}`;
|
||||||
|
alert("Added");
|
||||||
|
} else {
|
||||||
|
alert(
|
||||||
|
"Beer was not added successfully. Please check your data and try again.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
alert(
|
||||||
|
"Beer was not added successfully. Please check your data and try again.",
|
||||||
|
);
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={{ justifyContent: "center", alignItems: "center", flex: 1 }}>
|
<View style={styles.container}>
|
||||||
<Text>Beer Add</Text>
|
<View style={styles.form}>
|
||||||
|
<Text style={styles.text}>
|
||||||
|
Spill your thoughts about the beer you just sipped!
|
||||||
|
</Text>
|
||||||
|
<TextInput
|
||||||
|
style={styles.input}
|
||||||
|
placeholder="Name"
|
||||||
|
value={b_name}
|
||||||
|
onChangeText={(text) => setBName(text)}
|
||||||
|
placeholderTextColor="#aaaaaa"
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
style={styles.input}
|
||||||
|
placeholder="Brand"
|
||||||
|
value={b_brand}
|
||||||
|
onChangeText={(text) => setBBrand(text)}
|
||||||
|
placeholderTextColor="#aaaaaa"
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
style={styles.input}
|
||||||
|
placeholder="Degree"
|
||||||
|
value={b_degree}
|
||||||
|
onChangeText={(text) => validateDegreeInput(text)}
|
||||||
|
placeholderTextColor="#aaaaaa"
|
||||||
|
keyboardType="numeric"
|
||||||
|
maxLength={3}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<DropDownPicker
|
||||||
|
open={open}
|
||||||
|
value={b_packaging}
|
||||||
|
items={items}
|
||||||
|
setOpen={setOpen}
|
||||||
|
setValue={setBPackaging}
|
||||||
|
setItems={setItems}
|
||||||
|
placeholder={"What are you drinking from?"}
|
||||||
|
theme="DropdownTheme"
|
||||||
|
//searchable={true} //maybe we can use it later...
|
||||||
|
/>
|
||||||
|
|
||||||
|
<View style={styles.imageContainer}>
|
||||||
|
<Button
|
||||||
|
title="Open gallery"
|
||||||
|
onPress={pickImage}
|
||||||
|
buttonStyle={styles.imageButton}
|
||||||
|
textStyle={styles.imageTextButton}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{Platform.OS != "web" ? (
|
||||||
|
<Button
|
||||||
|
title="Open camera"
|
||||||
|
onPress={openCamera}
|
||||||
|
buttonStyle={styles.imageButton}
|
||||||
|
textStyle={styles.imageTextButton}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
false
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
{image && <Image source={{ uri: image.uri }} style={styles.image} />}
|
||||||
|
<Button title="Add beer" color={colors.gold} onPress={addBeer} />
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
alignItems: "center",
|
||||||
|
display: "flex",
|
||||||
|
},
|
||||||
|
form: {
|
||||||
|
alignItems: "center",
|
||||||
|
gap: 15,
|
||||||
|
width: "80%",
|
||||||
|
},
|
||||||
|
input: {
|
||||||
|
height: "auto",
|
||||||
|
width: "100%",
|
||||||
|
borderColor: "gray",
|
||||||
|
borderWidth: 1,
|
||||||
|
borderRadius: 10,
|
||||||
|
padding: 13,
|
||||||
|
color: "#fff",
|
||||||
|
},
|
||||||
|
imageContainer: {
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "row",
|
||||||
|
gap: 10,
|
||||||
|
},
|
||||||
|
imageButton: {
|
||||||
|
backgroundColor: colors.dark,
|
||||||
|
borderColor: "gray",
|
||||||
|
borderWidth: 1,
|
||||||
|
borderRadius: 10,
|
||||||
|
},
|
||||||
|
imageTextButton: {
|
||||||
|
color: colors.white,
|
||||||
|
},
|
||||||
|
image: {
|
||||||
|
width: 150,
|
||||||
|
height: 150,
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
color: colors.white,
|
||||||
|
fontSize: 24,
|
||||||
|
textAlign: "center",
|
||||||
|
paddingBottom: "3%",
|
||||||
|
paddingTop: "10%",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
@ -1,21 +1,128 @@
|
|||||||
import { View } from "react-native";
|
import {
|
||||||
|
StyleSheet,
|
||||||
|
View,
|
||||||
|
FlatList,
|
||||||
|
Dimensions,
|
||||||
|
StatusBar,
|
||||||
|
Image,
|
||||||
|
} from "react-native";
|
||||||
import Text from "@components/Text";
|
import Text from "@components/Text";
|
||||||
import Button from "@components/Button";
|
import Button from "@components/Button";
|
||||||
import { colors } from "@components/style";
|
import { colors } from "@components/style";
|
||||||
import { router } from "expo-router";
|
import { router } from "expo-router";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
// import { FlashList } from "@shopify/flash-list";
|
||||||
|
|
||||||
export default function Tab() {
|
export default function Tab() {
|
||||||
return (
|
const API_HOST = process.env.EXPO_PUBLIC_API_URL.replace("/api/v1", "");
|
||||||
<View style={{ justifyContent: "center", alignItems: "center", flex: 1 }}>
|
const [data, setData] = useState([]);
|
||||||
<Text>Tab BEER</Text>
|
useEffect(() => {
|
||||||
|
fetchData();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
async function fetchData() {
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${process.env.EXPO_PUBLIC_API_URL}/beer/get`, {
|
||||||
|
method: "GET",
|
||||||
|
credentials: "include",
|
||||||
|
});
|
||||||
|
const data = await res.json();
|
||||||
|
setData(data.data);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
alert("Something went wrong");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={styles.container}>
|
||||||
<Button
|
<Button
|
||||||
title="Add Beers"
|
title="Add new beer"
|
||||||
color={colors.gold}
|
color={colors.gold}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
router.replace("/beer/add");
|
router.replace("/beer/add");
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* <FlashList
|
||||||
|
data={data}
|
||||||
|
estimatedItemSize={100}
|
||||||
|
keyExtractor={(item) => String(item._id)}
|
||||||
|
renderItem={({ item }) => (
|
||||||
|
<View style={styles.item}>
|
||||||
|
<Text>Name: {item.name}</Text>
|
||||||
|
<Text>Brand: {item.brand}</Text>
|
||||||
|
<Text>Degree: {item.degree}</Text>
|
||||||
|
<Text>Packaging: {item.packaging}</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
/> */}
|
||||||
|
|
||||||
|
<FlatList
|
||||||
|
data={data}
|
||||||
|
style={styles.beerList}
|
||||||
|
keyExtractor={(item) => String(item._id)}
|
||||||
|
renderItem={({ item }) => (
|
||||||
|
<View style={styles.item}>
|
||||||
|
<Image
|
||||||
|
source={
|
||||||
|
item.imgs[0]
|
||||||
|
? {
|
||||||
|
uri: `${API_HOST}/public/uploads/${item.imgs[0]}`,
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
uri: "https://imagesvc.meredithcorp.io/v3/mm/image?url=https:%2F%2Fstatic.onecms.io%2Fwp-content%2Fuploads%2Fsites%2F44%2F2020%2F09%2F29%2Flight-beer.jpg",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
style={styles.itemImg}
|
||||||
|
/>
|
||||||
|
<View style={styles.itemDesc}>
|
||||||
|
<Text>Name: {item.name}</Text>
|
||||||
|
<Text>Brand: {item.brand}</Text>
|
||||||
|
<Text>Degree: {item.degree}</Text>
|
||||||
|
<Text>Packaging: {item.packaging}</Text>
|
||||||
|
</View>
|
||||||
|
<View style={styles.itemAddReview}>
|
||||||
|
<Button
|
||||||
|
title="Add review"
|
||||||
|
color={colors.gold}
|
||||||
|
onPress={() => {
|
||||||
|
router.push(`/review/add/${item._id}`);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
marginTop: "2%",
|
||||||
|
},
|
||||||
|
beerList: {
|
||||||
|
width: "100%",
|
||||||
|
paddingHorizontal: "15%",
|
||||||
|
marginTop: "2%",
|
||||||
|
},
|
||||||
|
item: {
|
||||||
|
borderColor: "gray",
|
||||||
|
borderWidth: 1,
|
||||||
|
borderRadius: 10,
|
||||||
|
padding: 13,
|
||||||
|
marginBottom: "5%",
|
||||||
|
},
|
||||||
|
itemImg: {
|
||||||
|
height: 300,
|
||||||
|
resizeMode: "contain",
|
||||||
|
},
|
||||||
|
itemDesc: {
|
||||||
|
alignItems: "center",
|
||||||
|
paddingBottom: "2%",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
@ -2,7 +2,7 @@ import { StyleSheet, Text, View } from "react-native";
|
|||||||
import Button from "@components/Button";
|
import Button from "@components/Button";
|
||||||
import { colors } from "@components/style";
|
import { colors } from "@components/style";
|
||||||
import { useAuth } from "@context/AuthContext";
|
import { useAuth } from "@context/AuthContext";
|
||||||
import Link from "@components/Link";
|
import { router } from "expo-router";
|
||||||
|
|
||||||
export default function Index() {
|
export default function Index() {
|
||||||
const { onLogout, authState } = useAuth();
|
const { onLogout, authState } = useAuth();
|
||||||
@ -10,8 +10,19 @@ export default function Index() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
<Text style={styles.h1}>Welcome {user.username}</Text>
|
<Text style={styles.h1}>Welcome {user.username}!</Text>
|
||||||
<Link href="/beer">Go to BEER</Link>
|
<Text style={styles.h2}>We hope, you're enjoying your beer.</Text>
|
||||||
|
<View style={styles.button}>
|
||||||
|
<Button
|
||||||
|
style={styles.button}
|
||||||
|
title="Add beer!"
|
||||||
|
color={colors.gold}
|
||||||
|
textColor={colors.white}
|
||||||
|
onPress={() => {
|
||||||
|
router.push("/beer");
|
||||||
|
}}
|
||||||
|
></Button>
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -27,4 +38,16 @@ const styles = StyleSheet.create({
|
|||||||
textAlign: "center",
|
textAlign: "center",
|
||||||
paddingTop: "20%",
|
paddingTop: "20%",
|
||||||
},
|
},
|
||||||
|
h2: {
|
||||||
|
color: "#FFF",
|
||||||
|
fontSize: 20,
|
||||||
|
textAlign: "center",
|
||||||
|
paddingTop: "1%",
|
||||||
|
},
|
||||||
|
button: {
|
||||||
|
color: colors.charcoal,
|
||||||
|
fontSize: 30,
|
||||||
|
textAlign: "center",
|
||||||
|
paddingTop: "2%",
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
@ -1,10 +0,0 @@
|
|||||||
import { View } from "react-native";
|
|
||||||
import Text from "@components/Text";
|
|
||||||
|
|
||||||
export default function Tab() {
|
|
||||||
return (
|
|
||||||
<View style={{ justifyContent: "center", alignItems: "center", flex: 1 }}>
|
|
||||||
<Text>Tab REVIEW</Text>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
521
frontend/app/(app)/(tabs)/review/add/[beer_id].js
Normal file
@ -0,0 +1,521 @@
|
|||||||
|
import { StyleSheet, TextInput, View, Image } from "react-native";
|
||||||
|
import { useCallback, useState } from "react";
|
||||||
|
import Button from "@components/Button";
|
||||||
|
import Text from "@components/Text";
|
||||||
|
import { colors } from "@components/style";
|
||||||
|
import * as ImagePicker from "expo-image-picker";
|
||||||
|
import DropDownPicker from "react-native-dropdown-picker";
|
||||||
|
const DropdownTheme = require("@components/DropdownTheme");
|
||||||
|
import { Platform } from "react-native";
|
||||||
|
import { useLocalSearchParams } from "expo-router";
|
||||||
|
|
||||||
|
export default function reviewAdd() {
|
||||||
|
// States for each dropdown
|
||||||
|
const routeParams = useLocalSearchParams();
|
||||||
|
const [openFoam, setOpenFoam] = useState(false);
|
||||||
|
const [openBitterSweetness, setOpenBitterSweetness] = useState(false);
|
||||||
|
const [openTaste, setOpenTaste] = useState(false);
|
||||||
|
const [openPackaging, setOpenPackaging] = useState(false);
|
||||||
|
const [openSourness, setOpenSourness] = useState(false);
|
||||||
|
const [openAgain, setOpenAgain] = useState(false);
|
||||||
|
|
||||||
|
// foam
|
||||||
|
const [itemFoam, setFoamValue] = useState(null);
|
||||||
|
const [foam, setFoam] = useState([
|
||||||
|
{
|
||||||
|
label: "Bad",
|
||||||
|
value: "1",
|
||||||
|
icon: () => (
|
||||||
|
<Image
|
||||||
|
source={require("@assets/smileys/smiley-x-eyes.png")}
|
||||||
|
style={styles.iconStyle}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Medium",
|
||||||
|
value: "2",
|
||||||
|
icon: () => (
|
||||||
|
<Image
|
||||||
|
source={require("@assets/smileys/smiley-meh.png")}
|
||||||
|
style={styles.iconStyle}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Excellent",
|
||||||
|
value: "3",
|
||||||
|
icon: () => (
|
||||||
|
<Image
|
||||||
|
source={require("@assets/smileys/smiley.png")}
|
||||||
|
style={styles.iconStyle}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
// bitter / sweetness
|
||||||
|
const [itemBitter_sweetness, setBitter_sweetnessValue] = useState(null);
|
||||||
|
const [bitter_sweetness, setBitter_sweetness] = useState([
|
||||||
|
{
|
||||||
|
label: "Bitter",
|
||||||
|
value: "1",
|
||||||
|
icon: () => (
|
||||||
|
<Image
|
||||||
|
source={require("@assets/smileys/smiley-x-eyes.png")}
|
||||||
|
style={styles.iconStyle}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Medium",
|
||||||
|
value: "2",
|
||||||
|
icon: () => (
|
||||||
|
<Image
|
||||||
|
source={require("@assets/smileys/smiley-meh.png")}
|
||||||
|
style={styles.iconStyle}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Sweet",
|
||||||
|
value: "3",
|
||||||
|
icon: () => (
|
||||||
|
<Image
|
||||||
|
source={require("@assets/smileys/smiley.png")}
|
||||||
|
style={styles.iconStyle}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
// taste
|
||||||
|
const [itemTaste, setTasteValue] = useState(null);
|
||||||
|
const [taste, setTaste] = useState([
|
||||||
|
{
|
||||||
|
label: "Disgust",
|
||||||
|
value: "1",
|
||||||
|
icon: () => (
|
||||||
|
<Image
|
||||||
|
source={require("@assets/smileys/smiley-blank.png")}
|
||||||
|
style={styles.iconStyle}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Not great, not terrible",
|
||||||
|
value: "2",
|
||||||
|
icon: () => (
|
||||||
|
<Image
|
||||||
|
source={require("@assets/smileys/smiley-nervous.png")}
|
||||||
|
style={styles.iconStyle}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Good",
|
||||||
|
value: "3",
|
||||||
|
icon: () => (
|
||||||
|
<Image
|
||||||
|
source={require("@assets/smileys/smiley-meh.png")}
|
||||||
|
style={styles.iconStyle}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Why not",
|
||||||
|
value: "4",
|
||||||
|
icon: () => (
|
||||||
|
<Image
|
||||||
|
source={require("@assets/smileys/smiley-wink.png")}
|
||||||
|
style={styles.iconStyle}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Excellent!",
|
||||||
|
value: "5",
|
||||||
|
icon: () => (
|
||||||
|
<Image
|
||||||
|
source={require("@assets/smileys/smiley.png")}
|
||||||
|
style={styles.iconStyle}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
// packaging
|
||||||
|
const [itemPackaging, setPackagingValue] = useState(null);
|
||||||
|
const [packaging, setPackaging] = useState([
|
||||||
|
{
|
||||||
|
label: "Disgust",
|
||||||
|
value: "1",
|
||||||
|
icon: () => (
|
||||||
|
<Image
|
||||||
|
source={require("@assets/smileys/smiley-blank.png")}
|
||||||
|
style={styles.iconStyle}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Not great, not terrible",
|
||||||
|
value: "2",
|
||||||
|
icon: () => (
|
||||||
|
<Image
|
||||||
|
source={require("@assets/smileys/smiley-nervous.png")}
|
||||||
|
style={styles.iconStyle}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Good",
|
||||||
|
value: "3",
|
||||||
|
icon: () => (
|
||||||
|
<Image
|
||||||
|
source={require("@assets/smileys/smiley-meh.png")}
|
||||||
|
style={styles.iconStyle}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Why not",
|
||||||
|
value: "4",
|
||||||
|
icon: () => (
|
||||||
|
<Image
|
||||||
|
source={require("@assets/smileys/smiley-wink.png")}
|
||||||
|
style={styles.iconStyle}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Excellent!",
|
||||||
|
value: "5",
|
||||||
|
icon: () => (
|
||||||
|
<Image
|
||||||
|
source={require("@assets/smileys/smiley.png")}
|
||||||
|
style={styles.iconStyle}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
// sourness
|
||||||
|
const [itemSourness, setSournessValue] = useState(null);
|
||||||
|
const [sourness, setSourness] = useState([
|
||||||
|
{
|
||||||
|
label: "True",
|
||||||
|
value: true,
|
||||||
|
icon: () => (
|
||||||
|
<Image
|
||||||
|
source={require("@assets/smileys/smiley-blank.png")}
|
||||||
|
style={styles.iconStyle}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "False",
|
||||||
|
value: false,
|
||||||
|
icon: () => (
|
||||||
|
<Image
|
||||||
|
source={require("@assets/smileys/smiley-nervous.png")}
|
||||||
|
style={styles.iconStyle}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
// would again
|
||||||
|
const [itemAgain, setAgainValue] = useState(null);
|
||||||
|
const [again, setAgain] = useState([
|
||||||
|
{
|
||||||
|
label: "Yes",
|
||||||
|
value: true,
|
||||||
|
icon: () => (
|
||||||
|
<Image
|
||||||
|
source={require("@assets/smileys/smiley.png")}
|
||||||
|
style={styles.iconStyle}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "No",
|
||||||
|
value: false,
|
||||||
|
icon: () => (
|
||||||
|
<Image
|
||||||
|
source={require("@assets/smileys/smiley-x-eyes.png")}
|
||||||
|
style={styles.iconStyle}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
//podmínky pro zavření ostatních dropdownů, pokud je jiný otevřený
|
||||||
|
const onOpenFoam = useCallback(() => {
|
||||||
|
setOpenBitterSweetness(false);
|
||||||
|
setOpenTaste(false);
|
||||||
|
setOpenPackaging(false);
|
||||||
|
setOpenSourness(false);
|
||||||
|
setOpenAgain(false);
|
||||||
|
setOpenFoam(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onOpenBitterSweetness = useCallback(() => {
|
||||||
|
setOpenFoam(false);
|
||||||
|
setOpenTaste(false);
|
||||||
|
setOpenPackaging(false);
|
||||||
|
setOpenSourness(false);
|
||||||
|
setOpenAgain(false);
|
||||||
|
setOpenBitterSweetness(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onOpenTaste = useCallback(() => {
|
||||||
|
setOpenFoam(false);
|
||||||
|
setOpenBitterSweetness(false);
|
||||||
|
setOpenPackaging(false);
|
||||||
|
setOpenSourness(false);
|
||||||
|
setOpenAgain(false);
|
||||||
|
setOpenTaste(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onOpenPackaging = useCallback(() => {
|
||||||
|
setOpenFoam(false);
|
||||||
|
setOpenBitterSweetness(false);
|
||||||
|
setOpenTaste(false);
|
||||||
|
setOpenSourness(false);
|
||||||
|
setOpenAgain(false);
|
||||||
|
setOpenPackaging(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onOpenSourness = useCallback(() => {
|
||||||
|
setOpenFoam(false);
|
||||||
|
setOpenBitterSweetness(false);
|
||||||
|
setOpenTaste(false);
|
||||||
|
setOpenPackaging(false);
|
||||||
|
setOpenAgain(false);
|
||||||
|
setOpenSourness(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onOpenAgain = useCallback(() => {
|
||||||
|
setOpenFoam(false);
|
||||||
|
setOpenBitterSweetness(false);
|
||||||
|
setOpenTaste(false);
|
||||||
|
setOpenPackaging(false);
|
||||||
|
setOpenSourness(false);
|
||||||
|
setOpenAgain(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
DropDownPicker.addTheme("DropdownTheme", DropdownTheme);
|
||||||
|
DropDownPicker.setTheme("DropdownTheme");
|
||||||
|
|
||||||
|
async function addBeer() {
|
||||||
|
const req = await fetch(`${process.env.EXPO_PUBLIC_API_URL}/review/add`, {
|
||||||
|
method: "POST",
|
||||||
|
credentials: "include",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({
|
||||||
|
beer_id: routeParams.beer_id,
|
||||||
|
foam: itemFoam,
|
||||||
|
bitter_sweetness: itemBitter_sweetness,
|
||||||
|
taste: itemTaste,
|
||||||
|
packaging: itemPackaging,
|
||||||
|
sourness: itemSourness,
|
||||||
|
would_again: itemAgain,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
const res = await req.json();
|
||||||
|
|
||||||
|
if (res.code == 201 && res.data._id) {
|
||||||
|
// window.location.href = `/review/${res.data._id}`;
|
||||||
|
// TODO: use react router for redirect
|
||||||
|
alert("Review was added!");
|
||||||
|
} else {
|
||||||
|
alert(
|
||||||
|
"Review was not added successfully. Please check your data and try again.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<View style={styles.form}>
|
||||||
|
<Text style={styles.text}>
|
||||||
|
How does your beer taste? Write a review!
|
||||||
|
</Text>
|
||||||
|
<Text style={styles.dropdownText} zIndex={6000} zIndexInverse={1000}>
|
||||||
|
How does the foam look like?
|
||||||
|
</Text>
|
||||||
|
<DropDownPicker
|
||||||
|
open={openFoam}
|
||||||
|
onOpen={onOpenFoam}
|
||||||
|
value={itemFoam}
|
||||||
|
items={foam}
|
||||||
|
setOpen={setOpenFoam}
|
||||||
|
setValue={setFoamValue}
|
||||||
|
setItems={setFoam}
|
||||||
|
placeholder="Please select..."
|
||||||
|
theme="DropdownTheme"
|
||||||
|
zIndex={6000}
|
||||||
|
zIndexInverse={1000}
|
||||||
|
/>
|
||||||
|
<Text style={styles.dropdownText} zIndex={5000} zIndexInverse={2000}>
|
||||||
|
More bitter, or more sweet?
|
||||||
|
</Text>
|
||||||
|
<DropDownPicker
|
||||||
|
open={openBitterSweetness}
|
||||||
|
onOpen={onOpenBitterSweetness}
|
||||||
|
value={itemBitter_sweetness}
|
||||||
|
items={bitter_sweetness}
|
||||||
|
setOpen={setOpenBitterSweetness}
|
||||||
|
setValue={setBitter_sweetnessValue}
|
||||||
|
setItems={setBitter_sweetness}
|
||||||
|
placeholder="Please select..."
|
||||||
|
theme="DropdownTheme"
|
||||||
|
zIndex={5000}
|
||||||
|
zIndexInverse={2000}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Text style={styles.dropdownText} zIndex={4000} zIndexInverse={3000}>
|
||||||
|
How does it taste?
|
||||||
|
</Text>
|
||||||
|
<DropDownPicker
|
||||||
|
open={openTaste}
|
||||||
|
onOpen={onOpenTaste}
|
||||||
|
value={itemTaste}
|
||||||
|
items={taste}
|
||||||
|
setOpen={setOpenTaste}
|
||||||
|
setValue={setTasteValue}
|
||||||
|
setItems={setTaste}
|
||||||
|
placeholder="Please select..."
|
||||||
|
theme="DropdownTheme"
|
||||||
|
zIndex={4000}
|
||||||
|
zIndexInverse={3000}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Text style={styles.dropdownText} zIndex={5000} zIndexInverse={4000}>
|
||||||
|
How do you like the packaging?
|
||||||
|
</Text>
|
||||||
|
<DropDownPicker
|
||||||
|
open={openPackaging}
|
||||||
|
onOpen={onOpenPackaging}
|
||||||
|
value={itemPackaging}
|
||||||
|
items={packaging}
|
||||||
|
setOpen={setOpenPackaging}
|
||||||
|
setValue={setPackagingValue}
|
||||||
|
setItems={setPackaging}
|
||||||
|
placeholder="Please select..."
|
||||||
|
theme="DropdownTheme"
|
||||||
|
zIndex={3000}
|
||||||
|
zIndexInverse={4000}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Text style={styles.dropdownText} zIndex={4000} zIndexInverse={5000}>
|
||||||
|
Is it sour?
|
||||||
|
</Text>
|
||||||
|
<DropDownPicker
|
||||||
|
open={openSourness}
|
||||||
|
onOpen={onOpenSourness}
|
||||||
|
value={itemSourness}
|
||||||
|
items={sourness}
|
||||||
|
setOpen={setOpenSourness}
|
||||||
|
setValue={setSournessValue}
|
||||||
|
setItems={setSourness}
|
||||||
|
placeholder="Please select..."
|
||||||
|
theme="DropdownTheme"
|
||||||
|
zIndex={2000}
|
||||||
|
zIndexInverse={5000}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Text style={styles.dropdownText} zIndex={5000} zIndexInverse={6000}>
|
||||||
|
Would you drink it again?
|
||||||
|
</Text>
|
||||||
|
<DropDownPicker
|
||||||
|
open={openAgain}
|
||||||
|
onOpen={onOpenAgain}
|
||||||
|
value={itemAgain}
|
||||||
|
items={again}
|
||||||
|
setOpen={setOpenAgain}
|
||||||
|
setValue={setAgainValue}
|
||||||
|
setItems={setAgain}
|
||||||
|
placeholder="Please select..."
|
||||||
|
theme="DropdownTheme"
|
||||||
|
zIndex={1000}
|
||||||
|
zIndexInverse={6000}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<View style={styles.buttonSend}>
|
||||||
|
<Button title="Add review" color={colors.gold} onPress={addBeer} />
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
alignItems: "center",
|
||||||
|
display: "flex",
|
||||||
|
},
|
||||||
|
form: {
|
||||||
|
gap: 5,
|
||||||
|
width: "80%",
|
||||||
|
},
|
||||||
|
buttonSend: {
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
marginTop: "2%",
|
||||||
|
},
|
||||||
|
input: {
|
||||||
|
height: "auto",
|
||||||
|
width: "100%",
|
||||||
|
borderColor: "gray",
|
||||||
|
borderWidth: 1,
|
||||||
|
borderRadius: 10,
|
||||||
|
padding: 13,
|
||||||
|
color: "#fff",
|
||||||
|
},
|
||||||
|
imageContainer: {
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "row",
|
||||||
|
gap: 10,
|
||||||
|
},
|
||||||
|
imageButton: {
|
||||||
|
backgroundColor: colors.dark,
|
||||||
|
borderColor: "gray",
|
||||||
|
borderWidth: 1,
|
||||||
|
borderRadius: 10,
|
||||||
|
},
|
||||||
|
imageTextButton: {
|
||||||
|
color: colors.white,
|
||||||
|
},
|
||||||
|
image: {
|
||||||
|
width: 150,
|
||||||
|
height: 150,
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
color: colors.white,
|
||||||
|
fontSize: 24,
|
||||||
|
textAlign: "center",
|
||||||
|
paddingBottom: "3%",
|
||||||
|
paddingTop: "10%",
|
||||||
|
},
|
||||||
|
iconStyle: {
|
||||||
|
width: 30,
|
||||||
|
height: 30,
|
||||||
|
},
|
||||||
|
dropdownContainer: {
|
||||||
|
width: "100%",
|
||||||
|
},
|
||||||
|
dropdownText: {
|
||||||
|
color: colors.white,
|
||||||
|
fontSize: 16,
|
||||||
|
paddingBottom: 1,
|
||||||
|
paddingTop: "1%",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "flex-start",
|
||||||
|
flexDirection: "column",
|
||||||
|
},
|
||||||
|
});
|
139
frontend/app/(app)/(tabs)/review/index.js
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
import { View, StyleSheet, FlatList, Image } from "react-native";
|
||||||
|
import Text from "@components/Text";
|
||||||
|
import Button from "@components/Button";
|
||||||
|
import { colors } from "@components/style";
|
||||||
|
import { router } from "expo-router";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { useAuth } from "@context/AuthContext";
|
||||||
|
|
||||||
|
export default function Tab() {
|
||||||
|
const { authState } = useAuth();
|
||||||
|
const user = authState.user;
|
||||||
|
const [data, setData] = useState([]);
|
||||||
|
useEffect(() => {
|
||||||
|
fetchData();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const API_HOST = process.env.EXPO_PUBLIC_API_URL.replace("/api/v1", "");
|
||||||
|
|
||||||
|
async function fetchData() {
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${process.env.EXPO_PUBLIC_API_URL}/review/get`, {
|
||||||
|
method: "GET",
|
||||||
|
credentials: "include",
|
||||||
|
});
|
||||||
|
let data = await res.json();
|
||||||
|
// show only logged in user's data
|
||||||
|
data = data.data.filter((review) => review.user_id == user._id);
|
||||||
|
|
||||||
|
let beers = await fetch(`${process.env.EXPO_PUBLIC_API_URL}/beer/get`, {
|
||||||
|
method: "GET",
|
||||||
|
credentials: "include",
|
||||||
|
});
|
||||||
|
beers = await beers.json();
|
||||||
|
beers = beers.data;
|
||||||
|
console.log(beers);
|
||||||
|
|
||||||
|
async function getBeerParam(search, beers) {
|
||||||
|
for (let i = 0; i < beers.length; i++) {
|
||||||
|
if (beers[i]._id == search) {
|
||||||
|
return beers[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
data.forEach(async (el) => {
|
||||||
|
el.beer = await getBeerParam(el.beer_id, beers);
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("reviews", data);
|
||||||
|
setData(data);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
alert("Something went wrong");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const opt3 = ["Bad", "Medium", "Excellent!"];
|
||||||
|
const opt5 = [
|
||||||
|
"Disgust",
|
||||||
|
"Not great, not terrible",
|
||||||
|
"Good",
|
||||||
|
"Why not?",
|
||||||
|
"Excellent!",
|
||||||
|
];
|
||||||
|
const opt2 = ["Yes", "No"];
|
||||||
|
const sourness = ["Good", "Bad"];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<FlatList
|
||||||
|
data={data}
|
||||||
|
style={styles.reviewList}
|
||||||
|
keyExtractor={(item) => String(item._id)}
|
||||||
|
renderItem={({ item }) => (
|
||||||
|
<View style={styles.itemContainer}>
|
||||||
|
<View>
|
||||||
|
<Text>{item.beer.name}</Text>
|
||||||
|
<Text>{item.beer.brand}</Text>
|
||||||
|
<Text>{item.beer.degree}°</Text>
|
||||||
|
<Text>{item.beer.packaging}</Text>
|
||||||
|
<Image
|
||||||
|
source={
|
||||||
|
item.beer.imgs[0]
|
||||||
|
? {
|
||||||
|
uri: `${API_HOST}/public/uploads/${item.beer.imgs[0]}`,
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
uri: "https://imagesvc.meredithcorp.io/v3/mm/image?url=https:%2F%2Fstatic.onecms.io%2Fwp-content%2Fuploads%2Fsites%2F44%2F2020%2F09%2F29%2Flight-beer.jpg",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
style={styles.itemImg}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
<View>
|
||||||
|
<Text>Foam → {opt3[item.foam - 1]}</Text>
|
||||||
|
<Text>
|
||||||
|
Bitter / Sweetness → {opt3[item.bitter_sweetness - 1]}
|
||||||
|
</Text>
|
||||||
|
<Text>Taste → {opt5[item.taste - 1]}</Text>
|
||||||
|
<Text>Packaging → {opt5[item.packaging - 1]}</Text>
|
||||||
|
<Text>Sourness → {sourness[item.sourness - 1]}</Text>
|
||||||
|
<Text>Would again? → {opt2[item.would_again - 1]}</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
marginTop: "5%",
|
||||||
|
},
|
||||||
|
reviewList: {
|
||||||
|
width: "100%",
|
||||||
|
paddingHorizontal: "15%",
|
||||||
|
marginTop: "5%",
|
||||||
|
},
|
||||||
|
itemContainer: {
|
||||||
|
borderColor: "gray",
|
||||||
|
borderWidth: 1,
|
||||||
|
borderRadius: 10,
|
||||||
|
padding: 13,
|
||||||
|
marginBottom: "5%",
|
||||||
|
flex: 1,
|
||||||
|
flexDirection: "row",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
},
|
||||||
|
itemImg: {
|
||||||
|
width: 150,
|
||||||
|
aspectRatio: 1,
|
||||||
|
resizeMode: "contain",
|
||||||
|
marginTop: "5%",
|
||||||
|
},
|
||||||
|
});
|
@ -1,7 +1,6 @@
|
|||||||
import { Redirect, Stack, Slot } from "expo-router";
|
import { Redirect, Stack, Slot } from "expo-router";
|
||||||
import { useAuth } from "@context/AuthContext";
|
import { useAuth } from "@context/AuthContext";
|
||||||
import { View, Text, StyleSheet } from "react-native";
|
import { View, Text, StyleSheet } from "react-native";
|
||||||
import { StatusBar } from "expo-status-bar";
|
|
||||||
|
|
||||||
export default function AppLayout() {
|
export default function AppLayout() {
|
||||||
const { authState } = useAuth();
|
const { authState } = useAuth();
|
||||||
@ -14,10 +13,12 @@ export default function AppLayout() {
|
|||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!authState.authenticated) {
|
if (!authState.authenticated) {
|
||||||
console.log("get the fuck out");
|
console.log("get the fuck out");
|
||||||
return <Redirect href="/login" />;
|
return <Redirect href="/login" />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack>
|
<Stack>
|
||||||
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
|
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
|
||||||
|
@ -90,7 +90,7 @@ export function AuthProvider({ children }) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (resUser.status != 200) {
|
if (resUser.status != 200) {
|
||||||
throw Error("user does not have user data");
|
throw Error("Username or password is incorrect!");
|
||||||
}
|
}
|
||||||
|
|
||||||
const userData = await resUser.json();
|
const userData = await resUser.json();
|
||||||
@ -104,7 +104,7 @@ export function AuthProvider({ children }) {
|
|||||||
await storageUtil.setItem(TOKEN_KEY, loginData.data.jwt);
|
await storageUtil.setItem(TOKEN_KEY, loginData.data.jwt);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Failed to log in", err);
|
console.error("Failed to log in", err);
|
||||||
return { error: true, msg: err.res };
|
return { error: true, msg: err };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,8 +20,12 @@ function LoginPage() {
|
|||||||
}
|
}
|
||||||
}, [authState.authenticated]);
|
}, [authState.authenticated]);
|
||||||
|
|
||||||
function login() {
|
async function login() {
|
||||||
onLogin(email, pass);
|
const res = await onLogin(email, pass);
|
||||||
|
if (res !== undefined && res.error === true) {
|
||||||
|
alert(res.msg);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -69,7 +73,9 @@ function LoginPage() {
|
|||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
<View>
|
<View>
|
||||||
<Link href="/signup">Don't have an account?</Link>
|
<Link href="/signup" style={styles.signup}>
|
||||||
|
Don't have an account?
|
||||||
|
</Link>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
@ -81,7 +87,6 @@ const styles = StyleSheet.create({
|
|||||||
container: {
|
container: {
|
||||||
width: "100%",
|
width: "100%",
|
||||||
height: "100%",
|
height: "100%",
|
||||||
backgroundColor: colors.dark,
|
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
},
|
},
|
||||||
@ -106,7 +111,7 @@ const styles = StyleSheet.create({
|
|||||||
header: {
|
header: {
|
||||||
width: "100%",
|
width: "100%",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
paddingTop: "10%",
|
paddingTop: "20%",
|
||||||
},
|
},
|
||||||
input: {
|
input: {
|
||||||
height: "auto",
|
height: "auto",
|
||||||
@ -131,6 +136,12 @@ const styles = StyleSheet.create({
|
|||||||
top: 0,
|
top: 0,
|
||||||
height: "100%",
|
height: "100%",
|
||||||
},
|
},
|
||||||
|
signup: {
|
||||||
|
textDecorationLine: "underline",
|
||||||
|
fontSize: 14,
|
||||||
|
marginTop: 10,
|
||||||
|
fontStyle: "italic",
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default LoginPage;
|
export default LoginPage;
|
||||||
|
@ -3,6 +3,8 @@ import { useState } from "react";
|
|||||||
import Button from "../components/Button";
|
import Button from "../components/Button";
|
||||||
import { colors } from "../components/style";
|
import { colors } from "../components/style";
|
||||||
import { Link, router } from "expo-router";
|
import { Link, router } from "expo-router";
|
||||||
|
import { LinearGradient } from "expo-linear-gradient";
|
||||||
|
import { Animated } from "react-native";
|
||||||
|
|
||||||
import { useAuth } from "./context/AuthContext";
|
import { useAuth } from "./context/AuthContext";
|
||||||
|
|
||||||
@ -14,27 +16,36 @@ function SignupPage() {
|
|||||||
const { onSignin } = useAuth();
|
const { onSignin } = useAuth();
|
||||||
|
|
||||||
async function signin() {
|
async function signin() {
|
||||||
if (pass1 == pass2) {
|
if (pass1 != pass2) {
|
||||||
const res = await onSignin(username, email, pass1);
|
alert("Passwords are not same!");
|
||||||
if (res.error) {
|
return;
|
||||||
if (res.msg.message == "validation error") {
|
}
|
||||||
alert(res.msg.data.message);
|
|
||||||
} else {
|
const res = await onSignin(username, email, pass1);
|
||||||
alert(res.msg.message);
|
const data = await res.json();
|
||||||
}
|
|
||||||
}
|
if (res.status == 400) {
|
||||||
if (!res.error) {
|
if (data.message == "validation error") {
|
||||||
alert("You have been successfully registered. Please Log In");
|
alert(data.data.message);
|
||||||
router.replace("/login");
|
} else {
|
||||||
|
alert("Something went wrong");
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
alert("Passwords are not same!");
|
if (res.status == 201) {
|
||||||
|
alert("You have been successfully registered. Please Log In");
|
||||||
|
router.replace("/login");
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
|
<AnimatedLinearGradient
|
||||||
|
colors={[colors.dark, colors.darkSecondary]}
|
||||||
|
style={styles.gradient}
|
||||||
|
/>
|
||||||
<View style={styles.header}>
|
<View style={styles.header}>
|
||||||
<Image
|
<Image
|
||||||
source={require("../assets/deguapp_logo.png")}
|
source={require("../assets/deguapp_logo.png")}
|
||||||
@ -88,7 +99,7 @@ function SignupPage() {
|
|||||||
color={colors.gold}
|
color={colors.gold}
|
||||||
onPress={signin}
|
onPress={signin}
|
||||||
/>
|
/>
|
||||||
<Link href="/login" style={styles.a}>
|
<Link href="/login" style={styles.login}>
|
||||||
Already have an account? Log In!
|
Already have an account? Log In!
|
||||||
</Link>
|
</Link>
|
||||||
</View>
|
</View>
|
||||||
@ -96,16 +107,20 @@ function SignupPage() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const AnimatedLinearGradient = Animated.createAnimatedComponent(LinearGradient);
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
width: "100%",
|
width: "100%",
|
||||||
height: "100%",
|
height: "100%",
|
||||||
backgroundColor: colors.dark,
|
backgroundColor: colors.dark,
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
maxHeight: "100%",
|
||||||
},
|
},
|
||||||
form: {
|
form: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
paddingTop: "10%",
|
|
||||||
width: "100%",
|
width: "100%",
|
||||||
gap: 15,
|
gap: 15,
|
||||||
},
|
},
|
||||||
@ -113,36 +128,45 @@ const styles = StyleSheet.create({
|
|||||||
color: "#FFF",
|
color: "#FFF",
|
||||||
fontSize: 30,
|
fontSize: 30,
|
||||||
textAlign: "center",
|
textAlign: "center",
|
||||||
paddingTop: "20%",
|
paddingTop: "3%",
|
||||||
},
|
paddingBottom: "3%",
|
||||||
a: {
|
|
||||||
color: "#FFF",
|
|
||||||
fontSize: 12,
|
|
||||||
fontStyle: "italic",
|
|
||||||
textDecorationLine: "underline",
|
|
||||||
},
|
},
|
||||||
logo: {
|
logo: {
|
||||||
width: "80%",
|
width: "80%",
|
||||||
resizeMode: "contain",
|
resizeMode: "contain",
|
||||||
|
marginTop: "15%",
|
||||||
},
|
},
|
||||||
header: {
|
header: {
|
||||||
width: "100%",
|
width: "100%",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
paddingTop: "20%",
|
|
||||||
},
|
},
|
||||||
input: {
|
input: {
|
||||||
height: "auto",
|
height: "auto",
|
||||||
width: "60%",
|
width: "60%",
|
||||||
borderColor: "gray",
|
borderColor: "gray",
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
borderRadius: 5,
|
borderRadius: 10,
|
||||||
padding: 10,
|
padding: 13,
|
||||||
color: "#fff",
|
color: "#fff",
|
||||||
},
|
},
|
||||||
btnContainer: {
|
btnContainer: {
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
gap: 5,
|
gap: 5,
|
||||||
},
|
},
|
||||||
|
login: {
|
||||||
|
color: "#FFF",
|
||||||
|
fontStyle: "italic",
|
||||||
|
textDecorationLine: "underline",
|
||||||
|
fontSize: 14,
|
||||||
|
marginTop: 10,
|
||||||
|
},
|
||||||
|
gradient: {
|
||||||
|
position: "absolute",
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
top: 0,
|
||||||
|
height: "100%",
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default SignupPage;
|
export default SignupPage;
|
||||||
|
BIN
frontend/assets/DropdownIcons/arrow-down.png
Normal file
After Width: | Height: | Size: 522 B |
BIN
frontend/assets/DropdownIcons/arrow-up.png
Normal file
After Width: | Height: | Size: 807 B |
BIN
frontend/assets/DropdownIcons/close.png
Normal file
After Width: | Height: | Size: 338 B |
BIN
frontend/assets/DropdownIcons/tick.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
frontend/assets/smileys/smiley-blank.png
Normal file
After Width: | Height: | Size: 7.9 KiB |
BIN
frontend/assets/smileys/smiley-meh.png
Normal file
After Width: | Height: | Size: 8.1 KiB |
BIN
frontend/assets/smileys/smiley-nervous.png
Normal file
After Width: | Height: | Size: 8.9 KiB |
BIN
frontend/assets/smileys/smiley-sad.png
Normal file
After Width: | Height: | Size: 8.9 KiB |
BIN
frontend/assets/smileys/smiley-wink.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
frontend/assets/smileys/smiley-x-eyes.png
Normal file
After Width: | Height: | Size: 8.9 KiB |
BIN
frontend/assets/smileys/smiley.png
Normal file
After Width: | Height: | Size: 9.0 KiB |
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://biomejs.dev/schemas/1.7.3/schema.json",
|
"$schema": "https://biomejs.dev/schemas/1.7.3/schema.json",
|
||||||
"files": {
|
"files": {
|
||||||
"ignore": [".expo/", ".vscode/", "node_modules/"]
|
"ignore": [".expo/", ".vscode/", "node_modules/", "dist/"]
|
||||||
},
|
},
|
||||||
"organizeImports": {
|
"organizeImports": {
|
||||||
"enabled": true
|
"enabled": true
|
||||||
|
@ -2,7 +2,14 @@ import React from "react";
|
|||||||
import { Text, StyleSheet, Pressable } from "react-native";
|
import { Text, StyleSheet, Pressable } from "react-native";
|
||||||
|
|
||||||
export default function Button(props) {
|
export default function Button(props) {
|
||||||
const { onPress, title = "Button", color = "black" } = props;
|
const {
|
||||||
|
onPress,
|
||||||
|
title = "Button",
|
||||||
|
color = "black",
|
||||||
|
textColor = "white",
|
||||||
|
buttonStyle,
|
||||||
|
textStyle,
|
||||||
|
} = props;
|
||||||
return (
|
return (
|
||||||
<Pressable
|
<Pressable
|
||||||
style={({ pressed }) => [
|
style={({ pressed }) => [
|
||||||
@ -14,10 +21,13 @@ export default function Button(props) {
|
|||||||
: "black",
|
: "black",
|
||||||
},
|
},
|
||||||
styles.button,
|
styles.button,
|
||||||
|
buttonStyle,
|
||||||
]}
|
]}
|
||||||
onPress={onPress}
|
onPress={onPress}
|
||||||
>
|
>
|
||||||
<Text style={styles.text}>{title}</Text>
|
<Text style={[styles.text, { color: textColor }, textStyle]}>
|
||||||
|
{title}
|
||||||
|
</Text>
|
||||||
</Pressable>
|
</Pressable>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -36,6 +46,6 @@ const styles = StyleSheet.create({
|
|||||||
lineHeight: 21,
|
lineHeight: 21,
|
||||||
fontWeight: "bold",
|
fontWeight: "bold",
|
||||||
letterSpacing: 0.25,
|
letterSpacing: 0.25,
|
||||||
color: "white",
|
//color: textColor,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
170
frontend/components/DropdownTheme.js
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
import { StyleSheet } from "react-native";
|
||||||
|
|
||||||
|
import { colors } from "@components/style";
|
||||||
|
|
||||||
|
export const ICONS = {
|
||||||
|
ARROW_DOWN: require("@assets/DropdownIcons/arrow-down.png"),
|
||||||
|
ARROW_UP: require("@assets/DropdownIcons/arrow-up.png"),
|
||||||
|
TICK: require("@assets/DropdownIcons/tick.png"),
|
||||||
|
CLOSE: require("../assets/DropdownIcons/close.png"),
|
||||||
|
};
|
||||||
|
|
||||||
|
export default StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
width: "100%",
|
||||||
|
},
|
||||||
|
style: {
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
width: "100%",
|
||||||
|
minHeight: 50,
|
||||||
|
borderRadius: 8,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: "gray",
|
||||||
|
paddingHorizontal: 10,
|
||||||
|
paddingVertical: 3,
|
||||||
|
backgroundColor: colors.dark,
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
flex: 1,
|
||||||
|
color: colors.placeholder,
|
||||||
|
},
|
||||||
|
labelContainer: {
|
||||||
|
flex: 1,
|
||||||
|
flexDirection: "row",
|
||||||
|
},
|
||||||
|
arrowIcon: {
|
||||||
|
width: 20,
|
||||||
|
height: 20,
|
||||||
|
},
|
||||||
|
tickIcon: {
|
||||||
|
width: 20,
|
||||||
|
height: 20,
|
||||||
|
},
|
||||||
|
closeIcon: {
|
||||||
|
width: 30,
|
||||||
|
height: 30,
|
||||||
|
},
|
||||||
|
badgeStyle: {
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
borderRadius: 15,
|
||||||
|
backgroundColor: colors.white,
|
||||||
|
paddingHorizontal: 10,
|
||||||
|
paddingVertical: 5,
|
||||||
|
},
|
||||||
|
badgeDotStyle: {
|
||||||
|
width: 10,
|
||||||
|
height: 10,
|
||||||
|
borderRadius: 10 / 2,
|
||||||
|
marginRight: 8,
|
||||||
|
backgroundColor: colors.white,
|
||||||
|
},
|
||||||
|
badgeSeparator: {
|
||||||
|
width: 5,
|
||||||
|
},
|
||||||
|
listBody: {
|
||||||
|
height: "100%",
|
||||||
|
},
|
||||||
|
listBodyContainer: {
|
||||||
|
flexGrow: 1,
|
||||||
|
alignItems: "center",
|
||||||
|
},
|
||||||
|
dropDownContainer: {
|
||||||
|
position: "absolute",
|
||||||
|
backgroundColor: colors.darkSecondary,
|
||||||
|
borderRadius: 10,
|
||||||
|
borderColor: colors.black,
|
||||||
|
borderWidth: 1,
|
||||||
|
width: "100%",
|
||||||
|
overflow: "hidden",
|
||||||
|
zIndex: 1000,
|
||||||
|
},
|
||||||
|
modalContentContainer: {
|
||||||
|
flexGrow: 1,
|
||||||
|
backgroundColor: colors.white,
|
||||||
|
},
|
||||||
|
listItemContainer: {
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
paddingHorizontal: 10,
|
||||||
|
height: 40,
|
||||||
|
},
|
||||||
|
listItemLabel: {
|
||||||
|
flex: 1,
|
||||||
|
color: colors.placeholder,
|
||||||
|
},
|
||||||
|
iconContainer: {
|
||||||
|
marginRight: 10,
|
||||||
|
},
|
||||||
|
arrowIconContainer: {
|
||||||
|
marginLeft: 10,
|
||||||
|
},
|
||||||
|
tickIconContainer: {
|
||||||
|
marginLeft: 10,
|
||||||
|
},
|
||||||
|
closeIconContainer: {
|
||||||
|
marginLeft: 10,
|
||||||
|
},
|
||||||
|
listParentLabel: {},
|
||||||
|
listChildLabel: {},
|
||||||
|
listParentContainer: {},
|
||||||
|
listChildContainer: {
|
||||||
|
paddingLeft: 40,
|
||||||
|
},
|
||||||
|
searchContainer: {
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
padding: 10,
|
||||||
|
borderBottomColor: colors.darkSecondary,
|
||||||
|
borderBottomWidth: 1,
|
||||||
|
},
|
||||||
|
searchTextInput: {
|
||||||
|
flexGrow: 1,
|
||||||
|
flexShrink: 1,
|
||||||
|
margin: 0,
|
||||||
|
paddingHorizontal: 10,
|
||||||
|
paddingVertical: 5,
|
||||||
|
borderRadius: 8,
|
||||||
|
borderColor: colors.darkSecondary,
|
||||||
|
borderWidth: 1,
|
||||||
|
color: colors.white,
|
||||||
|
},
|
||||||
|
itemSeparator: {
|
||||||
|
height: 1,
|
||||||
|
backgroundColor: colors.darkSecondary,
|
||||||
|
},
|
||||||
|
flatListContentContainer: {
|
||||||
|
flexGrow: 1,
|
||||||
|
},
|
||||||
|
customItemContainer: {},
|
||||||
|
customItemLabel: {
|
||||||
|
fontStyle: "italic",
|
||||||
|
},
|
||||||
|
listMessageContainer: {
|
||||||
|
flex: 1,
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
padding: 10,
|
||||||
|
},
|
||||||
|
listMessageText: {
|
||||||
|
color: colors.gold,
|
||||||
|
},
|
||||||
|
selectedItemContainer: {},
|
||||||
|
selectedItemLabel: {},
|
||||||
|
modalTitle: {
|
||||||
|
fontSize: 18,
|
||||||
|
color: colors.gold,
|
||||||
|
},
|
||||||
|
extendableBadgeContainer: {
|
||||||
|
flexDirection: "row",
|
||||||
|
flexWrap: "wrap",
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
extendableBadgeItemContainer: {
|
||||||
|
marginVertical: 3,
|
||||||
|
marginEnd: 7,
|
||||||
|
},
|
||||||
|
});
|
@ -7,4 +7,6 @@ export const colors = {
|
|||||||
black: "#020405ff",
|
black: "#020405ff",
|
||||||
dark: "#010409",
|
dark: "#010409",
|
||||||
darkSecondary: "#0D1117",
|
darkSecondary: "#0D1117",
|
||||||
|
white: "#FFFFFF",
|
||||||
|
placeholder: "#aaaaaa",
|
||||||
};
|
};
|
||||||
|
30658
frontend/package-lock.json
generated
@ -6,25 +6,33 @@
|
|||||||
"start": "expo start",
|
"start": "expo start",
|
||||||
"android": "expo start --android",
|
"android": "expo start --android",
|
||||||
"ios": "expo start --ios",
|
"ios": "expo start --ios",
|
||||||
"web": "expo start --web"
|
"web": "expo start --web",
|
||||||
|
"build:web": "npx expo export",
|
||||||
|
"format": "npx @biomejs/biome format --write ."
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@expo/metro-runtime": "~3.1.3",
|
"@expo/metro-runtime": "~3.2.1",
|
||||||
"@react-native-async-storage/async-storage": "^1.23.1",
|
"@react-native-async-storage/async-storage": "^1.23.1",
|
||||||
"@types/react": "~18.2.45",
|
"@types/react": "~18.2.45",
|
||||||
"axios": "^1.6.8",
|
"axios": "^1.6.8",
|
||||||
"expo": "~50.0.17",
|
"expo": "^51.0.2",
|
||||||
"expo-constants": "~15.4.6",
|
"expo-constants": "~16.0.1",
|
||||||
"expo-linking": "~6.2.2",
|
"expo-image-picker": "~15.0.4",
|
||||||
"expo-router": "~3.4.10",
|
"expo-linear-gradient": "~13.0.2",
|
||||||
"expo-secure-store": "^12.8.1",
|
"expo-linking": "~6.3.1",
|
||||||
"expo-status-bar": "~1.11.1",
|
"expo-router": "~3.5.11",
|
||||||
|
"expo-secure-store": "~13.0.1",
|
||||||
|
"expo-status-bar": "~1.12.1",
|
||||||
|
"expo-system-ui": "~3.0.4",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"react-native": "0.73.6",
|
"react-native": "0.74.1",
|
||||||
"react-native-safe-area-context": "4.8.2",
|
"react-native-dropdown-picker": "^5.4.6",
|
||||||
"react-native-screens": "~3.29.0",
|
"react-native-range-slider-expo": "^1.4.3",
|
||||||
"react-native-web": "~0.19.6"
|
"react-native-safe-area-context": "4.10.1",
|
||||||
|
"react-native-screens": "3.31.1",
|
||||||
|
"react-native-web": "~0.19.6",
|
||||||
|
"@shopify/flash-list": "1.6.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.20.0",
|
"@babel/core": "^7.20.0",
|
||||||
|