Initial commit: LACA parking management system
This commit is contained in:
6
backend/.dockerignore
Normal file
6
backend/.dockerignore
Normal file
@@ -0,0 +1,6 @@
|
||||
Dockerfile
|
||||
.dockerignore
|
||||
node_modules
|
||||
npm-debug.log
|
||||
dist
|
||||
deploy/*
|
||||
25
backend/.env.example
Normal file
25
backend/.env.example
Normal file
@@ -0,0 +1,25 @@
|
||||
DATABASE_URL="postgres://pguser:NmswPtleb0dquVSVhUWqcAtFl571JHEI@localhost:5432/LACA_DEV"
|
||||
DB_USER="pguser"
|
||||
DB_PASSWORD="NmswPtleb0dquVSVhUWqcAtFl571JHEI"
|
||||
REDIS_PASSWORD="cq21WUy8dBGxq4cHAeonKpsBXZtFylBJ"
|
||||
REDIS_URL="redis://:cq21WUy8dBGxq4cHAeonKpsBXZtFylBJ@172.21.128.1:9763/0"
|
||||
GOOGLE_CLIENT_ID=*
|
||||
GOOGLE_SECRET=*
|
||||
ENVIRONMENT=prod
|
||||
APP_NAME='NestJS Example App'
|
||||
APP_URL=http://localhost:3000
|
||||
PORT=3000
|
||||
APP_CORS_ENABLED=true
|
||||
JWT_ACCESS_SECRET=jcoabaHgA8j90rtyuiokAGhjkAIh
|
||||
JWT_REFRESH_SECRET=jcoabaHgA8j90rtyuiokAGhjkAIh
|
||||
JWT_EXPIRES_IN=15m
|
||||
JWT_REFRESH_IN=1d
|
||||
BCRYPT_SALT_ROUNDS=10
|
||||
GRAPHQL_PLAYGROUND_ENABLED=true
|
||||
GRAPHQL_DEBUG=true
|
||||
GRAPHQL_SCHEMA_DESTINATION='schema.graphql'
|
||||
GRAPHQL_SORT_SCHEMA=true
|
||||
SWAGGER_ENABLED=true
|
||||
SWAGGER_DESCRIPTION='NestJS example app API'
|
||||
SWAGGER_VERSION=1.5
|
||||
SWAGGER_PATH=api
|
||||
27
backend/.eslintrc.js
Normal file
27
backend/.eslintrc.js
Normal file
@@ -0,0 +1,27 @@
|
||||
module.exports = {
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: {
|
||||
project: 'tsconfig.json',
|
||||
tsconfigRootDir: __dirname,
|
||||
sourceType: 'module',
|
||||
},
|
||||
plugins: ['@typescript-eslint/eslint-plugin', 'prettier'],
|
||||
extends: [
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:prettier/recommended',
|
||||
'prettier'
|
||||
],
|
||||
root: true,
|
||||
env: {
|
||||
node: true,
|
||||
jest: true,
|
||||
},
|
||||
ignorePatterns: ['.eslintrc.js'],
|
||||
rules: {
|
||||
'@typescript-eslint/interface-name-prefix': 'off',
|
||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'prettier/prettier': 'off'
|
||||
},
|
||||
};
|
||||
43
backend/.gitignore
vendored
Normal file
43
backend/.gitignore
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
# compiled output
|
||||
/dist
|
||||
/node_modules
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
pnpm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
|
||||
# Tests
|
||||
/coverage
|
||||
/.nyc_output
|
||||
|
||||
# IDEs and editors
|
||||
/.idea
|
||||
.project
|
||||
.classpath
|
||||
.c9/
|
||||
*.launch
|
||||
.settings/
|
||||
*.sublime-workspace
|
||||
|
||||
# IDE - VSCode
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
|
||||
.env
|
||||
|
||||
notes
|
||||
audio
|
||||
/upload
|
||||
|
||||
**/docker-compose.*.y*
|
||||
4
backend/.prettierrc
Normal file
4
backend/.prettierrc
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"singleQuote": true,
|
||||
"trailingComma": "all"
|
||||
}
|
||||
12
backend/README.md
Normal file
12
backend/README.md
Normal file
@@ -0,0 +1,12 @@
|
||||
## Backend-REST-API-NestJS-Prisma
|
||||
|
||||
A simple backend REST API for a blog built using NestJS, Prisma, PostgreSQL and Swagger.
|
||||
|
||||
### Installation
|
||||
|
||||
1. Install dependencies: `npm install`
|
||||
2. Start a PostgreSQL database with docker using: `docker-compose up -d`.
|
||||
- If you have a local instance of PostgreSQL running, you can skip this step. In this case, you will need to change the `DATABASE_URL` inside the `.env` file with a valid [PostgreSQL connection string](https://www.prisma.io/docs/concepts/database-connectors/postgresql#connection-details) for your database.
|
||||
3. Apply database migrations: `npx prisma migrate dev`
|
||||
4. Start the project: `npm run start:dev`
|
||||
5. Access the project at http://localhost:3000/api
|
||||
44
backend/deploy/api/Dockerfile
Normal file
44
backend/deploy/api/Dockerfile
Normal file
@@ -0,0 +1,44 @@
|
||||
# PRODUCTION DOCKERFILE
|
||||
# ---------------------
|
||||
# This Dockerfile allows to build a Docker image of the NestJS application
|
||||
# and based on a NodeJS 20 image. The multi-stage mechanism allows to build
|
||||
# the application in a "builder" stage and then create a lightweight production
|
||||
# image containing the required dependencies and the JS build files.
|
||||
#
|
||||
# Dockerfile best practices
|
||||
# https://docs.docker.com/develop/develop-images/dockerfile_best-practices/
|
||||
# Dockerized NodeJS best practices
|
||||
# https://github.com/nodejs/docker-node/blob/master/docs/BestPractices.md
|
||||
# https://www.bretfisher.com/node-docker-good-defaults/
|
||||
# http://goldbergyoni.com/checklist-best-practice-of-node-js-in-production/
|
||||
|
||||
FROM node:16-alpine as builder
|
||||
|
||||
ENV NODE_ENV build
|
||||
|
||||
USER node
|
||||
WORKDIR /home/node
|
||||
|
||||
COPY ../../package*.json ./
|
||||
RUN npm ci
|
||||
|
||||
COPY --chown=node:node ../../. .
|
||||
|
||||
RUN npx prisma generate \
|
||||
&& npm run build \
|
||||
&& npm prune --omit=dev
|
||||
|
||||
# ---
|
||||
|
||||
FROM node:16-alpine as production
|
||||
|
||||
ENV NODE_ENV production
|
||||
|
||||
USER node
|
||||
WORKDIR /home/node
|
||||
|
||||
COPY --from=builder --chown=node:node /home/node/package*.json ./
|
||||
COPY --from=builder --chown=node:node /home/node/node_modules/ ./node_modules/
|
||||
COPY --from=builder --chown=node:node /home/node/dist/ ./dist/
|
||||
|
||||
CMD ["node", "dist/server.js"]
|
||||
0
backend/deploy/api/docker-compose.yaml
Normal file
0
backend/deploy/api/docker-compose.yaml
Normal file
40
backend/deploy/storage/docker-compose.yaml
Normal file
40
backend/deploy/storage/docker-compose.yaml
Normal file
@@ -0,0 +1,40 @@
|
||||
version: '3.8'
|
||||
|
||||
x-logging: &logging
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-file: "5"
|
||||
max-size: "10m"
|
||||
|
||||
x-network: &network
|
||||
network_mode: "host"
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:13.5
|
||||
restart: always
|
||||
environment:
|
||||
- POSTGRES_USER=${DB_USER}
|
||||
- POSTGRES_PASSWORD=${DB_PASSWORD}
|
||||
volumes:
|
||||
- postgres:/var/lib/postgresql/data
|
||||
ports:
|
||||
- "5432:5432"
|
||||
<<: [ *logging ]
|
||||
|
||||
redis:
|
||||
hostname: redis
|
||||
container_name: redis
|
||||
image: redis:6.2.11-alpine
|
||||
volumes:
|
||||
- redis-data:/data
|
||||
command: >
|
||||
--requirepass ${REDIS_PASSWORD}
|
||||
ports:
|
||||
- "6379:6379"
|
||||
<<: [ *logging ]
|
||||
|
||||
volumes:
|
||||
postgres:
|
||||
redis-data:
|
||||
33
backend/docker-compose.yml
Normal file
33
backend/docker-compose.yml
Normal file
@@ -0,0 +1,33 @@
|
||||
version: '3.8'
|
||||
|
||||
x-logging: &logging
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-file: "5"
|
||||
max-size: "10m"
|
||||
x-network: &network
|
||||
network_mode: "host"
|
||||
|
||||
services:
|
||||
|
||||
postgres:
|
||||
image: postgres:13.5
|
||||
restart: always
|
||||
environment:
|
||||
- POSTGRES_USER=${DB_USER}
|
||||
- POSTGRES_PASSWORD=${DB_PASSWORD}
|
||||
volumes:
|
||||
- postgres:/var/lib/postgresql/data
|
||||
<<: [*logging,*network]
|
||||
|
||||
redis:
|
||||
hostname: redis
|
||||
container_name: redis
|
||||
image: redis:6.2.11-alpine
|
||||
volumes:
|
||||
- redis-data:/data
|
||||
<<: [*logging,*network]
|
||||
volumes:
|
||||
postgres:
|
||||
redis-data:
|
||||
15
backend/nest-cli.json
Normal file
15
backend/nest-cli.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/nest-cli",
|
||||
"collection": "@nestjs/schematics",
|
||||
"sourceRoot": "src/",
|
||||
"compilerOptions": {
|
||||
"plugins": [
|
||||
{
|
||||
"name": "@nestjs/swagger",
|
||||
"options": {
|
||||
"introspectComments": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
21878
backend/package-lock.json
generated
Normal file
21878
backend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
108
backend/package.json
Normal file
108
backend/package.json
Normal file
@@ -0,0 +1,108 @@
|
||||
{
|
||||
"name": "nest-prisma",
|
||||
"version": "0.0.1",
|
||||
"description": "",
|
||||
"author": "",
|
||||
"private": true,
|
||||
"license": "UNLICENSED",
|
||||
"scripts": {
|
||||
"prebuild": "rimraf dist",
|
||||
"build": "nest build",
|
||||
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
|
||||
"start": "nest start",
|
||||
"start:dev": "nest start --watch",
|
||||
"start:debug": "nest start --debug --watch",
|
||||
"start:prod": "node dist/main",
|
||||
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
|
||||
"test": "jest",
|
||||
"test:watch": "jest --watch",
|
||||
"test:cov": "jest --coverage",
|
||||
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
|
||||
"test:e2e": "jest --config ./test/jest-e2e.json",
|
||||
"seed": "ts-node prisma/seed.ts",
|
||||
"ts-node": "ts-node",
|
||||
"generate": "npx prisma generate",
|
||||
"migrate:dev": "npx prisma migrate dev",
|
||||
"migrate:dev:create": "npx prisma migrate dev --create-only",
|
||||
"migrate:reset": "npx prisma migrate reset",
|
||||
"migrate:deploy": "npx prisma migrate deploy",
|
||||
"migrate:status": "npx prisma migrate status",
|
||||
"migrate:resolve": "npx prisma migrate resolve"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nestjs-modules/mailer": "^1.6.1",
|
||||
"@nestjs/common": "^10.3.10",
|
||||
"@nestjs/config": "^3.0.0",
|
||||
"@nestjs/core": "^10.3.10",
|
||||
"@nestjs/jwt": "^10.1.0",
|
||||
"@nestjs/mapped-types": "*",
|
||||
"@nestjs/passport": "^10.0.0",
|
||||
"@nestjs/platform-express": "^10.3.10",
|
||||
"@nestjs/swagger": "^7.4.0",
|
||||
"@prisma/client": "^4.7.0",
|
||||
"@types/multer": "^1.4.7",
|
||||
"bcrypt": "^5.1.1",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.14.1",
|
||||
"csv-parser": "^3.2.0",
|
||||
"dotenv": "^16.3.1",
|
||||
"google-auth-library": "^9.0.0",
|
||||
"helmet": "^7.0.0",
|
||||
"iconv-lite": "^0.6.3",
|
||||
"ioredis": "^5.3.2",
|
||||
"joi": "^17.9.2",
|
||||
"morgan": "^1.10.0",
|
||||
"nestjs-prisma": "^0.22.0",
|
||||
"passport-google-oauth20": "^2.0.0",
|
||||
"passport-headerapikey": "^1.2.2",
|
||||
"passport-jwt": "^4.0.1",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"rimraf": "^3.0.2",
|
||||
"rxjs": "^7.2.0",
|
||||
"swagger-ui-express": "^4.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nestjs/cli": "^10.4.2",
|
||||
"@nestjs/schematics": "^8.0.0",
|
||||
"@nestjs/testing": "^10.3.10",
|
||||
"@types/express": "^4.17.13",
|
||||
"@types/jest": "27.4.1",
|
||||
"@types/node": "^16.0.0",
|
||||
"@types/supertest": "^2.0.11",
|
||||
"@typescript-eslint/eslint-plugin": "^5.0.0",
|
||||
"@typescript-eslint/parser": "^5.0.0",
|
||||
"eslint": "^8.0.1",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-plugin-prettier": "^4.0.0",
|
||||
"jest": "^27.2.5",
|
||||
"prettier": "^2.3.2",
|
||||
"prisma": "^4.7.0",
|
||||
"source-map-support": "^0.5.20",
|
||||
"supertest": "^6.1.3",
|
||||
"ts-jest": "^27.0.3",
|
||||
"ts-loader": "^9.2.3",
|
||||
"ts-node": "^10.0.0",
|
||||
"tsconfig-paths": "^3.10.1",
|
||||
"typescript": "^4.3.5"
|
||||
},
|
||||
"jest": {
|
||||
"moduleFileExtensions": [
|
||||
"js",
|
||||
"json",
|
||||
"ts"
|
||||
],
|
||||
"rootDir": "src",
|
||||
"testRegex": ".*\\.spec\\.ts$",
|
||||
"transform": {
|
||||
"^.+\\.(t|j)s$": "ts-jest"
|
||||
},
|
||||
"collectCoverageFrom": [
|
||||
"**/*.(t|j)s"
|
||||
],
|
||||
"coverageDirectory": "../coverage",
|
||||
"testEnvironment": "node"
|
||||
},
|
||||
"prisma": {
|
||||
"seed": "ts-node prisma/seed.ts"
|
||||
}
|
||||
}
|
||||
82
backend/prisma/dump/slot.json
Normal file
82
backend/prisma/dump/slot.json
Normal file
@@ -0,0 +1,82 @@
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"lat": 21.00369935885945,
|
||||
"lng": 105.78221980819896,
|
||||
"address": "116 Miếu Đầm",
|
||||
"city": "Hà Nội",
|
||||
"district": "Nam Từ Liêm",
|
||||
"ward": "Mễ Trì",
|
||||
"directions": {},
|
||||
"name": "Bãi Đỗ Xe 1",
|
||||
"tenantId": 1,
|
||||
"total": 5,
|
||||
"empty": 0
|
||||
},
|
||||
{
|
||||
"lat": 21.028285188709756,
|
||||
"lng": 105.80518484452222,
|
||||
"address": "P. Kim Mã",
|
||||
"city": "Hà Nội",
|
||||
"district": "Đống Đa",
|
||||
"ward": "Láng Thượng",
|
||||
"directions": {},
|
||||
"name": "Bãi Đỗ Xe 2",
|
||||
"tenantId": 1,
|
||||
"total": 5,
|
||||
"empty": 0
|
||||
},
|
||||
{
|
||||
"lat": 21.032131562226848,
|
||||
"lng": 105.80935331171176,
|
||||
"address": "",
|
||||
"city": "Hà Nội",
|
||||
"district": "Ba Đình",
|
||||
"ward": "Ngọc Khánh",
|
||||
"directions": {},
|
||||
"name": "Bãi Đỗ Xe 3",
|
||||
"tenantId": 1,
|
||||
"total": 5,
|
||||
"empty": 0
|
||||
},
|
||||
{
|
||||
"lat": 21.041552080736658,
|
||||
"lng": 105.81240791131391,
|
||||
"address": "25 Ngh. 6/30 P. Đội Nhân",
|
||||
"city": "Hà Nội",
|
||||
"district": "Ba Đình",
|
||||
"ward": "Vĩnh Phú",
|
||||
"directions": {},
|
||||
"name": "Bãi Đỗ Xe 4",
|
||||
"tenantId": 1,
|
||||
"total": 5,
|
||||
"empty": 0
|
||||
},
|
||||
{
|
||||
"lat": 21.030215749159247,
|
||||
"lng": 105.79564506735449,
|
||||
"address": "116 Ng. 165 Đ. Cầu Giấy",
|
||||
"city": "Hà Nội",
|
||||
"district": "Cầu Giấy",
|
||||
"ward": " Dịch Vọng",
|
||||
"directions": {},
|
||||
"name": "Bãi Đỗ Xe 5",
|
||||
"tenantId": 1,
|
||||
"total": 5,
|
||||
"empty": 0
|
||||
},
|
||||
{
|
||||
"lat": 21.0187413220742,
|
||||
"lng": 105.82410499971033,
|
||||
"address": "36b P. Hoàng Cầu",
|
||||
"city": "Hà Nội",
|
||||
"district": "Đống Đa",
|
||||
"ward": "Chợ Dừa",
|
||||
"directions": {},
|
||||
"name": "Bãi Đỗ Xe 1",
|
||||
"tenantId": 1,
|
||||
"total": 5,
|
||||
"empty": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
13
backend/prisma/dump/tenant.json
Normal file
13
backend/prisma/dump/tenant.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"email": "danghoat.ptit@gmail.com",
|
||||
"username": "hoatdx",
|
||||
"password": "123456789",
|
||||
"password_confirmation": "",
|
||||
"first_name": "Hoat",
|
||||
"last_name" : "Dang",
|
||||
"phone": "0973465669"
|
||||
}
|
||||
]
|
||||
}
|
||||
163
backend/prisma/migrations/20240728061550_init_db/migration.sql
Normal file
163
backend/prisma/migrations/20240728061550_init_db/migration.sql
Normal file
@@ -0,0 +1,163 @@
|
||||
-- CreateEnum
|
||||
CREATE TYPE "Language" AS ENUM ('vi', 'en');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "Gender" AS ENUM ('Male', 'Female', 'LGBT');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "BookingStatus" AS ENUM ('done', 'pending', 'reject', 'cancel');
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "User" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"email" TEXT NOT NULL,
|
||||
"phone" TEXT,
|
||||
"username" TEXT,
|
||||
"fullname" TEXT,
|
||||
"dob" TIMESTAMP(3),
|
||||
"gender" "Gender" NOT NULL DEFAULT 'Female',
|
||||
"language" "Language" NOT NULL DEFAULT 'vi',
|
||||
"created_at" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMPTZ(3) NOT NULL,
|
||||
"deleted_at" TIMESTAMPTZ(3),
|
||||
|
||||
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Booking" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"path" TEXT,
|
||||
"image" TEXT,
|
||||
"public" BOOLEAN NOT NULL DEFAULT true,
|
||||
"user_id" INTEGER NOT NULL,
|
||||
"status" "BookingStatus" NOT NULL DEFAULT 'pending',
|
||||
"slot_id" INTEGER NOT NULL,
|
||||
"start_at" TIMESTAMPTZ(3) NOT NULL,
|
||||
"end_at" TIMESTAMPTZ(3),
|
||||
"created_at" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMPTZ(3) NOT NULL,
|
||||
"deleted_at" TIMESTAMPTZ(3),
|
||||
|
||||
CONSTRAINT "Booking_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Slot" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"lat" DOUBLE PRECISION NOT NULL,
|
||||
"lng" DOUBLE PRECISION NOT NULL,
|
||||
"address" TEXT NOT NULL,
|
||||
"district" TEXT NOT NULL,
|
||||
"ward" TEXT NOT NULL,
|
||||
"city" TEXT NOT NULL,
|
||||
"destination" TEXT,
|
||||
"total" INTEGER NOT NULL,
|
||||
"empty" INTEGER NOT NULL,
|
||||
"published" BOOLEAN NOT NULL DEFAULT false,
|
||||
"tenantId" INTEGER NOT NULL,
|
||||
"distance" DOUBLE PRECISION NOT NULL,
|
||||
"duration" DOUBLE PRECISION NOT NULL,
|
||||
"directions" JSONB NOT NULL,
|
||||
"created_at" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMPTZ(3) NOT NULL,
|
||||
"deleted_at" TIMESTAMPTZ(3),
|
||||
|
||||
CONSTRAINT "Slot_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Admin" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"username" TEXT NOT NULL,
|
||||
"password" TEXT NOT NULL,
|
||||
"password_confirmation" TEXT NOT NULL,
|
||||
"street" TEXT,
|
||||
"city" TEXT,
|
||||
"province" TEXT,
|
||||
"first_name" TEXT NOT NULL,
|
||||
"last_name" TEXT NOT NULL,
|
||||
"phone" TEXT NOT NULL,
|
||||
"dob" TIMESTAMP(3),
|
||||
"language" "Language" NOT NULL DEFAULT 'vi',
|
||||
"last_login_at" TIMESTAMP(3),
|
||||
"current_login_at" TIMESTAMP(3),
|
||||
"last_login_failed_at" TIMESTAMP(3),
|
||||
"consecutive_login_penalty" INTEGER NOT NULL DEFAULT 0,
|
||||
"avatarUrl" TEXT,
|
||||
"active" BOOLEAN NOT NULL DEFAULT true,
|
||||
"last_lockout_at" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Admin_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Tenant" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"email" TEXT,
|
||||
"username" TEXT NOT NULL,
|
||||
"password" TEXT NOT NULL,
|
||||
"password_confirmation" TEXT,
|
||||
"street" TEXT,
|
||||
"city" TEXT,
|
||||
"province" TEXT,
|
||||
"postal_code" TEXT,
|
||||
"first_name" TEXT NOT NULL,
|
||||
"last_name" TEXT NOT NULL,
|
||||
"phone" TEXT NOT NULL,
|
||||
"language" "Language" NOT NULL DEFAULT 'vi',
|
||||
"last_login_at" TIMESTAMP(3),
|
||||
"current_login_at" TIMESTAMP(3),
|
||||
"last_login_failed_at" TIMESTAMP(3),
|
||||
"consecutive_login_penalty" INTEGER NOT NULL DEFAULT 0,
|
||||
"avatarUrl" TEXT,
|
||||
"active" BOOLEAN NOT NULL DEFAULT true,
|
||||
"last_lockout_at" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Tenant_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "History" (
|
||||
"id" SERIAL NOT NULL,
|
||||
|
||||
CONSTRAINT "History_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "User_username_key" ON "User"("username");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "User_id_email_idx" ON "User"("id", "email");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Booking_user_id_id_idx" ON "Booking"("user_id", "id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Slot_id_idx" ON "Slot"("id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Admin_username_key" ON "Admin"("username");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Tenant_email_key" ON "Tenant"("email");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Tenant_username_key" ON "Tenant"("username");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Booking" ADD CONSTRAINT "Booking_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Booking" ADD CONSTRAINT "Booking_slot_id_fkey" FOREIGN KEY ("slot_id") REFERENCES "Slot"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Slot" ADD CONSTRAINT "Slot_tenantId_fkey" FOREIGN KEY ("tenantId") REFERENCES "Tenant"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
@@ -0,0 +1,10 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Admin" ALTER COLUMN "password_confirmation" DROP NOT NULL;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Slot" ALTER COLUMN "distance" DROP NOT NULL,
|
||||
ALTER COLUMN "duration" DROP NOT NULL,
|
||||
ALTER COLUMN "directions" DROP NOT NULL;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "User" ADD COLUMN "password" TEXT;
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- Added the required column `contact` to the `Booking` table without a default value. This is not possible if the table is not empty.
|
||||
- Added the required column `license_plates` to the `Booking` table without a default value. This is not possible if the table is not empty.
|
||||
- Added the required column `owner` to the `Booking` table without a default value. This is not possible if the table is not empty.
|
||||
- Made the column `end_at` on table `Booking` required. This step will fail if there are existing NULL values in that column.
|
||||
|
||||
*/
|
||||
-- AlterEnum
|
||||
ALTER TYPE "BookingStatus" ADD VALUE 'out';
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Booking" ADD COLUMN "contact" TEXT NOT NULL,
|
||||
ADD COLUMN "license_plates" TEXT NOT NULL,
|
||||
ADD COLUMN "owner" TEXT NOT NULL,
|
||||
ALTER COLUMN "end_at" SET NOT NULL;
|
||||
@@ -0,0 +1,5 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Booking" ADD COLUMN "pricing" INTEGER;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Slot" ADD COLUMN "pricing_per_hour" INTEGER NOT NULL DEFAULT 0;
|
||||
@@ -0,0 +1,13 @@
|
||||
-- CreateEnum
|
||||
CREATE TYPE "SlotType" AS ENUM ('Car', 'Mortorbike');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "BookingType" AS ENUM ('fulltime', 'parttime', 'all');
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Booking" ADD COLUMN "alow_booking_type" "BookingType" DEFAULT 'all',
|
||||
ADD COLUMN "slot_type" "SlotType" DEFAULT 'Car';
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Slot" ADD COLUMN "booking_type" "BookingType" DEFAULT 'all',
|
||||
ADD COLUMN "image" TEXT;
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `alow_booking_type` on the `Booking` table. All the data in the column will be lost.
|
||||
- You are about to drop the column `slot_type` on the `Booking` table. All the data in the column will be lost.
|
||||
- You are about to drop the column `booking_type` on the `Slot` table. All the data in the column will be lost.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "Booking" DROP COLUMN "alow_booking_type",
|
||||
DROP COLUMN "slot_type";
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Slot" DROP COLUMN "booking_type";
|
||||
|
||||
-- DropEnum
|
||||
DROP TYPE "BookingType";
|
||||
|
||||
-- DropEnum
|
||||
DROP TYPE "SlotType";
|
||||
@@ -0,0 +1,13 @@
|
||||
-- CreateEnum
|
||||
CREATE TYPE "SlotType" AS ENUM ('Car', 'Mortorbike', 'Orther');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "BookingType" AS ENUM ('Fulltime', 'Parttime', 'All');
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Booking" ADD COLUMN "booking_type" "BookingType" DEFAULT 'All',
|
||||
ADD COLUMN "slot_type" "SlotType" DEFAULT 'Car';
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Slot" ADD COLUMN "alow_booking_type" "BookingType" DEFAULT 'All',
|
||||
ADD COLUMN "slot_type" "SlotType" DEFAULT 'Car';
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `image` on the `Slot` table. All the data in the column will be lost.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "Slot" DROP COLUMN "image",
|
||||
ADD COLUMN "close" VARCHAR,
|
||||
ADD COLUMN "images" TEXT[] DEFAULT ARRAY[]::TEXT[],
|
||||
ADD COLUMN "open" VARCHAR;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "File" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"path" TEXT NOT NULL,
|
||||
"slot_id" INTEGER,
|
||||
"user_id" INTEGER,
|
||||
"size" INTEGER NOT NULL,
|
||||
"mine_type" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
"deletedAt" TIMESTAMP(3),
|
||||
|
||||
CONSTRAINT "File_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
21
backend/prisma/migrations/20241027172209_/migration.sql
Normal file
21
backend/prisma/migrations/20241027172209_/migration.sql
Normal file
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to alter the column `close` on the `Slot` table. The data in that column could be lost. The data in that column will be cast from `VarChar` to `VarChar(1)`.
|
||||
- You are about to alter the column `open` on the `Slot` table. The data in that column could be lost. The data in that column will be cast from `VarChar` to `VarChar(1)`.
|
||||
|
||||
*/
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "Slot" DROP CONSTRAINT "Slot_tenantId_fkey";
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Slot" ADD COLUMN "userId" INTEGER,
|
||||
ALTER COLUMN "tenantId" DROP NOT NULL,
|
||||
ALTER COLUMN "close" SET DATA TYPE VARCHAR(5),
|
||||
ALTER COLUMN "open" SET DATA TYPE VARCHAR(5);
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Slot" ADD CONSTRAINT "Slot_tenantId_fkey" FOREIGN KEY ("tenantId") REFERENCES "Tenant"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Slot" ADD CONSTRAINT "Slot_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
@@ -0,0 +1,34 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "Comment" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"slot_id" INTEGER NOT NULL,
|
||||
"content" TEXT,
|
||||
"tenantId" INTEGER,
|
||||
"userId" INTEGER,
|
||||
|
||||
CONSTRAINT "Comment_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Like" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"commentId" INTEGER NOT NULL,
|
||||
"userId" INTEGER,
|
||||
|
||||
CONSTRAINT "Like_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Comment" ADD CONSTRAINT "Comment_tenantId_fkey" FOREIGN KEY ("tenantId") REFERENCES "Tenant"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Comment" ADD CONSTRAINT "Comment_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Comment" ADD CONSTRAINT "Comment_slot_id_fkey" FOREIGN KEY ("slot_id") REFERENCES "Slot"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Like" ADD CONSTRAINT "Like_commentId_fkey" FOREIGN KEY ("commentId") REFERENCES "Comment"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Like" ADD CONSTRAINT "Like_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
@@ -0,0 +1,16 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "Vote" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"commentId" INTEGER NOT NULL,
|
||||
"userId" INTEGER,
|
||||
"source" INTEGER NOT NULL DEFAULT 0,
|
||||
"type" INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
CONSTRAINT "Vote_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Vote" ADD CONSTRAINT "Vote_commentId_fkey" FOREIGN KEY ("commentId") REFERENCES "Comment"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Vote" ADD CONSTRAINT "Vote_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `commentId` on the `Vote` table. All the data in the column will be lost.
|
||||
- Added the required column `slot_id` to the `Vote` table without a default value. This is not possible if the table is not empty.
|
||||
|
||||
*/
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "Vote" DROP CONSTRAINT "Vote_commentId_fkey";
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "User" ALTER COLUMN "email" DROP NOT NULL;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Vote" DROP COLUMN "commentId",
|
||||
ADD COLUMN "slot_id" INTEGER NOT NULL;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Vote" ADD CONSTRAINT "Vote_slot_id_fkey" FOREIGN KEY ("slot_id") REFERENCES "Slot"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
@@ -0,0 +1 @@
|
||||
CREATE EXTENSION IF NOT EXISTS unaccent;
|
||||
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- A unique constraint covering the columns `[lat,lng]` on the table `Slot` will be added. If there are existing duplicate values, this will fail.
|
||||
- A unique constraint covering the columns `[phone]` on the table `User` will be added. If there are existing duplicate values, this will fail.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "Slot" ADD COLUMN "phone" TEXT;
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "User_phone_key" ON "User"("phone");
|
||||
3
backend/prisma/migrations/migration_lock.toml
Normal file
3
backend/prisma/migrations/migration_lock.toml
Normal file
@@ -0,0 +1,3 @@
|
||||
# Please do not edit this file manually
|
||||
# It should be added in your version-control system (i.e. Git)
|
||||
provider = "postgresql"
|
||||
2078
backend/prisma/packingCrawler/csv/all-task-1-overview.csv
Normal file
2078
backend/prisma/packingCrawler/csv/all-task-1-overview.csv
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,2 @@
|
||||
place_id,name,description,is_spending_on_ads,reviews,rating,competitors,website,phone,can_claim,owner_name,owner_profile_link,featured_image,main_category,categories,workday_timing,is_temporarily_closed,closed_on,address,review_keywords,link,query
|
||||
ChIJLUYDEHKpNTERg5750vGFcfM,Bai do xe,,,0,0,,,,,,,https://lh3.ggpht.com/p/AB5caB8u2cDK_j-Vtg7Rz4pNJTEXCojwxupXEvhzqyiyxwEnSvWM_C95Oe_M91fGtbC1-b_TulmvtA8H1As5sorgQ8SvjssVASiUjHL7i6JLn5pu_DPfknMunN4YepWQbse63lyc9ETdog=s1024,Parking grounds,Parking grounds,,,Open All Days,"Phuc Dong, Long Bien, Hanoi",,"https://www.google.com/maps/place/Parking+lot,+Ph%C3%BAc+%C4%90%E1%BB%93ng,+Long+Bi%C3%AAn,+H%C3%A0+N%E1%BB%99i/@21.0387946,105.894429,17z/data=!3m1!4b1!4m6!3m5!1s0x3135a9721003462d:0xf37185f1d2f99e83!8m2!3d21.0388519!4d105.8944483!16s%2Fg%2F11cs6kx11q?authuser=0&entry=ttu&g_ep=EgoyMDI1MDQyMC4wIKXMDSoJLDEwMjExNjM5SAFQAw%3D%3D",bai do xe quan long bien
|
||||
|
2009
backend/prisma/packingCrawler/csv/all-task-15-overview.csv
Normal file
2009
backend/prisma/packingCrawler/csv/all-task-15-overview.csv
Normal file
File diff suppressed because it is too large
Load Diff
697
backend/prisma/packingCrawler/csv/all-task-3-overview.csv
Normal file
697
backend/prisma/packingCrawler/csv/all-task-3-overview.csv
Normal file
@@ -0,0 +1,697 @@
|
||||
place_id,name,description,is_spending_on_ads,reviews,rating,competitors,website,phone,can_claim,owner_name,owner_profile_link,featured_image,main_category,categories,workday_timing,is_temporarily_closed,closed_on,address,review_keywords,link,query
|
||||
ChIJoa2bdT4vdTERbzexHZTKq7Y,BAI XE PARKING PRO,,,50,2.6,"Name: Bai giu xe o to
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+gi%E1%BB%AF+xe+%C3%B4+t%C3%B4/@10.7635045,106.6826608?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 37 reviews
|
||||
|
||||
Name: Bai Giu Xe May
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+Gi%E1%BB%AF+Xe+M%C3%A1y/@10.768580199999999,106.69064639999999?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 32 reviews
|
||||
|
||||
Name: Bai Giu Xe 32 Le Anh Xuan
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+Gi%E1%BB%AF+Xe+32+L%C3%AA+Anh+Xu%C3%A2n/@10.7721232,106.6952631?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 9 reviews
|
||||
|
||||
Name: Bai do xe ParkTech
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+%C4%91%E1%BB%97+xe+ParkTech/@10.768542400000001,106.69345159999999?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 9 reviews
|
||||
|
||||
Name: 24/24 Car Parking
|
||||
Link: https://www.google.com/maps/search/24%2F24+Car+Parking/@10.7776628,106.69725009999999?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 5 reviews",,,,BAI XE PARKING PRO (Owner),https://www.google.com/maps/contrib/116397855136926446668,https://lh3.ggpht.com/p/AB5caB_afIK3BhYo9bWBAI1rGIw-GRBOZeRE7v4PP0oL1_nt3cFPNkmrnvtxGIuzRgFNLUFb3qxoMrQhPReVZxuJBVye6PmLFQeyAlvw4IS5wMUlnxRpoLQ-Sr8JXr7SIEg-S6moPiQuwg=s1024,Parking lot,Parking lot,Open 24 hours,,Open All Days,"2B D. Pham Ngu Lao, Phuong Pham Ngu Lao, Quan 1, Ho Chi Minh",price,https://www.google.com/maps/place/B%C3%83I+XE+PARKING+PRO/data=!4m7!3m6!1s0x31752f3e759bada1:0xb6abca941db1376f!8m2!3d10.7690767!4d106.6941737!16s%2Fg%2F11c37q7l48!19sChIJoa2bdT4vdTERbzexHZTKq7Y?authuser=0&hl=en&rclk=1,bai do xe quan 1
|
||||
ChIJjfOa30YvdTERpjMT1_kLJao,Bai Gui Xe Pho Di Bo,,,141,2.1,"Name: Bai Giu Xe May
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+Gi%E1%BB%AF+Xe+M%C3%A1y/@10.7739127,106.7029148?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 66 reviews
|
||||
|
||||
Name: BAI XE PARKING PRO
|
||||
Link: https://www.google.com/maps/search/B%C3%83I+XE+PARKING+PRO/@10.7690767,106.6941737?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 50 reviews
|
||||
|
||||
Name: Bai Giu Xe May
|
||||
Link: https://www.google.com/maps/search/Ba%CC%83i+Gi%C6%B0%CC%83+Xe+Ma%CC%81y/@10.772575999999999,106.703828?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 16 reviews
|
||||
|
||||
Name: 24/24 Car Parking
|
||||
Link: https://www.google.com/maps/search/24%2F24+Car+Parking/@10.7776628,106.69725009999999?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 5 reviews",,,1,Bai Gui Xe Pho Di Bo (Owner),,https://lh3.ggpht.com/p/AB5caB-6mwRuvc4-Z8akfTIL5JzoJOm6Q15lvzrjap3aQryQwSmZ1F-5MTWyMx-A0u4FW4Ao3KUvgYDo96NX80g6Ou2f_gU_75pz2YSmgsYCpBRoZp-SA6CtB0HYuSKu8ld1F6AuGR13=s1024,Parking lot for motorcycles,Parking lot for motorcycles,,,Open All Days,"8(Hoc vien Ngan hang Viet Nam) D. Ton That Thiep, Ben Nghe, Quan 1, Ho Chi Minh",,https://www.google.com/maps/place/B%C3%A3i+G%E1%BB%ADi+Xe+Ph%E1%BB%91+%C4%90i+B%E1%BB%99/data=!4m7!3m6!1s0x31752f46df9af38d:0xaa250bf9d71333a6!8m2!3d10.77389!4d106.702881!16s%2Fg%2F11c1ny0vmc!19sChIJjfOa30YvdTERpjMT1_kLJao?authuser=0&hl=en&rclk=1,bai do xe quan 1
|
||||
ChIJwTqf3HsvdTERZpjC1f9bJqc,Bai giu xe o to,,,37,4.2,"Name: BAI XE PARKING PRO
|
||||
Link: https://www.google.com/maps/search/B%C3%83I+XE+PARKING+PRO/@10.7690767,106.6941737?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 50 reviews
|
||||
|
||||
Name: Bai xe 24/24 606 Tran Hung Dao
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+xe+24%2F24+606+Tr%E1%BA%A7n+H%C6%B0ng+%C4%90%E1%BA%A1o/@10.7551774,106.6799095?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 21 reviews
|
||||
|
||||
Name: Bai Giu Xe 32 Le Anh Xuan
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+Gi%E1%BB%AF+Xe+32+L%C3%AA+Anh+Xu%C3%A2n/@10.7721232,106.6952631?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 9 reviews
|
||||
|
||||
Name: Bai giu xe Van Trung 24/24 o to - xe may
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+gi%E1%BB%AF+xe+V%C4%83n+Trung+24%2F24+%C3%B4+t%C3%B4+-+xe+m%C3%A1y/@10.756941699999999,106.6725671?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 5 reviews
|
||||
|
||||
Name: Bai giu xe 235 Nguyen Van Cu
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+gi%E1%BB%AF+xe+235+Nguy%E1%BB%85n+V%C4%83n+C%E1%BB%AB/@10.7631988,106.6829434?authuser=0&hl=en&entry=ttu
|
||||
Reviews: No Reviews reviews",,,1,Bai giu xe o to (Owner),,https://lh3.ggpht.com/p/AB5caB_I0BuOaRV6pAr_4vJeFMmPZ9pYCRFhvLwaGuOxwKoi-YN1RVVhuWpNVA8Hw_c2f3-CWKUh_KSw0xofPxQP7wHemjcjRGmqq43IJ3tuv6frHscptG62Q58YWonAOwsM7gEUFdaSlw=s1024,Parking lot,Parking lot,,,Open All Days,"235 D. Nguyen Van Cu, Phuong Nguyen Cu Trinh, Quan 1, Ho Chi Minh",,https://www.google.com/maps/place/B%C3%A3i+gi%E1%BB%AF+xe+%C3%B4+t%C3%B4/data=!4m7!3m6!1s0x31752f7bdc9f3ac1:0xa7265bffd5c29866!8m2!3d10.7635045!4d106.6826608!16s%2Fg%2F11jht84ldr!19sChIJwTqf3HsvdTERZpjC1f9bJqc?authuser=0&hl=en&rclk=1,bai do xe quan 1
|
||||
ChIJLegyLvUvdTERqLS05m0TH9Y,Bai Giu Xe May,,,17,2.8,"Name: Bai Gui Xe Pho Di Bo
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+G%E1%BB%ADi+Xe+Ph%E1%BB%91+%C4%90i+B%E1%BB%99/@10.77389,106.70288099999999?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 141 reviews
|
||||
|
||||
Name: Bai Giu Xe May
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+Gi%E1%BB%AF+Xe+M%C3%A1y/@10.7739127,106.7029148?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 66 reviews
|
||||
|
||||
Name: Bai gui xe may Song Lam
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+g%E1%BB%ADi+xe+m%C3%A1y+Song+L%C3%A2m/@10.773151499999999,106.70303740000001?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 22 reviews
|
||||
|
||||
Name: Bai giu xe TNXP
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+gi%E1%BB%AF+xe+TNXP/@10.771806999999999,106.699084?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 4 reviews",,,1,Bai Giu Xe May (Owner),,https://lh3.ggpht.com/p/AB5caB9rcPxeOAQ5QhYiF73WcKR17JscFjUTORRJprivtRDqGZ1y81zfBWcg4W1G6J7IpZYmzmO8aAavjZjfgb28zzRKQBsZMl_iAmn00p8jeY0cHQhIZSm1xXJClgH07vhvlK0_SZR5=s1024,Parking lot for motorcycles,Parking lot for motorcycles,,,Open All Days,"78 D. Ho Tung Mau, Ben Nghe, Quan 1, Ho Chi Minh",,https://www.google.com/maps/place/Ba%CC%83i+Gi%C6%B0%CC%83+Xe+Ma%CC%81y/data=!4m7!3m6!1s0x31752ff52e32e82d:0xd61f136de6b4b4a8!8m2!3d10.772576!4d106.703828!16s%2Fg%2F11g10gw83f!19sChIJLegyLvUvdTERqLS05m0TH9Y?authuser=0&hl=en&rclk=1,bai do xe quan 1
|
||||
ChIJ5xqW30YvdTERhwJ9Jh5Dpi0,Bai Giu Xe May,,,66,1.9,"Name: Bai Gui Xe Pho Di Bo
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+G%E1%BB%ADi+Xe+Ph%E1%BB%91+%C4%90i+B%E1%BB%99/@10.77389,106.70288099999999?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 141 reviews
|
||||
|
||||
Name: BAI XE PARKING PRO
|
||||
Link: https://www.google.com/maps/search/B%C3%83I+XE+PARKING+PRO/@10.7690767,106.6941737?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 50 reviews
|
||||
|
||||
Name: Bai Giu Xe May
|
||||
Link: https://www.google.com/maps/search/Ba%CC%83i+Gi%C6%B0%CC%83+Xe+Ma%CC%81y/@10.772575999999999,106.703828?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 16 reviews
|
||||
|
||||
Name: Bai Giu Xe 32 Le Anh Xuan
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+Gi%E1%BB%AF+Xe+32+L%C3%AA+Anh+Xu%C3%A2n/@10.7721232,106.6952631?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 9 reviews",,,1,Bai Giu Xe May (Owner),,https://lh3.ggpht.com/p/AB5caB8tbcCMOii8sWJO5et7JTs2YKAqLY9LVr4u7v5pzA1HLRHeQP2ZZkAlgVwlX6vRJfoO-SNx74TJHMa8hooK7vM9NocgZ3qRIOfJAz4JSKgnJCGMEL-XqyWflHXEXfSAF3gvFflZpQ=s1024,Parking lot for motorcycles,Parking lot for motorcycles,,,Open All Days,"3 D. Ton That Thiep, Ben Nghe, Quan 1, Ho Chi Minh",,https://www.google.com/maps/place/B%C3%A3i+Gi%E1%BB%AF+Xe+M%C3%A1y/data=!4m7!3m6!1s0x31752f46df961ae7:0x2da6431e267d0287!8m2!3d10.7739127!4d106.7029148!16s%2Fg%2F11ckqsgh0m!19sChIJ5xqW30YvdTERhwJ9Jh5Dpi0?authuser=0&hl=en&rclk=1,bai do xe quan 1
|
||||
ChIJiUas8swodTERzkVSNf51yuY,Bai Xe,,,1,3,"Name: Bai Giu Xe P
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+Gi%E1%BB%AF+Xe+P/@10.789042,106.690759?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 8 reviews
|
||||
|
||||
Name: Bai xe Rua xe Trung Khanh
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+xe+R%E1%BB%ADa+xe+Tr%C3%B9ng+Kh%C3%A1nh/@10.7862001,106.69589719999999?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 6 reviews
|
||||
|
||||
Name: Bai Xe oto 32 Nguyen Binh Khiem
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+Xe+oto+32+Nguy%E1%BB%85n+B%E1%BB%89nh+Khi%C3%AAm/@10.7912056,106.7010514?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 4 reviews
|
||||
|
||||
Name: Bai giu xe Parking 24/24 xe may Hai Ba Trung
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+gi%E1%BB%AF+xe+Parking+24%2F24+xe+m%C3%A1y+Hai+B%C3%A0+Tr%C6%B0ng/@10.7901027,106.6893982?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 1 reviews
|
||||
|
||||
Name: Bai xe Nguyen Van Chiem
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+xe+Nguy%E1%BB%85n+V%C4%83n+Chi%C3%AAm/@10.781750599999999,106.6981492?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 1 reviews",,,1,Bai Xe (Owner),,,Parking lot,Parking lot,,,Open All Days,"111 Nguyen Phi Khanh, Phuong Tan Dinh, Quan 1, Ho Chi Minh",,https://www.google.com/maps/place/B%C3%A3i+Xe/data=!4m7!3m6!1s0x317528ccf2ac4689:0xe6ca75fe355245ce!8m2!3d10.7916427!4d106.6924902!16s%2Fg%2F11flt90d22!19sChIJiUas8swodTERzkVSNf51yuY?authuser=0&hl=en&rclk=1,bai do xe quan 1
|
||||
ChIJAUiqcD0vdTERolFzh1pYDbs,Bai Giu Xe May,,,32,4.1,"Name: BAI XE PARKING PRO
|
||||
Link: https://www.google.com/maps/search/B%C3%83I+XE+PARKING+PRO/@10.7690767,106.6941737?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 50 reviews
|
||||
|
||||
Name: Bai Giu Xe 32 Le Anh Xuan
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+Gi%E1%BB%AF+Xe+32+L%C3%AA+Anh+Xu%C3%A2n/@10.7721232,106.6952631?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 9 reviews
|
||||
|
||||
Name: Bai Giu Xe May - O To
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+Gi%E1%BB%AF+Xe+M%C3%A1y+-+%C3%94+T%C3%B4/@10.77769,106.6875?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 9 reviews
|
||||
|
||||
Name: Bai do xe ParkTech
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+%C4%91%E1%BB%97+xe+ParkTech/@10.768542400000001,106.69345159999999?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 9 reviews",,,1,Bai Giu Xe May (Owner),,https://lh3.ggpht.com/p/AB5caB8YardWPkPstcoEsVDHDJSzbuYZlj9MwXKsIeU1G_pLBzF2BtCpbGhOtp691YnzN9Ir5jLJXquHW7s4acpMYGCULV6ZurmZjV9l8aB3ESeqH316pKZw7i7nyZtUmbC-22RaKW4=s1024,Parking lot for motorcycles,Parking lot for motorcycles,,,Open All Days,"200 D. Le Lai, Phuong Pham Ngu Lao, Quan 1, Ho Chi Minh",,https://www.google.com/maps/place/B%C3%A3i+Gi%E1%BB%AF+Xe+M%C3%A1y/data=!4m7!3m6!1s0x31752f3d70aa4801:0xbb0d585a877351a2!8m2!3d10.7685802!4d106.6906464!16s%2Fg%2F11cjnp106c!19sChIJAUiqcD0vdTERolFzh1pYDbs?authuser=0&hl=en&rclk=1,bai do xe quan 1
|
||||
ChIJy-E-tG0pdTERIYzL6_Tes8Q,Bai giu xe Parking 24/24 xe may Hai Ba Trung,"Cong ty TNHH Dau tu va Khai thac Parking co Giay chung nhan dang ky hoat dong chi nhanh, tai dia diem dang ky co trang bi bien bang",,1,5,"Name: Bai Giu Xe P
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+Gi%E1%BB%AF+Xe+P/@10.789042,106.690759?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 8 reviews
|
||||
|
||||
Name: 24/24 Car Parking
|
||||
Link: https://www.google.com/maps/search/24%2F24+Car+Parking/@10.7776628,106.69725009999999?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 5 reviews
|
||||
|
||||
Name: Bai Xe
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+Xe/@10.791642699999999,106.6924902?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 1 reviews
|
||||
|
||||
Name: Parking
|
||||
Link: https://www.google.com/maps/search/Parking/@10.773188,106.70186400000001?authuser=0&hl=en&entry=ttu
|
||||
Reviews: No Reviews reviews
|
||||
|
||||
Name: Dich Vu Giu Xe Trong Nha
|
||||
Link: https://www.google.com/maps/search/D%E1%BB%8Bch+V%E1%BB%A5+Gi%E1%BB%AF+Xe+Trong+Nh%C3%A0/@10.7725504,106.6967189?authuser=0&hl=en&entry=ttu
|
||||
Reviews: No Reviews reviews",https://vantrungparking.com/,0931 306 730,,Bai giu xe Parking 24/24 xe may Hai Ba Trung (Owner),https://www.google.com/maps/contrib/110070225170831270453,https://lh3.ggpht.com/p/AF1QipO5gOjIs3R6oOdwnTr0Kqpst6JF2nBYMEctiXtd=s1024,Parking lot,Parking lot,Open 24 hours,,Open All Days,"338 Hai Ba Trung, P.Tan Dinh, 235-237 Tran Hung Dao, P, Quan 1, Ho Chi Minh",,https://www.google.com/maps/place/B%C3%A3i+gi%E1%BB%AF+xe+Parking+24%2F24+xe+m%C3%A1y+Hai+B%C3%A0+Tr%C6%B0ng/data=!4m7!3m6!1s0x3175296db43ee1cb:0xc4b3def4ebcb8c21!8m2!3d10.7901027!4d106.6893982!16s%2Fg%2F11w8vpsnt1!19sChIJy-E-tG0pdTERIYzL6_Tes8Q?authuser=0&hl=en&rclk=1,bai do xe quan 1
|
||||
ChIJ3W8fBgAvdTERh7YFXFOrL6Y,Bai giu xe 235 Nguyen Van Cu,,,0,0,"Name: Bai giu xe o to
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+gi%E1%BB%AF+xe+%C3%B4+t%C3%B4/@10.7635045,106.6826608?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 37 reviews
|
||||
|
||||
Name: Bai do xe ParkTech
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+%C4%91%E1%BB%97+xe+ParkTech/@10.768542400000001,106.69345159999999?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 9 reviews
|
||||
|
||||
Name: Bai xe may va o to
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+xe+m%C3%A1y+v%C3%A0+%C3%B4+t%C3%B4/@10.764699,106.6899339?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 5 reviews
|
||||
|
||||
Name: Bai xe Nguyen Van Chiem
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+xe+Nguy%E1%BB%85n+V%C4%83n+Chi%C3%AAm/@10.781750599999999,106.6981492?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 1 reviews
|
||||
|
||||
Name: Bai giu xe Parking 24/24 xe may Hai Ba Trung
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+gi%E1%BB%AF+xe+Parking+24%2F24+xe+m%C3%A1y+Hai+B%C3%A0+Tr%C6%B0ng/@10.7901027,106.6893982?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 1 reviews",,,1,Bai giu xe 235 Nguyen Van Cu (Owner),,,Parking lot,Parking lot,Open 24 hours,,Open All Days,"QM7M+75G, Phuong Nguyen Cu Trinh, Quan 1, Ho Chi Minh",,https://www.google.com/maps/place/B%C3%A3i+gi%E1%BB%AF+xe+235+Nguy%E1%BB%85n+V%C4%83n+C%E1%BB%AB/data=!4m7!3m6!1s0x31752f00061f6fdd:0xa62fab535c05b687!8m2!3d10.7631988!4d106.6829434!16s%2Fg%2F11y54vjg6t!19sChIJ3W8fBgAvdTERh7YFXFOrL6Y?authuser=0&hl=en&rclk=1,bai do xe quan 1
|
||||
ChIJo7pENwAvdTERz2TIVb1HnJc,Bai giu xe TNXP,,,19,2.9,"Name: Bai Giu Xe May
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+Gi%E1%BB%AF+Xe+M%C3%A1y/@10.7739127,106.7029148?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 66 reviews
|
||||
|
||||
Name: BAI XE PARKING PRO
|
||||
Link: https://www.google.com/maps/search/B%C3%83I+XE+PARKING+PRO/@10.7690767,106.6941737?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 50 reviews
|
||||
|
||||
Name: Bai Giu Xe 32 Le Anh Xuan
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+Gi%E1%BB%AF+Xe+32+L%C3%AA+Anh+Xu%C3%A2n/@10.7721232,106.6952631?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 9 reviews
|
||||
|
||||
Name: Dich Vu Giu Xe Trong Nha
|
||||
Link: https://www.google.com/maps/search/D%E1%BB%8Bch+V%E1%BB%A5+Gi%E1%BB%AF+Xe+Trong+Nh%C3%A0/@10.7725504,106.6967189?authuser=0&hl=en&entry=ttu
|
||||
Reviews: No Reviews reviews
|
||||
|
||||
Name: Parking
|
||||
Link: https://www.google.com/maps/search/Parking/@10.773188,106.70186400000001?authuser=0&hl=en&entry=ttu
|
||||
Reviews: No Reviews reviews",https://goonus.io/signup/6277729706696687518?utm_campaign=invite,,1,Bai giu xe TNXP (Owner),,https://lh3.ggpht.com/p/AB5caB8mAWCdzjjbVjmNlHojcURQ3tzmtXIEAYlRDq-VdTfRrMejO4I1bDoQMJ-SBijUVxU7JqWpH4UAwVaQ30723rqhT-4eEPU-i0hhpcbWTfv0jSudnRfsv35Hgu0gN0Z5IzgEVIuP=s1024,Parking lot for motorcycles,Parking lot for motorcycles,Open 24 hours,,Open All Days,"4 D. Truong Dinh, Phuong Pham Ngu Lao, Quan 1, Ho Chi Minh",,https://www.google.com/maps/place/B%C3%A3i+gi%E1%BB%AF+xe+TNXP/data=!4m7!3m6!1s0x31752f003744baa3:0x979c47bd55c864cf!8m2!3d10.7710666!4d106.6969077!16s%2Fg%2F11wvd4vq07!19sChIJo7pENwAvdTERz2TIVb1HnJc?authuser=0&hl=en&rclk=1,bai do xe quan 1
|
||||
ChIJ9XQycnUvdTERZjC5UrhE79w,Bai Giu Xe 29 Nam Ki,,,6,3.7,"Name: BAI XE PARKING PRO
|
||||
Link: https://www.google.com/maps/search/B%C3%83I+XE+PARKING+PRO/@10.7690767,106.6941737?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 50 reviews
|
||||
|
||||
Name: Bai Giu Xe 32 Le Anh Xuan
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+Gi%E1%BB%AF+Xe+32+L%C3%AA+Anh+Xu%C3%A2n/@10.7721232,106.6952631?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 9 reviews
|
||||
|
||||
Name: 24/24 Car Parking
|
||||
Link: https://www.google.com/maps/search/24%2F24+Car+Parking/@10.7776628,106.69725009999999?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 5 reviews
|
||||
|
||||
Name: Bai Xe oto 32 Nguyen Binh Khiem
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+Xe+oto+32+Nguy%E1%BB%85n+B%E1%BB%89nh+Khi%C3%AAm/@10.7912056,106.7010514?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 4 reviews
|
||||
|
||||
Name: Huynh Huu Ngoc Vehicle Keeping Store
|
||||
Link: https://www.google.com/maps/search/Huynh+Huu+Ngoc+Vehicle+Keeping+Store/@10.7710957,106.70500179999999?authuser=0&hl=en&entry=ttu
|
||||
Reviews: No Reviews reviews",,0776 287 435,1,Bai Giu Xe 29 Nam Ki (Owner),,https://lh3.ggpht.com/p/AB5caB_JHXNOgdqygDLqbwElMdKbsTBcp3-aF-zSI9nkkSCXvS-0P1vSCM4aP87lvW9AoyNHaAp4fQMwP6pHZrrg4aUMRB49375Nl77kbf7SeXVeH1egHiOUsGk9zI1xdblSlzBeJP5j2A=s1024,Transportation service,Transportation service,,,Open All Days,"29 D. Nam Ky Khoi Nghia, Phuong Nguyen Thai Binh, Quan 1, Ho Chi Minh",,https://www.google.com/maps/place/B%C3%A3i+Gi%E1%BB%AF+Xe+29+Nam+K%C3%AC/data=!4m7!3m6!1s0x31752f75723274f5:0xdcef44b852b93066!8m2!3d10.76939!4d106.7017186!16s%2Fg%2F11fmd9thyp!19sChIJ9XQycnUvdTERZjC5UrhE79w?authuser=0&hl=en&rclk=1,bai do xe quan 1
|
||||
ChIJvRMP_z4vdTERYsj9rHHIOow,Bai xe Nguyen Van Chiem,,,1,5,"Name: Bai Giu Xe P
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+Gi%E1%BB%AF+Xe+P/@10.789042,106.690759?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 8 reviews
|
||||
|
||||
Name: Bai xe Rua xe Trung Khanh
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+xe+R%E1%BB%ADa+xe+Tr%C3%B9ng+Kh%C3%A1nh/@10.7862001,106.69589719999999?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 6 reviews
|
||||
|
||||
Name: 24/24 Car Parking
|
||||
Link: https://www.google.com/maps/search/24%2F24+Car+Parking/@10.7776628,106.69725009999999?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 5 reviews
|
||||
|
||||
Name: Bai Xe oto 32 Nguyen Binh Khiem
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+Xe+oto+32+Nguy%E1%BB%85n+B%E1%BB%89nh+Khi%C3%AAm/@10.7912056,106.7010514?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 4 reviews
|
||||
|
||||
Name: Bai Xe
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+Xe/@10.791642699999999,106.6924902?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 1 reviews",,,1,Bai xe Nguyen Van Chiem (Owner),,https://lh3.ggpht.com/p/AB5caB_Y9YbgICHRzqiQj4qolpihxTWQ94IKV-Gw1u4V13cYwCjzPMo29GzOdMJGM9nw4FU77PhTnsMsBu4oSULIycwc-xADMTE82SUCCPqDTrkyiFq5_8OsRv4Gb0DgXImyict7-1Gm=s1024,Parking lot,Parking lot,,,Open All Days,"7 Nguyen Van Chiem, Ben Nghe, Quan 1, Ho Chi Minh 700000",,https://www.google.com/maps/place/B%C3%A3i+xe+Nguy%E1%BB%85n+V%C4%83n+Chi%C3%AAm/data=!4m7!3m6!1s0x31752f3eff0f13bd:0x8c3ac871acfdc862!8m2!3d10.7817506!4d106.6981492!16s%2Fg%2F11kr6y8238!19sChIJvRMP_z4vdTERYsj9rHHIOow?authuser=0&hl=en&rclk=1,bai do xe quan 1
|
||||
ChIJufuso3UvdTERzQ4fqNDtikA,Bai giu xe oto - Cuu ho accquy,Co Cuu ho accquy khu vuc q1.,,20,4.7,"Name: Bai Giu Xe 32 Le Anh Xuan
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+Gi%E1%BB%AF+Xe+32+L%C3%AA+Anh+Xu%C3%A2n/@10.7721232,106.6952631?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 9 reviews
|
||||
|
||||
Name: 24/24 Car Parking
|
||||
Link: https://www.google.com/maps/search/24%2F24+Car+Parking/@10.7776628,106.69725009999999?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 5 reviews
|
||||
|
||||
Name: Bai Xe oto 32 Nguyen Binh Khiem
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+Xe+oto+32+Nguy%E1%BB%85n+B%E1%BB%89nh+Khi%C3%AAm/@10.7912056,106.7010514?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 4 reviews
|
||||
|
||||
Name: Bai xe Nguyen Van Chiem
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+xe+Nguy%E1%BB%85n+V%C4%83n+Chi%C3%AAm/@10.781750599999999,106.6981492?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 1 reviews
|
||||
|
||||
Name: Dich Vu Giu Xe Trong Nha
|
||||
Link: https://www.google.com/maps/search/D%E1%BB%8Bch+V%E1%BB%A5+Gi%E1%BB%AF+Xe+Trong+Nh%C3%A0/@10.7725504,106.6967189?authuser=0&hl=en&entry=ttu
|
||||
Reviews: No Reviews reviews",,0789 181 981,,Bai giu xe oto - Cuu ho accquy (Owner),https://www.google.com/maps/contrib/103869263184088623842,https://lh3.ggpht.com/p/AF1QipPHTCky4kici02w79F0V6wH5rGl7GCeB0gQc8ou=s1024,Automobile storage facility,Automobile storage facility,,,Open All Days,"8B Nguyen Trung Truc, Phuong Ben Thanh, Quan 1, Ho Chi Minh",,https://www.google.com/maps/place/B%C3%A3i+gi%E1%BB%AF+xe+%C3%B4t%C3%B4+-+C%E1%BB%A9u+h%E1%BB%99+accquy/data=!4m7!3m6!1s0x31752f75a3acfbb9:0x408aedd0a81f0ecd!8m2!3d10.7744357!4d106.6988466!16s%2Fg%2F11v0x38z8c!19sChIJufuso3UvdTERzQ4fqNDtikA?authuser=0&hl=en&rclk=1,bai do xe quan 1
|
||||
ChIJ69uUK54pdTER-02eyEivpGc,Bai Xe oto 32 Nguyen Binh Khiem,,,4,2,"Name: Bai Giu Xe P
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+Gi%E1%BB%AF+Xe+P/@10.789042,106.690759?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 8 reviews
|
||||
|
||||
Name: Bai giu xe O to - Xe may Phuong Nam 24/24
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+gi%E1%BB%AF+xe+%C3%94+t%C3%B4+-+Xe+m%C3%A1y+Ph%C6%B0%C6%A1ng+Nam+24%2F24/@10.8083634,106.71442719999999?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 8 reviews
|
||||
|
||||
Name: Bai xe Nguyen Van Chiem
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+xe+Nguy%E1%BB%85n+V%C4%83n+Chi%C3%AAm/@10.781750599999999,106.6981492?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 1 reviews
|
||||
|
||||
Name: Bai Xe
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+Xe/@10.791642699999999,106.6924902?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 1 reviews
|
||||
|
||||
Name: Bai giu xe o to
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+gi%E1%BB%AF+xe+%C3%B4+t%C3%B4/@10.8100449,106.70267849999999?authuser=0&hl=en&entry=ttu
|
||||
Reviews: No Reviews reviews",,,1,Bai Xe oto 32 Nguyen Binh Khiem (Owner),,,Parking lot,Parking lot,,,Open All Days,"32 D. Nguyen Binh Khiem, Da Kao, Quan 1, Ho Chi Minh",,https://www.google.com/maps/place/B%C3%A3i+Xe+oto+32+Nguy%E1%BB%85n+B%E1%BB%89nh+Khi%C3%AAm/data=!4m7!3m6!1s0x3175299e2b94dbeb:0x67a4af48c89e4dfb!8m2!3d10.7912056!4d106.7010514!16s%2Fg%2F11tt6lmpm1!19sChIJ69uUK54pdTER-02eyEivpGc?authuser=0&hl=en&rclk=1,bai do xe quan 1
|
||||
ChIJA7Je_NcvdTERL1JFUKx39-M,Bai Giu Xe P,"Bai giu xe 24/7, su dung he thong gui xe hien dai, thoang mat, sach se",,8,3,"Name: 24/24 Car Parking
|
||||
Link: https://www.google.com/maps/search/24%2F24+Car+Parking/@10.7776628,106.69725009999999?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 5 reviews
|
||||
|
||||
Name: Bai Xe oto 32 Nguyen Binh Khiem
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+Xe+oto+32+Nguy%E1%BB%85n+B%E1%BB%89nh+Khi%C3%AAm/@10.7912056,106.7010514?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 4 reviews
|
||||
|
||||
Name: Bai giu xe Parking 24/24 xe may Hai Ba Trung
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+gi%E1%BB%AF+xe+Parking+24%2F24+xe+m%C3%A1y+Hai+B%C3%A0+Tr%C6%B0ng/@10.7901027,106.6893982?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 1 reviews
|
||||
|
||||
Name: Bai Xe
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+Xe/@10.791642699999999,106.6924902?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 1 reviews
|
||||
|
||||
Name: Bai xe Nguyen Van Chiem
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+xe+Nguy%E1%BB%85n+V%C4%83n+Chi%C3%AAm/@10.781750599999999,106.6981492?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 1 reviews",,0933 316 328,,Bai Giu Xe P (Owner),https://www.google.com/maps/contrib/101136618639883553008,https://lh3.ggpht.com/p/AF1QipPYSyAhjBwiCjEckJmYvAYDIgetyyr3guzMoo2F=s1024,Parking lot,Parking lot,Open 24 hours,,Open All Days,"282/1 Hai Ba Trung, Phuong Tan Dinh, Quan 1, Ho Chi Minh 700000",,https://www.google.com/maps/place/B%C3%A3i+Gi%E1%BB%AF+Xe+P/data=!4m7!3m6!1s0x31752fd7fc5eb203:0xe3f777ac5045522f!8m2!3d10.789042!4d106.690759!16s%2Fg%2F11stfdty7j!19sChIJA7Je_NcvdTERL1JFUKx39-M?authuser=0&hl=en&rclk=1,bai do xe quan 1
|
||||
ChIJ0YEXMjgvdTERA5orAJg2uYw,24/24 Car Parking,,,5,3.4,"Name: Bai Gui Xe Pho Di Bo
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+G%E1%BB%ADi+Xe+Ph%E1%BB%91+%C4%90i+B%E1%BB%99/@10.77389,106.70288099999999?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 141 reviews
|
||||
|
||||
Name: BAI XE PARKING PRO
|
||||
Link: https://www.google.com/maps/search/B%C3%83I+XE+PARKING+PRO/@10.7690767,106.6941737?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 50 reviews
|
||||
|
||||
Name: Parking Parkson 24/24
|
||||
Link: https://www.google.com/maps/search/Parking+Parkson+24%2F24/@10.777451,106.7021209?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 16 reviews
|
||||
|
||||
Name: Bai Giu Xe P
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+Gi%E1%BB%AF+Xe+P/@10.789042,106.690759?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 8 reviews
|
||||
|
||||
Name: Bai Giu Xe 29 Nam Ki
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+Gi%E1%BB%AF+Xe+29+Nam+K%C3%AC/@10.76939,106.70171859999999?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 6 reviews",,0367 338 676,1,24/24 Car Parking (Owner),,https://lh3.ggpht.com/p/AB5caB8jNYbCMe39GAD4LLtdW7BrUFVYZjRQ7dUyoMap7-4pdVxnrqXRUG8cGKH6j3d08NlbkiRBkhUG0OakthZvcdeYYUY14FIAwYpY-NFTLRMjscnq1EE2fvuUqgf4oiEtZ4oR1kqO=s1024,Parking garage,Parking garage,,,Open All Days,"136, Duong D. Nam Ky Khoi Nghia, Ben Nghe, Quan 1, Ho Chi Minh",,https://www.google.com/maps/place/24%2F24+Car+Parking/data=!4m7!3m6!1s0x31752f38321781d1:0x8cb93698002b9a03!8m2!3d10.7776628!4d106.6972501!16s%2Fg%2F1pzt6z64y!19sChIJ0YEXMjgvdTERA5orAJg2uYw?authuser=0&hl=en&rclk=1,bai do xe quan 1
|
||||
ChIJ4Z7amfsvdTERZleMPioD0LI,Bai Dau Xe 104 Yersin 24/24,"Bai do xe thong minh, su dung the an toan, tien loi, phuc vu 24/24, gia ca phu hop",,2,5,"Name: Bai xe Rua xe Trung Khanh
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+xe+R%E1%BB%ADa+xe+Tr%C3%B9ng+Kh%C3%A1nh/@10.7862001,106.69589719999999?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 6 reviews
|
||||
|
||||
Name: Bai xe may va o to
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+xe+m%C3%A1y+v%C3%A0+%C3%B4+t%C3%B4/@10.764699,106.6899339?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 5 reviews
|
||||
|
||||
Name: Bai dau xe
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+%C4%91%E1%BA%ADu+xe/@10.788573999999999,106.6560835?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 2 reviews
|
||||
|
||||
Name: Bai xe Nguyen Van Chiem
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+xe+Nguy%E1%BB%85n+V%C4%83n+Chi%C3%AAm/@10.781750599999999,106.6981492?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 1 reviews
|
||||
|
||||
Name: Bai giu xe Parking 24/24 xe may Hai Ba Trung
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+gi%E1%BB%AF+xe+Parking+24%2F24+xe+m%C3%A1y+Hai+B%C3%A0+Tr%C6%B0ng/@10.7901027,106.6893982?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 1 reviews",,0522 252 229,,Bai Dau Xe 104 Yersin 24/24 (Owner),https://www.google.com/maps/contrib/115612160117540812700,,Parking lot,Parking lot,Open 24 hours,,Open All Days,"104 D. Yersin, Phuong Nguyen Thai Binh, Quan 1, Ho Chi Minh 70000",,https://www.google.com/maps/place/B%C3%A3i+%C4%90%E1%BA%ADu+Xe+104+Yersin+24%2F24/data=!4m7!3m6!1s0x31752ffb99da9ee1:0xb2d0032a3e8c5766!8m2!3d10.7669625!4d106.6976759!16s%2Fg%2F11vjxdl9t_!19sChIJ4Z7amfsvdTERZleMPioD0LI?authuser=0&hl=en&rclk=1,bai do xe quan 1
|
||||
ChIJf2-KyDUvdTERGKLWAH4miqY,Bai do xe ParkTech,"ParkTech - Giai phap do xe thong minh, an toan va tien loi cho moi phuong tien
|
||||
Ban dang tim kiem mot bai do xe dang tin cay, an toan va tien loi cho chiec xe may hay o to cua minh? ParkTech tu hao la giai phap hoan hao dap ung moi nhu cau cua ban. Voi he thong bai do xe hien dai, rong rai va doi ngu nhan vien chuyen nghiep, tan tam, chung toi cam ket mang den cho ban trai nghiem do xe thoai mai va an tam tuyet doi.
|
||||
ParkTech cung cap dich vu do xe cho ca xe may va o to voi muc gia canh tranh, nhieu uu dai hap dan. He thong camera giam sat 24/7 cung doi ngu bao ve chuyen nghiep dam bao an ninh tuyet doi cho phuong tien cua ban. Khong gian bai do xe duoc thiet ke khoa hoc, thong thoang, giup ban de dang di chuyen va tim kiem cho dau xe.",,9,4.6,"Name: BAI XE PARKING PRO
|
||||
Link: https://www.google.com/maps/search/B%C3%83I+XE+PARKING+PRO/@10.7690767,106.6941737?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 50 reviews
|
||||
|
||||
Name: Bai Giu Xe May
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+Gi%E1%BB%AF+Xe+M%C3%A1y/@10.768580199999999,106.69064639999999?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 32 reviews
|
||||
|
||||
Name: Bai giu xe Parking 24/24 xe may Hai Ba Trung
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+gi%E1%BB%AF+xe+Parking+24%2F24+xe+m%C3%A1y+Hai+B%C3%A0+Tr%C6%B0ng/@10.7901027,106.6893982?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 1 reviews
|
||||
|
||||
Name: Parking
|
||||
Link: https://www.google.com/maps/search/Parking/@10.773188,106.70186400000001?authuser=0&hl=en&entry=ttu
|
||||
Reviews: No Reviews reviews
|
||||
|
||||
Name: Bai giu xe 235 Nguyen Van Cu
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+gi%E1%BB%AF+xe+235+Nguy%E1%BB%85n+V%C4%83n+C%E1%BB%AB/@10.7631988,106.6829434?authuser=0&hl=en&entry=ttu
|
||||
Reviews: No Reviews reviews",,0938 720 422,,Bai do xe ParkTech (Owner),https://www.google.com/maps/contrib/108296089831127979910,https://lh3.ggpht.com/p/AF1QipMqdfJ7ukgdOreVhn4ZAbDLjTWFrEA0Uy2n6isE=s1024,Parking lot,Parking lot,,,Open All Days,"04 D. Pham Ngu Lao, Phuong Pham Ngu Lao, Quan 1, Ho Chi Minh 71012",,https://www.google.com/maps/place/B%C3%A3i+%C4%91%E1%BB%97+xe+ParkTech/data=!4m7!3m6!1s0x31752f35c88a6f7f:0xa68a267e00d6a218!8m2!3d10.7685424!4d106.6934516!16s%2Fg%2F11lnvmlqnv!19sChIJf2-KyDUvdTERGKLWAH4miqY?authuser=0&hl=en&rclk=1,bai do xe quan 1
|
||||
ChIJ1aomeAAvdTERZ9EZZxI1ZBk,"IFC ONE , Bai trong giu xe O to , xe may 24h",,,5,4.4,"Name: Bai Gui Xe Pho Di Bo
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+G%E1%BB%ADi+Xe+Ph%E1%BB%91+%C4%90i+B%E1%BB%99/@10.77389,106.70288099999999?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 141 reviews
|
||||
|
||||
Name: Bai Xe
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+Xe/@10.791642699999999,106.6924902?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 1 reviews
|
||||
|
||||
Name: Parking
|
||||
Link: https://www.google.com/maps/search/Parking/@10.773188,106.70186400000001?authuser=0&hl=en&entry=ttu
|
||||
Reviews: No Reviews reviews
|
||||
|
||||
Name: Dich Vu Giu Xe Trong Nha
|
||||
Link: https://www.google.com/maps/search/D%E1%BB%8Bch+V%E1%BB%A5+Gi%E1%BB%AF+Xe+Trong+Nh%C3%A0/@10.7725504,106.6967189?authuser=0&hl=en&entry=ttu
|
||||
Reviews: No Reviews reviews",,0394 091 197,1,"IFC ONE , Bai trong giu xe O to , xe may 24h (Owner)",,https://lh3.ggpht.com/p/AB5caB-JiORzOaM-TJH_-wAWtu2BKxHa9JwD_yhaSvT0GfrSJkB7GvtYIGT7HUposeApwCVQiXtYcBxjE73BA9P1Gn6d7YNYWKmFW62G99NWSCUiwNWpXXuQxDGDstbR_u4HOBlMf4wn=s1024,Parking lot,Parking lot,Open 24 hours,,Open All Days,"34 D. Ton Duc Thang, Phuong Nguyen Thai Binh, Quan 1, Ho Chi Minh",,"https://www.google.com/maps/place/IFC+ONE+,+B%C3%A3i+tr%C3%B4ng+gi%E1%BB%AF+xe+%C3%94+t%C3%B4+,+xe+m%C3%A1y+24h/data=!4m7!3m6!1s0x31752f007826aad5:0x196435126719d167!8m2!3d10.7701701!4d106.7057348!16s%2Fg%2F11wxgny1hz!19sChIJ1aomeAAvdTERZ9EZZxI1ZBk?authuser=0&hl=en&rclk=1",bai do xe quan 1
|
||||
ChIJnRly0EkvdTERMrWbScH7dhc,Ham xe mPlaza Saigon,,,15,3,"Name: Bai Giu Xe P
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+Gi%E1%BB%AF+Xe+P/@10.789042,106.690759?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 8 reviews
|
||||
|
||||
Name: 24/24 Car Parking
|
||||
Link: https://www.google.com/maps/search/24%2F24+Car+Parking/@10.7776628,106.69725009999999?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 5 reviews
|
||||
|
||||
Name: Bai Xe oto 32 Nguyen Binh Khiem
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+Xe+oto+32+Nguy%E1%BB%85n+B%E1%BB%89nh+Khi%C3%AAm/@10.7912056,106.7010514?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 4 reviews
|
||||
|
||||
Name: Bai xe Nguyen Van Chiem
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+xe+Nguy%E1%BB%85n+V%C4%83n+Chi%C3%AAm/@10.781750599999999,106.6981492?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 1 reviews
|
||||
|
||||
Name: Huynh Huu Ngoc Vehicle Keeping Store
|
||||
Link: https://www.google.com/maps/search/Huynh+Huu+Ngoc+Vehicle+Keeping+Store/@10.7710957,106.70500179999999?authuser=0&hl=en&entry=ttu
|
||||
Reviews: No Reviews reviews",,,1,Ham xe mPlaza Saigon (Owner),,https://lh3.ggpht.com/p/AB5caB_GRmEfc_odcI_OO5VykUpXfKEn062yKz4H0JM3VS8vcH5iW6kAD3Y-b66wdUmuhPLHuijO7gk0FFHZYa9ywxkJxoRIB8brUK1QtFRAsa--MmmnIuVV0LI58QJfy3fDr703Hzk=s1024,Parking,Parking,,,Open All Days,"39 D. Le Duan, Ben Nghe, Quan 1, Ho Chi Minh",,https://www.google.com/maps/place/H%E1%BA%A7m+xe+mPlaza+Saigon/data=!4m7!3m6!1s0x31752f49d072199d:0x1776fbc1499bb532!8m2!3d10.7816039!4d106.7005008!16s%2Fg%2F1tgjz3pb!19sChIJnRly0EkvdTERMrWbScH7dhc?authuser=0&hl=en&rclk=1,bai do xe quan 1
|
||||
ChIJsanVkxMvdTERMWb0IPEcDEA,Samco Parking,,,16,3.3,"Name: SAMCO Building - Managed by SAVISTA
|
||||
Link: https://www.google.com/maps/search/SAMCO+Building+-+Managed+by+SAVISTA/@10.7616111,106.695775?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 141 reviews
|
||||
|
||||
Name: BAI XE PARKING PRO
|
||||
Link: https://www.google.com/maps/search/B%C3%83I+XE+PARKING+PRO/@10.7690767,106.6941737?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 50 reviews
|
||||
|
||||
Name: Ham xe mPlaza Saigon
|
||||
Link: https://www.google.com/maps/search/H%E1%BA%A7m+xe+mPlaza+Saigon/@10.781603900000002,106.7005008?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 15 reviews
|
||||
|
||||
Name: Khu nha de xe oto va van phong SAMCO
|
||||
Link: https://www.google.com/maps/search/Khu+nh%C3%A0+%C4%91%E1%BB%83+xe+oto+v%C3%A0+v%C4%83n+ph%C3%B2ng+SAMCO/@10.7621453,106.6954688?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 6 reviews
|
||||
|
||||
Name: Saigon Transportation Mechanical Corporation - Samco
|
||||
Link: https://www.google.com/maps/search/Saigon+Transportation+Mechanical+Corporation+-+Samco/@10.7645726,106.6927357?authuser=0&hl=en&entry=ttu
|
||||
Reviews: No Reviews reviews",,028 3838 5864,1,Samco Parking (Owner),,https://lh3.ggpht.com/p/AB5caB_94B8ibj2Ca729yQl57ToHEJE7Xz6NWAENZvHgGdEYnAu8sEohV7i-p3XDerPj8t_dbV4oA6LZ2-EVk7zjDJyp8s7npY-QIR6w9iI_eyKMnwXm_xGnrspQU6jStVnGkk4ib8qu=s1024,Parking garage,Parking garage,Open 24 hours,,Open All Days,"326 D. Vo Van Kiet, Phuong Co Giang, Quan 1, Ho Chi Minh",,https://www.google.com/maps/place/Samco+Parking/data=!4m7!3m6!1s0x31752f1393d5a9b1:0x400c1cf120f46631!8m2!3d10.7617031!4d106.6958517!16s%2Fg%2F11c5750x7k!19sChIJsanVkxMvdTERMWb0IPEcDEA?authuser=0&hl=en&rclk=1,bai do xe quan 1
|
||||
ChIJV7s3EywvdTERIydnxA2aqcY,motorbike park,,,6,2.7,"Name: Bai xe Rua xe Trung Khanh
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+xe+R%E1%BB%ADa+xe+Tr%C3%B9ng+Kh%C3%A1nh/@10.7862001,106.69589719999999?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 6 reviews
|
||||
|
||||
Name: 24/24 Car Parking
|
||||
Link: https://www.google.com/maps/search/24%2F24+Car+Parking/@10.7776628,106.69725009999999?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 5 reviews
|
||||
|
||||
Name: Parking
|
||||
Link: https://www.google.com/maps/search/Parking/@10.773188,106.70186400000001?authuser=0&hl=en&entry=ttu
|
||||
Reviews: No Reviews reviews
|
||||
|
||||
Name: Bai giu xe 235 Nguyen Van Cu
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+gi%E1%BB%AF+xe+235+Nguy%E1%BB%85n+V%C4%83n+C%E1%BB%AB/@10.7631988,106.6829434?authuser=0&hl=en&entry=ttu
|
||||
Reviews: No Reviews reviews
|
||||
|
||||
Name: Huynh Huu Ngoc Vehicle Keeping Store
|
||||
Link: https://www.google.com/maps/search/Huynh+Huu+Ngoc+Vehicle+Keeping+Store/@10.7710957,106.70500179999999?authuser=0&hl=en&entry=ttu
|
||||
Reviews: No Reviews reviews",,,1,motorbike park (Owner),,,Parking lot,Parking lot,,,Open All Days,"QPG4+X7M, Thi Sach, Ben Nghe, Quan 1, Ho Chi Minh",,https://www.google.com/maps/place/motorbike+park/data=!4m7!3m6!1s0x31752f2c1337bb57:0xc6a99a0dc4672723!8m2!3d10.7774744!4d106.7056624!16s%2Fg%2F11vjj4c_z1!19sChIJV7s3EywvdTERIydnxA2aqcY?authuser=0&hl=en&rclk=1,bai do xe quan 1
|
||||
ChIJMUjx1j4vdTERSq6FbAkhs80,Dich Vu Giu Xe Trong Nha,,,0,0,"Name: Parking Parkson 24/24
|
||||
Link: https://www.google.com/maps/search/Parking+Parkson+24%2F24/@10.777451,106.7021209?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 16 reviews
|
||||
|
||||
Name: Bai Giu Xe 32 Le Anh Xuan
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+Gi%E1%BB%AF+Xe+32+L%C3%AA+Anh+Xu%C3%A2n/@10.7721232,106.6952631?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 9 reviews
|
||||
|
||||
Name: 24/24 Car Parking
|
||||
Link: https://www.google.com/maps/search/24%2F24+Car+Parking/@10.7776628,106.69725009999999?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 5 reviews
|
||||
|
||||
Name: Bai giu xe TNXP
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+gi%E1%BB%AF+xe+TNXP/@10.771806999999999,106.699084?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 4 reviews
|
||||
|
||||
Name: Parking
|
||||
Link: https://www.google.com/maps/search/Parking/@10.773188,106.70186400000001?authuser=0&hl=en&entry=ttu
|
||||
Reviews: No Reviews reviews",,,1,Dich Vu Giu Xe Trong Nha (Owner),,,Parking lot,Parking lot,,,Open All Days,"220 D. Le Thanh Ton, Phuong Ben Thanh, Quan 1, Ho Chi Minh",,https://www.google.com/maps/place/D%E1%BB%8Bch+V%E1%BB%A5+Gi%E1%BB%AF+Xe+Trong+Nh%C3%A0/data=!4m7!3m6!1s0x31752f3ed6f14831:0xcdb321096c85ae4a!8m2!3d10.7725504!4d106.6967189!16s%2Fg%2F11c1t59s6r!19sChIJMUjx1j4vdTERSq6FbAkhs80?authuser=0&hl=en&rclk=1,bai do xe quan 1
|
||||
ChIJ3aKVYwAvdTERWa4aLmDb_Js,Bai giu xe TNXP,,,0,0,"Name: Bai Giu Xe May
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+Gi%E1%BB%AF+Xe+M%C3%A1y/@10.7739127,106.7029148?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 66 reviews
|
||||
|
||||
Name: motorbike park
|
||||
Link: https://www.google.com/maps/search/motorbike+park/@10.7774744,106.7056624?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 6 reviews
|
||||
|
||||
Name: phannguyen
|
||||
Link: https://www.google.com/maps/search/phannguyen/@10.7753882,106.70282499999999?authuser=0&hl=en&entry=ttu
|
||||
Reviews: No Reviews reviews
|
||||
|
||||
Name: Parking
|
||||
Link: https://www.google.com/maps/search/Parking/@10.773188,106.70186400000001?authuser=0&hl=en&entry=ttu
|
||||
Reviews: No Reviews reviews",,,1,Bai giu xe TNXP (Owner),,https://lh3.ggpht.com/p/AB5caB_Iidh0kSfMuSS1YQIj0WjRnMbjAITJk8DAvxofwknJBwU7acldwCAIKWYe8ZgkhpTGAajwpGQOTHxlZ1EyiuHfDUnGTs7klj38kOzFVmnBKPD9yAMUEUWJLLGnaeeJTVRELfU=s1024,Parking lot for motorcycles,Parking lot for motorcycles,5:30 AM-11 PM,,Open All Days,"33 D. Nguyen Thi Minh Khai, Ben Nghe, Quan 1, Ho Chi Minh",,https://www.google.com/maps/place/B%C3%A3i+gi%E1%BB%AF+xe+TNXP/data=!4m7!3m6!1s0x31752f006395a2dd:0x9bfcdb602e1aae59!8m2!3d10.782474!4d106.6979534!16s%2Fg%2F11ww4_894f!19sChIJ3aKVYwAvdTERWa4aLmDb_Js?authuser=0&hl=en&rclk=1,bai do xe quan 1
|
||||
ChIJz83OZQAvdTERzBr-TEJ1x2k,Bai xe Rua xe Trung Khanh,,,6,3,"Name: Bai Giu Xe P
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+Gi%E1%BB%AF+Xe+P/@10.789042,106.690759?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 8 reviews
|
||||
|
||||
Name: bai gui xe tai ben tau water bus Bach Dang
|
||||
Link: https://www.google.com/maps/search/b%C3%A3i+g%E1%BB%ADi+xe+t%E1%BA%A1i+b%E1%BA%BFn+t%C3%A0u+water+bus+B%E1%BA%A1ch+%C4%90%E1%BA%B1ng/@10.775931199999999,106.7069201?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 2 reviews
|
||||
|
||||
Name: Bai xe Nguyen Van Chiem
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+xe+Nguy%E1%BB%85n+V%C4%83n+Chi%C3%AAm/@10.781750599999999,106.6981492?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 1 reviews
|
||||
|
||||
Name: Bai Xe
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+Xe/@10.791642699999999,106.6924902?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 1 reviews
|
||||
|
||||
Name: Bai giu xe 235 Nguyen Van Cu
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+gi%E1%BB%AF+xe+235+Nguy%E1%BB%85n+V%C4%83n+C%E1%BB%AB/@10.7631988,106.6829434?authuser=0&hl=en&entry=ttu
|
||||
Reviews: No Reviews reviews",,,1,Bai xe Rua xe Trung Khanh (Owner),,https://lh3.ggpht.com/p/AB5caB93E64sLPIX0Dtc6wvxvp-J1ziFdpA1v6DRAHqyjt6mTiuMkMU_Dm-KIKzpThTQlsCJAzors-Mvp4lvu_sD7LQ4bgQzKHQxWsetGtO6-Szu7MlvvKAXWA_-KLb7Uosm6_vYLGaZ3w=s1024,Parking lot,Parking lot,Open 24 hours,,Open All Days,"33 D. Phung Khac Khoan, Da Kao, Quan 1, Ho Chi Minh 700000",,https://www.google.com/maps/place/B%C3%A3i+xe+R%E1%BB%ADa+xe+Tr%C3%B9ng+Kh%C3%A1nh/data=!4m7!3m6!1s0x31752f0065cecdcf:0x69c775424cfe1acc!8m2!3d10.7862001!4d106.6958972!16s%2Fg%2F11w3jhlklt!19sChIJz83OZQAvdTERzBr-TEJ1x2k?authuser=0&hl=en&rclk=1,bai do xe quan 1
|
||||
ChIJ96oK4lgvdTERN5g443ryeD8,Parking Parkson 24/24,"CONG TY TNHH DAU TU & KHAI THAC PARKING co tren 05 nam kinh nghiem kinh doanh Dich vu giu xe, chuyen thiet ke, khai thac bai giu xe Oto va xe gan may va quan ly van hanh cac bai giu xe chuyen nghiep... Hien nay dang lien ket khai thac cac bai giu xe Oto va xe gan may thuoc he thong Trung tam thuong mai, Benh vien, Truong hoc, Cac trung tam thuong mai, Can ho cao cap, Nha xe, Bai giu xe ngoai troi dien tich lon co mai che,... thuoc dia ban Thanh pho Ho Chi Minh.",,16,1.7,"Name: Bai Gui Xe Pho Di Bo
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+G%E1%BB%ADi+Xe+Ph%E1%BB%91+%C4%90i+B%E1%BB%99/@10.77389,106.70288099999999?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 141 reviews
|
||||
|
||||
Name: Bai Giu Xe 32 Le Anh Xuan
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+Gi%E1%BB%AF+Xe+32+L%C3%AA+Anh+Xu%C3%A2n/@10.7721232,106.6952631?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 9 reviews
|
||||
|
||||
Name: 24/24 Car Parking
|
||||
Link: https://www.google.com/maps/search/24%2F24+Car+Parking/@10.7776628,106.69725009999999?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 5 reviews
|
||||
|
||||
Name: Dich Vu Giu Xe Trong Nha
|
||||
Link: https://www.google.com/maps/search/D%E1%BB%8Bch+V%E1%BB%A5+Gi%E1%BB%AF+Xe+Trong+Nh%C3%A0/@10.7725504,106.6967189?authuser=0&hl=en&entry=ttu
|
||||
Reviews: No Reviews reviews
|
||||
|
||||
Name: Parking
|
||||
Link: https://www.google.com/maps/search/Parking/@10.773188,106.70186400000001?authuser=0&hl=en&entry=ttu
|
||||
Reviews: No Reviews reviews",http://vantrungparking.com/,0794 094 346,,Parking Parkson 24/24 (Owner),https://www.google.com/maps/contrib/107026325240484114673,https://lh3.ggpht.com/p/AF1QipN0Pf7SD1BlNqW_nC2BRGVEYti6LbVCl77HDRiZ=s1024,Parking lot,Parking lot,Open 24 hours,,Open All Days,"45 D. Le Thanh Ton, Ben Nghe, Quan 1, Ho Chi Minh",,https://www.google.com/maps/place/Parking+Parkson+24%2F24/data=!4m7!3m6!1s0x31752f58e20aaaf7:0x3f78f27ae3389837!8m2!3d10.777451!4d106.7021209!16s%2Fg%2F11t2snr1g7!19sChIJ96oK4lgvdTERN5g443ryeD8?authuser=0&hl=en&rclk=1,bai do xe quan 1
|
||||
ChIJL17bopkvdTERYWBBIog_Ic0,phannguyen,,,0,0,"Name: BAI XE PARKING PRO
|
||||
Link: https://www.google.com/maps/search/B%C3%83I+XE+PARKING+PRO/@10.7690767,106.6941737?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 50 reviews
|
||||
|
||||
Name: 24/24 Car Parking
|
||||
Link: https://www.google.com/maps/search/24%2F24+Car+Parking/@10.7776628,106.69725009999999?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 5 reviews
|
||||
|
||||
Name: Parking
|
||||
Link: https://www.google.com/maps/search/Parking/@10.773188,106.70186400000001?authuser=0&hl=en&entry=ttu
|
||||
Reviews: No Reviews reviews
|
||||
|
||||
Name: Bai giu xe TNXP
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+gi%E1%BB%AF+xe+TNXP/@10.782473999999999,106.69795339999999?authuser=0&hl=en&entry=ttu
|
||||
Reviews: No Reviews reviews",,,,phannguyen (Owner),https://www.google.com/maps/contrib/106533406392369257795,,Free parking lot,Free parking lot,,,Open All Days,"Ben Nghe, District 1, Ho Chi Minh City",,https://www.google.com/maps/place/phannguyen/data=!4m7!3m6!1s0x31752f99a2db5e2f:0xcd213f8822416061!8m2!3d10.7753882!4d106.702825!16s%2Fg%2F11lz1g0x_x!19sChIJL17bopkvdTERYWBBIog_Ic0?authuser=0&hl=en&rclk=1,bai do xe quan 1
|
||||
ChIJG4lrRQAvdTER8zB-CXb-jb4,Bai Giu Xe 32 Le Anh Xuan,Diem coi giu xe o to xe may,,9,2.8,"Name: BAI XE PARKING PRO
|
||||
Link: https://www.google.com/maps/search/B%C3%83I+XE+PARKING+PRO/@10.7690767,106.6941737?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 50 reviews
|
||||
|
||||
Name: Bai giu xe o to
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+gi%E1%BB%AF+xe+%C3%B4+t%C3%B4/@10.7635045,106.6826608?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 37 reviews
|
||||
|
||||
Name: Bai Giu Xe May
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+Gi%E1%BB%AF+Xe+M%C3%A1y/@10.768580199999999,106.69064639999999?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 32 reviews
|
||||
|
||||
Name: Bai giu xe TNXP
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+gi%E1%BB%AF+xe+TNXP/@10.7710666,106.6969077?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 19 reviews
|
||||
|
||||
Name: Dich Vu Giu Xe Trong Nha
|
||||
Link: https://www.google.com/maps/search/D%E1%BB%8Bch+V%E1%BB%A5+Gi%E1%BB%AF+Xe+Trong+Nh%C3%A0/@10.7725504,106.6967189?authuser=0&hl=en&entry=ttu
|
||||
Reviews: No Reviews reviews",,0902 099 591,,Bai Giu Xe 32 Le Anh Xuan (Owner),https://www.google.com/maps/contrib/106445545452877152227,https://lh3.ggpht.com/p/AF1QipPbV6P2eYViSq3tD0_j3LVu2JykQBrob5vU5-Ij=s1024,Parking lot,Parking lot,5 AM-10 PM,,Open All Days,"32 Le Anh Xuan, Phuong Ben Thanh, Quan 1, Ho Chi Minh",,https://www.google.com/maps/place/B%C3%A3i+Gi%E1%BB%AF+Xe+32+L%C3%AA+Anh+Xu%C3%A2n/data=!4m7!3m6!1s0x31752f00456b891b:0xbe8dfe76097e30f3!8m2!3d10.7721232!4d106.6952631!16s%2Fg%2F11vpsrzw1j!19sChIJG4lrRQAvdTER8zB-CXb-jb4?authuser=0&hl=en&rclk=1,bai do xe quan 1
|
||||
ChIJgS41fgAvdTERbIxmEKu2Lik,Bai giu xe TNXP,,,4,1,"Name: BAI XE PARKING PRO
|
||||
Link: https://www.google.com/maps/search/B%C3%83I+XE+PARKING+PRO/@10.7690767,106.6941737?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 50 reviews
|
||||
|
||||
Name: Bai Giu Xe May
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+Gi%E1%BB%AF+Xe+M%C3%A1y/@10.768580199999999,106.69064639999999?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 32 reviews
|
||||
|
||||
Name: Giu xe Metro Ben Thanh
|
||||
Link: https://www.google.com/maps/search/Gi%E1%BB%AF+xe+Metro+B%E1%BA%BFn+Th%C3%A0nh/@10.7710475,106.6968809?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 3 reviews
|
||||
|
||||
Name: Bai giu xe Parking 24/24 xe may Hai Ba Trung
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+gi%E1%BB%AF+xe+Parking+24%2F24+xe+m%C3%A1y+Hai+B%C3%A0+Tr%C6%B0ng/@10.7901027,106.6893982?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 1 reviews
|
||||
|
||||
Name: Dich Vu Giu Xe Trong Nha
|
||||
Link: https://www.google.com/maps/search/D%E1%BB%8Bch+V%E1%BB%A5+Gi%E1%BB%AF+Xe+Trong+Nh%C3%A0/@10.7725504,106.6967189?authuser=0&hl=en&entry=ttu
|
||||
Reviews: No Reviews reviews",https://goonus.io/signup/6277729707482660766?uiid=q55kNtwTXX&utm_campaign=invite,0916 187 187,1,Bai giu xe TNXP (Owner),,https://lh3.ggpht.com/p/AB5caB_ywBM5rEz5y7VgpUw0zxfZahBVB3Nozhe8ZX-bR2BLSc_24F1ZtTRQAuE7xAyOSjbRPX1cFAiCQZvgXfWOgr1j2LNUGutvZ3oXtgOXxtbuOs4hyftIzGDyRXY7TrZajy2GMx4=s1024,Parking lot for motorcycles,Parking lot for motorcycles,Open 24 hours,,Open All Days,"161 D. Le Lai, Phuong Ben Thanh, Quan 1, Ho Chi Minh",,https://www.google.com/maps/place/B%C3%A3i+gi%E1%BB%AF+xe+TNXP/data=!4m7!3m6!1s0x31752f007e352e81:0x292eb6ab10668c6c!8m2!3d10.771807!4d106.699084!16s%2Fg%2F11ww2b10br!19sChIJgS41fgAvdTERbIxmEKu2Lik?authuser=0&hl=en&rclk=1,bai do xe quan 1
|
||||
ChIJyYxAaCgvdTERewqdYcl-D-0,Bai gui xe cong vien Tao Dan,,,33,2,"Name: Bai Gui Xe Pho Di Bo
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+G%E1%BB%ADi+Xe+Ph%E1%BB%91+%C4%90i+B%E1%BB%99/@10.77389,106.70288099999999?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 141 reviews
|
||||
|
||||
Name: BAI XE PARKING PRO
|
||||
Link: https://www.google.com/maps/search/B%C3%83I+XE+PARKING+PRO/@10.7690767,106.6941737?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 50 reviews
|
||||
|
||||
Name: Bai Giu Xe May - O To
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+Gi%E1%BB%AF+Xe+M%C3%A1y+-+%C3%94+T%C3%B4/@10.77769,106.6875?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 9 reviews
|
||||
|
||||
Name: Bai Giu Xe May - O To - Ngo Thoi Nhiem
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+Gi%E1%BB%AF+Xe+M%C3%A1y+-+%C3%94+T%C3%B4+-+Ng%C3%B4+Th%E1%BB%9Di+Nhi%E1%BB%87m/@10.77765,106.6869?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 8 reviews
|
||||
|
||||
Name: Bai giu xe o to 76
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+gi%E1%BB%AF+xe+%C3%B4+t%C3%B4+76/@10.7837695,106.6862185?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 8 reviews",,,1,Bai gui xe cong vien Tao Dan (Owner),,https://lh3.ggpht.com/p/AB5caB8VU1nhiKIAom7g64qkzlBto2Ce3wmQHkwStO8uNpgn5H9PebXC7gWG9RtZ6XG2v3bOpJ3CbNe7v8xhqgQz0Ea3PXbNvouVMVI4xtELGWxeyx42Jzfy41h5pkfMJoDzFQceqMRg=s1024,Parking lot,Parking lot,,,Open All Days,"QMGR+7V8, D. Truong Dinh, Phuong 7, Quan 3, Ho Chi Minh",,https://www.google.com/maps/place/B%C3%A3i+g%E1%BB%ADi+xe+c%C3%B4ng+vi%C3%AAn+Tao+%C4%90%C3%A0n/data=!4m7!3m6!1s0x31752f2868408cc9:0xed0f7ec9619d0a7b!8m2!3d10.7756656!4d106.6922168!16s%2Fg%2F11rc9wdpq0!19sChIJyYxAaCgvdTERewqdYcl-D-0?authuser=0&hl=en&rclk=1,bai do xe quan 1
|
||||
ChIJRf3GMwApdTER7Q2SFeiTJU8,Sac xe dien,,,0,0,"Name: motorbike park
|
||||
Link: https://www.google.com/maps/search/motorbike+park/@10.7774744,106.7056624?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 6 reviews
|
||||
|
||||
Name: Bai xe Rua xe Trung Khanh
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+xe+R%E1%BB%ADa+xe+Tr%C3%B9ng+Kh%C3%A1nh/@10.7862001,106.69589719999999?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 6 reviews
|
||||
|
||||
Name: Bai xe Nguyen Van Chiem
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+xe+Nguy%E1%BB%85n+V%C4%83n+Chi%C3%AAm/@10.781750599999999,106.6981492?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 1 reviews
|
||||
|
||||
Name: Bai Xe
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+Xe/@10.791642699999999,106.6924902?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 1 reviews
|
||||
|
||||
Name: Bai giu xe 235 Nguyen Van Cu
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+gi%E1%BB%AF+xe+235+Nguy%E1%BB%85n+V%C4%83n+C%E1%BB%AB/@10.7631988,106.6829434?authuser=0&hl=en&entry=ttu
|
||||
Reviews: No Reviews reviews",,,1,Sac xe dien (Owner),,,Parking lot,Parking lot,,,Open All Days,"39 D. Nguyen Van Giai, Da Kao, Quan 1, Ho Chi Minh 70000",,https://www.google.com/maps/place/S%E1%BA%A1c+xe+%C4%91i%E1%BB%87n/data=!4m7!3m6!1s0x3175290033c6fd45:0x4f2593e815920ded!8m2!3d10.7917768!4d106.6974467!16s%2Fg%2F11v_8y0t90!19sChIJRf3GMwApdTER7Q2SFeiTJU8?authuser=0&hl=en&rclk=1,bai do xe quan 1
|
||||
ChIJmVCfewAvdTERjIoOGtSJ9-0,Ham de xe may - xe o to Ga Tau Dien Metro Ben Thanh - Suoi Tien,,,1,5,"Name: Bai do xe ParkTech
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+%C4%91%E1%BB%97+xe+ParkTech/@10.768542400000001,106.69345159999999?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 9 reviews
|
||||
|
||||
Name: Bai xe may va o to
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+xe+m%C3%A1y+v%C3%A0+%C3%B4+t%C3%B4/@10.764699,106.6899339?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 5 reviews
|
||||
|
||||
Name: Giu xe Metro Ben Thanh
|
||||
Link: https://www.google.com/maps/search/Gi%E1%BB%AF+xe+Metro+B%E1%BA%BFn+Th%C3%A0nh/@10.7710475,106.6968809?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 3 reviews
|
||||
|
||||
Name: Gate 1 Ben Thanh Metro Station
|
||||
Link: https://www.google.com/maps/search/Gate+1+Ben+Thanh+Metro+Station/@10.770459599999999,106.69676179999999?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 1 reviews
|
||||
|
||||
Name: Bai giu xe 235 Nguyen Van Cu
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+gi%E1%BB%AF+xe+235+Nguy%E1%BB%85n+V%C4%83n+C%E1%BB%AB/@10.7631988,106.6829434?authuser=0&hl=en&entry=ttu
|
||||
Reviews: No Reviews reviews",,,1,Ham de xe may - xe o to Ga Tau Dien Metro Ben Thanh - Suoi Tien (Owner),,,Parking garage,Parking garage,,,Open All Days,"QM9V+WFQ, Phuong Pham Ngu Lao, Quan 1, Ho Chi Minh",,https://www.google.com/maps/place/H%E1%BA%A7m+%C4%91%E1%BB%83+xe+m%C3%A1y+-+xe+%C3%B4+t%C3%B4+Ga+T%C3%A0u+%C4%90i%E1%BB%87n+Metro+B%E1%BA%BFn+Th%C3%A0nh+-+Su%E1%BB%91i+Ti%C3%AAn/data=!4m7!3m6!1s0x31752f007b9f5099:0xedf789d41a0e8a8c!8m2!3d10.7698306!4d106.6937411!16s%2Fg%2F11wx4fgccy!19sChIJmVCfewAvdTERjIoOGtSJ9-0?authuser=0&hl=en&rclk=1,bai do xe quan 1
|
||||
ChIJQwhJSCUvdTERm4GTgx_DdGI,Bai Giu Xe May - O To,,,9,4.1,"Name: Bai gui xe cong vien Tao Dan
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+g%E1%BB%ADi+xe+c%C3%B4ng+vi%C3%AAn+Tao+%C4%90%C3%A0n/@10.7756656,106.6922168?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 33 reviews
|
||||
|
||||
Name: Dich vu giu xe Thanh Dat
|
||||
Link: https://www.google.com/maps/search/D%E1%BB%8Bch+v%E1%BB%A5+gi%E1%BB%AF+xe+Th%C3%A0nh+%C4%90%E1%BA%A1t/@10.770982799999999,106.6857112?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 17 reviews
|
||||
|
||||
Name: Bai Giu Xe May - O To - Ngo Thoi Nhiem
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+Gi%E1%BB%AF+Xe+M%C3%A1y+-+%C3%94+T%C3%B4+-+Ng%C3%B4+Th%E1%BB%9Di+Nhi%E1%BB%87m/@10.77765,106.6869?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 8 reviews
|
||||
|
||||
Name: Bai giu xe o to 76
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+gi%E1%BB%AF+xe+%C3%B4+t%C3%B4+76/@10.7837695,106.6862185?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 8 reviews
|
||||
|
||||
Name: Giu xe ngay dem
|
||||
Link: https://www.google.com/maps/search/Gi%E1%BB%AF+xe+ng%C3%A0y+%C4%91%C3%AAm/@10.7810271,106.68328009999999?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 7 reviews",,0932 090 467,1,Bai Giu Xe May - O To (Owner),,https://lh3.ggpht.com/p/AB5caB9dCqk6UbGC6QUH61oF_R8P1nFgwUxiL12rSWo1HpyqL0wgVklnyHmtjkoClm39Km1z_KBYR9u-gZ9kYPAkJpSJ2hkVhdO-8ORHUzAMda8wuLsSqW7_Djdw6vPJ0Wt8xo3DxJvsgg=s1024,Parking lot,Parking lot,,,Open All Days,"40 Ba Huyen Thanh Quan, Phuong 6, Quan 3, Ho Chi Minh 700000",,https://www.google.com/maps/place/B%C3%A3i+Gi%E1%BB%AF+Xe+M%C3%A1y+-+%C3%94+T%C3%B4/data=!4m7!3m6!1s0x31752f2548490843:0x6274c31f8393819b!8m2!3d10.77769!4d106.6875!16s%2Fg%2F11ft89p6hh!19sChIJQwhJSCUvdTERm4GTgx_DdGI?authuser=0&hl=en&rclk=1,bai do xe quan 1
|
||||
ChIJkXfANAAvdTERAL1sKAOF6CU,Bai giu xe,,,0,0,"Name: BAI XE PARKING PRO
|
||||
Link: https://www.google.com/maps/search/B%C3%83I+XE+PARKING+PRO/@10.7690767,106.6941737?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 50 reviews
|
||||
|
||||
Name: Bai Giu Xe P
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+Gi%E1%BB%AF+Xe+P/@10.789042,106.690759?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 8 reviews
|
||||
|
||||
Name: Bai Giu Xe, Rua Xe Oto 635 Nguyen Trai
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+Gi%E1%BB%AF+Xe%2C+R%E1%BB%ADa+Xe+%C3%94t%C3%B4+635+Nguy%E1%BB%85n+Tr%C3%A3i/@10.7536375,106.66504809999999?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 7 reviews
|
||||
|
||||
Name: Bai xe Cho Lon
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+xe+Ch%E1%BB%A3+L%E1%BB%9Bn/@10.7527185,106.65376599999999?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 1 reviews
|
||||
|
||||
Name: Bai giu xe 235 Nguyen Van Cu
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+gi%E1%BB%AF+xe+235+Nguy%E1%BB%85n+V%C4%83n+C%E1%BB%AB/@10.7631988,106.6829434?authuser=0&hl=en&entry=ttu
|
||||
Reviews: No Reviews reviews",,,1,Bai giu xe (Owner),,,Public parking space,Public parking space,,,Open All Days,"203 D. Nguyen Trai, Phuong Nguyen Cu Trinh, Quan 1, Ho Chi Minh",,https://www.google.com/maps/place/B%C3%A3i+gi%E1%BB%AF+xe/data=!4m7!3m6!1s0x31752f0034c07791:0x25e88503286cbd00!8m2!3d10.7648336!4d106.6854205!16s%2Fg%2F11lyjmxbvg!19sChIJkXfANAAvdTERAL1sKAOF6CU?authuser=0&hl=en&rclk=1,bai do xe quan 1
|
||||
ChIJBQ8X-H0vdTERIwd7aun-Hgo,Limuzin,,,0,0,"Name: Giu xe Metro Ben Thanh
|
||||
Link: https://www.google.com/maps/search/Gi%E1%BB%AF+xe+Metro+B%E1%BA%BFn+Th%C3%A0nh/@10.7710475,106.6968809?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 3 reviews
|
||||
|
||||
Name: Parking Xe May
|
||||
Link: https://www.google.com/maps/search/Parking+Xe+May/@10.8345076,106.6580572?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 2 reviews
|
||||
|
||||
Name: Bai Dau Xe
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+%C4%90%E1%BA%ADu+Xe/@10.8029897,106.65281519999999?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 1 reviews
|
||||
|
||||
Name: Parking
|
||||
Link: https://www.google.com/maps/search/Parking/@10.784569,106.6939766?authuser=0&hl=en&entry=ttu
|
||||
Reviews: No Reviews reviews
|
||||
|
||||
Name: phannguyen
|
||||
Link: https://www.google.com/maps/search/phannguyen/@10.7753882,106.70282499999999?authuser=0&hl=en&entry=ttu
|
||||
Reviews: No Reviews reviews",,,1,Limuzin (Owner),,,Parking lot,Parking lot,,,Open All Days,"175 Nguyen Thai Binh, Phuong Nguyen Thai Binh, Quan 1, Ho Chi Minh",,https://www.google.com/maps/place/%D0%9B%D0%B8%D0%BC%D1%83%D0%B7%D0%B8%D0%BD/data=!4m7!3m6!1s0x31752f7df8170f05:0xa1efee96a7b0723!8m2!3d10.7678165!4d106.6983687!16s%2Fg%2F11n0hmc_xq!19sChIJBQ8X-H0vdTERIwd7aun-Hgo?authuser=0&hl=en&rclk=1,bai do xe quan 1
|
||||
ChIJW4rgaAAvdTERTMD7WzRU-4A,Giu xe Metro Ben Thanh,,,3,5,"Name: Bai Giu Xe 32 Le Anh Xuan
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+Gi%E1%BB%AF+Xe+32+L%C3%AA+Anh+Xu%C3%A2n/@10.7721232,106.6952631?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 9 reviews
|
||||
|
||||
Name: Bai giu xe TNXP
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+gi%E1%BB%AF+xe+TNXP/@10.771806999999999,106.699084?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 4 reviews
|
||||
|
||||
Name: Ham de xe may - xe o to Ga Tau Dien Metro Ben Thanh - Suoi Tien
|
||||
Link: https://www.google.com/maps/search/H%E1%BA%A7m+%C4%91%E1%BB%83+xe+m%C3%A1y+-+xe+%C3%B4+t%C3%B4+Ga+T%C3%A0u+%C4%90i%E1%BB%87n+Metro+B%E1%BA%BFn+Th%C3%A0nh+-+Su%E1%BB%91i+Ti%C3%AAn/@10.769830599999999,106.6937411?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 1 reviews
|
||||
|
||||
Name: Dich Vu Giu Xe Trong Nha
|
||||
Link: https://www.google.com/maps/search/D%E1%BB%8Bch+V%E1%BB%A5+Gi%E1%BB%AF+Xe+Trong+Nh%C3%A0/@10.7725504,106.6967189?authuser=0&hl=en&entry=ttu
|
||||
Reviews: No Reviews reviews
|
||||
|
||||
Name: Parking
|
||||
Link: https://www.google.com/maps/search/Parking/@10.773188,106.70186400000001?authuser=0&hl=en&entry=ttu
|
||||
Reviews: No Reviews reviews",,,1,Giu xe Metro Ben Thanh (Owner),,https://lh3.ggpht.com/p/AB5caB8wVzNK7bFKNvLVElu9Tl2adUh61-ifiP_Nm6jKh6JAU_uQNyPUY-1mLs7pjajwdjy2eA4pT6GWW2TthS1H9-RunhRXsSE2N1vQlfG1TGp1NVmRfEC7X9PCZtml4R1cJXDSx3RzJD19Jtaw=s1024,Parking lot,Parking lot,,,Open All Days,"52 D. Le Lai, Phuong Ben Thanh, Quan 1, Ho Chi Minh",,https://www.google.com/maps/place/Gi%E1%BB%AF+xe+Metro+B%E1%BA%BFn+Th%C3%A0nh/data=!4m7!3m6!1s0x31752f0068e08a5b:0x80fb54345bfbc04c!8m2!3d10.7710475!4d106.6968809!16s%2Fg%2F11x1n41bk1!19sChIJW4rgaAAvdTERTMD7WzRU-4A?authuser=0&hl=en&rclk=1,bai do xe quan 1
|
||||
ChIJUSdzaEEvdTER9MsIga4zW0Q,Huynh Huu Ngoc Vehicle Keeping Store,,,0,0,"Name: Bai Giu Xe 29 Nam Ki
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+Gi%E1%BB%AF+Xe+29+Nam+K%C3%AC/@10.76939,106.70171859999999?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 6 reviews
|
||||
|
||||
Name: Bai xe Nguyen Van Chiem
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+xe+Nguy%E1%BB%85n+V%C4%83n+Chi%C3%AAm/@10.781750599999999,106.6981492?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 1 reviews
|
||||
|
||||
Name: Bai giu xe Parking 24/24 xe may Hai Ba Trung
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+gi%E1%BB%AF+xe+Parking+24%2F24+xe+m%C3%A1y+Hai+B%C3%A0+Tr%C6%B0ng/@10.7901027,106.6893982?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 1 reviews
|
||||
|
||||
Name: Parking
|
||||
Link: https://www.google.com/maps/search/Parking/@10.773188,106.70186400000001?authuser=0&hl=en&entry=ttu
|
||||
Reviews: No Reviews reviews
|
||||
|
||||
Name: Dich Vu Giu Xe Trong Nha
|
||||
Link: https://www.google.com/maps/search/D%E1%BB%8Bch+V%E1%BB%A5+Gi%E1%BB%AF+Xe+Trong+Nh%C3%A0/@10.7725504,106.6967189?authuser=0&hl=en&entry=ttu
|
||||
Reviews: No Reviews reviews",,0912 475 978,1,Huynh Huu Ngoc Vehicle Keeping Store (Owner),,,Parking,Parking,,,Open All Days,"11 D. Ham Nghi, Street, Quan 1, Ho Chi Minh",,https://www.google.com/maps/place/Huynh+Huu+Ngoc+Vehicle+Keeping+Store/data=!4m7!3m6!1s0x31752f4168732751:0x445b33ae8108cbf4!8m2!3d10.7710957!4d106.7050018!16s%2Fg%2F11b6dtd20_!19sChIJUSdzaEEvdTER9MsIga4zW0Q?authuser=0&hl=en&rclk=1,bai do xe quan 1
|
||||
ChIJYavMfAAvdTERAzA-PsE-3Oo,Hi tieu sieu to,,,2,1,"Name: motorbike park
|
||||
Link: https://www.google.com/maps/search/motorbike+park/@10.7774744,106.7056624?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 6 reviews
|
||||
|
||||
Name: Bai xe Rua xe Trung Khanh
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+xe+R%E1%BB%ADa+xe+Tr%C3%B9ng+Kh%C3%A1nh/@10.7862001,106.69589719999999?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 6 reviews
|
||||
|
||||
Name: 24/24 Car Parking
|
||||
Link: https://www.google.com/maps/search/24%2F24+Car+Parking/@10.7776628,106.69725009999999?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 5 reviews
|
||||
|
||||
Name: Parking
|
||||
Link: https://www.google.com/maps/search/Parking/@10.773188,106.70186400000001?authuser=0&hl=en&entry=ttu
|
||||
Reviews: No Reviews reviews
|
||||
|
||||
Name: Bai xe Nguyen Thien Thuat
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+xe+Nguy%E1%BB%85n+Thi%E1%BB%87n+Thu%E1%BA%ADt/@10.7700669,106.67720179999999?authuser=0&hl=en&entry=ttu
|
||||
Reviews: No Reviews reviews",,,1,Hi tieu sieu to (Owner),,,Parking lot,Parking lot,,,Open All Days,"11d Thi Sach, Ben Nghe, Quan 1, Ho Chi Minh",,https://www.google.com/maps/place/H%E1%BB%89+ti%E1%BA%BFu+si%C3%AAu+to/data=!4m7!3m6!1s0x31752f007cccab61:0xeadc3ec13e3e3003!8m2!3d10.777885!4d106.70497!16s%2Fg%2F11vz4yy35h!19sChIJYavMfAAvdTERAzA-PsE-3Oo?authuser=0&hl=en&rclk=1,bai do xe quan 1
|
||||
|
58
backend/prisma/packingCrawler/csv/all-task-5-overview.csv
Normal file
58
backend/prisma/packingCrawler/csv/all-task-5-overview.csv
Normal file
@@ -0,0 +1,58 @@
|
||||
place_id,name,description,is_spending_on_ads,reviews,rating,competitors,website,phone,can_claim,owner_name,owner_profile_link,featured_image,main_category,categories,workday_timing,is_temporarily_closed,closed_on,address,review_keywords,link,query
|
||||
ChIJIwCfs3GsNTERFqKd_XtomRs,Diem Gui Xe May,,,9,4.7,"Name: Bai gui xe D3-5
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+g%E1%BB%ADi+xe+D3-5/@21.0047785,105.8454584?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 5 reviews
|
||||
|
||||
Name: Bai gui xe D9
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+g%E1%BB%ADi+xe+D9/@21.0039847,105.8441366?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 5 reviews
|
||||
|
||||
Name: Bai gui xe CV Thong Nhat
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+g%E1%BB%ADi+xe+CV+Th%E1%BB%91ng+Nh%E1%BA%A5t/@21.0082606,105.8456587?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 4 reviews
|
||||
|
||||
Name: Bai Gui Xe
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+G%E1%BB%ADi+Xe/@21.006453,105.821833?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 3 reviews
|
||||
|
||||
Name: Bai Gui Xe D4-D6
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+G%E1%BB%ADi+Xe+D4-D6/@21.004815,105.8422449?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 2 reviews",,0903 043 636,1,Diem Gui Xe May (Owner),,https://lh3.ggpht.com/p/AB5caB-Af95Pce1cFsZXl_bZJyK9eblLao5B8rRW3Qb5dE1vqxYF2ihFqE0t7nk_khEjWuTJu-x-UXPTLHnJC2JYC4mfbOMaUxst-28_VpLiQqd87Hsm7WWdXhUv3zmhjNagt5DrJbfb-w=s1024,Parking lot,Parking lot,Open 24 hours,,Open All Days,"51/2, Ngo 128C Pho Dai La, Quan Hai Ba Trung, Thanh Pho Ha Noi, Dong Tam, Hai Ba Trung, Ha Noi",,https://www.google.com/maps/place/%C4%90i%E1%BB%83m+G%E1%BB%ADi+Xe+M%C3%A1y/data=!4m7!3m6!1s0x3135ac71b39f0023:0x1b99687bfd9da216!8m2!3d20.9984002!4d105.8444061!16s%2Fg%2F11g6rkgsbg!19sChIJIwCfs3GsNTERFqKd_XtomRs?authuser=0&hl=en&rclk=1,bai do xe quan 2
|
||||
ChIJ30uWsxkmdTERfyKZmiUj4V0,Bai Giu Xe,,,0,0,"Name: Bai Giu Xe 24/24 NHAN DUC
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+Gi%E1%BB%AF+Xe+24%2F24+NH%C3%82N+%C4%90%E1%BB%A8C/@10.8027331,106.7525635?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 11 reviews
|
||||
|
||||
Name: Bai xe
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+xe/@10.8003738,106.7306989?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 7 reviews
|
||||
|
||||
Name: Bai do xe
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+%C4%91%E1%BB%97+xe/@10.8038765,106.7439412?authuser=0&hl=en&entry=ttu
|
||||
Reviews: No Reviews reviews
|
||||
|
||||
Name: Bai gui xe oto 4 banh
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+g%E1%BB%ADi+xe+%C3%B4t%C3%B4+4+b%C3%A1nh/@10.7906345,106.7304389?authuser=0&hl=en&entry=ttu
|
||||
Reviews: No Reviews reviews
|
||||
|
||||
Name: Bai xe oto xe may
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+xe+%C3%B4t%C3%B4+xe+m%C3%A1y/@10.8033743,106.73299879999999?authuser=0&hl=en&entry=ttu
|
||||
Reviews: No Reviews reviews",,0919 191 911,1,Bai Giu Xe (Owner),,,Parking lot,Parking lot,5:30 AM-11 PM,,Open All Days,"2, Tong Huu Dinh, Phuong Thao Dien, Quan 2, Thanh Pho Ho Chi Minh, Thao Dien, Thu Duc, Ho Chi Minh",,https://www.google.com/maps/place/B%C3%A3i+Gi%E1%BB%AF+Xe/data=!4m7!3m6!1s0x31752619b3964bdf:0x5de123259a99227f!8m2!3d10.8061749!4d106.7333204!16s%2Fg%2F11c5stql7r!19sChIJ30uWsxkmdTERfyKZmiUj4V0?authuser=0&hl=en&rclk=1,bai do xe quan 2
|
||||
ChIJwUD57jwndTERzsgRXe3FC3A,Bai gui xe oto 4 banh,"Xin chao , o day co mai che thon mat . Ai co nhu cau gui xe xin lien he tai dia chi tren.",,0,0,"Name: Bai Giu Xe 24/24 NHAN DUC
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+Gi%E1%BB%AF+Xe+24%2F24+NH%C3%82N+%C4%90%E1%BB%A8C/@10.8027331,106.7525635?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 11 reviews
|
||||
|
||||
Name: Bai o to Khanh Hoi - Quan 4
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+%C3%B4+t%C3%B4+Kh%C3%A1nh+H%E1%BB%99i+-+Qu%E1%BA%ADn+4/@10.755133599999999,106.69118189999999?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 7 reviews
|
||||
|
||||
Name: Bai Giu Xe O To So 2
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+Gi%E1%BB%AF+Xe+%C3%94+T%C3%B4+S%E1%BB%91+2/@10.770363399999999,106.6437936?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 5 reviews
|
||||
|
||||
Name: BAI GIU XE TAN AN NGUYEN
|
||||
Link: https://www.google.com/maps/search/B%C3%83I+GI%E1%BB%AE+XE+T%C3%82N+AN+NGUY%C3%8AN/@10.788878799999999,106.7297475?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 2 reviews
|
||||
|
||||
Name: Bai Giu Xe
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+Gi%E1%BB%AF+Xe/@10.8061749,106.73332040000001?authuser=0&hl=en&entry=ttu
|
||||
Reviews: No Reviews reviews",,,,Bai gui xe oto 4 banh (Owner),https://www.google.com/maps/contrib/114289761630274458697,https://lh3.ggpht.com/p/AF1QipMdDGrL0VTg7r-lfWGYEwKj4EGjsOuHc3ggfHfE=s1024,Parking lot,Parking lot,7:30 AM-10 PM,,Open All Days,"163 Tran Nao, Binh Khanh, Quan 2, Ho Chi Minh",,https://www.google.com/maps/place/B%C3%A3i+g%E1%BB%ADi+xe+%C3%B4t%C3%B4+4+b%C3%A1nh/data=!4m7!3m6!1s0x3175273ceef940c1:0x700bc5ed5d11c8ce!8m2!3d10.7906345!4d106.7304389!16s%2Fg%2F11fsq0xcwx!19sChIJwUD57jwndTERzsgRXe3FC3A?authuser=0&hl=en&rclk=1,bai do xe quan 2
|
||||
|
369
backend/prisma/packingCrawler/csv/all-task-7-overview.csv
Normal file
369
backend/prisma/packingCrawler/csv/all-task-7-overview.csv
Normal file
@@ -0,0 +1,369 @@
|
||||
place_id,name,description,is_spending_on_ads,reviews,rating,competitors,website,phone,can_claim,owner_name,owner_profile_link,featured_image,main_category,categories,workday_timing,is_temporarily_closed,closed_on,address,review_keywords,link,query
|
||||
ChIJ2d2KrRirNTER-Hj5W4gt7dE,Bai do xe cong vien Thu Le,,,50,3.8,"Name: BAI DO XE O TO
|
||||
Link: https://www.google.com/maps/search/B%C3%83I+%C4%90%E1%BB%96+XE+%C3%94+T%C3%94/@21.0478608,105.8457672?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 6 reviews
|
||||
|
||||
Name: Bai gui xe
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+g%E1%BB%ADi+xe/@20.9867769,105.8198008?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 6 reviews
|
||||
|
||||
Name: Bai Gui Xe D4-D6
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+G%E1%BB%ADi+Xe+D4-D6/@21.004815,105.8422449?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 2 reviews
|
||||
|
||||
Name: Bai do xe tho quan
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+%C4%91%E1%BB%97+xe+th%E1%BB%95+quan/@21.015855,105.83514410000001?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 2 reviews
|
||||
|
||||
Name: Diem Do Xe O To
|
||||
Link: https://www.google.com/maps/search/%C4%90i%E1%BB%83m+%C4%90%E1%BB%97+Xe+%C3%94+T%C3%B4/@21.0477423,105.8104151?authuser=0&hl=en&entry=ttu
|
||||
Reviews: No Reviews reviews",,,1,Bai do xe cong vien Thu Le (Owner),,https://lh3.ggpht.com/p/AB5caB-rhnMb-p8jejTKC5YGuwJqnI5tLQRrRsxf35StDD-9xOsy2DmIJ1Zgt-msGJJz_nQWqkx9F74R-FdxX63imtHXUXsqPeuJT83Gkl40hllZEhTw3tvACHdH4uDtleFd9oN9iO6bvA=s1024,Parking lot,Parking lot,,,Open All Days,"2RJ5+QPF, Ngoc Khanh, Ba Dinh, Ha Noi",,https://www.google.com/maps/place/B%C3%A3i+%C4%91%E1%BB%97+xe+c%C3%B4ng+vi%C3%AAn+Th%E1%BB%A7+L%E1%BB%87/data=!4m7!3m6!1s0x3135ab18ad8addd9:0xd1ed2d885bf978f8!8m2!3d21.0319313!4d105.8093104!16s%2Fg%2F11gr1fh6vc!19sChIJ2d2KrRirNTER-Hj5W4gt7dE?authuser=0&hl=en&rclk=1,bai do xe quan 9
|
||||
ChIJi7DTZ9kndTERYQqwzdO_4Nw,Bai giu xe o to Phong Phu,,,9,2.8,"Name: Bai Giu Xe Minh Phuc
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+Gi%E1%BB%AF+Xe+Minh+Phu%CC%81c/@10.8185941,106.7768261?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 4 reviews
|
||||
|
||||
Name: Bai Giu Xe So 5
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+Gi%E1%BB%AF+Xe+S%E1%BB%91+5/@10.8389165,106.7777339?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 2 reviews
|
||||
|
||||
Name: Bai Giu Xe Ot WINPHAT
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+Gi%E1%BB%AF+Xe+%C3%94t+WINPHAT/@10.8356292,106.77462299999999?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 2 reviews
|
||||
|
||||
Name: Cong Ty Tnhh Dich Vu Giu Xe 24/24
|
||||
Link: https://www.google.com/maps/search/C%C3%B4ng+Ty+Tnhh+D%E1%BB%8Bch+V%E1%BB%A5+Gi%E1%BB%AF+Xe+24%2F24/@10.839639,106.77959919999999?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 1 reviews
|
||||
|
||||
Name: Bai xe Huu Duyen
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+xe+H%E1%BB%AFu+Duy%C3%AAn/@10.839926,106.7804362?authuser=0&hl=en&entry=ttu
|
||||
Reviews: No Reviews reviews",,0936 303 759,,Bai giu xe o to Phong Phu (Owner),https://www.google.com/maps/contrib/113829690035271610483,,Parking lot,Parking lot,Open 24 hours,,Open All Days,"Tang Nhon Phu B, Thu Duc City, Ho Chi Minh City",,https://www.google.com/maps/place/B%C3%A3i+gi%E1%BB%AF+xe+%C3%B4+t%C3%B4+Phong+Ph%C3%BA/data=!4m7!3m6!1s0x317527d967d3b08b:0xdce0bfd3cdb00a61!8m2!3d10.8334033!4d106.784837!16s%2Fg%2F11fhy2b_6j!19sChIJi7DTZ9kndTERYQqwzdO_4Nw?authuser=0&hl=en&rclk=1,bai do xe quan 9
|
||||
ChIJ3frC2iAndTER0JGG-CO7Trw,Bai gui xe 989,"bai giu xe o to 989 la dia chi uy tin dang tin cay hien nhan duoc su tin tuong cua nhieu khach hang khi chon giu xe, gui xe o to tai phuong Truong Thanh, Thu Duc",,7,5,"Name: Bai Giu xe o to Thu Duc
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+Gi%E1%BB%AF+xe+%C3%B4+t%C3%B4+Th%E1%BB%A7+%C4%90%E1%BB%A9c/@10.8336864,106.762632?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 9 reviews
|
||||
|
||||
Name: Bai xe
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+xe/@10.8003738,106.7306989?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 7 reviews
|
||||
|
||||
Name: Bai Giu Xe OTo Kim Map
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+Gi%E1%BB%AF+Xe+%C3%94T%C3%B4+Kim+M%E1%BA%ADp/@10.8512491,106.74670259999999?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 4 reviews
|
||||
|
||||
Name: Bai Giu Xe o To - Hiep Binh Phuoc
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+Gi%E1%BB%AF+Xe+%C3%B4+T%C3%B4+-+Hi%E1%BB%87p+B%C3%ACnh+Ph%C6%B0%E1%BB%9Bc/@10.8480198,106.7207609?authuser=0&hl=en&entry=ttu
|
||||
Reviews: No Reviews reviews
|
||||
|
||||
Name: Bai do xe
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+%C4%91%E1%BB%97+xe/@10.8038765,106.7439412?authuser=0&hl=en&entry=ttu
|
||||
Reviews: No Reviews reviews",https://baixe989.com/,0906 733 939,,Bai gui xe 989 (Owner),https://www.google.com/maps/contrib/111329002236345694192,https://lh3.ggpht.com/p/AF1QipObeTl9on_qS86SUWhNX2ZMxu7IMM74qjYQzuhF=s1024,Parking lot,Parking lot,Open 24 hours,,Open All Days,"5A Duong Lo Lu, KP. Phuoc Hiep, P, Thu Duc, Ho Chi Minh 700000",,https://www.google.com/maps/place/B%C3%A3i+g%E1%BB%ADi+xe+989/data=!4m7!3m6!1s0x31752720dac2fadd:0xbc4ebb23f88691d0!8m2!3d10.8246151!4d106.8136735!16s%2Fg%2F11vyvsp7n9!19sChIJ3frC2iAndTER0JGG-CO7Trw?authuser=0&hl=en&rclk=1,bai do xe quan 9
|
||||
ChIJcb6sjaasNTERaPWSDd4IA0U,117 Tran Duy Hung Parking,,,27,3.8,"Name: Bac Co Parking
|
||||
Link: https://www.google.com/maps/search/Bac+Co+Parking/@21.0264429,105.85906609999999?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 7 reviews
|
||||
|
||||
Name: Dien dung xe
|
||||
Link: https://www.google.com/maps/search/%C4%90i%C3%AA%CC%89n+d%C6%B0%CC%80ng+xe/@21.0413088,105.7729712?authuser=0&hl=en&entry=ttu
|
||||
Reviews: No Reviews reviews
|
||||
|
||||
Name: Bai gui xe trong nha 24/7 - 32 Trung Van 800k 1 thang
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+g%E1%BB%ADi+xe+trong+nh%C3%A0+24%2F7+-+32+Trung+V%C4%83n+800k+1+th%C3%A1ng/@20.9918493,105.7932795?authuser=0&hl=en&entry=ttu
|
||||
Reviews: No Reviews reviews",,,1,117 Tran Duy Hung Parking (Owner),,https://lh3.ggpht.com/p/AB5caB-DMxTYByXjBvgQJXvimMgMb4O2-VDRyzb8EBxYZgpa1P2Lytr7DsEJ7n-w4GjCaJGOl0hpfC-au03cigQ5KZ4Hbo1MzxXru3maaRKvoUSkXdYbS7XZB4E_pYU_VZXfCZ5gM4SArA=s1024,Parking lot,Parking lot,,,Open All Days,"117 D. Tran Duy Hung, Trung Hoa, Cau Giay, Ha Noi",,https://www.google.com/maps/place/117+Tr%E1%BA%A7n+Duy+H%C6%B0ng+Parking/data=!4m7!3m6!1s0x3135aca68dacbe71:0x450308de0d92f568!8m2!3d21.0079468!4d105.7972974!16s%2Fg%2F11gd67tr_f!19sChIJcb6sjaasNTERaPWSDd4IA0U?authuser=0&hl=en&rclk=1,bai do xe quan 9
|
||||
ChIJcYUrXcKtNTERoySFi_oB2eQ,15A Phuong Mai,,,6,4.2,"Name: Cua hang xe
|
||||
Link: https://www.google.com/maps/search/C%E1%BB%ADa+h%C3%A0ng+xe/@20.9833592,105.8568603?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 2 reviews
|
||||
|
||||
Name: Bai Do Xe
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+%C4%90%E1%BB%97+Xe/@20.961006299999998,105.8216358?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 2 reviews
|
||||
|
||||
Name: Bai giu xe dinh Kim Lien
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+gi%E1%BB%AF+xe+%C4%91%C3%ACnh+Kim+Li%C3%AAn/@21.010115,105.83831699999999?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 2 reviews
|
||||
|
||||
Name: Bai gui xe BV Bach Mai
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+g%E1%BB%ADi+xe+BV+B%E1%BA%A1ch+Mai/@21.0033904,105.840864?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 1 reviews
|
||||
|
||||
Name: Gara Tai Da Nang
|
||||
Link: https://www.google.com/maps/search/Gara+T%C3%A0i+%C4%90%C3%A0+N%E1%BA%B5ng/@20.960684399999998,105.8412022?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 1 reviews",,,1,15A Phuong Mai (Owner),,https://lh3.ggpht.com/p/AB5caB-k-aW6MhffDFIxY5TRG1PQ0hceWG0Vb12naeXnzQ9ixmBFuO0LveF0GGf_uH98xJ2IJ12J70C2ZMhw_6iJOrr3mWuGYfv3B22rQWmtK2IBuKm5aN7tD09N4U6jzSRYYt9UJxz0sXOgR2Se=s1024,Parking garage,Parking garage,,,Open All Days,"15A Phuong Mai, Dong Da, Ha Noi 100000",,https://www.google.com/maps/place/15A+Ph%C6%B0%C6%A1ng+Mai/data=!4m7!3m6!1s0x3135adc25d2b8571:0xe4d901fa8b8524a3!8m2!3d21.0038019!4d105.8386591!16s%2Fg%2F11mvqn104r!19sChIJcYUrXcKtNTERoySFi_oB2eQ?authuser=0&hl=en&rclk=1,bai do xe quan 9
|
||||
ChIJHWoJslUmdTERc0T4hKjS9IE,Bai xe o to 232 Do Xuan Hop,,,0,0,"Name: Bai xe o to 72/56
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+xe+%C3%B4+t%C3%B4+72%2F56/@10.837803899999999,106.72201260000001?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 2 reviews
|
||||
|
||||
Name: Bai do -rua xe
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+%C4%91%E1%BB%97+-r%E1%BB%AFa+xe/@10.8437118,106.7640045?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 2 reviews
|
||||
|
||||
Name: Bai do xe oto va xe may
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+%C4%91%E1%BB%97+xe+oto+v%C3%A0+xe+m%C3%A1y/@10.8424607,106.746427?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 1 reviews
|
||||
|
||||
Name: Bai Goi Xe o to 24H
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+G%E1%BB%9Fi+Xe+%C3%B4+t%C3%B4+24H/@10.8337703,106.77053660000001?authuser=0&hl=en&entry=ttu
|
||||
Reviews: No Reviews reviews
|
||||
|
||||
Name: Bai do xe
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+%C4%91%E1%BB%97+xe/@10.8038765,106.7439412?authuser=0&hl=en&entry=ttu
|
||||
Reviews: No Reviews reviews",,0934 141 050,1,Bai xe o to 232 Do Xuan Hop (Owner),,,Parking lot,Parking lot,,,Open All Days,"232 Do Xuan Hop, Phuoc Long A, Thu Duc, Ho Chi Minh",,https://www.google.com/maps/place/B%C3%A3i+xe+%C3%B4+t%C3%B4+232+%C4%90%E1%BB%97+Xu%C3%A2n+H%E1%BB%A3p/data=!4m7!3m6!1s0x31752655b2096a1d:0x81f4d2a884f84473!8m2!3d10.826632!4d106.7684338!16s%2Fg%2F11dxdcy26y!19sChIJHWoJslUmdTERc0T4hKjS9IE?authuser=0&hl=en&rclk=1,bai do xe quan 9
|
||||
ChIJxS0oHVGrNTERiZO3z0nvgxw,Tram rua xe so 9,,,1,4,"Name: Dich Vu Rua Xe O To - Xe May
|
||||
Link: https://www.google.com/maps/search/D%E1%BB%8Bch+V%E1%BB%A5+R%E1%BB%ADa+Xe+%C3%94+T%C3%B4+-+Xe+M%C3%A1y/@21.016498199999997,105.85398719999999?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 6 reviews
|
||||
|
||||
Name: Manh Cuong Chuyen Rua Xe O To
|
||||
Link: https://www.google.com/maps/search/M%E1%BA%A1nh+C%C6%B0%E1%BB%9Dng+Chuy%C3%AAn+R%E1%BB%ADa+Xe+%C3%94+T%C3%B4/@20.990290400000003,105.81172880000001?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 2 reviews
|
||||
|
||||
Name: Dich Vu Rua Xe
|
||||
Link: https://www.google.com/maps/search/D%E1%BB%8Bch+V%E1%BB%A5+R%E1%BB%ADa+Xe/@21.017882099999998,105.8332604?authuser=0&hl=en&entry=ttu
|
||||
Reviews: No Reviews reviews
|
||||
|
||||
Name: Tiem Rua Xe Thuc
|
||||
Link: https://www.google.com/maps/search/Ti%E1%BB%87m+R%E1%BB%ADa+Xe+Th%E1%BB%B1c/@21.0144567,105.86282790000001?authuser=0&hl=en&entry=ttu
|
||||
Reviews: No Reviews reviews
|
||||
|
||||
Name: Dich Vu Rua Xe 19
|
||||
Link: https://www.google.com/maps/search/D%E1%BB%8Bch+V%E1%BB%A5+R%E1%BB%ADa+Xe+19/@20.9990055,105.8179736?authuser=0&hl=en&entry=ttu
|
||||
Reviews: No Reviews reviews",,0918 001 289,1,Tram rua xe so 9 (Owner),,https://lh3.ggpht.com/p/AB5caB8RZD-325TMOnrw2QmyDUk81LpV95t3_irY7dKV4Frmd1N5UXCei1ZRh6qgCiJTnRAQ646SO9p6g9Ovc4UcJ7Rnef0TXY-Mp6vo2b9uBIAggMh0XYLWltyLF88uPQXtwZ1U_D4=s1024,Car wash,Car wash,Open 24 hours,,Open All Days,"2V76+J2M, P. Tran Thanh Tong, Pham Dinh Ho, Hai Ba Trung, Ha Noi",,https://www.google.com/maps/place/Tr%E1%BA%A1m+r%E1%BB%ADa+xe+s%E1%BB%91+9/data=!4m7!3m6!1s0x3135ab511d282dc5:0x1c83ef49cfb79389!8m2!3d21.0140838!4d105.8600617!16s%2Fg%2F11sp51sbgy!19sChIJxS0oHVGrNTERiZO3z0nvgxw?authuser=0&hl=en&rclk=1,bai do xe quan 9
|
||||
ChIJ23IAIwKrNTEROob_zmPsohw,Nha xe Ngoc Chinh,,,0,0,"Name: Ngoc Cuong Bus Station
|
||||
Link: https://www.google.com/maps/search/Ngoc+Cuong+Bus+Station/@21.0290335,105.77622170000001?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 6 reviews
|
||||
|
||||
Name: Diem don khach nha xe Anh Dung
|
||||
Link: https://www.google.com/maps/search/%C4%90i%E1%BB%83m+%C4%91%C3%B3n+kh%C3%A1ch+nh%C3%A0+xe+Anh+D%C5%A9ng/@21.029701499999998,105.79267?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 1 reviews
|
||||
|
||||
Name: Nha xe HN Ngoc Lac
|
||||
Link: https://www.google.com/maps/search/Nh%C3%A0+xe+HN+Ng%E1%BB%8Dc+L%E1%BA%B7c/@20.971558599999998,105.84700579999999?authuser=0&hl=en&entry=ttu
|
||||
Reviews: No Reviews reviews
|
||||
|
||||
Name: Nn
|
||||
Link: https://www.google.com/maps/search/Nn/@21.033067,105.78416899999999?authuser=0&hl=en&entry=ttu
|
||||
Reviews: No Reviews reviews",,,1,Nha xe Ngoc Chinh (Owner),,,Parking lot,Parking lot,,,Open All Days,"9 Ng. 11 P. Duy Tan, Dich Vong Hau, Cau Giay, Ha Noi",,https://www.google.com/maps/place/Nh%C3%A0+xe+Ng%E1%BB%8Dc+Ch%E1%BB%89nh/data=!4m7!3m6!1s0x3135ab02230072db:0x1ca2ec63ceff863a!8m2!3d21.0292791!4d105.7840138!16s%2Fg%2F11n0v2v0th!19sChIJ23IAIwKrNTEROob_zmPsohw?authuser=0&hl=en&rclk=1,bai do xe quan 9
|
||||
ChIJNXzQmHCsNTERvq_h6GVZfLM,Benh Vien Bach Mai - 78 Giai Phong,,,124,4.2,"Name: Bach Mai Hospital
|
||||
Link: https://www.google.com/maps/search/Bach+Mai+Hospital/@21.0019284,105.84038419999999?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 705 reviews
|
||||
|
||||
Name: 368-370 Bach Mai
|
||||
Link: https://www.google.com/maps/search/368-370+B%E1%BA%A1ch+Mai/@21.000446699999998,105.850348?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 5 reviews
|
||||
|
||||
Name: 215 Bach Mai
|
||||
Link: https://www.google.com/maps/search/215+B%E1%BA%A1ch+Mai/@21.004305199999997,105.8511248?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 5 reviews
|
||||
|
||||
Name: 216-218 Bach Mai
|
||||
Link: https://www.google.com/maps/search/216-218+B%E1%BA%A1ch+Mai/@21.003979800000003,105.8509624?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 5 reviews
|
||||
|
||||
Name: 327 Bach Mai
|
||||
Link: https://www.google.com/maps/search/327+B%E1%BA%A1ch+Mai/@21.00102,105.850695?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 3 reviews",,,,Benh Vien Bach Mai - 78 Giai Phong (Owner),,https://lh3.ggpht.com/p/AB5caB8-B69UJgmYmT2yGI3zKkn9VGGizIL27pZmr-rc6GkQDbwKCPm8bpPO1kuuWbRBUf_i_1oCxZAw5z_4OP1dbSbDgr06VFE_qoAVo25K8pMSOw4wsPRlJrB0jSIrKMr6IkwphZxV6g=s1024,Bus stop,Bus stop,,,Open All Days,"Dong Da, Hanoi",,https://www.google.com/maps/place/B%E1%BB%87nh+Vi%E1%BB%87n+B%E1%BA%A1ch+Mai+-+78+Gi%E1%BA%A3i+Ph%C3%B3ng/data=!4m7!3m6!1s0x3135ac7098d07c35:0xb37c5965e8e1afbe!8m2!3d21.0010301!4d105.8411324!16s%2Fg%2F11n86srb4l!19sChIJNXzQmHCsNTERvq_h6GVZfLM?authuser=0&hl=en&rclk=1,bai do xe quan 9
|
||||
ChIJcSXkV94hdTERW4cSP0KMd60,Bai do xe S9,,,2,5,"Name: Bai do -rua xe
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+%C4%91%E1%BB%97+-r%E1%BB%AFa+xe/@10.8437118,106.7640045?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 2 reviews
|
||||
|
||||
Name: Bai Dau Xe 96
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+%C4%90%E1%BA%ADu+Xe+96/@10.82227,106.7195?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 2 reviews
|
||||
|
||||
Name: Bai xe o to 232 Do Xuan Hop
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+xe+%C3%B4+t%C3%B4+232+%C4%90%E1%BB%97+Xu%C3%A2n+H%E1%BB%A3p/@10.826632,106.7684338?authuser=0&hl=en&entry=ttu
|
||||
Reviews: No Reviews reviews
|
||||
|
||||
Name: Bai do xe
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+%C4%91%E1%BB%97+xe/@10.8038765,106.7439412?authuser=0&hl=en&entry=ttu
|
||||
Reviews: No Reviews reviews
|
||||
|
||||
Name: Bai Goi Xe o to 24H
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+G%E1%BB%9Fi+Xe+%C3%B4+t%C3%B4+24H/@10.8337703,106.77053660000001?authuser=0&hl=en&entry=ttu
|
||||
Reviews: No Reviews reviews",,,1,Bai do xe S9 (Owner),,https://lh3.ggpht.com/p/AB5caB_xqluuaqF8oxWs8Pk86veThtT7cMYWLAji5OSlfGNkdVEj63MaxcIjHQ33uWp-GEK-uj_D0U_oGjvT173HaIrFzuBRTUnJ4bo6_QQeTdhR3JTV04lNFR4fskGWP0RMvulp4QcfEg=s1024,Parking lot,Parking lot,,,Open All Days,"RRWP+JP7, Long Thanh My, Thu Duc, Ho Chi Minh",,https://www.google.com/maps/place/B%C3%A3i+%C4%91%E1%BB%97+xe+S9/data=!4m7!3m6!1s0x317521de57e42571:0xad778c423f12875b!8m2!3d10.8458975!4d106.8370903!16s%2Fg%2F11tt274pyr!19sChIJcSXkV94hdTERW4cSP0KMd60?authuser=0&hl=en&rclk=1,bai do xe quan 9
|
||||
ChIJYXhLT0CtNTERodtRW1xxnSk,Nuoc Ngam Bus Station,,,2444,4.2,"Name: Nuoc Ngam Bus Station
|
||||
Link: https://www.google.com/maps/search/Nuoc+Ngam+Bus+Station/@20.9654615,105.84324509999999?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 2707 reviews
|
||||
|
||||
Name: Ben Xe Nuoc Ngam
|
||||
Link: https://www.google.com/maps/search/B%E1%BA%BFn+Xe+N%C6%B0%E1%BB%9Bc+Ng%E1%BA%A7m/@20.9648046,105.8421563?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 383 reviews
|
||||
|
||||
Name: Nuoc Ngam Bus Station
|
||||
Link: https://www.google.com/maps/search/Nuoc+Ngam+Bus+Station/@20.964973399999998,105.8430513?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 92 reviews
|
||||
|
||||
Name: ben xe nuoc ngam
|
||||
Link: https://www.google.com/maps/search/b%E1%BA%BFn+xe+n%C6%B0%E1%BB%9Bc+ng%E1%BA%A7m/@20.9649906,105.8431569?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 1 reviews
|
||||
|
||||
Name: Tra khach nuoc ngam
|
||||
Link: https://www.google.com/maps/search/Tr%E1%BA%A3+kh%C3%A1ch+n%C6%B0%E1%BB%9Bc+ng%E1%BA%A7m/@20.9627223,105.8469093?authuser=0&hl=en&entry=ttu
|
||||
Reviews: No Reviews reviews",http://benxenuocngam.vn/,,1,Nuoc Ngam Bus Station (Owner),,https://lh3.ggpht.com/p/AB5caB8sDh4fEW9j624Th8DftslC7yiKcwSF70srUNHSAuB1Ia_g1ohOA40LskvsI80mjsl1VqlheoUuTjr74t4iSOM7WyJNfqbyO-TrxeY9klpndI00tPsnGocwL5zwkXblwPcS9ah9=s1024,Parking lot,Parking lot,,,Open All Days,"1 D. Ngoc Hoi, Hoang Liet, Hoang Mai, Ha Noi","ticket, prices, travel, taxi drivers, town, passengers, traffic, number, washroom, massage",https://www.google.com/maps/place/Nuoc+Ngam+Bus+Station/data=!4m7!3m6!1s0x3135ad404f4b7861:0x299d715c5b51dba1!8m2!3d20.9654452!4d105.8423284!16s%2Fg%2F11gqqnwhsh!19sChIJYXhLT0CtNTERodtRW1xxnSk?authuser=0&hl=en&rclk=1,bai do xe quan 9
|
||||
ChIJ0wHuOX6rNTERylMRuJt-RrQ,332 P. Tay Son - Quang Trung,,,24,4.3,"Name: 290 Tay Son
|
||||
Link: https://www.google.com/maps/search/290+T%C3%A2y+S%C6%A1n/@21.007122000000003,105.823125?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 19 reviews
|
||||
|
||||
Name: 254-256 Le Duan
|
||||
Link: https://www.google.com/maps/search/254-256+L%C3%AA+Du%E1%BA%A9n/@21.0156946,105.8413029?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 13 reviews
|
||||
|
||||
Name: 140 Son Tay
|
||||
Link: https://www.google.com/maps/search/140+S%C6%A1n+T%C3%A2y/@21.0326679,105.8293242?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 3 reviews
|
||||
|
||||
Name: 50
|
||||
Link: https://www.google.com/maps/search/50/@21.0126846,105.77041?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 3 reviews
|
||||
|
||||
Name: 133 Thai Thinh
|
||||
Link: https://www.google.com/maps/search/133+Th%C3%A1i+Th%E1%BB%8Bnh/@21.0139566,105.81475239999999?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 1 reviews",,,,332 P. Tay Son - Quang Trung (Owner),,https://lh3.ggpht.com/p/AB5caB-3iWtKOo1gSkOlDK4fZZ2hQF3eMMg3PFC2BMtvZJTr2kRZ9HsQ7ybUYfXKJNOoqRqsKENHz_JjGUFgY-ZGCOzOh14AhbVN-X6E43wznMN_FyQVQAzl9PFdXKWllQUy3TWHCX0=s1024,Bus stop,Bus stop,,,Open All Days,"doi dien 145 P. Tay Son, Quang Trung, Dong Da, Ha Noi",,https://www.google.com/maps/place/332+P.+T%C3%A2y+S%C6%A1n+-+Quang+Trung/data=!4m7!3m6!1s0x3135ab7e39ee01d3:0xb4467e9bb81153ca!8m2!3d21.0111629!4d105.8252533!16s%2Fg%2F1tyct3dc!19sChIJ0wHuOX6rNTERylMRuJt-RrQ?authuser=0&hl=en&rclk=1,bai do xe quan 9
|
||||
ChIJrwU5-56rNTERCecYnwnKphs,Ga Ha Noi,,,3390,4.3,"Name: Hanoi Railway Station
|
||||
Link: https://www.google.com/maps/search/Hanoi+Railway+Station/@21.0242529,105.84102899999999?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 1330 reviews
|
||||
|
||||
Name: Thai Ha Station
|
||||
Link: https://www.google.com/maps/search/Thai+Ha+Station/@21.0144833,105.8194491?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 264 reviews
|
||||
|
||||
Name: Ga Ha Noi
|
||||
Link: https://www.google.com/maps/search/Ga+H%C3%A0+N%E1%BB%99i/@21.0250615,105.8411814?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 177 reviews
|
||||
|
||||
Name: Ga Ha Noi - 120 Le Duan
|
||||
Link: https://www.google.com/maps/search/Ga+H%C3%A0+N%E1%BB%99i+-+120+L%C3%AA+Du%E1%BA%A9n/@21.023294999999997,105.84137799999999?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 9 reviews
|
||||
|
||||
Name: Arret Bus pour Sapa
|
||||
Link: https://www.google.com/maps/search/Arr%C3%AAt+Bus+pour+Sapa/@21.0243033,105.841093?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 1 reviews",,,,Ga Ha Noi (Owner),https://www.google.com/maps/contrib/105655510386027356630,https://lh3.ggpht.com/p/AB5caB8ssXBm_1WPnxz5YmK3FuDM2tuosS1pmv5w3eMTka7St8PTz9wpJt6bcMTfRPiV9izmYwh1dZKyVC1lCZVhAAEUdNb_BKfFdAHpBPyrh5eNZf1wXsiqeTUIzKLxWNNJR4eQnVs=s1024,Train yard,Train yard,6:36 AM-8:02 PM,,Open All Days,"120 D. Le Duan, Van Mieu, Hoan Kiem, Ha Noi","experience, luggage, tickets, toilet, snacks, morning, sleeper, locker, capital, tourist",https://www.google.com/maps/place/Ga+H%C3%A0+N%E1%BB%99i/data=!4m7!3m6!1s0x3135ab9efb3905af:0x1ba6ca099f18e709!8m2!3d21.0243303!4d105.8410933!16s%2Fg%2F11h23l0q7j!19sChIJrwU5-56rNTERCecYnwnKphs?authuser=0&hl=en&rclk=1,bai do xe quan 9
|
||||
ChIJhwIS7QYndTER86qSWz8wiIs,Bai do xe,,,0,0,"Name: Bai Giu Xe 24/24 NHAN DUC
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+Gi%E1%BB%AF+Xe+24%2F24+NH%C3%82N+%C4%90%E1%BB%A8C/@10.8027331,106.7525635?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 11 reviews
|
||||
|
||||
Name: Bai xe
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+xe/@10.8003738,106.7306989?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 7 reviews
|
||||
|
||||
Name: Bai do -rua xe
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+%C4%91%E1%BB%97+-r%E1%BB%AFa+xe/@10.8437118,106.7640045?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 2 reviews
|
||||
|
||||
Name: Bai Giu Xe
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+Gi%E1%BB%AF+Xe/@10.8061749,106.73332040000001?authuser=0&hl=en&entry=ttu
|
||||
Reviews: No Reviews reviews
|
||||
|
||||
Name: Bai xe oto xe may
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+xe+%C3%B4t%C3%B4+xe+m%C3%A1y/@10.8033743,106.73299879999999?authuser=0&hl=en&entry=ttu
|
||||
Reviews: No Reviews reviews",,0908 284 839,1,Bai do xe (Owner),,https://lh3.ggpht.com/p/AB5caB_NqldsCCbzA7r4n7LZa0QtmzjcYOzW_WTgLKhxGx8ARZ8qgXlprX7rm2Zwcq0W5rkVZv8kaL_xyhoSOMZ3eRpxzl3zXVsjwuM9kD6GYQmF2bOb9IwC5nfgphvdb-v_q3_CTnuc=s1024,Parking lot,Parking lot,Open 24 hours,,Open All Days,"17 D. So 10, Thao Dien, Thu Duc, Ho Chi Minh",,https://www.google.com/maps/place/B%C3%A3i+%C4%91%E1%BB%97+xe/data=!4m7!3m6!1s0x31752706ed120287:0x8b88303f5b92aaf3!8m2!3d10.8038765!4d106.7439412!16s%2Fg%2F11fvxz3h52!19sChIJhwIS7QYndTER86qSWz8wiIs?authuser=0&hl=en&rclk=1,bai do xe quan 9
|
||||
ChIJvfV5iewndTERJURTQuXGcKg,Giu xe Hoang Nhat,,,22,5,"Name: Bai giu xe o to Phong Phu
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+gi%E1%BB%AF+xe+%C3%B4+t%C3%B4+Phong+Ph%C3%BA/@10.833403299999999,106.784837?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 9 reviews
|
||||
|
||||
Name: Bai Giu Xe Ot WINPHAT
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+Gi%E1%BB%AF+Xe+%C3%94t+WINPHAT/@10.8356292,106.77462299999999?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 2 reviews
|
||||
|
||||
Name: Bai giu xe oto Thanh Dat
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+gi%E1%BB%AF+xe+%C3%B4t%C3%B4+Th%C3%A0nh+%C4%90%E1%BA%A1t/@10.8420755,106.7437722?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 2 reviews
|
||||
|
||||
Name: Cong Ty Tnhh Dich Vu Giu Xe 24/24
|
||||
Link: https://www.google.com/maps/search/C%C3%B4ng+Ty+Tnhh+D%E1%BB%8Bch+V%E1%BB%A5+Gi%E1%BB%AF+Xe+24%2F24/@10.839639,106.77959919999999?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 1 reviews
|
||||
|
||||
Name: Bai giu xe
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+gi%E1%BB%AF+xe/@10.8342263,106.7811237?authuser=0&hl=en&entry=ttu
|
||||
Reviews: No Reviews reviews",,0986 251 779,,Giu xe Hoang Nhat (Owner),https://www.google.com/maps/contrib/118009397686028168027,https://lh3.ggpht.com/p/AB5caB_rejryvgZ5X0ijhADj-9KyaV8WJsWNoqXnrED70fKgcPw2wbgmd58kvnWEJXKX9oZ4qmLQ1hP6sczju6wYQss-NHtTcjToA6NK3wJIayI9vFKau4K80Si3ysvAMAfi0pddhEvf=s1024,Automobile storage facility,Automobile storage facility,Open 24 hours,,Open All Days,"RQPG+MM8, Duong 10, Tang Nhon Phu B, Thu Duc, Ho Chi Minh",,https://www.google.com/maps/place/Gi%E1%BB%AF+xe+Ho%C3%A0ng+Nh%E1%BA%ADt/data=!4m7!3m6!1s0x317527ec8979f5bd:0xa870c6e542534425!8m2!3d10.8359249!4d106.7779336!16s%2Fg%2F11fmrj_kjr!19sChIJvfV5iewndTERJURTQuXGcKg?authuser=0&hl=en&rclk=1,bai do xe quan 9
|
||||
ChIJPw42_ZKrNTEROD7sk_GKCBQ,58B Ba Trieu,,,6,4.2,"Name: 22B Hai Ba Trung
|
||||
Link: https://www.google.com/maps/search/22B+Hai+B%C3%A0+Tr%C6%B0ng/@21.024086999999998,105.8536307?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 20 reviews
|
||||
|
||||
Name: 92-94 Ba Trieu
|
||||
Link: https://www.google.com/maps/search/92-94+B%C3%A0+Tri%E1%BB%87u/@21.0192062,105.8493922?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 12 reviews
|
||||
|
||||
Name: 25A-25B Pho Hue - Hang Bai - Hoan Kiem
|
||||
Link: https://www.google.com/maps/search/25A-25B+Ph%E1%BB%91+Hu%E1%BA%BF+-+H%C3%A0ng+B%C3%A0i+-+Ho%C3%A0n+Ki%E1%BA%BFm/@21.0187945,105.8517851?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 5 reviews
|
||||
|
||||
Name: 08, 31, 35, 38
|
||||
Link: https://www.google.com/maps/search/08%2C+31%2C+35%2C+38/@21.015871900000004,105.8491006?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 2 reviews
|
||||
|
||||
Name: 180 - 182 Ba Trieu
|
||||
Link: https://www.google.com/maps/search/180+-+182+B%C3%A0+Tri%E1%BB%87u/@21.0159523,105.84914789999999?authuser=0&hl=en&entry=ttu
|
||||
Reviews: No Reviews reviews",,,,58B Ba Trieu (Owner),,https://lh3.ggpht.com/p/AB5caB-lqsog5ElXwYmPY0HvQuWBsKXMQXutm3EnocaNocjlV8InSTh8Ls2ZXwD-TIsQ2CI40hpyktl-CQUpdtl6POjEmi_BQQL5_njHqk1c4iJnLW3iy3f6mxz_QQlNm2OJhRPPFVFQ=s1024,Bus stop,Bus stop,,,Open All Days,"58B P. Ba Trieu, Tran Hung Dao, Hoan Kiem, Ha Noi",,https://www.google.com/maps/place/58B+B%C3%A0+Tri%E1%BB%87u/data=!4m7!3m6!1s0x3135ab92fd360e3f:0x14088af193ec3e38!8m2!3d21.0220545!4d105.8503593!16s%2Fg%2F1tdgrwm1!19sChIJPw42_ZKrNTEROD7sk_GKCBQ?authuser=0&hl=en&rclk=1,bai do xe quan 9
|
||||
ChIJmadfJOurNTER9AEGhTvL4O4,Buu Dien Thanh Pho Ha Noi - 75B Dinh Tien Hoang,,,73,4.3,"Name: Hanoi Post Office
|
||||
Link: https://www.google.com/maps/search/Hanoi+Post+Office/@21.0266056,105.85380029999999?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 251 reviews
|
||||
|
||||
Name: Central Post office
|
||||
Link: https://www.google.com/maps/search/Central+Post+office/@21.0270743,105.8537868?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 96 reviews
|
||||
|
||||
Name: 162 Tran Quang Khai
|
||||
Link: https://www.google.com/maps/search/162+Tr%E1%BA%A7n+Quang+Kh%E1%BA%A3i/@21.032008299999998,105.85610299999999?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 24 reviews
|
||||
|
||||
Name: Bai Do Xe Buyt Tran Khanh Du - Phan Chu Trinh
|
||||
Link: https://www.google.com/maps/search/B%C3%A3i+%C4%90%E1%BB%97+Xe+Bu%C3%BDt+Tr%E1%BA%A7n+Kh%C3%A1nh+D%C6%B0+-+Phan+Chu+Trinh/@21.0234788,105.86072340000001?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 22 reviews
|
||||
|
||||
Name: Hanoi Post - Customer Sevice Centre
|
||||
Link: https://www.google.com/maps/search/Hanoi+Post+-+Customer+Sevice+Centre/@21.0265421,105.8536137?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 13 reviews",,,,Buu Dien Thanh Pho Ha Noi - 75B Dinh Tien Hoang (Owner),,https://lh3.ggpht.com/p/AB5caB9nA4H2TKwUCysnnRo_8982ZumjZ2--osCx8aKVKjFMqCAOTF_OtHXRTDl7mk-v0PJOkY-Vt0XFMalq3sp2DLNl50qauDsPrtyFfAbAo06D_cyNMIoBPpsCSdoSqTHVCJ-9G8AAlQ=s1024,Bus stop,Bus stop,,,Open All Days,"75 P. Dinh Tien Hoang, French Quarter, Hoan Kiem, Ha Noi","airport, opera house, platform, lake",https://www.google.com/maps/place/B%C6%B0u+%C4%90i%E1%BB%87n+Th%C3%A0nh+Ph%E1%BB%91+H%C3%A0+N%E1%BB%99i+-+75B+%C4%90inh+Ti%C3%AAn+Ho%C3%A0ng/data=!4m7!3m6!1s0x3135abeb245fa799:0xeee0cb3b850601f4!8m2!3d21.0269327!4d105.8536015!16s%2Fg%2F1tfx2cnt!19sChIJmadfJOurNTER9AEGhTvL4O4?authuser=0&hl=en&rclk=1,bai do xe quan 9
|
||||
ChIJsYgoI0KrNTERSHYF7-zBU-8,Diem Trung Chuyen Cau Giay - GTVT 01,,,5,3.6,"Name: So 148-150 Cau Giay
|
||||
Link: https://www.google.com/maps/search/S%E1%BB%91+148-150+C%E1%BA%A7u+Gi%E1%BA%A5y/@21.032951699999998,105.7985623?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 48 reviews
|
||||
|
||||
Name: Diem Trung Chuyen Cau Giay - Thu Le 01
|
||||
Link: https://www.google.com/maps/search/%C4%90i%E1%BB%83m+Trung+Chuy%E1%BB%83n+C%E1%BA%A7u+Gi%E1%BA%A5y+-+Th%E1%BB%A7+L%E1%BB%87+01/@21.0291604,105.80397099999999?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 4 reviews
|
||||
|
||||
Name: Diem Trung Chuyen Cau Giay - GTVT 02
|
||||
Link: https://www.google.com/maps/search/%C4%90i%E1%BB%83m+Trung+Chuy%E1%BB%83n+C%E1%BA%A7u+Gi%E1%BA%A5y+-+GTVT+02/@21.0287042,105.8034199?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 4 reviews
|
||||
|
||||
Name: Diem Trung Chuyen Cau Giay - Thu Le 02
|
||||
Link: https://www.google.com/maps/search/%C4%90i%E1%BB%83m+Trung+Chuy%E1%BB%83n+C%E1%BA%A7u+Gi%E1%BA%A5y+-+Th%E1%BB%A7+L%E1%BB%87+02/@21.0293608,105.803603?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 4 reviews
|
||||
|
||||
Name: Dai Giua Diem Trung Chuyen Cau Giay - Nha Ga S8-04
|
||||
Link: https://www.google.com/maps/search/D%E1%BA%A3i+Gi%E1%BB%AFa+%C4%90i%E1%BB%83m+Trung+Chuy%E1%BB%83n+C%E1%BA%A7u+Gi%E1%BA%A5y+-+Nh%C3%A0+Ga+S8-04/@21.0294179,105.8026538?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 1 reviews",,,,Diem Trung Chuyen Cau Giay - GTVT 01 (Owner),,https://lh3.ggpht.com/p/AB5caB9H-Kp33vIQLU0xubjCTvj2QDyDV3DpSS9TSSJn6ROPd8Ez1HOYHSCkuPK8ddH5_ucEWc47DeDMv7G2QWDaj0hX8w3-95QUrpOlZ6IpIpK3s1KeudepO519ueieVfJnbSCKuKdk=s1024,Bus stop,Bus stop,,,Open All Days,"Lang Thuong, Dong Da, Hanoi",,https://www.google.com/maps/place/%C4%90i%E1%BB%83m+Trung+Chuy%E1%BB%83n+C%E1%BA%A7u+Gi%E1%BA%A5y+-+GTVT+01/data=!4m7!3m6!1s0x3135ab42232888b1:0xef53c1ecef057648!8m2!3d21.028522!4d105.8038578!16s%2Fg%2F11n86s27_v!19sChIJsYgoI0KrNTERSHYF7-zBU-8?authuser=0&hl=en&rclk=1,bai do xe quan 9
|
||||
ChIJmcAx9LJUNDERKBFGzbpDQOQ,Ben Xe My Dinh,,,1280,3.7,"Name: Ben Xe Khach My Dinh
|
||||
Link: https://www.google.com/maps/search/B%E1%BA%BFn+Xe+Kh%C3%A1ch+M%E1%BB%B9+%C4%90%C3%ACnh/@21.0283653,105.7782948?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 74 reviews
|
||||
|
||||
Name: Ben xe My Dinh
|
||||
Link: https://www.google.com/maps/search/B%E1%BA%BFn+xe+M%E1%BB%B9+%C4%90%C3%ACnh/@21.02758,105.77875900000001?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 32 reviews
|
||||
|
||||
Name: Ben Xe Bus My Dinh
|
||||
Link: https://www.google.com/maps/search/B%E1%BA%BFn+Xe+Bus+M%E1%BB%B9+%C4%90%C3%ACnh/@21.0263377,105.77622129999999?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 12 reviews
|
||||
|
||||
Name: Ben Xe My Dinh Ha Noi
|
||||
Link: https://www.google.com/maps/search/B%E1%BA%BFn+Xe+M%E1%BB%B9+%C4%90%C3%ACnh+H%C3%A0+N%E1%BB%99i/@21.0284308,105.77647569999999?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 1 reviews
|
||||
|
||||
Name: Trung Tam Dieu Hanh Ben Xe My Dinh
|
||||
Link: https://www.google.com/maps/search/Trung+T%C3%A2m+%C4%90i%E1%BB%81u+H%C3%A0nh+B%E1%BA%BFn+Xe+M%E1%BB%B9+%C4%90%C3%ACnh/@21.027563699999998,105.7786282?authuser=0&hl=en&entry=ttu
|
||||
Reviews: No Reviews reviews",,,,Ben Xe My Dinh (Owner),,https://lh3.ggpht.com/p/AB5caB9D3l5UTZ3rE-Fu2X2U-XRgNBnjXy66oS41VK9OPyd1KIv4ryekcLLi-9MCDG3JUo4xOu6aVAiolnq4rzkzeXnAR22h1ha12i7Tiw1sn-uZxjxbnlRxQn1Oe_N44821vaKtBeNE=s1024,Bus stop,Bus stop,,,Open All Days,"My Dinh, Nam Tu Liem, Hanoi","ticket, intercity, bus terminus, money, scam, cost, buses in japan, washroom, driver",https://www.google.com/maps/place/B%E1%BA%BFn+Xe+M%E1%BB%B9+%C4%90%C3%ACnh/data=!4m7!3m6!1s0x313454b2f431c099:0xe44043bacd461128!8m2!3d21.0284297!4d105.7782687!16s%2Fg%2F1tgdqlwg!19sChIJmcAx9LJUNDERKBFGzbpDQOQ?authuser=0&hl=en&rclk=1,bai do xe quan 9
|
||||
ChIJGUI1vimtNTERYIqayJ3SdFE,BINH MINH LIMOUSINE,,,167,2.7,"Name: NINH BINH CAR
|
||||
Link: https://www.google.com/maps/search/NINH+B%C3%8CNH+CAR/@20.971687499999998,105.84706249999999?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 180 reviews
|
||||
|
||||
Name: Binh Hoai Limousine
|
||||
Link: https://www.google.com/maps/search/B%C3%ACnh+Ho%C3%A0i+Limousine/@20.970512300000003,105.8476529?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 39 reviews
|
||||
|
||||
Name: Binh An Limousine Hoang Mai
|
||||
Link: https://www.google.com/maps/search/B%C3%ACnh+An+Limousine+Ho%C3%A0ng+Mai/@20.971035,105.8491718?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 12 reviews
|
||||
|
||||
Name: MINH THUY LIMOUSINE
|
||||
Link: https://www.google.com/maps/search/MINH+TH%C3%9AY+LIMOUSINE/@20.9609871,105.8429326?authuser=0&hl=en&entry=ttu
|
||||
Reviews: 8 reviews
|
||||
|
||||
Name: Nha Xe Minh Quan LMS
|
||||
Link: https://www.google.com/maps/search/Nh%C3%A0+Xe+Minh+Qu%C3%A2n+LMS/@21.0184095,105.8407493?authuser=0&hl=en&entry=ttu
|
||||
Reviews: No Reviews reviews",http://binhminhlimousine.com.vn/,1900 1531,,BINH MINH LIMOUSINE (Owner),https://www.google.com/maps/contrib/114622283759755294871,https://lh3.ggpht.com/p/AB5caB9zgtRuxMeQPetmVAvYZ7-FSacUF5mN10kYoZwhxHGM3zwMu0FmIhf_SlpVLYBph2Ozr8EZ9h0g8ZZaZ6RWHHrzb6FleRoJvo5mhJp78_p-kvU5qdOA5EdixqlS6ISzU2O5bleJTA=s1024,Trucking company,Trucking company,Open 24 hours,,Open All Days,"Dong Tau, N 7, KDT DONG TAU, Hoang Mai, Ha Noi","driver, bus, communication, price, seats, book, pictures, management, whatsapp, passengers",https://www.google.com/maps/place/B%C3%8CNH+MINH+LIMOUSINE/data=!4m7!3m6!1s0x3135ad29be354219:0x5174d29dc89a8a60!8m2!3d20.970282!4d105.8482623!16s%2Fg%2F11gj1lfkx0!19sChIJGUI1vimtNTERYIqayJ3SdFE?authuser=0&hl=en&rclk=1,bai do xe quan 9
|
||||
|
2019
backend/prisma/packingCrawler/csv/packing_1.csv
Normal file
2019
backend/prisma/packingCrawler/csv/packing_1.csv
Normal file
File diff suppressed because it is too large
Load Diff
32
backend/prisma/packingCrawler/model.ts
Normal file
32
backend/prisma/packingCrawler/model.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
export class CSVRow {
|
||||
place_id?: string;
|
||||
name?: string;
|
||||
description?: string;
|
||||
is_spending_on_ads?: string;
|
||||
reviews?: string;
|
||||
rating?: string;
|
||||
competitors?: string;
|
||||
website?: string;
|
||||
phone?: string;
|
||||
can_claim?: string;
|
||||
owner_name?: string;
|
||||
owner_profile_link?: string;
|
||||
featured_image?: string;
|
||||
main_category?: string;
|
||||
categories?: string;
|
||||
workday_timing?: string;
|
||||
is_temporarily_closed?: string;
|
||||
closed_on?: string;
|
||||
address?: string;
|
||||
review_keywords?: string;
|
||||
link?: string;
|
||||
query?: string;
|
||||
lat?: number;
|
||||
lng?: number;
|
||||
city?: string;
|
||||
district?: string;
|
||||
ward?: string;
|
||||
open?: string;
|
||||
close?: string;
|
||||
|
||||
}
|
||||
89
backend/prisma/packingCrawler/seed.ts
Normal file
89
backend/prisma/packingCrawler/seed.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
import { BookingType, PrismaClient, SlotType } from '@prisma/client';
|
||||
// import { PrismaClient } from '@prisma/client';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import csv from 'csv-parser';
|
||||
import { CSVRow } from './model';
|
||||
import { URLtoLatLng, convertAddressToDetail, convertWorkdayTiming } from '../../src/share/tool';
|
||||
async function readCsv(csvFileName): Promise<CSVRow[]> {
|
||||
function doParse(resolve) {
|
||||
const results = [];
|
||||
fs.createReadStream(csvFileName)
|
||||
.pipe(csv())
|
||||
.on('data', (data: CSVRow) => {
|
||||
|
||||
// convert lat lng
|
||||
const latLng = URLtoLatLng(data.link);
|
||||
// convert workday timing
|
||||
const workdayTiming = convertWorkdayTiming(data.workday_timing);
|
||||
// convert address to detail
|
||||
const addressDetail = convertAddressToDetail(data.address);
|
||||
results.push(<CSVRow>{ ...data, ...latLng, ...addressDetail, ...workdayTiming });
|
||||
})
|
||||
.on('end', () => {
|
||||
resolve(results);
|
||||
});
|
||||
}
|
||||
return new Promise((resolve) => doParse(resolve));
|
||||
}
|
||||
const prisma = new PrismaClient();
|
||||
async function main() {
|
||||
prisma.$transaction(async (tx: PrismaClient) => {
|
||||
console.log('Seeding...');
|
||||
try {
|
||||
// get list file csv from forder dumps
|
||||
const directoryPath = path.join(__dirname, 'csv');
|
||||
// read directory
|
||||
const files = fs.readdirSync(directoryPath);
|
||||
// listing all files using forEach
|
||||
for (const file of files) {
|
||||
if (!file.endsWith('.csv')) continue;
|
||||
const filePath = path.join(directoryPath, file);
|
||||
const slotsData = await readCsv(filePath);
|
||||
// convert to json
|
||||
|
||||
// insert to db
|
||||
await tx.slot.createMany({
|
||||
data: slotsData.map((e) => ({
|
||||
lat: e.lat,
|
||||
lng: e.lng,
|
||||
address: e.address,
|
||||
city: e.city,
|
||||
district: e.district,
|
||||
ward: e.ward,
|
||||
name: e.name,
|
||||
total: 99,
|
||||
empty: 99,
|
||||
alow_booking_type: BookingType.All,
|
||||
slot_type: SlotType.Orther,
|
||||
images: [],
|
||||
phone: e.phone,
|
||||
close: e.close,
|
||||
open: e.open,
|
||||
directions: {
|
||||
competitors: e.competitors,
|
||||
rating: e.rating,
|
||||
reviews: e.reviews,
|
||||
website: e.website,
|
||||
},
|
||||
})),
|
||||
skipDuplicates: true,
|
||||
});
|
||||
console.log(`Inserted ${slotsData.length} slots from ${file}`);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error reading CSV file:', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
main()
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
})
|
||||
.finally(async () => {
|
||||
// close the Prisma Client at the end
|
||||
// await prisma.$disconnect();
|
||||
});
|
||||
226
backend/prisma/schema.prisma
Normal file
226
backend/prisma/schema.prisma
Normal file
@@ -0,0 +1,226 @@
|
||||
// This is your Prisma schema file,
|
||||
// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
||||
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
|
||||
// https://github.com/prisma/prisma-client-js/issues/616#issuecomment-616107821
|
||||
binaryTargets = ["native", "darwin", "debian-openssl-1.1.x"]
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
model User {
|
||||
id Int @id @default(autoincrement())
|
||||
email String? @unique
|
||||
username String? @unique
|
||||
password String?
|
||||
phone String? @unique
|
||||
fullname String?
|
||||
dob DateTime?
|
||||
gender Gender @default(Female)
|
||||
bookings Booking[]
|
||||
language Language @default(vi)
|
||||
Slots Slot[]
|
||||
Comments Comment[]
|
||||
created_at DateTime @default(now()) @db.Timestamptz(3)
|
||||
updated_at DateTime @updatedAt @db.Timestamptz(3)
|
||||
deleted_at DateTime? @db.Timestamptz(3)
|
||||
Like Like[]
|
||||
Vote Vote[]
|
||||
|
||||
@@index([id, email])
|
||||
}
|
||||
|
||||
model Booking {
|
||||
id Int @id @default(autoincrement())
|
||||
path String?
|
||||
image String?
|
||||
public Boolean @default(true)
|
||||
user User @relation(fields: [user_id], references: [id], onUpdate: Cascade, onDelete: Cascade)
|
||||
user_id Int
|
||||
status BookingStatus @default(pending)
|
||||
slot Slot @relation(fields: [slot_id], references: [id])
|
||||
slot_id Int
|
||||
start_at DateTime @db.Timestamptz(3)
|
||||
end_at DateTime @db.Timestamptz(3)
|
||||
owner String
|
||||
license_plates String
|
||||
contact String
|
||||
pricing Int?
|
||||
booking_type BookingType? @default(All)
|
||||
slot_type SlotType? @default(Car)
|
||||
created_at DateTime @default(now()) @db.Timestamptz(3)
|
||||
updated_at DateTime @updatedAt @db.Timestamptz(3)
|
||||
deleted_at DateTime? @db.Timestamptz(3)
|
||||
|
||||
@@index([user_id, id])
|
||||
}
|
||||
|
||||
model Slot {
|
||||
id Int @id @default(autoincrement())
|
||||
name String
|
||||
lat Float
|
||||
lng Float
|
||||
images String[] @default([])
|
||||
address String
|
||||
district String
|
||||
ward String
|
||||
city String
|
||||
destination String?
|
||||
phone String?
|
||||
total Int
|
||||
empty Int
|
||||
published Boolean @default(false)
|
||||
tenant Tenant? @relation(fields: [tenantId], references: [id])
|
||||
tenantId Int?
|
||||
user User? @relation(fields: [userId], references: [id])
|
||||
userId Int?
|
||||
distance Float?
|
||||
duration Float?
|
||||
directions Json?
|
||||
slot_type SlotType? @default(Car)
|
||||
alow_booking_type BookingType? @default(All)
|
||||
open String? @db.VarChar(5)
|
||||
close String? @db.VarChar(5)
|
||||
created_at DateTime @default(now()) @db.Timestamptz(3)
|
||||
updated_at DateTime @updatedAt @db.Timestamptz(3)
|
||||
deleted_at DateTime? @db.Timestamptz(3)
|
||||
pricing_per_hour Int @default(0)
|
||||
Bookings Booking[]
|
||||
Comments Comment[]
|
||||
Vote Vote[]
|
||||
|
||||
@@index([id])
|
||||
}
|
||||
|
||||
model Admin {
|
||||
id Int @id @default(autoincrement())
|
||||
username String @unique
|
||||
password String
|
||||
password_confirmation String?
|
||||
street String?
|
||||
city String?
|
||||
province String?
|
||||
first_name String
|
||||
last_name String
|
||||
phone String
|
||||
dob DateTime?
|
||||
language Language @default(vi)
|
||||
last_login_at DateTime?
|
||||
current_login_at DateTime?
|
||||
last_login_failed_at DateTime?
|
||||
consecutive_login_penalty Int @default(0)
|
||||
avatarUrl String?
|
||||
active Boolean @default(true)
|
||||
last_lockout_at DateTime?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
|
||||
model Tenant {
|
||||
id Int @id @default(autoincrement())
|
||||
email String? @unique
|
||||
username String @unique
|
||||
password String
|
||||
password_confirmation String?
|
||||
street String?
|
||||
city String?
|
||||
province String?
|
||||
postal_code String?
|
||||
first_name String
|
||||
last_name String
|
||||
phone String
|
||||
language Language @default(vi)
|
||||
last_login_at DateTime?
|
||||
current_login_at DateTime?
|
||||
last_login_failed_at DateTime?
|
||||
consecutive_login_penalty Int @default(0)
|
||||
avatarUrl String?
|
||||
active Boolean @default(true)
|
||||
last_lockout_at DateTime?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
Slots Slot[]
|
||||
Comments Comment[]
|
||||
}
|
||||
|
||||
model File {
|
||||
id Int @id @default(autoincrement())
|
||||
path String
|
||||
slot_id Int?
|
||||
user_id Int?
|
||||
size Int
|
||||
mine_type String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime?
|
||||
}
|
||||
|
||||
model History {
|
||||
id Int @id @default(autoincrement())
|
||||
}
|
||||
|
||||
model Comment {
|
||||
id Int @id @default(autoincrement())
|
||||
slot_id Int
|
||||
content String?
|
||||
tenant Tenant? @relation(fields: [tenantId], references: [id])
|
||||
tenantId Int?
|
||||
user User? @relation(fields: [userId], references: [id])
|
||||
userId Int?
|
||||
slot Slot @relation(fields: [slot_id], references: [id])
|
||||
likes Like[]
|
||||
}
|
||||
|
||||
model Like {
|
||||
id Int @id @default(autoincrement())
|
||||
Comment Comment @relation(fields: [commentId], references: [id])
|
||||
commentId Int
|
||||
user User? @relation(fields: [userId], references: [id])
|
||||
userId Int?
|
||||
}
|
||||
|
||||
model Vote {
|
||||
id Int @id @default(autoincrement())
|
||||
slot Slot @relation(fields: [slot_id], references: [id])
|
||||
slot_id Int
|
||||
user User? @relation(fields: [userId], references: [id])
|
||||
userId Int?
|
||||
source Int @default(0)
|
||||
type Int @default(0)
|
||||
}
|
||||
|
||||
enum Language {
|
||||
vi
|
||||
en
|
||||
}
|
||||
|
||||
enum Gender {
|
||||
Male
|
||||
Female
|
||||
LGBT
|
||||
}
|
||||
|
||||
enum BookingStatus {
|
||||
done
|
||||
pending
|
||||
reject
|
||||
cancel
|
||||
out
|
||||
}
|
||||
|
||||
enum SlotType {
|
||||
Car
|
||||
Mortorbike
|
||||
Orther
|
||||
}
|
||||
|
||||
enum BookingType {
|
||||
Fulltime
|
||||
Parttime
|
||||
All
|
||||
}
|
||||
23
backend/prisma/seed.ts
Normal file
23
backend/prisma/seed.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import tenant from './tenant';
|
||||
// initialize the Prisma Client
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
async function main() {
|
||||
// create two dummy articles
|
||||
return prisma.$transaction(async (tx: PrismaClient) => {
|
||||
console.log('Seeding...');
|
||||
await tenant(tx);
|
||||
});
|
||||
}
|
||||
|
||||
// execute the main function
|
||||
main()
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
})
|
||||
.finally(async () => {
|
||||
// close the Prisma Client at the end
|
||||
await prisma.$disconnect();
|
||||
});
|
||||
23
backend/prisma/slot.ts
Normal file
23
backend/prisma/slot.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
import { data as slots } from './dump/slot.json';
|
||||
async function main(prisma: PrismaClient) {
|
||||
console.log(`seed tenant data: ${slots}`);
|
||||
await prisma.slot.createMany({
|
||||
data: slots.map((e) => ({
|
||||
lat: e.lat,
|
||||
lng: e.lng,
|
||||
address: e.address,
|
||||
city: e.city,
|
||||
district: e.district,
|
||||
ward: e.ward,
|
||||
directions: e.directions,
|
||||
name: e.name,
|
||||
tenantId: e.tenantId,
|
||||
total: 5,
|
||||
empty: 0,
|
||||
})),
|
||||
});
|
||||
}
|
||||
|
||||
export default main;
|
||||
39
backend/prisma/tenant.ts
Normal file
39
backend/prisma/tenant.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import { hashSync } from 'bcrypt';
|
||||
|
||||
import { data as tenants } from './dump/tenant.json';
|
||||
import { data as slots } from './dump/slot.json';
|
||||
|
||||
async function main(prisma: PrismaClient) {
|
||||
console.log(`seed tenant data: ${tenants}`);
|
||||
|
||||
await prisma.tenant.create({
|
||||
data: {
|
||||
email: tenants[0].email,
|
||||
phone: tenants[0].phone,
|
||||
username: tenants[0].username,
|
||||
password: hashSync(tenants[0].password, 10),
|
||||
first_name: tenants[0].first_name,
|
||||
last_name: tenants[0].last_name,
|
||||
Slots: {
|
||||
create: slots.map((e) => ({
|
||||
lat: e.lat,
|
||||
lng: e.lng,
|
||||
address: e.address,
|
||||
city: e.city,
|
||||
district: e.district,
|
||||
ward: e.ward,
|
||||
directions: e.directions,
|
||||
name: e.name,
|
||||
total: 5,
|
||||
empty: 0,
|
||||
})),
|
||||
},
|
||||
},
|
||||
include: {
|
||||
Slots: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export default main;
|
||||
0
backend/simple-seed.ts
Normal file
0
backend/simple-seed.ts
Normal file
22
backend/src/app/app.controller.spec.ts
Normal file
22
backend/src/app/app.controller.spec.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { AppController } from './app.controller';
|
||||
import { AppService } from './app.service';
|
||||
|
||||
describe('AppController', () => {
|
||||
let appController: AppController;
|
||||
|
||||
beforeEach(async () => {
|
||||
const app: TestingModule = await Test.createTestingModule({
|
||||
controllers: [AppController],
|
||||
providers: [AppService],
|
||||
}).compile();
|
||||
|
||||
appController = app.get<AppController>(AppController);
|
||||
});
|
||||
|
||||
describe('root', () => {
|
||||
it('should return "Hello World!"', () => {
|
||||
expect(appController.healthCheck()).toBe('Hello World!');
|
||||
});
|
||||
});
|
||||
});
|
||||
14
backend/src/app/app.controller.ts
Normal file
14
backend/src/app/app.controller.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { Controller, Get } from '@nestjs/common';
|
||||
import { AppService } from './app.service';
|
||||
import { Public } from 'src/auth/decorators/public.auth';
|
||||
|
||||
@Controller()
|
||||
export class AppController {
|
||||
constructor(private readonly appService: AppService) {}
|
||||
|
||||
@Get('health-check')
|
||||
@Public()
|
||||
healthCheck(): string {
|
||||
return this.appService.healthCheck();
|
||||
}
|
||||
}
|
||||
47
backend/src/app/app.module.ts
Normal file
47
backend/src/app/app.module.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { AppController } from './app.controller';
|
||||
import { AppService } from './app.service';
|
||||
import { PrismaModule } from '../prisma/prisma.module';
|
||||
import { AppConfigModule } from '../config/app/app-config.module';
|
||||
import { UserController } from '../modules/user/user.controller';
|
||||
import { HookController } from '../modules/hook/hook.controller';
|
||||
import { APP_GUARD } from '@nestjs/core';
|
||||
import { JwtAuthGuard } from '../auth/guard/jwt-auth.guard';
|
||||
import { AuthModule } from '../auth/auth.module';
|
||||
import { RedisModule } from '../cache/cache.module';
|
||||
import { HeaderApiKeyStrategy } from '../auth/strategies/api-key.strategy';
|
||||
// import { BookingModule } from '../modules/booking/booking.module';
|
||||
import { SlotModule } from '../modules/slot/slot.module';
|
||||
import { BookingSlotModule } from '../modules/booking-slot/booking-slot.module';
|
||||
import { AppConfigService } from '../config/app/app-config.service';
|
||||
import { MailModule } from 'src/share/mail/mail.module';
|
||||
import { FilesModule } from 'src/modules/files/files.module';
|
||||
import { CommentModule } from 'src/modules/comment/comment.module';
|
||||
import { ZNSModule } from 'src/modules/zns/zns.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
AppConfigModule,
|
||||
PrismaModule,
|
||||
CommentModule,
|
||||
AuthModule,
|
||||
RedisModule,
|
||||
// BookingModule,
|
||||
BookingSlotModule,
|
||||
SlotModule,
|
||||
MailModule,
|
||||
FilesModule,
|
||||
ZNSModule,
|
||||
],
|
||||
controllers: [AppController, UserController, HookController],
|
||||
providers: [
|
||||
AppService,
|
||||
AppConfigService,
|
||||
{
|
||||
provide: APP_GUARD,
|
||||
useClass: JwtAuthGuard,
|
||||
},
|
||||
HeaderApiKeyStrategy,
|
||||
],
|
||||
})
|
||||
export class AppModule {}
|
||||
8
backend/src/app/app.service.ts
Normal file
8
backend/src/app/app.service.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
@Injectable()
|
||||
export class AppService {
|
||||
healthCheck(): string {
|
||||
return 'Done!';
|
||||
}
|
||||
}
|
||||
71
backend/src/auth/auth.controller.ts
Normal file
71
backend/src/auth/auth.controller.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import { Controller, Post, Body } from '@nestjs/common';
|
||||
import {
|
||||
AdminLoginGoogle,
|
||||
ChangePassword,
|
||||
TenantSignUp,
|
||||
UserConfirmSignUp,
|
||||
UserLogin,
|
||||
UserSignUp,
|
||||
} from './dto/login.dto';
|
||||
import { AuthService } from './auth.service';
|
||||
import { Public } from './decorators/public.auth';
|
||||
import { RefreshTokenDto } from './dto/refresh-token.dto';
|
||||
import { AuthUser } from 'src/common/auth/decorators';
|
||||
import { AuthPayload } from './decorators/auth.payload';
|
||||
|
||||
@Controller('auth')
|
||||
export class AuthController {
|
||||
constructor(private readonly authService: AuthService) {}
|
||||
|
||||
@Public()
|
||||
@Post('login')
|
||||
async loginByUsername(@Body() body: UserLogin) {
|
||||
return await this.authService.userLogin(body);
|
||||
}
|
||||
|
||||
@Public()
|
||||
@Post('/sign-up')
|
||||
async registerUser(@Body() body: UserSignUp) {
|
||||
return await this.authService.signUpUser(body);
|
||||
}
|
||||
|
||||
@Post('/change-password')
|
||||
async changePassword(
|
||||
@Body() body: ChangePassword,
|
||||
@AuthUser() user: AuthPayload,
|
||||
) {
|
||||
return await this.authService.changePassword(body, user);
|
||||
}
|
||||
|
||||
// @Public()
|
||||
// @Post('/confirm-sign-up')
|
||||
// async confirmSignUp(@Body() body: UserConfirmSignUp) {
|
||||
// return await this.authService.confirmSignUpUser(body);
|
||||
// }
|
||||
|
||||
@Public()
|
||||
@Post('login-google')
|
||||
async loginGoogle(@Body() googleLogin: AdminLoginGoogle) {
|
||||
return await this.authService.userLoginByGoogleOAuth2(
|
||||
googleLogin.google_token,
|
||||
);
|
||||
}
|
||||
|
||||
@Public()
|
||||
@Post('refresh-token')
|
||||
async userRefreshToken(@Body() data: RefreshTokenDto) {
|
||||
return await this.authService.refreshToken(data.refresh_token);
|
||||
}
|
||||
|
||||
@Public()
|
||||
@Post('tenant/login')
|
||||
async userLogin(@Body() data: UserLogin) {
|
||||
return await this.authService.tenantLogin(data);
|
||||
}
|
||||
|
||||
@Public()
|
||||
@Post('tenant/sign-up')
|
||||
async registerTenant(@Body() data: TenantSignUp) {
|
||||
return await this.authService.signUpTenant(data);
|
||||
}
|
||||
}
|
||||
50
backend/src/auth/auth.module.ts
Normal file
50
backend/src/auth/auth.module.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { GoogleStrategy } from './strategies/google.strategy';
|
||||
import { PassportModule } from '@nestjs/passport';
|
||||
import { JwtModule } from '@nestjs/jwt';
|
||||
import { HeaderApiKeyStrategy } from './strategies/api-key.strategy';
|
||||
import { JwtStrategy } from './strategies/jwt.strategy';
|
||||
import { AppConfigModule } from 'src/config/app/app-config.module';
|
||||
import { RedisModule } from 'src/cache/cache.module';
|
||||
import { AuthService } from './auth.service';
|
||||
import { AppConfigService } from 'src/config/app/app-config.service';
|
||||
import { AuthController } from './auth.controller';
|
||||
import { PrismaModule } from 'src/prisma/prisma.module';
|
||||
import { PrismaService } from 'src/prisma/prisma.service';
|
||||
import { MailModule } from 'src/share/mail/mail.module';
|
||||
import { MailService } from 'src/share/mail/mail.service';
|
||||
// import { PrismaModule, PrismaService } from 'nestjs-prisma';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
AppConfigModule,
|
||||
PassportModule.register({ defaultStrategy: 'jwt' }),
|
||||
JwtModule.registerAsync({
|
||||
inject: [AppConfigService],
|
||||
imports: [AppConfigModule],
|
||||
useFactory: (cf: AppConfigService) => {
|
||||
return {
|
||||
secret: cf.jwtAccessSecret,
|
||||
signOptions: {
|
||||
expiresIn: cf.jwtExpiresIn,
|
||||
},
|
||||
};
|
||||
},
|
||||
}),
|
||||
RedisModule,
|
||||
PrismaModule,
|
||||
MailModule,
|
||||
],
|
||||
controllers: [AuthController],
|
||||
providers: [
|
||||
AppConfigService,
|
||||
GoogleStrategy,
|
||||
HeaderApiKeyStrategy,
|
||||
JwtStrategy,
|
||||
AuthService,
|
||||
PrismaService,
|
||||
MailService,
|
||||
],
|
||||
})
|
||||
export class AuthModule {}
|
||||
393
backend/src/auth/auth.service.ts
Normal file
393
backend/src/auth/auth.service.ts
Normal file
@@ -0,0 +1,393 @@
|
||||
import {
|
||||
BadRequestException,
|
||||
Injectable,
|
||||
Logger,
|
||||
UnauthorizedException,
|
||||
} from '@nestjs/common';
|
||||
import { hashSync, compareSync } from 'bcrypt';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
import {
|
||||
LoginTicket,
|
||||
OAuth2Client as GoogleOAuth2Client,
|
||||
OAuth2Client,
|
||||
} from 'google-auth-library';
|
||||
import { RedisClientService } from 'src/cache/cache.service';
|
||||
import {
|
||||
EMAIL_EXISTS,
|
||||
EXP_FIRST_LOGIN,
|
||||
PHONE_EXISTS,
|
||||
TENANT_EXISTS,
|
||||
TENANT_NOTFOUND,
|
||||
USER_NOTFOUND,
|
||||
VERIFY_GOOGLE_TOKEN_FAILED,
|
||||
VERIFY_TENANT_FAILED,
|
||||
VERIFY_USER_FAILED,
|
||||
} from 'src/share/eCode';
|
||||
import { AppConfigService } from 'src/config/app/app-config.service';
|
||||
import { UserEntity } from 'src/modules/user/entities/user.entity';
|
||||
import { PrismaService } from 'src/prisma/prisma.service';
|
||||
import { convertShortTimeToSecond } from 'src/share/tool';
|
||||
import { ChangePassword, TenantSignUp, UserConfirmSignUp, UserLogin, UserSignUp } from './dto/login.dto';
|
||||
import { eUserType } from 'src/common/auth/type';
|
||||
import { AuthPayload } from './decorators/auth.payload';
|
||||
import { bool } from 'joi';
|
||||
|
||||
@Injectable()
|
||||
export class AuthService {
|
||||
private readonly logger = new Logger(AuthService.name);
|
||||
private readonly oAuth2client: GoogleOAuth2Client;
|
||||
|
||||
constructor(
|
||||
private readonly redisService: RedisClientService,
|
||||
private readonly jwtService: JwtService,
|
||||
private readonly configService: AppConfigService,
|
||||
private prisma: PrismaService,
|
||||
) {
|
||||
// const { clientId, clientSecret } = this.configService.get('googleAuth');
|
||||
this.oAuth2client = new OAuth2Client({
|
||||
clientId: process.env.GOOGLE_CLIENT_ID,
|
||||
clientSecret: process.env.GOOGLE_SECRET,
|
||||
});
|
||||
}
|
||||
|
||||
async generateToken(
|
||||
account: string,
|
||||
dataToken: AuthPayload,
|
||||
first_login = Date.now(),
|
||||
) {
|
||||
const tid = hashSync(account, 10);
|
||||
const payload = {
|
||||
tid,
|
||||
...dataToken,
|
||||
first_login,
|
||||
};
|
||||
|
||||
const payloadRefreshToken = {
|
||||
tid,
|
||||
...dataToken,
|
||||
first_login,
|
||||
};
|
||||
const [access_token, refresh_token] = await Promise.all([
|
||||
this.jwtService.signAsync(payload, {
|
||||
secret: this.configService.jwtAccessSecret,
|
||||
expiresIn: this.configService.jwtExpiresIn,
|
||||
}),
|
||||
this.jwtService.signAsync(payloadRefreshToken, {
|
||||
secret: this.configService.jwtRefreshSecret,
|
||||
expiresIn: this.configService.jwtRefreshIn,
|
||||
}),
|
||||
]);
|
||||
|
||||
return { access_token, refresh_token, tid };
|
||||
}
|
||||
|
||||
async userLoginByGoogleOAuth2(token: string): Promise<{
|
||||
access_token: string;
|
||||
refresh_token: string;
|
||||
user: UserEntity;
|
||||
}> {
|
||||
try {
|
||||
const googleLoginTicket: LoginTicket =
|
||||
await this.oAuth2client.verifyIdToken({
|
||||
idToken: token,
|
||||
audience: process.env.GOOGLE_CLIENT_ID,
|
||||
});
|
||||
|
||||
const { email } = googleLoginTicket.getPayload();
|
||||
|
||||
let user = await this.prisma.user.findFirst({
|
||||
where: {
|
||||
email,
|
||||
},
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
user = await this.prisma.user.create({
|
||||
data: {
|
||||
email,
|
||||
fullname: email,
|
||||
},
|
||||
});
|
||||
}
|
||||
const { access_token, refresh_token, tid } = await this.generateToken(
|
||||
user.email,
|
||||
{
|
||||
id: user.id,
|
||||
account: user.email,
|
||||
type: eUserType.USER,
|
||||
containsPassword: Boolean(user.password),
|
||||
},
|
||||
);
|
||||
await this.redisService.markLogin(user.id, tid);
|
||||
return {
|
||||
user: user,
|
||||
access_token,
|
||||
refresh_token,
|
||||
};
|
||||
} catch (error) {
|
||||
this.logger.error(error);
|
||||
if (error instanceof BadRequestException) {
|
||||
throw error;
|
||||
}
|
||||
throw new UnauthorizedException(VERIFY_GOOGLE_TOKEN_FAILED);
|
||||
}
|
||||
}
|
||||
|
||||
async userLogin(data: UserLogin): Promise<{
|
||||
access_token: string;
|
||||
refresh_token: string;
|
||||
user: UserEntity;
|
||||
}> {
|
||||
try {
|
||||
const user = await this.prisma.user.findFirst({
|
||||
where: {
|
||||
username: data.username,
|
||||
},
|
||||
});
|
||||
if (!user) {
|
||||
throw new BadRequestException(USER_NOTFOUND);
|
||||
}
|
||||
const { access_token, refresh_token, tid } = await this.generateToken(
|
||||
user.username,
|
||||
{
|
||||
id: user.id,
|
||||
account: user.username,
|
||||
type: eUserType.USER,
|
||||
containsPassword: Boolean(user.password),
|
||||
},
|
||||
);
|
||||
// Check pass
|
||||
if (!compareSync(data.password, user.password)) {
|
||||
throw new UnauthorizedException(VERIFY_TENANT_FAILED);
|
||||
}
|
||||
await this.redisService.markLogin(user.id, tid);
|
||||
return {
|
||||
access_token,
|
||||
refresh_token,
|
||||
user: user,
|
||||
};
|
||||
} catch (error) {
|
||||
this.logger.error(error);
|
||||
if (error instanceof BadRequestException) {
|
||||
throw error;
|
||||
}
|
||||
throw new UnauthorizedException(VERIFY_USER_FAILED);
|
||||
}
|
||||
}
|
||||
|
||||
async tenantLogin(data: UserLogin): Promise<{
|
||||
access_token: string;
|
||||
refresh_token: string;
|
||||
}> {
|
||||
try {
|
||||
const tenant = await this.prisma.tenant.findFirst({
|
||||
where: {
|
||||
username: data.username,
|
||||
},
|
||||
});
|
||||
|
||||
if (!tenant) {
|
||||
throw new BadRequestException(TENANT_NOTFOUND);
|
||||
}
|
||||
|
||||
const { access_token, refresh_token, tid } = await this.generateToken(
|
||||
tenant.username,
|
||||
{
|
||||
id: tenant.id,
|
||||
account: tenant.username,
|
||||
type: eUserType.TENANT,
|
||||
containsPassword: Boolean(tenant.password),
|
||||
},
|
||||
);
|
||||
// Check pass
|
||||
if (!compareSync(data.password, tenant.password)) {
|
||||
throw new UnauthorizedException(VERIFY_TENANT_FAILED);
|
||||
}
|
||||
await this.redisService.markLogin(tenant.id, tid);
|
||||
return {
|
||||
access_token,
|
||||
refresh_token,
|
||||
};
|
||||
} catch (error) {
|
||||
this.logger.error(error);
|
||||
if (error instanceof BadRequestException) {
|
||||
throw error;
|
||||
}
|
||||
throw new UnauthorizedException(VERIFY_TENANT_FAILED);
|
||||
}
|
||||
}
|
||||
|
||||
async refreshToken(refreshToken: string) {
|
||||
let decodedRefreshToken = null;
|
||||
try {
|
||||
decodedRefreshToken = await this.jwtService.verifyAsync(refreshToken, {
|
||||
secret: this.configService.jwtRefreshSecret,
|
||||
});
|
||||
const { id: sub, tid, type } = decodedRefreshToken;
|
||||
if (!(await this.redisService.checkRefreshTokenFound(sub, tid))) {
|
||||
throw new UnauthorizedException('Decoded token not found');
|
||||
}
|
||||
//check first login
|
||||
const dateExp: any = new Date(decodedRefreshToken.first_login);
|
||||
const timeAutoLogout = this.configService.jwtRefreshIn || '30d';
|
||||
dateExp.setSeconds(
|
||||
dateExp.getSeconds() + convertShortTimeToSecond(timeAutoLogout),
|
||||
);
|
||||
|
||||
if (Date.now() > dateExp) {
|
||||
const { sub, tid } = decodedRefreshToken;
|
||||
await this.logout(sub, tid);
|
||||
throw new UnauthorizedException(EXP_FIRST_LOGIN);
|
||||
}
|
||||
|
||||
const user =
|
||||
type == eUserType.USER
|
||||
? await this.prisma.user.findFirstOrThrow({
|
||||
where: {
|
||||
email: decodedRefreshToken.account,
|
||||
},
|
||||
})
|
||||
: await this.prisma.tenant.findFirstOrThrow({
|
||||
where: {
|
||||
username: decodedRefreshToken.account,
|
||||
},
|
||||
});
|
||||
|
||||
const {
|
||||
access_token,
|
||||
refresh_token,
|
||||
tid: _tid,
|
||||
} = await this.generateToken(
|
||||
decodedRefreshToken.account,
|
||||
{
|
||||
id: user.id,
|
||||
account: type == eUserType.USER ? user.email : user.username,
|
||||
type,
|
||||
containsPassword: Boolean(user.password),
|
||||
},
|
||||
decodedRefreshToken.first_login,
|
||||
);
|
||||
|
||||
await this.logout(sub, tid);
|
||||
await this.redisService.markLogin(user.id, _tid);
|
||||
return { access_token, refresh_token };
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
|
||||
this.logger.error(error.message);
|
||||
throw new UnauthorizedException(error.message);
|
||||
}
|
||||
}
|
||||
|
||||
async logout(uid: string, tid: string) {
|
||||
try {
|
||||
if (!tid) return false;
|
||||
await this.redisService.delMarkLogin(uid, tid);
|
||||
return true;
|
||||
} catch (err) {
|
||||
this.logger.error(err.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async signUpTenant(data: TenantSignUp) {
|
||||
const { username, password } = data;
|
||||
delete data.password_confirmation;
|
||||
return await this.prisma.$transaction(async (tx) => {
|
||||
const teannt = await tx.tenant.findFirst({
|
||||
where: {
|
||||
username,
|
||||
},
|
||||
});
|
||||
if (teannt) {
|
||||
throw new BadRequestException(TENANT_EXISTS);
|
||||
}
|
||||
const newTenant = await tx.tenant.create({
|
||||
data: {
|
||||
...data,
|
||||
password: hashSync(password, 10),
|
||||
},
|
||||
});
|
||||
|
||||
return { ...newTenant, password: undefined };
|
||||
});
|
||||
}
|
||||
|
||||
async signUpUser(data: UserSignUp) {
|
||||
const { username, password } = data;
|
||||
delete data.password_confirmation;
|
||||
const userQuery: any[] = [
|
||||
{
|
||||
username: username,
|
||||
},
|
||||
];
|
||||
if (data.phone) {
|
||||
userQuery.push({ phone: data.phone });
|
||||
}
|
||||
if (data.email) {
|
||||
userQuery.push({ email: data.email });
|
||||
}
|
||||
return await this.prisma.$transaction(async (tx) => {
|
||||
const user = await tx.user.findFirst({
|
||||
where: {
|
||||
OR: userQuery,
|
||||
},
|
||||
});
|
||||
if (user) {
|
||||
if (user.username == username) {
|
||||
throw new BadRequestException(TENANT_EXISTS);
|
||||
}
|
||||
if (user.phone == data.phone) {
|
||||
throw new BadRequestException(PHONE_EXISTS);
|
||||
}
|
||||
if (user.email == data.email) {
|
||||
throw new BadRequestException(EMAIL_EXISTS);
|
||||
}
|
||||
throw new BadRequestException(TENANT_EXISTS);
|
||||
}
|
||||
const newUser = await tx.user.create({
|
||||
data: {
|
||||
...data,
|
||||
password: hashSync(password, 10),
|
||||
},
|
||||
});
|
||||
|
||||
return { ...newUser, password: undefined };
|
||||
});
|
||||
}
|
||||
|
||||
async confirmSignUpUser(data: UserConfirmSignUp) {
|
||||
this.logger.log('confirmSignUpUser', data);
|
||||
}
|
||||
|
||||
async changePassword(data: ChangePassword, auth: AuthPayload) {
|
||||
const { old_password, password } = data;
|
||||
return await this.prisma.$transaction(async (tx) => {
|
||||
const user = await tx.user.findFirst({
|
||||
where: {
|
||||
id: +auth.id,
|
||||
},
|
||||
});
|
||||
if (!user) {
|
||||
throw new BadRequestException(USER_NOTFOUND);
|
||||
}
|
||||
if (!user.password?.length) {
|
||||
return;
|
||||
}
|
||||
if (!compareSync(old_password, user.password)) {
|
||||
throw new UnauthorizedException(VERIFY_USER_FAILED);
|
||||
}
|
||||
const newUser = await tx.user.update({
|
||||
where: {
|
||||
id: +auth.id,
|
||||
},
|
||||
data: {
|
||||
password: hashSync(password, 10),
|
||||
},
|
||||
});
|
||||
|
||||
return { ...newUser, password: undefined };
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
53
backend/src/auth/decorators/auth.payload.ts
Normal file
53
backend/src/auth/decorators/auth.payload.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import {
|
||||
ExecutionContext,
|
||||
UnauthorizedException,
|
||||
createParamDecorator,
|
||||
} from '@nestjs/common';
|
||||
import { eUserType } from 'src/common/auth/type';
|
||||
|
||||
export type AuthPayload = {
|
||||
id: string | number;
|
||||
account: string;
|
||||
type: eUserType;
|
||||
containsPassword: boolean;
|
||||
};
|
||||
export type RefreshTokenPayload = AuthPayload & {
|
||||
prefix: string;
|
||||
tid: string;
|
||||
};
|
||||
// export type AuthUserPayloadType = JwtAccessTokenPayload;
|
||||
|
||||
export const AuthAllPayload = createParamDecorator(
|
||||
async (_: unknown, ctx: ExecutionContext) => {
|
||||
const request = ctx.switchToHttp().getRequest();
|
||||
const user = request.user;
|
||||
return user;
|
||||
},
|
||||
);
|
||||
|
||||
export const AuthUser = createParamDecorator(
|
||||
async (_: unknown, ctx: ExecutionContext) => {
|
||||
const request = ctx.switchToHttp().getRequest();
|
||||
const user = request.user;
|
||||
if (user.type !== eUserType.USER) throw new UnauthorizedException();
|
||||
return user;
|
||||
},
|
||||
);
|
||||
|
||||
export const AuthTenant = createParamDecorator(
|
||||
async (_: unknown, ctx: ExecutionContext) => {
|
||||
const request = ctx.switchToHttp().getRequest();
|
||||
const user = <AuthPayload>request.user;
|
||||
if (user.type !== eUserType.TENANT) throw new UnauthorizedException();
|
||||
return user;
|
||||
},
|
||||
);
|
||||
|
||||
export const AuthAdmin = createParamDecorator(
|
||||
async (_: unknown, ctx: ExecutionContext) => {
|
||||
const request = ctx.switchToHttp().getRequest();
|
||||
const user = <AuthPayload>request.user;
|
||||
if (user.type !== eUserType.ADMIN) throw new UnauthorizedException();
|
||||
return user;
|
||||
},
|
||||
);
|
||||
28
backend/src/auth/decorators/match.decorator.ts
Normal file
28
backend/src/auth/decorators/match.decorator.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import {
|
||||
registerDecorator,
|
||||
ValidationArguments,
|
||||
ValidationOptions,
|
||||
ValidatorConstraint,
|
||||
ValidatorConstraintInterface,
|
||||
} from 'class-validator';
|
||||
|
||||
export function Match(property: string, validationOptions?: ValidationOptions) {
|
||||
return (object: any, propertyName: string) => {
|
||||
registerDecorator({
|
||||
target: object.constructor,
|
||||
propertyName,
|
||||
options: validationOptions,
|
||||
constraints: [property],
|
||||
validator: MatchConstraint,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@ValidatorConstraint({ name: 'Match' })
|
||||
export class MatchConstraint implements ValidatorConstraintInterface {
|
||||
validate(value: any, args: ValidationArguments) {
|
||||
const [relatedPropertyName] = args.constraints;
|
||||
const relatedValue = (args.object as any)[relatedPropertyName];
|
||||
return value === relatedValue;
|
||||
}
|
||||
}
|
||||
6
backend/src/auth/decorators/public.auth.ts
Normal file
6
backend/src/auth/decorators/public.auth.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { SetMetadata } from '@nestjs/common';
|
||||
|
||||
export const IS_PUBLIC_KEY = 'isPublic';
|
||||
export const IS_PUBLIC_API_KEY = 'isPublicApiKey';
|
||||
export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);
|
||||
export const PublicApiKey = () => SetMetadata(IS_PUBLIC_API_KEY, true);
|
||||
174
backend/src/auth/dto/login.dto.ts
Normal file
174
backend/src/auth/dto/login.dto.ts
Normal file
@@ -0,0 +1,174 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
// import { Language } from '@prisma/client';
|
||||
import {
|
||||
IsEmail,
|
||||
IsMobilePhone,
|
||||
IsNotEmpty,
|
||||
IsNumberString,
|
||||
IsOptional,
|
||||
IsString,
|
||||
Length,
|
||||
MaxLength,
|
||||
MinLength,
|
||||
} from 'class-validator';
|
||||
import { Match } from '../decorators/match.decorator';
|
||||
|
||||
export class AdminLoginGoogle {
|
||||
@ApiProperty({ example: 'token', type: 'string' })
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
google_token!: string;
|
||||
}
|
||||
|
||||
export class UserLogin {
|
||||
@ApiProperty({ example: 'username', type: 'string' })
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
username!: string;
|
||||
|
||||
@ApiProperty({ example: 'password', type: 'string' })
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
password!: string;
|
||||
}
|
||||
|
||||
export class TenantSignUp {
|
||||
@ApiProperty({ example: 'abc@gmail,com', type: 'string' })
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
email: string;
|
||||
|
||||
@ApiProperty({ example: 'street', type: 'string' })
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
street: string;
|
||||
|
||||
@ApiProperty({ example: 'city', type: 'string' })
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
city: string;
|
||||
|
||||
@ApiProperty({ example: 'province', type: 'string' })
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
province: string;
|
||||
|
||||
@ApiProperty({ example: '100000', type: 'string' })
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
postal_code: string;
|
||||
|
||||
@ApiProperty({ example: 'B', type: 'string' })
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
first_name: string;
|
||||
|
||||
@ApiProperty({ example: 'A', type: 'string' })
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
last_name: string;
|
||||
|
||||
@ApiProperty({ example: '09658623548', type: 'string' })
|
||||
@IsMobilePhone('vi-VN')
|
||||
@IsNotEmpty()
|
||||
phone: string;
|
||||
|
||||
@ApiProperty({ example: 'username', type: 'string' })
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
username!: string;
|
||||
|
||||
@ApiProperty({ example: 'POSvQUfwxG', type: 'string' })
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@MinLength(8)
|
||||
@MaxLength(20)
|
||||
// @Matches(/((?=.*\d)|(?=.*\W+))(?![.\n])(?=.*[A-Z])(?=.*[a-z]).*$/, {
|
||||
// message: 'password too weak',
|
||||
// })
|
||||
password!: string;
|
||||
|
||||
@ApiProperty({ example: 'POSvQUfwxG', type: 'string' })
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@Match('password')
|
||||
password_confirmation: string;
|
||||
}
|
||||
|
||||
export class UserSignUp {
|
||||
@ApiProperty({ example: 'A', type: 'string' })
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
fullname: string;
|
||||
|
||||
@ApiProperty({ example: '0965862358', type: 'string' })
|
||||
@IsMobilePhone('vi-VN')
|
||||
@IsNotEmpty()
|
||||
phone: string;
|
||||
|
||||
@ApiProperty({ example: 'abc@gmail,com', type: 'string' })
|
||||
@IsOptional()
|
||||
@IsEmail()
|
||||
email: string;
|
||||
|
||||
@ApiProperty({ example: 'username', type: 'string' })
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@MaxLength(15)
|
||||
username!: string;
|
||||
|
||||
@ApiProperty({ example: 'POSvQUfwxG', type: 'string' })
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@MinLength(6)
|
||||
@MaxLength(20)
|
||||
// @Matches(/((?=.*\d)|(?=.*\W+))(?![.\n])(?=.*[A-Z])(?=.*[a-z]).*$/, {
|
||||
// message: 'password too weak',
|
||||
// })
|
||||
password!: string;
|
||||
|
||||
@ApiProperty({ example: 'POSvQUfwxG', type: 'string' })
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@Match('password')
|
||||
password_confirmation: string;
|
||||
}
|
||||
|
||||
export class UserConfirmSignUp {
|
||||
@ApiProperty({ example: 'abc@gmail,com', type: 'string' })
|
||||
@IsString()
|
||||
id: string;
|
||||
|
||||
@ApiProperty({ example: '123456', type: 'string' })
|
||||
@IsNumberString()
|
||||
@IsNotEmpty()
|
||||
@Length(6, 6)
|
||||
otp: string;
|
||||
}
|
||||
|
||||
export class ChangePassword {
|
||||
@ApiProperty({ example: 'POSvQUfwxG', type: 'string' })
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@MaxLength(32)
|
||||
// @Matches(/((?=.*\d)|(?=.*\W+))(?![.\n])(?=.*[A-Z])(?=.*[a-z]).*$/, {
|
||||
// message: 'password too weak',
|
||||
// })
|
||||
old_password!: string;
|
||||
|
||||
@ApiProperty({ example: 'POSvQUfwxG', type: 'string' })
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@MinLength(8)
|
||||
@MaxLength(32)
|
||||
// @Matches(/((?=.*\d)|(?=.*\W+))(?![.\n])(?=.*[A-Z])(?=.*[a-z]).*$/, {
|
||||
// message: 'password too weak',
|
||||
// })
|
||||
password!: string;
|
||||
|
||||
@ApiProperty({ example: 'POSvQUfwxG', type: 'string' })
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@Match('password')
|
||||
password_confirmation: string;
|
||||
}
|
||||
13
backend/src/auth/dto/refresh-token.dto.ts
Normal file
13
backend/src/auth/dto/refresh-token.dto.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsNotEmpty, IsString } from 'class-validator';
|
||||
import { REFRESH_TOKEN_EXAMPLE } from 'src/share/constans';
|
||||
|
||||
export class RefreshTokenDto {
|
||||
@ApiProperty({
|
||||
example: REFRESH_TOKEN_EXAMPLE,
|
||||
description: 'refresh token of user which has sent with access token',
|
||||
})
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
refresh_token: string;
|
||||
}
|
||||
22
backend/src/auth/guard/jwt-auth.guard.ts
Normal file
22
backend/src/auth/guard/jwt-auth.guard.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { ExecutionContext, Injectable } from '@nestjs/common';
|
||||
import { Reflector } from '@nestjs/core';
|
||||
import { AuthGuard } from '@nestjs/passport';
|
||||
import { IS_PUBLIC_KEY } from '../decorators/public.auth';
|
||||
|
||||
@Injectable()
|
||||
export class JwtAuthGuard extends AuthGuard('jwt') {
|
||||
constructor(private reflector: Reflector) {
|
||||
super();
|
||||
}
|
||||
|
||||
canActivate(context: ExecutionContext) {
|
||||
const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
|
||||
context.getHandler(),
|
||||
context.getClass(),
|
||||
]);
|
||||
if (isPublic) {
|
||||
return true;
|
||||
}
|
||||
return super.canActivate(context);
|
||||
}
|
||||
}
|
||||
31
backend/src/auth/strategies/api-key.strategy.ts
Normal file
31
backend/src/auth/strategies/api-key.strategy.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { Injectable, UnauthorizedException } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { PassportStrategy } from '@nestjs/passport';
|
||||
import Strategy from 'passport-headerapikey';
|
||||
|
||||
@Injectable()
|
||||
export class HeaderApiKeyStrategy extends PassportStrategy(
|
||||
Strategy,
|
||||
'api-key',
|
||||
) {
|
||||
constructor(private readonly configService: ConfigService) {
|
||||
super(
|
||||
{ header: 'x-api-key', prefix: '' },
|
||||
true,
|
||||
async (apiKey: string, done: any) => {
|
||||
return this.validate(apiKey, done);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
public validate = (
|
||||
apiKey: string,
|
||||
done: (error: Error, data: any) => any,
|
||||
) => {
|
||||
if (this.configService.get<string>('externalApiKey') === apiKey) {
|
||||
done(null, true);
|
||||
}
|
||||
|
||||
done(new UnauthorizedException(), null);
|
||||
};
|
||||
}
|
||||
37
backend/src/auth/strategies/google.strategy.ts
Normal file
37
backend/src/auth/strategies/google.strategy.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { PassportStrategy } from '@nestjs/passport';
|
||||
import { Strategy, VerifyCallback } from 'passport-google-oauth20';
|
||||
import { config } from 'dotenv';
|
||||
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
config();
|
||||
|
||||
@Injectable()
|
||||
export class GoogleStrategy extends PassportStrategy(Strategy) {
|
||||
constructor() {
|
||||
super({
|
||||
clientID: process.env.GOOGLE_CLIENT_ID,
|
||||
clientSecret: process.env.GOOGLE_SECRET,
|
||||
callbackURL:
|
||||
process.env.GOOGLE_CALLBACK || 'http://localhost:3000/google/redirect',
|
||||
scope: ['email', 'profile'],
|
||||
});
|
||||
}
|
||||
|
||||
async validate(
|
||||
accessToken: string,
|
||||
refreshToken: string,
|
||||
profile: any,
|
||||
done: VerifyCallback,
|
||||
): Promise<any> {
|
||||
const { name, emails, photos } = profile;
|
||||
const user = {
|
||||
email: emails[0].value,
|
||||
firstName: name.givenName,
|
||||
lastName: name.familyName,
|
||||
picture: photos[0].value,
|
||||
accessToken,
|
||||
};
|
||||
done(null, user);
|
||||
}
|
||||
}
|
||||
42
backend/src/auth/strategies/jwt.strategy.ts
Normal file
42
backend/src/auth/strategies/jwt.strategy.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { Injectable, Logger, UnauthorizedException } from '@nestjs/common';
|
||||
import { PassportStrategy } from '@nestjs/passport';
|
||||
import { ExtractJwt, Strategy } from 'passport-jwt';
|
||||
import { RedisClientService } from '../../cache/cache.service';
|
||||
import { AppConfigService } from '../../config/app/app-config.service';
|
||||
import { AuthPayload, RefreshTokenPayload } from '../decorators/auth.payload';
|
||||
|
||||
@Injectable()
|
||||
export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
|
||||
private readonly logger = new Logger(JwtStrategy.name);
|
||||
constructor(
|
||||
private readonly configService: AppConfigService,
|
||||
private readonly redisService: RedisClientService,
|
||||
) {
|
||||
super({
|
||||
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
|
||||
ignoreExpiration: false,
|
||||
secretOrKey: configService.jwtAccessSecret,
|
||||
});
|
||||
}
|
||||
|
||||
async validate(payload: RefreshTokenPayload): Promise<AuthPayload> {
|
||||
const { id, tid, prefix } = payload;
|
||||
const { isMark } = await this.redisService.checkMarkLogin(id, tid, prefix);
|
||||
// console.log(payload);
|
||||
|
||||
if (!isMark) {
|
||||
this.logger.debug(
|
||||
`token in black list tid: ${tid}, sub: ${id}, prefix: ${prefix}`,
|
||||
);
|
||||
throw new UnauthorizedException('Token is in blacklist');
|
||||
}
|
||||
|
||||
return {
|
||||
id: payload.id,
|
||||
// tid: payload.tid,
|
||||
account: payload.account,
|
||||
type: payload.type,
|
||||
containsPassword: payload.containsPassword,
|
||||
};
|
||||
}
|
||||
}
|
||||
65
backend/src/cache/cache.const.ts
vendored
Normal file
65
backend/src/cache/cache.const.ts
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
export class CacheConst {
|
||||
static readonly AUTO_LOGOUT = 'auto_logout:';
|
||||
static readonly ACCESS_TOKEN = 'access_token:';
|
||||
static readonly REFRESH_TOKEN = 'refresh_token:';
|
||||
static readonly HOTP = 'hotp:';
|
||||
static readonly LINE = 'line:';
|
||||
static readonly DOCUMENT = 'document:';
|
||||
static readonly IMPORT_JOB = 'import-job';
|
||||
static readonly PUBLIC_SIGN = 'public-sign';
|
||||
|
||||
async getDocumetChangeKey(userId: number) {
|
||||
return `${CacheConst.DOCUMENT}:change:${userId}`;
|
||||
}
|
||||
|
||||
getDocumetFirstChangeKey(userId: number) {
|
||||
return `${CacheConst.DOCUMENT}change:${userId}:first`;
|
||||
}
|
||||
|
||||
getDocumetSecondChangeKey(userId: number) {
|
||||
return `${CacheConst.DOCUMENT}change:${userId}:second`;
|
||||
}
|
||||
|
||||
static getImportJobKey(uid: string) {
|
||||
return `${CacheConst.IMPORT_JOB}:${uid}`;
|
||||
}
|
||||
|
||||
static getKeyDataImport(uid: string) {
|
||||
return `${CacheConst.IMPORT_JOB}:${uid}:`;
|
||||
}
|
||||
|
||||
static getKeyMapData(uid: string) {
|
||||
return `${CacheConst.IMPORT_JOB}:${uid}:map`;
|
||||
}
|
||||
|
||||
static genKeyAccessToken = (
|
||||
uid: string | number,
|
||||
tid: string,
|
||||
prefix = 'admin',
|
||||
) => {
|
||||
return `${this.ACCESS_TOKEN}${prefix}:${uid}:${tid}`;
|
||||
};
|
||||
|
||||
static genKeyRefreshToken = (
|
||||
uid: string | number,
|
||||
tid: string,
|
||||
prefix = 'admin',
|
||||
) => {
|
||||
return `${this.REFRESH_TOKEN}${prefix}:${uid}:${tid}`;
|
||||
};
|
||||
|
||||
static genKeyPubicName = (
|
||||
uid: string | number,
|
||||
tid: string,
|
||||
prefix = 'public_name',
|
||||
) => {
|
||||
return `${this.PUBLIC_SIGN}:${prefix}:${uid}:${tid}`;
|
||||
};
|
||||
}
|
||||
|
||||
export const ALL_TOPIC_LINE = 'all_topic_line';
|
||||
export const TOPIC_LINE = 'topic_line:';
|
||||
export const ALL_QUESTION_LINE = 'all_question_line:';
|
||||
export const ANSWER_LINE = 'answer_line:';
|
||||
export const ENTERING_DATA_LINE = 'entering_data_line';
|
||||
export const CONTRACT_LOGIN = 'contract_login';
|
||||
11
backend/src/cache/cache.module.ts
vendored
Normal file
11
backend/src/cache/cache.module.ts
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { RedisClientService } from './cache.service';
|
||||
import { AppConfigService } from 'src/config/app/app-config.service';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
|
||||
@Module({
|
||||
imports: [],
|
||||
providers: [RedisClientService, ConfigService, AppConfigService],
|
||||
exports: [RedisClientService],
|
||||
})
|
||||
export class RedisModule {}
|
||||
122
backend/src/cache/cache.service.ts
vendored
Normal file
122
backend/src/cache/cache.service.ts
vendored
Normal file
@@ -0,0 +1,122 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import IORedis, { Redis } from 'ioredis';
|
||||
|
||||
import { CacheConst } from './cache.const';
|
||||
import { convertShortTimeToSecond, generateOTP } from 'src/share/tool';
|
||||
import { AppConfigService } from 'src/config/app/app-config.service';
|
||||
|
||||
@Injectable()
|
||||
export class RedisClientService {
|
||||
private client: Redis;
|
||||
private logger: Logger = new Logger(RedisClientService.name);
|
||||
public readonly TTL_IMPORT: number = 60 * 60 * 24;
|
||||
public readonly TTL_OTP: number = 90;
|
||||
|
||||
constructor(private configService: AppConfigService) {
|
||||
this.client = new IORedis(this.configService.redisUri);
|
||||
}
|
||||
|
||||
getClient() {
|
||||
if (!this.client) {
|
||||
this.client = new IORedis(this.configService.redisUri);
|
||||
}
|
||||
return this.client;
|
||||
}
|
||||
|
||||
private async delKeys(key: string) {
|
||||
try {
|
||||
const client = this.client;
|
||||
const keys = await this.client.keys(key);
|
||||
await Promise.all(keys.map((e) => client.del(e)));
|
||||
} catch (error) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
async markLogin(uid: number, tid: string, prefix = 'admin') {
|
||||
try {
|
||||
const keyAccess = CacheConst.genKeyAccessToken(uid, tid, prefix);
|
||||
const keyRefresh = CacheConst.genKeyRefreshToken(uid, tid, prefix);
|
||||
const accessTokenTTL =
|
||||
convertShortTimeToSecond(this.configService.jwtExpiresIn) || 300;
|
||||
const refreshTokenTTL =
|
||||
convertShortTimeToSecond(this.configService.jwtRefreshIn) || 1000;
|
||||
await this.client.set(keyAccess, '1', 'EX', accessTokenTTL);
|
||||
await this.client.set(keyRefresh, '1', 'EX', refreshTokenTTL);
|
||||
} catch (error) {
|
||||
this.logger.error(error.message);
|
||||
}
|
||||
}
|
||||
|
||||
async delMarkLogin(uid: string, tid: string, prefix = 'admin') {
|
||||
try {
|
||||
const keyAccess = CacheConst.genKeyAccessToken(uid, tid, prefix);
|
||||
const keyRefresh = CacheConst.genKeyRefreshToken(uid, tid, prefix);
|
||||
const keyPublicName = CacheConst.genKeyPubicName(uid, tid);
|
||||
await this.delKeys(keyAccess);
|
||||
await this.delKeys(keyRefresh);
|
||||
await this.delKeys(keyPublicName);
|
||||
} catch (error) {
|
||||
this.logger.error(error.message);
|
||||
}
|
||||
}
|
||||
|
||||
async getPublicName(uid: string, tid: string) {
|
||||
const keyPublicName = CacheConst.genKeyPubicName(uid, tid);
|
||||
|
||||
this.logger.debug(
|
||||
`${this.getPublicName.name} keyPublicName: ${keyPublicName}`,
|
||||
);
|
||||
|
||||
const publicName = await this.getClient().get(keyPublicName);
|
||||
|
||||
this.logger.debug(`${this.getPublicName.name} publicName: ${publicName}`);
|
||||
|
||||
return publicName;
|
||||
}
|
||||
|
||||
async setPublicName(uid: string | number, tid: string, name: string) {
|
||||
const keyPublicName = CacheConst.genKeyPubicName(uid, tid);
|
||||
|
||||
await this.getClient().set(keyPublicName, name);
|
||||
}
|
||||
|
||||
async checkMarkLogin(uid: string | number, tid: string, prefix = 'admin') {
|
||||
try {
|
||||
const keyadminAccess = await this.client.get(
|
||||
`${CacheConst.genKeyAccessToken(uid, tid, prefix)}`,
|
||||
);
|
||||
return {
|
||||
key: keyadminAccess,
|
||||
isMark: Boolean(keyadminAccess),
|
||||
};
|
||||
} catch (error) {
|
||||
this.logger.error(error.message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async checkRefreshTokenFound(uid: string, tid: string, prefix = 'admin') {
|
||||
try {
|
||||
const keyRefresh = await this.client.get(
|
||||
`${CacheConst.genKeyRefreshToken(uid, tid, prefix)}`,
|
||||
);
|
||||
return Boolean(keyRefresh);
|
||||
} catch (error) {
|
||||
this.logger.error(error.message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async setOTP(id: string, data: any) {
|
||||
const otp = generateOTP();
|
||||
if (!id) {
|
||||
throw new Error('id not found');
|
||||
}
|
||||
|
||||
const jsonData = JSON.stringify(data);
|
||||
const key = `${otp}:${id}`;
|
||||
await this.client.set(key, jsonData, 'EX', this.TTL_OTP);
|
||||
return otp;
|
||||
}
|
||||
}
|
||||
34
backend/src/common/auth/decorators.ts
Normal file
34
backend/src/common/auth/decorators.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import {
|
||||
createParamDecorator,
|
||||
ExecutionContext,
|
||||
UnauthorizedException,
|
||||
} from '@nestjs/common';
|
||||
import { JwtAccessTokenPayload } from './type';
|
||||
|
||||
export type AuthUserPayloadType = JwtAccessTokenPayload;
|
||||
|
||||
export const AuthUser = createParamDecorator(
|
||||
async (_: unknown, ctx: ExecutionContext) => {
|
||||
const request = ctx.switchToHttp().getRequest();
|
||||
const user = request.user;
|
||||
return user;
|
||||
},
|
||||
);
|
||||
|
||||
export const AuthTenant = createParamDecorator(
|
||||
async (_: unknown, ctx: ExecutionContext) => {
|
||||
const request = ctx.switchToHttp().getRequest();
|
||||
const user = request.user;
|
||||
if (user.type !== 'tenant') throw new UnauthorizedException();
|
||||
return user;
|
||||
},
|
||||
);
|
||||
|
||||
export const AuthAdmin = createParamDecorator(
|
||||
async (_: unknown, ctx: ExecutionContext) => {
|
||||
const request = ctx.switchToHttp().getRequest();
|
||||
const user = request.user;
|
||||
if (user.type !== 'admin') throw new UnauthorizedException();
|
||||
return user;
|
||||
},
|
||||
);
|
||||
20
backend/src/common/auth/type.ts
Normal file
20
backend/src/common/auth/type.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
export enum eUserType {
|
||||
USER,
|
||||
TENANT,
|
||||
ADMIN,
|
||||
}
|
||||
|
||||
export type TokenUserType = eUserType.ADMIN | eUserType.TENANT | eUserType.USER;
|
||||
|
||||
export type JwtAccessTokenPayload = {
|
||||
id: number;
|
||||
roleId: number;
|
||||
tokenId: string;
|
||||
type: TokenUserType;
|
||||
};
|
||||
|
||||
export type JwtRefreshTokenPayload = {
|
||||
id: number;
|
||||
tokenId: string;
|
||||
type: TokenUserType;
|
||||
};
|
||||
57
backend/src/common/dto/search.ts
Normal file
57
backend/src/common/dto/search.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import { ApiExtraModels, ApiProperty } from '@nestjs/swagger';
|
||||
import { Type } from 'class-transformer';
|
||||
import { IsNumber, IsOptional, IsString, Max, Min } from 'class-validator';
|
||||
|
||||
export enum Order {
|
||||
Desc = 'DESC',
|
||||
Asc = 'ASC',
|
||||
}
|
||||
|
||||
@ApiExtraModels()
|
||||
export class PagingReqDto {
|
||||
@ApiProperty({ example: 'abc', required: false, type: String })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
search? = '';
|
||||
|
||||
@ApiProperty({ example: 20, required: false, type: Number })
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
@Max(100)
|
||||
@Type(() => Number)
|
||||
pageSize = 10;
|
||||
|
||||
@ApiProperty({ example: 20, required: false, type: Number })
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@Type(() => Number)
|
||||
@Min(1)
|
||||
pageNumber = 1;
|
||||
|
||||
@ApiProperty({
|
||||
name: 'sort',
|
||||
type: 'object',
|
||||
additionalProperties: { type: 'string' },
|
||||
description: 'Data object with key-value pairs',
|
||||
required: false,
|
||||
example: {
|
||||
created_at: 'asc',
|
||||
},
|
||||
})
|
||||
@IsOptional()
|
||||
sort: Record<string, string> = {
|
||||
created_at: 'asc',
|
||||
};
|
||||
}
|
||||
|
||||
export class PagingRes {
|
||||
@ApiProperty({ example: 100 })
|
||||
pages: number;
|
||||
|
||||
@ApiProperty({ example: 1000 })
|
||||
count: number;
|
||||
|
||||
@ApiProperty({ example: 5 })
|
||||
pageNumber: number;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import {
|
||||
Injectable,
|
||||
NestInterceptor,
|
||||
ExecutionContext,
|
||||
CallHandler,
|
||||
Logger,
|
||||
HttpException,
|
||||
} from '@nestjs/common';
|
||||
import { Observable, catchError } from 'rxjs';
|
||||
|
||||
@Injectable()
|
||||
export class TransformErrorInterceptor implements NestInterceptor {
|
||||
private readonly logger = new Logger(TransformErrorInterceptor.name);
|
||||
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
|
||||
// next.handle() is an Observable of the controller's result value
|
||||
return next.handle().pipe(
|
||||
catchError((error) => {
|
||||
const response = error.response || {};
|
||||
const { statusCode, message } = response;
|
||||
throw new HttpException(
|
||||
{
|
||||
status: statusCode,
|
||||
code: error['code'],
|
||||
details: {
|
||||
type: 'error',
|
||||
message: message,
|
||||
},
|
||||
},
|
||||
statusCode,
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import {
|
||||
Injectable,
|
||||
NestInterceptor,
|
||||
ExecutionContext,
|
||||
CallHandler,
|
||||
StreamableFile,
|
||||
} from '@nestjs/common';
|
||||
import { Observable } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { DEFAULT_SUCCESS_MSG } from 'src/share/constans';
|
||||
|
||||
export interface Response<T> {
|
||||
statusCode: number;
|
||||
message: string;
|
||||
data: T;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class TransformResponseInterceptor<T>
|
||||
implements NestInterceptor<T, Response<T> | StreamableFile>
|
||||
{
|
||||
intercept(
|
||||
context: ExecutionContext,
|
||||
next: CallHandler,
|
||||
): Observable<Response<any> | StreamableFile> {
|
||||
return next.handle().pipe(
|
||||
map((data) => {
|
||||
if (data instanceof StreamableFile) {
|
||||
return data;
|
||||
}
|
||||
return {
|
||||
statusCode:
|
||||
<number>data?.statusCode ||
|
||||
<number>context.switchToHttp().getResponse().statusCode,
|
||||
message: <string>data?.message || DEFAULT_SUCCESS_MSG,
|
||||
data: data,
|
||||
};
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
28
backend/src/common/interceptor/uid.interceptor.ts
Normal file
28
backend/src/common/interceptor/uid.interceptor.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import {
|
||||
Injectable,
|
||||
NestInterceptor,
|
||||
ExecutionContext,
|
||||
CallHandler,
|
||||
StreamableFile,
|
||||
} from '@nestjs/common';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
export interface Response<T> {
|
||||
statusCode: number;
|
||||
message: string;
|
||||
data: T;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class UUIDRequestInterceptor<T>
|
||||
implements NestInterceptor<T, Response<T> | StreamableFile>
|
||||
{
|
||||
intercept(
|
||||
context: ExecutionContext,
|
||||
next: CallHandler,
|
||||
): Observable<Response<any> | StreamableFile> {
|
||||
const request = context.switchToHttp().getRequest();
|
||||
request.headers['x-request-id'] = 'asf';
|
||||
return next.handle();
|
||||
}
|
||||
}
|
||||
17
backend/src/config/app/app-config.module.ts
Normal file
17
backend/src/config/app/app-config.module.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||
import appConfiguration from './app-configuration';
|
||||
import { AppConfigService } from './app-config.service';
|
||||
import { appConfigValidationSchema } from './app-config.schema';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
ConfigModule.forRoot({
|
||||
load: [appConfiguration],
|
||||
validationSchema: appConfigValidationSchema,
|
||||
}),
|
||||
],
|
||||
providers: [ConfigService, AppConfigService],
|
||||
exports: [ConfigService, AppConfigService],
|
||||
})
|
||||
export class AppConfigModule {}
|
||||
24
backend/src/config/app/app-config.schema.ts
Normal file
24
backend/src/config/app/app-config.schema.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import * as Joi from 'joi';
|
||||
|
||||
export const appConfigValidationSchema = Joi.object({
|
||||
ENVIRONMENT: Joi.string().valid('dev', 'stag', 'prod').default('dev'),
|
||||
APP_NAME: Joi.string().default('NestJS Example App'),
|
||||
APP_URL: Joi.string().default('http://localhost:3000'),
|
||||
PORT: Joi.number().default(3000),
|
||||
APP_CORS_ENABLED: Joi.boolean().default(true),
|
||||
JWT_ACCESS_SECRET: Joi.string().required(),
|
||||
JWT_REFRESH_SECRET: Joi.string().required(),
|
||||
JWT_EXPIRES_IN: Joi.string().required(),
|
||||
JWT_REFRESH_IN: Joi.string().required(),
|
||||
BCRYPT_SALT_ROUNDS: Joi.number().default(10),
|
||||
GRAPHQL_PLAYGROUND_ENABLED: Joi.boolean().default(true),
|
||||
GRAPHQL_DEBUG: Joi.boolean().default(true),
|
||||
GRAPHQL_SCHEMA_DESTINATION: Joi.string().default('schema.graphql'),
|
||||
GRAPHQL_SORT_SCHEMA: Joi.boolean().default(true),
|
||||
SWAGGER_ENABLED: Joi.boolean().default(true),
|
||||
SWAGGER_DESCRIPTION: Joi.string().default('NestJS example app API'),
|
||||
SWAGGER_VERSION: Joi.string().default('1.5'),
|
||||
SWAGGER_PATH: Joi.string().default('api'),
|
||||
REDIS_URL: Joi.string().default('redis://localhost:6379'),
|
||||
UPLOAD_LOCATION: Joi.string().default('audio'),
|
||||
});
|
||||
100
backend/src/config/app/app-config.service.ts
Normal file
100
backend/src/config/app/app-config.service.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
|
||||
@Injectable()
|
||||
export class AppConfigService {
|
||||
constructor(private configService: ConfigService) {}
|
||||
|
||||
get environment(): string {
|
||||
return this.configService.get<string>('app.environment');
|
||||
}
|
||||
get name(): string {
|
||||
return this.configService.get<string>('app.name');
|
||||
}
|
||||
|
||||
get url(): string {
|
||||
return this.configService.get<string>('app.url');
|
||||
}
|
||||
|
||||
get port(): number {
|
||||
return Number(this.configService.get<number>('app.port'));
|
||||
}
|
||||
|
||||
get corsEnabled(): boolean {
|
||||
return this.configService.get<boolean>('app.corsEnabled');
|
||||
}
|
||||
|
||||
get jwtAccessSecret(): string {
|
||||
return this.configService.get<string>('app.jwtAccessSecret');
|
||||
}
|
||||
|
||||
get jwtRefreshSecret(): string {
|
||||
return this.configService.get<string>('app.jwtRefreshSecret');
|
||||
}
|
||||
|
||||
get jwtExpiresIn(): string {
|
||||
return this.configService.get<string>('app.jwtExpiresIn');
|
||||
}
|
||||
|
||||
get jwtRefreshIn(): string {
|
||||
return this.configService.get<string>('app.jwtRefreshIn');
|
||||
}
|
||||
|
||||
get bcryptSaltRounds(): number {
|
||||
return this.configService.get<number>('app.bcryptSaltRounds');
|
||||
}
|
||||
|
||||
get graphqlPlaygroundEnabled(): boolean {
|
||||
return this.configService.get<boolean>('app.graphqlPlaygroundEnabled');
|
||||
}
|
||||
|
||||
get graphqlDebug(): boolean {
|
||||
return this.configService.get<boolean>('app.graphqlDebug');
|
||||
}
|
||||
|
||||
get graphqlSchemaDestination(): string {
|
||||
return this.configService.get<string>('app.graphqlSchemaDestination');
|
||||
}
|
||||
|
||||
get graphqlSortSchema(): boolean {
|
||||
return this.configService.get<boolean>('app.graphqlSortSchema');
|
||||
}
|
||||
|
||||
get swaggerEnabled(): boolean {
|
||||
return this.configService.get<boolean>('app.swaggerEnabled');
|
||||
}
|
||||
|
||||
get swaggerDescription(): string {
|
||||
return this.configService.get<string>('app.swaggerDescription');
|
||||
}
|
||||
|
||||
get swaggerVersion(): string {
|
||||
return this.configService.get<string>('app.swaggerVersion');
|
||||
}
|
||||
|
||||
get swaggerPath(): string {
|
||||
return this.configService.get<string>('app.swaggerPath');
|
||||
}
|
||||
|
||||
get gClientId(): string {
|
||||
return this.configService.get<string>('app.gClientId');
|
||||
}
|
||||
|
||||
get gClientSecret(): string {
|
||||
return this.configService.get<string>('app.gClientSecret');
|
||||
}
|
||||
|
||||
get redisUri(): string {
|
||||
return this.configService.get<string>('app.redis');
|
||||
}
|
||||
|
||||
get loggingType(): string {
|
||||
return this.configService.get<string>('app.loggingType');
|
||||
}
|
||||
get googleMailer(): string {
|
||||
return this.configService.get<any>('app.googleMailer');
|
||||
}
|
||||
get contactMailer(): string {
|
||||
return this.configService.get<any>('app.googleMailer.contactMail');
|
||||
}
|
||||
}
|
||||
39
backend/src/config/app/app-configuration.ts
Normal file
39
backend/src/config/app/app-configuration.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { registerAs } from '@nestjs/config';
|
||||
|
||||
export default registerAs('app', () => ({
|
||||
environment: process.env.ENVIRONMENT,
|
||||
name: process.env.APP_NAME,
|
||||
url: process.env.APP_URL,
|
||||
port: process.env.PORT,
|
||||
redis: process.env.REDIS_URL,
|
||||
corsEnabled: process.env.APP_CORS_ENABLED,
|
||||
jwtAccessSecret: process.env.JWT_ACCESS_SECRET,
|
||||
jwtRefreshSecret: process.env.JWT_REFRESH_SECRET,
|
||||
jwtExpiresIn: process.env.JWT_EXPIRES_IN,
|
||||
jwtRefreshIn: process.env.JWT_REFRESH_IN,
|
||||
bcryptSaltRounds: process.env.BCRYPT_SALT_ROUNDS,
|
||||
graphqlPlaygroundEnabled: process.env.GRAPHQL_PLAYGROUND_ENABLED,
|
||||
graphqlDebug: process.env.GRAPHQL_DEBUG,
|
||||
graphqlSchemaDestination: process.env.GRAPHQL_SCHEMA_DESTINATION,
|
||||
graphqlSortSchema: process.env.GRAPHQL_SORT_SCHEMA,
|
||||
swaggerEnabled: process.env.SWAGGER_ENABLED,
|
||||
swaggerDescription: process.env.SWAGGER_DESCRIPTION,
|
||||
swaggerVersion: process.env.SWAGGER_VERSION,
|
||||
swaggerPath: process.env.SWAGGER_PATH,
|
||||
gClientId: process.env.GOOGLE_CLIENT_ID,
|
||||
gClientSecret: process.env.GOOGLE_SECRET,
|
||||
loggingType: process.env.LOGGING_TYPE || 'dev',
|
||||
googleMailer: {
|
||||
contactMail: process.env.CONTACT_MAIL || '',
|
||||
clientId: process.env.GOOGLE_MAILER_CLIENT_ID || '',
|
||||
clientSecret: process.env.GOOGLE_MAILER_CLIENT_SECRET || '',
|
||||
refreshToken: process.env.GOOGLE_MAILER_REFRESH_TOKEN || '',
|
||||
host: process.env.MAILER_HOST || '',
|
||||
port: process.env.MAILER_PORT || '',
|
||||
secure: process.env.MAILER_REFRESH_TOKEN || '',
|
||||
auth: {
|
||||
user: process.env.ADMIN_EMAIL_ADDRESS || '',
|
||||
pass: process.env.ADMIN_EMAIL_PASS || '',
|
||||
},
|
||||
},
|
||||
}));
|
||||
5
backend/src/main.ts
Normal file
5
backend/src/main.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
// src/main.ts
|
||||
|
||||
import bootstrap from './www/bin/bootstrap';
|
||||
|
||||
bootstrap();
|
||||
20
backend/src/modules/articles/articles.controller.spec.ts
Normal file
20
backend/src/modules/articles/articles.controller.spec.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { ArticlesController } from './articles.controller';
|
||||
import { ArticlesService } from './articles.service';
|
||||
|
||||
describe('ArticlesController', () => {
|
||||
let controller: ArticlesController;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
controllers: [ArticlesController],
|
||||
providers: [ArticlesService],
|
||||
}).compile();
|
||||
|
||||
controller = module.get<ArticlesController>(ArticlesController);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(controller).toBeDefined();
|
||||
});
|
||||
});
|
||||
60
backend/src/modules/articles/articles.controller.ts
Normal file
60
backend/src/modules/articles/articles.controller.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import {
|
||||
Controller,
|
||||
Get,
|
||||
Post,
|
||||
Body,
|
||||
Patch,
|
||||
Param,
|
||||
Delete,
|
||||
ParseIntPipe,
|
||||
} from '@nestjs/common';
|
||||
import { ArticlesService } from './articles.service';
|
||||
import { CreateArticleDto } from './dto/create-article.dto';
|
||||
import { UpdateArticleDto } from './dto/update-article.dto';
|
||||
import { ApiCreatedResponse, ApiOkResponse, ApiTags } from '@nestjs/swagger';
|
||||
import { ArticleEntity } from './entities/article.entity';
|
||||
|
||||
@Controller('articles')
|
||||
@ApiTags('articles')
|
||||
export class ArticlesController {
|
||||
constructor(private readonly articlesService: ArticlesService) {}
|
||||
|
||||
@Post()
|
||||
@ApiCreatedResponse({ type: ArticleEntity })
|
||||
create(@Body() createArticleDto: CreateArticleDto) {
|
||||
return this.articlesService.create(createArticleDto);
|
||||
}
|
||||
|
||||
@Get()
|
||||
@ApiOkResponse({ type: ArticleEntity, isArray: true })
|
||||
findAll() {
|
||||
return this.articlesService.findAll();
|
||||
}
|
||||
|
||||
@Get('drafts')
|
||||
@ApiOkResponse({ type: ArticleEntity, isArray: true })
|
||||
findDrafts() {
|
||||
return this.articlesService.findDrafts();
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@ApiOkResponse({ type: ArticleEntity })
|
||||
findOne(@Param('id', ParseIntPipe) id: number) {
|
||||
return this.articlesService.findOne(id);
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
@ApiCreatedResponse({ type: ArticleEntity })
|
||||
update(
|
||||
@Param('id', ParseIntPipe) id: number,
|
||||
@Body() updateArticleDto: UpdateArticleDto,
|
||||
) {
|
||||
return this.articlesService.update(id, updateArticleDto);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@ApiOkResponse({ type: ArticleEntity })
|
||||
remove(@Param('id', ParseIntPipe) id: number) {
|
||||
return this.articlesService.remove(id);
|
||||
}
|
||||
}
|
||||
11
backend/src/modules/articles/articles.module.ts
Normal file
11
backend/src/modules/articles/articles.module.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ArticlesService } from './articles.service';
|
||||
import { ArticlesController } from './articles.controller';
|
||||
import { PrismaModule } from 'src/prisma/prisma.module';
|
||||
|
||||
@Module({
|
||||
controllers: [ArticlesController],
|
||||
providers: [ArticlesService],
|
||||
imports: [PrismaModule],
|
||||
})
|
||||
export class ArticlesModule {}
|
||||
18
backend/src/modules/articles/articles.service.spec.ts
Normal file
18
backend/src/modules/articles/articles.service.spec.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { ArticlesService } from './articles.service';
|
||||
|
||||
describe('ArticlesService', () => {
|
||||
let service: ArticlesService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [ArticlesService],
|
||||
}).compile();
|
||||
|
||||
service = module.get<ArticlesService>(ArticlesService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
36
backend/src/modules/articles/articles.service.ts
Normal file
36
backend/src/modules/articles/articles.service.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { PrismaService } from 'src/prisma/prisma.service';
|
||||
import { CreateArticleDto } from './dto/create-article.dto';
|
||||
import { UpdateArticleDto } from './dto/update-article.dto';
|
||||
|
||||
@Injectable()
|
||||
export class ArticlesService {
|
||||
constructor(private prisma: PrismaService) {}
|
||||
|
||||
create(createArticleDto: CreateArticleDto) {
|
||||
// return this.prisma.article.create({ data: createArticleDto });
|
||||
}
|
||||
|
||||
findDrafts() {
|
||||
// return this.prisma.article.findMany({ where: { published: false } });
|
||||
}
|
||||
|
||||
findAll() {
|
||||
// return this.prisma.article.findMany({ where: { published: true } });
|
||||
}
|
||||
|
||||
findOne(id: number) {
|
||||
// return this.prisma.article.findUnique({ where: { id } });
|
||||
}
|
||||
|
||||
update(id: number, updateArticleDto: UpdateArticleDto) {
|
||||
// return this.prisma.article.update({
|
||||
// where: { id },
|
||||
// data: updateArticleDto,
|
||||
// });
|
||||
}
|
||||
|
||||
remove(id: number) {
|
||||
// return this.prisma.article.delete({ where: { id } });
|
||||
}
|
||||
}
|
||||
36
backend/src/modules/articles/dto/create-article.dto.ts
Normal file
36
backend/src/modules/articles/dto/create-article.dto.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
// src/articles/dto/create-article.dto.ts
|
||||
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import {
|
||||
IsBoolean,
|
||||
IsNotEmpty,
|
||||
IsOptional,
|
||||
IsString,
|
||||
MaxLength,
|
||||
MinLength,
|
||||
} from 'class-validator';
|
||||
|
||||
export class CreateArticleDto {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@MinLength(5)
|
||||
@ApiProperty()
|
||||
title: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@IsNotEmpty()
|
||||
@MaxLength(300)
|
||||
@ApiProperty({ required: false })
|
||||
description?: string;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@ApiProperty()
|
||||
body: string;
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
@ApiProperty({ required: false, default: false })
|
||||
published?: boolean = false;
|
||||
}
|
||||
4
backend/src/modules/articles/dto/update-article.dto.ts
Normal file
4
backend/src/modules/articles/dto/update-article.dto.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { PartialType } from '@nestjs/swagger';
|
||||
import { CreateArticleDto } from './create-article.dto';
|
||||
|
||||
export class UpdateArticleDto extends PartialType(CreateArticleDto) {}
|
||||
25
backend/src/modules/articles/entities/article.entity.ts
Normal file
25
backend/src/modules/articles/entities/article.entity.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
// import { Article } from '@prisma/client';
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
export class ArticleEntity {
|
||||
@ApiProperty()
|
||||
id: number;
|
||||
|
||||
@ApiProperty()
|
||||
title: string;
|
||||
|
||||
@ApiProperty({ required: false, nullable: true })
|
||||
description: string | null;
|
||||
|
||||
@ApiProperty()
|
||||
body: string;
|
||||
|
||||
@ApiProperty()
|
||||
published: boolean;
|
||||
|
||||
@ApiProperty()
|
||||
createdAt: Date;
|
||||
|
||||
@ApiProperty()
|
||||
updatedAt: Date;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { BookingSlotController } from './booking-slot.controller';
|
||||
import { BookingSlotService } from './booking-slot.service';
|
||||
|
||||
describe('BookingSlotController', () => {
|
||||
let controller: BookingSlotController;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
controllers: [BookingSlotController],
|
||||
providers: [BookingSlotService],
|
||||
}).compile();
|
||||
|
||||
controller = module.get<BookingSlotController>(BookingSlotController);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(controller).toBeDefined();
|
||||
});
|
||||
});
|
||||
78
backend/src/modules/booking-slot/booking-slot.controller.ts
Normal file
78
backend/src/modules/booking-slot/booking-slot.controller.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import {
|
||||
Controller,
|
||||
Get,
|
||||
Post,
|
||||
Body,
|
||||
Patch,
|
||||
Param,
|
||||
Delete,
|
||||
Query,
|
||||
} from '@nestjs/common';
|
||||
import { BookingSlotService } from './booking-slot.service';
|
||||
import { CreateBookingSlotDto } from './dto/create-booking-slot.dto';
|
||||
import { UpdateBookingSlotDto } from './dto/update-booking-slot.dto';
|
||||
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
|
||||
import { AuthPayload, AuthUser } from 'src/auth/decorators/auth.payload';
|
||||
import { HistoryBookingSlot } from './dto/search-booking.dto';
|
||||
// import { Public } from 'src/auth/decorators/public.auth';
|
||||
|
||||
@Controller('booking-slot')
|
||||
@ApiTags('Booking slot API')
|
||||
@ApiBearerAuth()
|
||||
export class BookingSlotController {
|
||||
constructor(private readonly bookingSlotService: BookingSlotService) { }
|
||||
|
||||
@Post()
|
||||
create(
|
||||
@Body() createBookingSlotDto: CreateBookingSlotDto,
|
||||
@AuthUser() user: AuthPayload,
|
||||
) {
|
||||
return this.bookingSlotService.create(createBookingSlotDto, user);
|
||||
}
|
||||
|
||||
@Get('/history')
|
||||
history(
|
||||
@Query() query: HistoryBookingSlot,
|
||||
@AuthUser() user: AuthPayload,
|
||||
) {
|
||||
return this.bookingSlotService.historyBooking(query, user);
|
||||
}
|
||||
|
||||
@Get('/submit/:id')
|
||||
submitMyBooking(
|
||||
@Param('id') id: string,
|
||||
@AuthUser() user: AuthPayload,
|
||||
) {
|
||||
return this.bookingSlotService.submitMyBooking(+id, user);
|
||||
}
|
||||
|
||||
@Get('/out/:id')
|
||||
outMyBooking(@Param('id') id: string, @AuthUser() user: AuthPayload) {
|
||||
return this.bookingSlotService.outMyBooking(+id, user);
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
findOne(@Param('id') id: string, @AuthUser() user: AuthPayload) {
|
||||
return this.bookingSlotService.findOne(+id, user);
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
update(
|
||||
@Param('id') id: string,
|
||||
@Body() updateBookingSlotDto: UpdateBookingSlotDto,
|
||||
@AuthUser() user: AuthPayload,
|
||||
) {
|
||||
return this.bookingSlotService.update(+id, updateBookingSlotDto, user);
|
||||
}
|
||||
|
||||
@Delete('/cancel/:id')
|
||||
cancel(@Param('id') id: string, @AuthUser() user: AuthPayload) {
|
||||
return this.bookingSlotService.cancel(+id, user);
|
||||
}
|
||||
|
||||
// @Get('/test/sql')
|
||||
// @Public()
|
||||
// test() {
|
||||
// return this.bookingSlotService.test();
|
||||
// }
|
||||
}
|
||||
13
backend/src/modules/booking-slot/booking-slot.module.ts
Normal file
13
backend/src/modules/booking-slot/booking-slot.module.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { BookingSlotService } from './booking-slot.service';
|
||||
import { BookingSlotController } from './booking-slot.controller';
|
||||
import { PrismaModule } from 'src/prisma/prisma.module';
|
||||
import { SlotService } from '../slot/slot.service';
|
||||
|
||||
@Module({
|
||||
controllers: [BookingSlotController],
|
||||
providers: [BookingSlotService, SlotService],
|
||||
imports: [PrismaModule],
|
||||
exports: [BookingSlotService],
|
||||
})
|
||||
export class BookingSlotModule {}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { BookingSlotService } from './booking-slot.service';
|
||||
|
||||
describe('BookingSlotService', () => {
|
||||
let service: BookingSlotService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [BookingSlotService],
|
||||
}).compile();
|
||||
|
||||
service = module.get<BookingSlotService>(BookingSlotService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
247
backend/src/modules/booking-slot/booking-slot.service.ts
Normal file
247
backend/src/modules/booking-slot/booking-slot.service.ts
Normal file
@@ -0,0 +1,247 @@
|
||||
import {
|
||||
BadRequestException,
|
||||
forwardRef,
|
||||
Inject,
|
||||
Injectable,
|
||||
Logger,
|
||||
} from '@nestjs/common';
|
||||
import { CreateBookingSlotDto } from './dto/create-booking-slot.dto';
|
||||
import { UpdateBookingSlotDto } from './dto/update-booking-slot.dto';
|
||||
import { AuthPayload } from 'src/auth/decorators/auth.payload';
|
||||
import { PrismaService } from 'src/prisma/prisma.service';
|
||||
import {
|
||||
SLOT_BOOKING_EXISTS,
|
||||
SLOT_BOOKING_FULL,
|
||||
SLOT_BOOKING_NOTFOUND,
|
||||
} from 'src/share/eCode';
|
||||
import { SlotService } from '../slot/slot.service';
|
||||
import { HistoryBookingSlot } from './dto/search-booking.dto';
|
||||
|
||||
@Injectable()
|
||||
export class BookingSlotService {
|
||||
private readonly LOG = new Logger(BookingSlotService.name);
|
||||
|
||||
constructor(
|
||||
private prisma: PrismaService,
|
||||
@Inject(forwardRef(() => SlotService))
|
||||
private readonly slotService: SlotService,
|
||||
) {}
|
||||
|
||||
public async countBookingActiveByGroup(slots: number[]) {
|
||||
const now = new Date();
|
||||
const results = await this.prisma.booking.groupBy({
|
||||
by: ['slot_id'],
|
||||
_count: {
|
||||
_all: true,
|
||||
},
|
||||
where: {
|
||||
slot_id: {
|
||||
in: slots,
|
||||
},
|
||||
status: 'done',
|
||||
start_at: {
|
||||
lte: now,
|
||||
},
|
||||
end_at: {
|
||||
gte: now,
|
||||
},
|
||||
},
|
||||
});
|
||||
const mapIdToCount = {};
|
||||
for (const iterator of results) {
|
||||
const slot_id = iterator.slot_id;
|
||||
const all = iterator._count._all;
|
||||
mapIdToCount[slot_id] = all;
|
||||
}
|
||||
return mapIdToCount;
|
||||
}
|
||||
|
||||
public async countBookingActive(slotId: number) {
|
||||
const now = new Date();
|
||||
const count = await this.prisma.booking.count({
|
||||
where: {
|
||||
slot_id: slotId,
|
||||
status: 'done',
|
||||
start_at: {
|
||||
lte: now,
|
||||
},
|
||||
end_at: {
|
||||
gte: now,
|
||||
},
|
||||
slot: {
|
||||
id: slotId,
|
||||
deleted_at: null,
|
||||
},
|
||||
},
|
||||
});
|
||||
this.LOG.debug(`${now}slot :${slotId} exits ${count} booking`);
|
||||
return count;
|
||||
}
|
||||
|
||||
public async checkBookingSlot(bookingId: number, user_id?: number) {
|
||||
const query = { id: bookingId };
|
||||
if (user_id) query['user_id'] = user_id;
|
||||
const booking = await this.prisma.booking.findFirst({
|
||||
where: query,
|
||||
});
|
||||
if (!booking) {
|
||||
throw new BadRequestException(SLOT_BOOKING_EXISTS);
|
||||
}
|
||||
return booking;
|
||||
}
|
||||
|
||||
async create(data: CreateBookingSlotDto, user: AuthPayload) {
|
||||
// Check slot
|
||||
const slot = await this.slotService.getSlot(+data.slotId);
|
||||
const result = await this.prisma.booking.create({
|
||||
data: {
|
||||
start_at: data.startAt,
|
||||
user_id: +user.id,
|
||||
slot_id: slot.id,
|
||||
contact: data.contact,
|
||||
end_at: data.endAt,
|
||||
owner: data.owner,
|
||||
license_plates: data.licensePlates,
|
||||
slot_type: data.slot_type,
|
||||
},
|
||||
});
|
||||
return result;
|
||||
}
|
||||
// isolation need
|
||||
async submitMyBooking(id: number, user: AuthPayload) {
|
||||
return this.prisma.$transaction(async (trans) => {
|
||||
const booking = await this.checkBookingSlot(id, +user.id);
|
||||
const slotBooking = await this.slotService.getSlot(booking.slot_id);
|
||||
const totalSlot = await this.countBookingActive(slotBooking.id);
|
||||
if (totalSlot >= slotBooking.total) {
|
||||
throw new BadRequestException(SLOT_BOOKING_FULL);
|
||||
}
|
||||
if (booking.status == 'pending') {
|
||||
await trans.booking.update({
|
||||
data: {
|
||||
status: 'done',
|
||||
},
|
||||
where: {
|
||||
id: booking.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
return `This action submit a #${id} bookingSlot`;
|
||||
});
|
||||
}
|
||||
|
||||
async outMyBooking(id: number, user: AuthPayload) {
|
||||
return this.prisma.$transaction(async (trans) => {
|
||||
const booking = await this.checkBookingSlot(id, +user.id);
|
||||
|
||||
if (booking.status !== 'done') {
|
||||
await trans.booking.update({
|
||||
data: {
|
||||
status: 'out',
|
||||
},
|
||||
where: {
|
||||
id: booking.id,
|
||||
},
|
||||
});
|
||||
return `This action submit a #${id} bookingSlot`;
|
||||
}
|
||||
throw new BadRequestException();
|
||||
});
|
||||
}
|
||||
async findOne(id: number, user: AuthPayload) {
|
||||
const booking = await this.prisma.booking.findFirst({
|
||||
where: {
|
||||
id,
|
||||
user_id: +user.id,
|
||||
},
|
||||
include: {
|
||||
slot: true,
|
||||
},
|
||||
});
|
||||
if (!booking) {
|
||||
throw new BadRequestException(SLOT_BOOKING_NOTFOUND);
|
||||
}
|
||||
this.LOG.debug(`Find booking by id: ${id} ${JSON.stringify(booking)} `);
|
||||
return booking;
|
||||
}
|
||||
|
||||
async update(
|
||||
id: number,
|
||||
updateBookingSlotDto: UpdateBookingSlotDto,
|
||||
user: AuthPayload,
|
||||
) {
|
||||
const booking = await this.findOne(id, user);
|
||||
if (booking.status == 'done') {
|
||||
return `This action updates a #${id} block`;
|
||||
}
|
||||
const result = await this.prisma.$transaction([
|
||||
this.prisma.booking.update({
|
||||
where: {
|
||||
id,
|
||||
},
|
||||
data: {
|
||||
owner: updateBookingSlotDto.owner || booking.owner,
|
||||
start_at: updateBookingSlotDto.startAt || booking.start_at,
|
||||
end_at: updateBookingSlotDto.endAt || booking.end_at,
|
||||
license_plates:
|
||||
updateBookingSlotDto.licensePlates || booking.license_plates,
|
||||
},
|
||||
}),
|
||||
]);
|
||||
|
||||
this.LOG.debug(result);
|
||||
return `This action updates a #${id} bookingSlot`;
|
||||
}
|
||||
|
||||
async cancel(id: number, user: AuthPayload) {
|
||||
return this.prisma.$transaction(async (trans) => {
|
||||
const booking = await this.checkBookingSlot(id, +user.id);
|
||||
|
||||
if (booking.status !== 'done') {
|
||||
await trans.booking.update({
|
||||
data: {
|
||||
status: 'cancel',
|
||||
},
|
||||
where: {
|
||||
id: booking.id,
|
||||
},
|
||||
});
|
||||
return `This action cancel a #${id} bookingSlot`;
|
||||
} else {
|
||||
await trans.booking.update({
|
||||
data: {
|
||||
status: 'reject',
|
||||
},
|
||||
where: {
|
||||
id: booking.id,
|
||||
},
|
||||
});
|
||||
return `This action reject a #${id} bookingSlot`;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async test() {
|
||||
return this.countBookingActiveByGroup([1]);
|
||||
}
|
||||
|
||||
async historyBooking(query: HistoryBookingSlot, user: AuthPayload) {
|
||||
const { pageSize, pageNumber, sort } = query;
|
||||
const take = pageSize;
|
||||
const skip = (pageNumber - 1) * pageSize;
|
||||
const condition = {
|
||||
where: {
|
||||
user_id: +user.id,
|
||||
},
|
||||
skip,
|
||||
take,
|
||||
orderBy: sort,
|
||||
};
|
||||
|
||||
const [bookings, total] = await this.prisma.$transaction([
|
||||
this.prisma.booking.findMany(condition),
|
||||
this.prisma.booking.count({ where: condition.where }),
|
||||
]);
|
||||
return { bookings, total };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { SlotType } from '@prisma/client';
|
||||
import { Type } from 'class-transformer';
|
||||
import {
|
||||
IsDate,
|
||||
IsDateString,
|
||||
IsEnum,
|
||||
IsNotEmpty,
|
||||
IsNumber,
|
||||
IsOptional,
|
||||
IsString,
|
||||
MinDate,
|
||||
} from 'class-validator';
|
||||
|
||||
export class CreateBookingSlotDto {
|
||||
@ApiProperty({
|
||||
example: 'Hello word',
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@Type(() => String)
|
||||
owner: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: 'Hello word',
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@Type(() => String)
|
||||
licensePlates: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: '10H 346543',
|
||||
})
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@Type(() => String)
|
||||
contact: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: '',
|
||||
})
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@Type(() => String)
|
||||
image: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: 1,
|
||||
})
|
||||
@IsNumber()
|
||||
@IsNotEmpty()
|
||||
@Type(() => Number)
|
||||
slotId: number;
|
||||
|
||||
@ApiProperty({
|
||||
example: new Date(),
|
||||
})
|
||||
@IsDate()
|
||||
@IsNotEmpty()
|
||||
// @MinDate(new Date())
|
||||
@Type(() => Date)
|
||||
startAt: Date;
|
||||
|
||||
@ApiProperty({
|
||||
example: new Date(),
|
||||
})
|
||||
@IsDate()
|
||||
@IsNotEmpty()
|
||||
@MinDate(new Date())
|
||||
@Type(() => Date)
|
||||
endAt: Date;
|
||||
|
||||
@ApiProperty({
|
||||
example: SlotType.Orther,
|
||||
})
|
||||
@IsEnum(SlotType)
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
slot_type: SlotType = SlotType.Orther;
|
||||
}
|
||||
11
backend/src/modules/booking-slot/dto/search-booking.dto.ts
Normal file
11
backend/src/modules/booking-slot/dto/search-booking.dto.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { Type } from 'class-transformer';
|
||||
import { IsNotEmpty, IsNumber, IsOptional, IsString } from 'class-validator';
|
||||
import { PagingReqDto } from 'src/common/dto/search';
|
||||
|
||||
export class HistoryBookingSlot extends PagingReqDto {
|
||||
@ApiProperty({ example: 'abc', required: false, type: String })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
name? = '';
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import { PartialType } from '@nestjs/swagger';
|
||||
import { CreateBookingSlotDto } from './create-booking-slot.dto';
|
||||
|
||||
export class UpdateBookingSlotDto extends PartialType(CreateBookingSlotDto) {}
|
||||
@@ -0,0 +1 @@
|
||||
export class BookingSlot {}
|
||||
20
backend/src/modules/comment/comment.controller.spec.ts
Normal file
20
backend/src/modules/comment/comment.controller.spec.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { CommentController } from './comment.controller';
|
||||
import { CommentService } from './comment.service';
|
||||
|
||||
describe('CommentController', () => {
|
||||
let controller: CommentController;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
controllers: [CommentController],
|
||||
providers: [CommentService],
|
||||
}).compile();
|
||||
|
||||
controller = module.get<CommentController>(CommentController);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(controller).toBeDefined();
|
||||
});
|
||||
});
|
||||
67
backend/src/modules/comment/comment.controller.ts
Normal file
67
backend/src/modules/comment/comment.controller.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import {
|
||||
Controller,
|
||||
Get,
|
||||
Post,
|
||||
Body,
|
||||
Patch,
|
||||
Param,
|
||||
Delete,
|
||||
Query,
|
||||
} from '@nestjs/common';
|
||||
import { CommentService } from './comment.service';
|
||||
import { CreateCommentDto } from './dto/create-comment.dto';
|
||||
import { UpdateCommentDto } from './dto/update-comment.dto';
|
||||
import { AuthPayload, AuthUser } from 'src/auth/decorators/auth.payload';
|
||||
import { SearchCommentDto } from './dto/search-comment,dto';
|
||||
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
|
||||
@ApiTags('Comment API')
|
||||
@ApiBearerAuth()
|
||||
@Controller('comment')
|
||||
export class CommentController {
|
||||
constructor(private readonly commentService: CommentService) { }
|
||||
|
||||
@Post()
|
||||
create(
|
||||
@Body() createCommentDto: CreateCommentDto,
|
||||
@AuthUser() user: AuthPayload,
|
||||
) {
|
||||
return this.commentService.create(createCommentDto, user);
|
||||
}
|
||||
|
||||
@Get('by-slot/:slot_id')
|
||||
findAll(
|
||||
@Param('slot_id') slot_id: number,
|
||||
@Query() query: SearchCommentDto,
|
||||
@AuthUser() user: AuthPayload,
|
||||
) {
|
||||
return this.commentService.findAll(+slot_id, query, user);
|
||||
}
|
||||
|
||||
@Post('react/:id')
|
||||
react(
|
||||
@Param('id') slot_id: number,
|
||||
// @Query() query: SearchCommentDto,
|
||||
@AuthUser() user: AuthPayload,
|
||||
) {
|
||||
return this.commentService.reactComment(+slot_id, user);
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
findOne(@Param('id') id: number) {
|
||||
return this.commentService.findOne(+id);
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
update(
|
||||
@Param('id') id: number,
|
||||
@Body() updateCommentDto: UpdateCommentDto,
|
||||
@AuthUser() user: AuthPayload,
|
||||
) {
|
||||
return this.commentService.update(+id, updateCommentDto, user);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
remove(@Param('id') id: number, @AuthUser() user: AuthPayload,) {
|
||||
return this.commentService.remove(+id, user);
|
||||
}
|
||||
}
|
||||
12
backend/src/modules/comment/comment.module.ts
Normal file
12
backend/src/modules/comment/comment.module.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { CommentService } from './comment.service';
|
||||
import { CommentController } from './comment.controller';
|
||||
import { PrismaModule } from 'src/prisma/prisma.module';
|
||||
|
||||
@Module({
|
||||
controllers: [CommentController],
|
||||
providers: [CommentService],
|
||||
imports: [PrismaModule],
|
||||
exports: [CommentService],
|
||||
})
|
||||
export class CommentModule {}
|
||||
18
backend/src/modules/comment/comment.service.spec.ts
Normal file
18
backend/src/modules/comment/comment.service.spec.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { CommentService } from './comment.service';
|
||||
|
||||
describe('CommentService', () => {
|
||||
let service: CommentService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [CommentService],
|
||||
}).compile();
|
||||
|
||||
service = module.get<CommentService>(CommentService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
172
backend/src/modules/comment/comment.service.ts
Normal file
172
backend/src/modules/comment/comment.service.ts
Normal file
@@ -0,0 +1,172 @@
|
||||
import { Logger, Injectable, BadRequestException } from '@nestjs/common';
|
||||
import { CreateCommentDto } from './dto/create-comment.dto';
|
||||
import { UpdateCommentDto } from './dto/update-comment.dto';
|
||||
import { PrismaService } from 'src/prisma/prisma.service';
|
||||
import { AuthPayload } from 'src/auth/decorators/auth.payload';
|
||||
import { COMMENT_NOTFOUND, SLOT_NOTFOUND } from 'src/share/eCode';
|
||||
import { SearchCommentDto } from './dto/search-comment,dto';
|
||||
import { eUserType } from 'src/common/auth/type';
|
||||
@Injectable()
|
||||
export class CommentService {
|
||||
private readonly LOG = new Logger(CommentService.name);
|
||||
|
||||
constructor(private prisma: PrismaService) { }
|
||||
async find_comment_by_user(id: number, user: AuthPayload) {
|
||||
const comment = await this.prisma.comment.findFirst({
|
||||
where: {
|
||||
id,
|
||||
userId: +user.id,
|
||||
},
|
||||
});
|
||||
|
||||
if (comment) return comment;
|
||||
throw new BadRequestException(COMMENT_NOTFOUND);
|
||||
}
|
||||
async create(createCommentDto: CreateCommentDto, user: AuthPayload) {
|
||||
try {
|
||||
// check slot
|
||||
const userCreate = {};
|
||||
if (user.type == eUserType.TENANT) {
|
||||
userCreate['tenantId'] = +user.id;
|
||||
} else {
|
||||
userCreate['userId'] = +user.id;
|
||||
}
|
||||
const slot = await this.prisma.slot.findFirst({
|
||||
where: { id: createCommentDto.slot_id },
|
||||
});
|
||||
if (!slot) {
|
||||
throw new BadRequestException(SLOT_NOTFOUND);
|
||||
}
|
||||
await this.prisma.$transaction([
|
||||
this.prisma.comment.create({
|
||||
data: { ...createCommentDto, ...userCreate },
|
||||
}),
|
||||
]);
|
||||
return true;
|
||||
} catch (e) {
|
||||
this.LOG.error(e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
async findAll(
|
||||
slot_id: number,
|
||||
query: SearchCommentDto,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
user: AuthPayload = null,
|
||||
) {
|
||||
try {
|
||||
const { pageSize, pageNumber, sort, search } = query;
|
||||
const take = pageSize;
|
||||
const skip = (pageNumber - 1) * pageSize;
|
||||
const condition = {
|
||||
where: { slot_id },
|
||||
skip,
|
||||
take,
|
||||
// orderBy: sort,
|
||||
include: {
|
||||
tenant: true,
|
||||
user: true,
|
||||
_count: { select: { likes: true } },
|
||||
},
|
||||
};
|
||||
const [comments, total] = await this.prisma.$transaction([
|
||||
this.prisma.comment.findMany(condition),
|
||||
this.prisma.comment.count({ where: condition.where }),
|
||||
]);
|
||||
return {
|
||||
comments: comments?.map((e) => {
|
||||
delete e?.user?.password;
|
||||
delete e?.user?.username;
|
||||
delete e?.tenant?.username;
|
||||
delete e?.tenant?.username;
|
||||
return {
|
||||
...e,
|
||||
};
|
||||
}),
|
||||
total,
|
||||
};
|
||||
} catch (error) {
|
||||
this.LOG.error(error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async findOne(id: number, user: AuthPayload = null) {
|
||||
try {
|
||||
const comment = await this.prisma.comment.findFirst({
|
||||
where: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
|
||||
if (comment) return comment;
|
||||
throw new BadRequestException(COMMENT_NOTFOUND);
|
||||
} catch (error) {
|
||||
this.LOG.error(error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async update(
|
||||
id: number,
|
||||
updateCommentDto: UpdateCommentDto,
|
||||
user: AuthPayload,
|
||||
) {
|
||||
try {
|
||||
await this.find_comment_by_user(id, user);
|
||||
await this.prisma.$transaction([
|
||||
this.prisma.comment.update({
|
||||
where: { id },
|
||||
data: {
|
||||
content: updateCommentDto.content,
|
||||
},
|
||||
}),
|
||||
]);
|
||||
return `This action updates a #${id} comment`;
|
||||
} catch (error) {
|
||||
this.LOG.error(error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async remove(id: number, user: AuthPayload) {
|
||||
try {
|
||||
await this.find_comment_by_user(id, user);
|
||||
await this.prisma.$transaction([
|
||||
this.prisma.comment.deleteMany({
|
||||
where: { id },
|
||||
}),
|
||||
]);
|
||||
} catch (error) {
|
||||
this.LOG.error(error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async reactComment(id: number, user: AuthPayload = null) {
|
||||
try {
|
||||
const comment = await this.find_comment_by_user(id, user);
|
||||
const like = await this.prisma.like.findFirst({
|
||||
where: {
|
||||
commentId: comment.id,
|
||||
userId: +user.id,
|
||||
},
|
||||
});
|
||||
if (like) {
|
||||
await this.prisma.like.delete({ where: { id: +like.id } });
|
||||
} else {
|
||||
await this.prisma.like.create({
|
||||
data: {
|
||||
commentId: comment.id,
|
||||
userId: +user.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
return true;
|
||||
} catch (error) {
|
||||
this.LOG.error(error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
19
backend/src/modules/comment/dto/create-comment.dto.ts
Normal file
19
backend/src/modules/comment/dto/create-comment.dto.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import {
|
||||
IsNotEmpty,
|
||||
IsNumber,
|
||||
IsString,
|
||||
} from 'class-validator';
|
||||
|
||||
export class CreateCommentDto {
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
content: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsNumber()
|
||||
@IsNotEmpty()
|
||||
slot_id: number
|
||||
}
|
||||
|
||||
6
backend/src/modules/comment/dto/search-comment,dto.ts
Normal file
6
backend/src/modules/comment/dto/search-comment,dto.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
|
||||
import { PagingReqDto } from 'src/common/dto/search';
|
||||
|
||||
export class SearchCommentDto extends PagingReqDto {
|
||||
|
||||
}
|
||||
4
backend/src/modules/comment/dto/update-comment.dto.ts
Normal file
4
backend/src/modules/comment/dto/update-comment.dto.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { PartialType } from '@nestjs/swagger';
|
||||
import { CreateCommentDto } from './create-comment.dto';
|
||||
|
||||
export class UpdateCommentDto extends PartialType(CreateCommentDto) {}
|
||||
1
backend/src/modules/comment/entities/comment.entity.ts
Normal file
1
backend/src/modules/comment/entities/comment.entity.ts
Normal file
@@ -0,0 +1 @@
|
||||
export class Comment {}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user